Skip to content

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.

Terminal
$ 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.

FieldTypeMeaning
promptstring, required1–12,000 characters.
aspect_ratio16:9 · 9:16 · 1:1 · 4:5Default 16:9. 21:9 isn’t offered: Heavenly’s frame has these four shapes.
duration<10s · 10-30s · 30s-1min · 1-5minDefault 10-30s. Longer designs are planned in passes of a minute at most.
design_system_ida lookheavenly (Heavenly), editorial (Editorial), mono (Mono), neon (Neon), sunset (Sunset), ocean (Ocean), forest (Forest), paper (Paper), broadcast (Broadcast), playful (Playful). Default heavenly.
design_mdstring or objectYour 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_urlYouTube URLA public YouTube video whose pacing the design should match.
attachmentsarray, up to 100References: { url, name?, type?, content_type? }. Public https links only.
202 Accepted
{
  "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.

StatusMeaning
queuedAccepted, waiting for a worker. Credits are reserved.
createdA worker has it and is getting ready.
runningReading references, designing, building the project.
awaiting_user_inputThe designer couldn’t make anything of the prompt and asks a question (question). Answer with a followup.
completedDone. output and sources are set. Credits are charged.
failederror and error_code say why. Credits are returned.
200 OK, completed
{
  "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:

  1. POST https://lenslab.dev/api/heavenly-motion/uploads with { filename, content_type } returns upload_url, complete_url and the size limit.
  2. Send the file to upload_url: an HTTP PUT, multipart form data with cacheControl=3600 and the file as the unnamed part, header x-upsert: false. The link is good for an hour.
  3. POST to complete_url. It checks the file finished and fits, and returns attachment_url, valid for 7 days. Pass that in attachments.
KindLargestTypes
video5,120 MBvideo/mp4, video/quicktime, video/x-msvideo, video/webm, video/x-matroska, video/x-m4v
audio500 MBaudio/mpeg, audio/mp3, audio/wav, audio/mp4, audio/x-m4a, audio/flac, audio/ogg, audio/aac, audio/opus, audio/webm
image50 MBimage/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

StatusBodyWhen
400error: Invalid request, details[]A field is missing, too long or not one of the values.
401errorNo token, or the token is expired or revoked.
402error: insufficient_credits, code, minimum_required_credits, current_balanceNo plan, or not enough credits.
403code: token_scope / token_not_allowedThe token lacks edit or read.
404error: Job not foundNo such job, or it belongs to another account.
409error: followup_requires_completed_job, statusA followup on a job that is still running or failed; or an upload not finished.
413error: file_too_large / request_too_largeA file over its limit, or a body that is too large.
429error: rate_limited / code: token_daily_capToo many uploads, or the token’s daily limit.
503error: storage_unavailableUploads 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.