Skip to Content
Automation APIOverview

Automation API

Send us a project, swap any layer by its name, and render it as many times as you like over HTTP.

curl -X POST https://motion-back-production.up.railway.app/api/v1/renders \ -H "Authorization: Bearer pm_live_…" \ -H "Content-Type: application/json" \ -d '{ "templateId": "tpl_7f8a9b", "inputs": { "headline": "Launch Special 2026", "character": "https://cdn.example.com/photo.jpg" } }'
{ "jobId": "clx9zz110000", "status": "queued" }

The API is a hosted-build feature and is plan-gated. The open-source local edition has no backend to call, and a free account cannot mint keys — GET /v1/usage reports limits.apiEnabled: false and key creation answers 403 upgrade_required. See Plans, quotas & limits.

What it is for

The API renders templates — a frozen composition plus a manifest of what may vary. There is no layer CRUD, no keyframe endpoint and no way to build a composition over HTTP; that is the editor’s job. What crosses the wire is a template id and a flat bag of named values.

Getting a template is a one-time step, and there are two ways to do it:

Upload a projectAuthor in the editor
HowPOST /v1/templates/import with a zipped .motionExpose fields, then Save as Animation Template
InputsEvery media layer, named after the layerExactly the ones you choose
ControlNone — you get what the project hasRename, group, set slot fit, lock the rest
Editor neededNoYes

Both produce the same kind of template, and POST /v1/renders cannot tell them apart. Import is the fast path; the editor is the precise one.

After that, forever:

POST /v1/renders { templateId, inputs } → { jobId, status } GET /v1/renders/{jobId} → status, progress, videoUrl GET /v1/renders/{jobId}/download → 302 to the MP4

Everything you did not expose stays exactly as authored. Swapping a media input’s src does not disturb its Position, Scale, Rotation, Opacity or easing tracks — that is the whole point of “create once, automate many times”.

Inputs are named after layers

An input id is a slug of the layer nameHero Image becomes heroImage. Never the internal node id, in either path. Addressing a layer by id would make Premation’s node graph a public contract that no project could ever be refactored out of, so it is not one.

Practically: name your layers before you import.

Files, not just URLs

image and media inputs take a public http(s) URL, and the render service fetches them itself. When your workflow is holding the bytes rather than a URL — an n8n form upload, a Drive node, a mail attachment — POST /v1/assets takes the file and hands back a URL to put straight into inputs. No bucket in the middle whose only job is to bridge two systems that both already have the file.

Why the output matches your export

The render service runs the same renderOffline path the desktop File ▸ Export uses, against the same frozen document. It is not a re-implementation and not a headless browser screenshotting the canvas.

Pixel-identical is best-effort, not guaranteed: the hosted renderer runs on SwiftShader (CPU), your desktop runs on your GPU. Geometry, timing and compositing are identical; a driver-level rounding difference in a gradient or a blur is possible.

The shape of it

Base URL

All paths in this section are relative to your backend origin plus /api.

DeploymentBase
Premation hostedhttps://motion-back-production.up.railway.app/api
Self-hostedhttps://<your-motion-back-host>/api
Local developmenthttp://localhost:4000/api

So POST /v1/renders means POST https://motion-back-production.up.railway.app/api/v1/renders.

The hosted origin is the deployment this documentation is written against. If you run your own motion-back, substitute your origin everywhere — the paths, verbs and payloads below are unchanged.

Versioning

Everything public lives under /v1. Routes outside /v1/auth, /projects, /assets, /ai, /sync, /admin — back the editor and the dashboard, are session-authenticated, and are not a public contract. An API key cannot call them: any /v1 route without an explicit scope mapping is session-only by default, and everything outside /v1 rejects pm_live_ keys outright.

Last updated on