Motion API
Overview
A job turns plain English into a finished motion design: titles, callouts and lower thirds timed across the length you choose, in a look you choose. You create a job, poll it until it completes, then open the design in the Motion designer, download its scene document, or refine it with a followup. Jobs run in the background, so a create returns at once.
$ curl -X POST https://lenslab.dev/api/heavenly-motion/sessions \
-H "Authorization: Bearer hv_pat_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"prompt":"A 20-second launch teaser for Heavenly","aspect_ratio":"9:16"}'The same API is also reachable at /api/heavenly-workspace/motion/…, and from Claude Code or any MCP client (see MCP tools below).
Authentication
Send an access token as Authorization: Bearer hv_pat_…. Make one in Settings → Workspace → API tokens. Creating jobs, followups and uploads needs the edit permission; polling and listing need read. The account needs a Heavenly plan, and a job is visible only to the account that made it (anyone else gets a 404). A token may make 25 credit-spending calls a day. Responses are never cached.
Create a job
POST https://lenslab.dev/api/heavenly-motion/sessions returns 202. Unknown fields are refused.
| Field | Type | Meaning |
|---|---|---|
| prompt | string, required | 1–12,000 characters. |
| aspect_ratio | 16:9 · 9:16 · 1:1 · 4:5 | Default 16:9. 21:9 isn’t offered: Heavenly’s frame has these four shapes. |
| duration | <10s · 10-30s · 30s-1min · 1-5min | Default 10-30s. Longer designs are planned in passes of a minute at most. |
| design_system_id | a look | heavenly (Heavenly), editorial (Editorial), mono (Mono), neon (Neon), sunset (Sunset), ocean (Ocean), forest (Forest), paper (Paper), broadcast (Broadcast), playful (Playful). Default heavenly. |
| design_md | string or object | Your own look as a DESIGN.md, up to 60,000 characters. Either the text, or { filename, content, jsonFilename?, jsonContent? }. It wins over design_system_id. |
| style_reference_url | YouTube URL | A public YouTube video whose pacing the design should match. |
| attachments | array, up to 100 | References: { url, name?, type?, content_type? }. Public https links only. |
{
"job_id": "5c1f0e7a-9b3a-4b6e-8f0e-2d9c6a1b7e44",
"status": "queued",
"status_url": "https://lenslab.dev/api/heavenly-motion/sessions/5c1f0e7a-9b3a-4b6e-8f0e-2d9c6a1b7e44",
"chat_url": "https://lenslab.dev/heavenly/dashboard/motion/jobs?job=5c1f0e7a-…",
"credits": 1
}Send an Idempotency-Key header (8–100 characters) when you might retry: the same key from the same account returns the first job with 200 and never spends twice.
Poll a job
GET https://lenslab.dev/api/heavenly-motion/sessions/{job_id}. Wait poll_after_seconds (3) between calls; when it is null nothing more will change by itself. GET https://lenslab.dev/api/heavenly-motion/sessions lists your recent jobs.
| Status | Meaning |
|---|---|
| queued | Accepted, waiting for a worker. Credits are reserved. |
| created | A worker has it and is getting ready. |
| running | Reading references, designing, building the project. |
| awaiting_user_input | The designer couldn’t make anything of the prompt and asks a question (question). Answer with a followup. |
| completed | Done. output and sources are set. Credits are charged. |
| failed | error and error_code say why. Credits are returned. |
{
"job_id": "5c1f0e7a-…",
"status": "completed",
"stage": "Done",
"poll_after_seconds": null,
"output": {
"status": "completed",
"project_id": "0b6c…",
"project_url": "/heavenly/dashboard/motion?project=0b6c…",
"download_url": "https://…signed…",
"expires_at": "2026-10-04T02:00:00.000Z",
"format": "heavenly-motion-scene+json",
"summary": { "pieces": 5, "layers": 14, "duration_seconds": 20,
"frame": { "width": 1080, "height": 1920 },
"rounds": 2, "planner": "model", "notes": [] }
},
"sources": [ … ]
}download_url (set on a single poll, left null in the list) is a signed link to the design’s scene document, valid for an hour and signed again on every poll. Statuses: queued, created, running, awaiting_user_input, completed, failed. A job that hasn’t finished 20 minutes after it started is failed rather than left waiting, and an interrupted worker’s job is picked up again within a minute.
Followups
POST https://lenslab.dev/api/heavenly-motion/sessions/{job_id}/followups takes the same body as a create (only prompt is required) and returns the same 202. It is allowed when the job is completed or awaiting_user_input; any other status is a 409. A followup keeps the earlier job’s shape, length, look and references unless you set them again, reads the earlier design back, and makes a new project; the earlier result is untouched. Answering a waiting job closes it and returns its credits.
Uploads and attachments
Attachments are links the server can reach without cookies or headers. For a local file, upload it first:
POST https://lenslab.dev/api/heavenly-motion/uploadswith{ filename, content_type }returnsupload_url,complete_urland the size limit.- Send the file to
upload_url: an HTTPPUT, multipart form data withcacheControl=3600and the file as the unnamed part, headerx-upsert: false. The link is good for an hour. POSTtocomplete_url. It checks the file finished and fits, and returnsattachment_url, valid for 7 days. Pass that inattachments.
| Kind | Largest | Types |
|---|---|---|
| video | 5,120 MB | video/mp4, video/quicktime, video/x-msvideo, video/webm, video/x-matroska, video/x-m4v |
| audio | 500 MB | audio/mpeg, audio/mp3, audio/wav, audio/mp4, audio/x-m4a, audio/flac, audio/ogg, audio/aac, audio/opus, audio/webm |
| image | 50 MB | image/png, image/jpeg, image/jpg, image/gif, image/webp, image/svg+xml |
You can start 100 uploads every 5 hours (429 after that). Uploaded files are deleted after 14 days. A link that points at a private address, redirects, or isn’t https is not fetched.
What the designer does with an attachment: up to 4 JPEG, PNG or WebP images are shown to it for colour, subject and mood. Anything else is kept on record as a reference and not analysed. Nothing you attach is placed in the design itself; the sources audit says so for each one.
Style reference
Give a public YouTube link and a model watches it for timing only: the average time between changes, the tempo, how text enters and leaves. The designer is told to match that rhythm. No footage, audio or text is copied. If the video can’t be read the job carries on without it, and the audit says whether it was read.
Looks and DESIGN.md
Ten built-in looks, each a set of colours, a typeface and a feel for how things move: heavenly, editorial, mono, neon, sunset, ocean, forest, paper, broadcast, playful. Or pass your own DESIGN.md. Heavenly reads the lines it can use: a hex colour on a line that names a role (primary or background, accent, text, on-primary), a typeface (serif, Inter, Montserrat), and a motion word (snappy, smooth, playful, cinematic). A JSON token file is read the same way. The rest of the file goes to the designer as written direction, so a file that only names a background still works: the text colour is chosen to be readable on it.
Sources audit
Every completed job lists where its material came from, built from what actually happened. source_kind is generated for the motion Heavenly made (with a usages entry per piece: start, end, scene, slot and element), upload or web for your attachments, and web for a style reference. rights_status is generated, user_provided or restricted (a style reference, used for timing only). Heavenly’s designer doesn’t place stock footage, articles or web media in a design, so none is claimed. Links to your own storage are never repeated back.
Credits
Credits are reserved when a job is accepted, charged when it completes, and returned if it fails or is replaced. A job costs 1 for <10s, 1 for 10-30s, 1 for 30s-1min, 3 for 1-5min. With too few you get a 402 and nothing is queued. Credits are bought in the app, not through the API.
Errors
| Status | Body | When |
|---|---|---|
| 400 | error: Invalid request, details[] | A field is missing, too long or not one of the values. |
| 401 | error | No token, or the token is expired or revoked. |
| 402 | error: insufficient_credits, code, minimum_required_credits, current_balance | No plan, or not enough credits. |
| 403 | code: token_scope / token_not_allowed | The token lacks edit or read. |
| 404 | error: Job not found | No such job, or it belongs to another account. |
| 409 | error: followup_requires_completed_job, status | A followup on a job that is still running or failed; or an upload not finished. |
| 413 | error: file_too_large / request_too_large | A file over its limit, or a body that is too large. |
| 429 | error: rate_limited / code: token_daily_cap | Too many uploads, or the token’s daily limit. |
| 503 | error: storage_unavailable | Uploads aren’t available on this server. |
A job can fail with designer_not_configured, designer_unavailable, designer_busy (retried automatically before it fails), project_limit, timed_out or internal_error. A failed job never delivers a half-made design.
MCP tools
With the Heavenly MCP an agent can do all of this: create_motion_design, create_motion_followup, get_motion_design, list_motion_designs, upload_motion_asset and finish_motion_upload. They are the same calls under the same token, plan and daily limit. Buying credits, changing plans and managing tokens are not available to a token, by design.
What a job is, and isn’t
A job’s result is a Heavenly motion design: a validated scene (text, shapes, particles and grain with their timing), saved as a project. It plays and exports in the Motion designer; the server doesn’t render video pixels. It isn’t footage: there is none to edit, so the designer composes on the look’s own background. Open it in the Motion designer to add your footage and export.
