Skip to Content
Automation APIEndpoint reference

Endpoint reference

Base URL: your backend origin + /api. On the hosted deployment that is https://motion-back-production.up.railway.app/api.

Conventions

AuthAuthorization: Bearer <key or JWT>, or X-API-Key: <key>
Content typeapplication/json on every request with a body
Pagination?limit=&offset=limit defaults to 50, capped at 100
List envelope{ items, total, limit, offset }
TimestampsISO 8601, UTC
Errors{ statusCode, message, error, requestId, path }

In the Credential column, session means a signed-in JWT only; key means an API key with the named scope, and a session JWT also works.

API keys

MethodPathCredentialReturns
GET/v1/keyssessionPaginated ApiKeySummary[]
POST/v1/keyssessionApiKeySummary + secret
DELETE/v1/keys/{id}session{ "revoked": true }

These three are session-only by design. An API key calling them gets 403 session_required regardless of its scopes.

Create body

{ "name": "n8n production", "scopes": ["renders:read", "renders:write", "templates:read", "usage:read"], "expiresAt": "2027-08-27T00:00:00.000Z" }

name defaults to "API key" and is truncated to 80 characters. scopes defaults to the four above. expiresAt must be in the future, or omitted.

ApiKeySummary

{ id: string name: string prefix: string // "pm_live_ab12cd" — not the secret scopes: string[] createdAt: string lastUsedAt: string | null requestCount: number expiresAt: string | null revokedAt: string | null }

The create response adds secret: string, returned once and never again.

Templates

MethodPathCredentialReturns
GET/v1/templateskey · templates:readPaginated templates
GET/v1/templates/{id}key · templates:readOne template
POST/v1/templateskey · templates:writeThe created template
POST/v1/templates/importkey · templates:writeTemplate + requiredInputs + warnings
DELETE/v1/templates/{id}key · templates:write{ "deleted": true }

Import takes multipart/form-data, not JSON:

FieldRequiredNotes
fileyesThe .motion bundle directory, zipped. Legacy single-file projects work unzipped.
namenoDefaults to the composition’s name
descriptionnoUp to 500 characters
compIdnoWhich composition; defaults to the project’s active one
includeTextno"true" also exposes text layers

Inputs are derived from layer names, and a layer whose source is local becomes required. See Automating a project you already have.

ErrorMeaning
nothing_to_exposeNo media layers, so an automation would have nothing to change
unrenderable_assetsA layer’s source cannot be fetched and nothing will replace it

Template

{ id: string name: string description: string | null width: number height: number fps: number durationSeconds: number inputs: TemplateInput[] createdAt: string updatedAt: string }

TemplateInput

{ id: string // /^[a-z][a-zA-Z0-9]{0,63}$/ label: string kind: 'text' | 'color' | 'number' | 'image' | 'media' required?: boolean // defaults true for image/media }

Publish bodyname, document, inputs, width, height, fps, durationSeconds required; description and projectId optional. See Templates & inputs.

Renders

MethodPathCredentialReturns
POST/v1/renderskey · renders:write{ jobId, status }
GET/v1/renderskey · renders:readPaginated jobs
GET/v1/renders/{id}key · renders:readOne job
POST/v1/renders/{id}/cancelkey · renders:write{ jobId, status }
GET/v1/renders/{id}/downloadkey · renders:read302 to the MP4

Create body

{ templateId: string inputs: Record<string, string | number> output?: { format?: 'mp4' width?: number // 64–4096 height?: number // 64–4096, w × h ≤ 16 MP fps?: number // 1–120 } callbackUrl?: string // public http(s) only }

Optional header: Idempotency-Key, 8–128 characters from A-Z a-z 0-9 . _ : -.

Job

{ jobId: string status: 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled' progress: number videoUrl: string | null error: string | null createdAt: string }

Assets

Upload bytes, get a URL a render can read. The route that removes S3 from the middle of a workflow.

MethodPathCredentialReturns
POST/v1/assetskey · renders:write{ url, bytes, mime, expiresAt }

multipart/form-data with the file in a file field.

{ url: string // pass straight into a render's `inputs` bytes: number mime: string // determined from the bytes, not from what you declared expiresAt: string // 24 hours out }

Accepted: PNG, JPEG, WebP, GIF, SVG, MP4, WebM, QuickTime, MP3, WAV, M4A. Size is capped by your plan’s maxUploadBytes; over it is 413 naming the limit. A declared type that contradicts the actual bytes is 400 — but a generic application/octet-stream is read as “no type given” and the bytes decide.

Uploads are temporary by design and count against assetProcessingBytes, the same meter URL ingestion uses. For a permanent asset library, use the editor — /assets is a separate, session-authenticated surface and is not part of this API.

Usage

MethodPathCredentialReturns
GET/v1/usagekey · usage:readCurrent period usage and limits

Shape in Plans, quotas & limits.

Animation presets

The cloud-synced preset library. Not video templates — see the note.

MethodPathCredentialReturns
GET/v1/animationskey · templates:readPaginated presets
GET/v1/animations/{id}key · templates:readOne preset
POST/v1/animationskey · templates:writeUpserted preset
DELETE/v1/animations/{id}key · templates:write{ "deleted": true }

POST upserts on name: publishing the same name again replaces the data and increments version rather than creating a second row.

{ "name": "Bouncy fade in", "animationData": { "…": "preset payload" } }

Webhook delivery

Not an endpoint you call — one Premation sends. POST to your callbackUrl, once per terminal transition.

HeaderValue
X-Premation-Eventrender.finished
X-Premation-Job-IdThe job id
{ jobId: string status: 'completed' | 'failed' | 'cancelled' videoUrl: string | null error: string | null renderDurationMs: number | null }

Unsigned. Confirm with GET /v1/renders/{id} before acting — see Webhooks.

Not public

Everything outside /v1/auth, /projects, /assets, /ai, /sync, /files, /billing, /plugins, /admin — backs the editor, the dashboard and the operator console. Those routes are session-authenticated, change without notice, and reject pm_live_ keys. Do not build on them.

Last updated on