n8n, Zapier & CI
There is no Premation node to install and no SDK to pull in. Every one of these is a plain HTTP request, which is the point — the API was shaped around what a generic HTTP node can already do.
Three design choices exist specifically for this:
X-API-Keyis accepted alongsideAuthorization, because it is the default header of n8n’s generic credential type.- Download answers 302 rather than streaming bytes, because HTTP nodes follow redirects natively.
- Rate limiting answers 429 with
retryAfterSeconds, because that is what automation tools back off on.
Sending a file: upload it, don’t host it
image and media inputs take a URL, and in n8n you usually have the opposite
— a PNG sitting in $binary.data from a form trigger, a Drive node or a mail
attachment.
POST /v1/assets takes the bytes and hands back a URL:
Method: POST
URL: {{ $env.PREMATION_BASE }}/v1/assets
Authentication: Predefined ▸ Header Auth ▸ Premation API
Send Body: ✓ Body Content Type: n8n Binary File
Input Data Field Name: data{
"url": "https://res.cloudinary.com/…/a1b2c3.png",
"bytes": 284119,
"mime": "image/png",
"expiresAt": "2026-08-28T09:00:00.000Z"
}Put {{ $json.url }} straight into the next node’s inputs. Scope:
renders:write, which is in the default grant.
The file is temporary — 24 hours — because it exists to feed a render, not
to be a library. Render well inside that window, or upload again. Bytes count
against the same assetProcessingBytes meter that URL ingestion uses, so the
two routes cost the same.
The declared content type is checked against the actual bytes. A file sent as
image/png that is really an MP4 is refused rather than stored under the
wrong type and failing later, at render, with an error about something else.
A generic application/octet-stream — what n8n usually sends — is fine: it
reads as “no type given”, and the bytes decide.
Or point at a URL you already have
If the asset is already public, skip the upload and pass the URL directly. A signed URL works provided it is valid when the render is created — the asset is fetched at job creation, not at render time. A URL needing a header or a cookie will not work: the fetcher sends neither.
n8n
Create the credential
Credentials ▸ New ▸ Header Auth
| Field | Value |
|---|---|
| Name | Premation API |
| Header Name | Authorization |
| Header Value | Bearer pm_live_… |
Header Auth, not the Generic Credential’s API-key type — it keeps the secret
out of the node body where it would otherwise be visible in every execution
log. If you prefer the API-key type, set the header name to X-API-Key and
the value to the bare key with no Bearer prefix.
Queue the render
HTTP Request node:
| Field | Value |
|---|---|
| Method | POST |
| URL | {{$env.PREMATION_BASE}}/v1/renders |
| Authentication | Predefined ▸ Header Auth ▸ Premation API |
| Send Headers | Idempotency-Key → {{ $json.orderId }}-render |
| Send Body | JSON |
{
"templateId": "tpl_7f8a9b",
"inputs": {
"headline": "{{ $json.title }}",
"character": "{{ $json.imageUrl }}"
},
"callbackUrl": "{{ $execution.resumeUrl }}"
}Wait for it
Put a Wait node in “On webhook call” mode immediately after the HTTP
Request, and pass its resume URL as callbackUrl above. The workflow parks
until the render settles and resumes with the payload — no polling loop, no
wasted API requests against your quota.
This needs an n8n reachable from the public internet. A local n8n’s resume URL
is http://localhost:5678/…, which the callback validator rejects. Expose it
through a tunnel and set WEBHOOK_URL in n8n so the resume URL it generates
is the public one.
Branch on the result
An IF node on {{ $json.status }} === "completed". The success branch has
videoUrl ready to hand to the next step — upload, post, email, whatever the
workflow is for.
Polling instead
If a tunnel is out of the question, drop the callbackUrl and loop:
- HTTP Request —
GET {{ $env.PREMATION_BASE }}/v1/renders/{{ $json.jobId }} - IF — is
statusone ofcompleted,failed,cancelled? - Wait 5 seconds on the false branch, then back to step 1.
Remember each poll spends one API request from your monthly quota.
Downloading the file
HTTP Request, GET {{ $env.PREMATION_BASE }}/v1/renders/{{ $json.jobId }}/download,
Response Format File. Redirect following is on by default, so the node ends
up holding the MP4 as binary data.
Zapier
Zapier has no long-running step, so the webhook path is the only sane one.
Catch Hook
Add Webhooks by Zapier ▸ Catch Hook as the first step of a second Zap and copy its URL.
Queue the render
In your main Zap, Webhooks by Zapier ▸ POST:
| Field | Value |
|---|---|
| URL | <base>/v1/renders |
| Payload Type | JSON |
| Headers | Authorization: Bearer pm_live_… |
| Data | templateId, inputs, and callbackUrl set to the Catch Hook URL |
Act on the result
The second Zap fires when the render settles, with jobId, status and
videoUrl in the payload.
GitHub Actions
A nightly render, start to finish:
name: Render weekly reel
on:
schedule: [{ cron: '0 6 * * 1' }]
workflow_dispatch:
jobs:
render:
runs-on: ubuntu-latest
steps:
- name: Queue the render
id: queue
env:
KEY: ${{ secrets.PREMATION_API_KEY }}
BASE: ${{ vars.PREMATION_BASE }}
run: |
job=$(curl -sS -X POST "$BASE/v1/renders" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: gha-${{ github.run_id }}" \
-d '{
"templateId": "tpl_7f8a9b",
"inputs": { "headline": "Week ${{ github.run_number }}" }
}')
echo "id=$(echo "$job" | jq -r .jobId)" >> "$GITHUB_OUTPUT"
- name: Wait for it
id: wait
env:
KEY: ${{ secrets.PREMATION_API_KEY }}
BASE: ${{ vars.PREMATION_BASE }}
run: |
for _ in $(seq 1 120); do
job=$(curl -sS "$BASE/v1/renders/${{ steps.queue.outputs.id }}" \
-H "Authorization: Bearer $KEY")
status=$(echo "$job" | jq -r .status)
echo "status=$status"
case "$status" in
completed) echo "url=$(echo "$job" | jq -r .videoUrl)" >> "$GITHUB_OUTPUT"; exit 0 ;;
failed|cancelled) echo "$job" | jq -r .error >&2; exit 1 ;;
esac
sleep 10
done
echo "Timed out waiting for the render." >&2
exit 1
- name: Download
run: curl -sSL -o reel.mp4 "${{ steps.wait.outputs.url }}"
- uses: actions/upload-artifact@v4
with:
name: reel
path: reel.mp4Idempotency-Key: gha-${{ github.run_id }} is what makes re-running a failed
workflow safe. The re-run returns the job the first attempt already queued
instead of rendering the same reel twice.
Make.com and everything else
Any tool that can send an HTTP request with a header works the same way:
POST <base>/v1/renderswith the key inAuthorization.- Either supply a
callbackUrlyour tool can receive on, or pollGET <base>/v1/renders/{jobId}untilstatusis terminal. - Use
videoUrl, orGET …/downloadfor the bytes.
That is the entire integration surface. See the endpoint reference for the rest.