Skip to Content
Automation APIn8n, Zapier & CI

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-Key is accepted alongside Authorization, 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

FieldValue
NamePremation API
Header NameAuthorization
Header ValueBearer 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:

FieldValue
MethodPOST
URL{{$env.PREMATION_BASE}}/v1/renders
AuthenticationPredefined ▸ Header Auth ▸ Premation API
Send HeadersIdempotency-Key{{ $json.orderId }}-render
Send BodyJSON
{ "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:

  1. HTTP RequestGET {{ $env.PREMATION_BASE }}/v1/renders/{{ $json.jobId }}
  2. IF — is status one of completed, failed, cancelled?
  3. 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:

FieldValue
URL<base>/v1/renders
Payload TypeJSON
HeadersAuthorization: Bearer pm_live_…
DatatemplateId, 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.mp4

Idempotency-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:

  1. POST <base>/v1/renders with the key in Authorization.
  2. Either supply a callbackUrl your tool can receive on, or poll GET <base>/v1/renders/{jobId} until status is terminal.
  3. Use videoUrl, or GET …/download for the bytes.

That is the entire integration surface. See the endpoint reference for the rest.

Last updated on