Heavenly Docs
Your guide to editing videos, connecting AI apps, and working with Heavenly.
Introduction
What Heavenly is
Heavenly is LensLab’s video workspace, with a timeline and a Gemini assistant beside it. Describe an edit, review the proposed operations, then apply or undo them. Clip discovery and rendered MP4 downloads require a connected private media service.
The path that ships today:
- Choose your footage. Clip discovery uploads it for processing.
- Tell the assistant what to change and inspect its proposal.
- Apply the complete edit. Undo and redo walk revisions.
- Download a supported MP4 cut, or export an EDL, Final Cut XML, or shot sheet.
Four pieces
| Piece | What it is |
|---|---|
| Application | The studio at /heavenly/dashboard/edit. Timeline, captions, looks, export. |
| Agent | Gemini proposes validated edit operations. You preview and apply them. |
| API | The Clipping API turns a video link or file into short clips. The Motion API makes a motion design from a prompt. |
| MCP | https://lenslab.dev/api/heavenly-mcp lists Heavenly tools with no key. Project and publish calls need an hv_pat_ token. Footage stays on the machine. |
Timeline look settings: Clean, Handheld, Fast, Loud, Talk. Caption presets: Pop, Karaoke, Boxed, Bounce, Minimal, Outline. A setting in the editor is not a promise of rendered support: export checks the sequence and explains unsupported operations.
Footage
Local timeline editing preserves the original file. Clip discovery uploads the source to the private media service for transcription, candidate selection, previews, and export. Gemini chat sends your prompt and relevant editing context to the server. Jobs and media are scoped to your signed-in account; server credentials are never sent to the browser.
Quickstart
Your first edit
- Sign in with the same LensLab account.
- Open the workspace and choose a file you already have.
- For clip discovery, upload it and follow the processing job.
- Ask for an edit, review the proposal, and apply it. Undo if it is wrong.
- Preview the sound and picture, then download a supported MP4 or an edit interchange file.
$ open https://lenslab.dev/heavenly/dashboard/edit// Local deterministic helper, separate from Gemini chat. Not a published SDK.
const plan = planEdits("cut the pause", state);
// plan.steps are edit operations. plan.questions is what it will not guess.If the media service or a provider is unavailable, Heavenly reports that state. Processing and downloads are not simulated. Your existing local timeline remains editable.
Public API
The Motion API is live: a prompt in, a motion design out, over HTTP or MCP.
The Clipping API is live too: POST /api/heavenly/v1/jobs with a video link, and get scored, captioned clips back.
Authentication
App session
The studio uses the LensLab sign-in session. Media jobs validate that session and enforce ownership. Gemini and media-service credentials stay on the server; you do not paste keys into the chat. The local deterministic planning helper needs no provider key.
Access tokens
Tools that aren't a browser, such as Claude Code, sign in with an access token you make in Settings → Workspace → API tokens. A token does only what you allow (read, edit, publish), expires, and stops working the moment you revoke it. It can't change billing, manage teams, connect accounts, or make more tokens. It works with the Heavenly MCP, the Clipping API and the Motion API only.
The Motion API and the Clipping API accept the same token as a Bearer header. There is no general secret key.
The public LensLab MCP URL does not use a Heavenly token. A burst of bad requests is limited. See Rate Limits.
Rate limits and plans
Plans
Prices are USD. Yearly is the monthly rate when twelve months are prepaid.
| Plan | Monthly | Yearly | Published allowance |
|---|---|---|---|
| Hobby | 20 | 16 | 50 / month |
| Creator | 50 | 40 | 300 / month |
| Arena | 100 | 80 | 800 / month |
| Arena 2× | 200 | 160 | 1,600 / month |
The editor does not meter that allowance. Unused credits do not roll over (rollover is off). They are not cash.
Public MCP
These numbers are the live cap on https://lenslab.dev/api/heavenly-mcp. They are not a clipping quota.
| Requests / minute | 60 |
|---|---|
| Failed auth / minute | 10 |
| Body | 256 KB |
| Batch | 10 |
Over the cap the server answers HTTP 429 and sends Retry-After in seconds.
Errors
MCP errors
The public MCP speaks JSON-RPC. A limit looks like this. The Motion API's REST errors are listed on its own page.
{
"jsonrpc": "2.0",
"id": null,
"error": { "code": -32000, "message": "rate_limit" }
}HTTP
Public MCP rate limits return 429 with Retry-After. The app’s private media gateway returns 503 when its service is not configured or reachable. Jobs expose failure and cancellation in the workspace; an unsuccessful job never becomes a finished clip.
The Motion API has its own error bodies and status codes.
The Clipping API lists its error codes on its own page.
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.
Clipping
Overview
Give Heavenly a long video and get short clips back: it finds the moments worth posting, scores each one, reframes it to 9:16, 1:1 or 16:9 and burns in captions. The same Clips you use in the app, from code, an agent or the terminal. A job starts at once and runs in the background; poll it, read its clips, export the ones you want at 1080p, and put them on the calendar.
$ curl -X POST https://lenslab.dev/api/heavenly/v1/jobs \
-H "Authorization: Bearer hv_pat_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: launch-video-1" \
-d '{"video_url":"https://www.youtube.com/watch?v=VIDEO_ID","aspect_ratio":"9:16","caption_style":"hormozi"}'Authentication and limits
Send an access token as Authorization: Bearer hv_pat_…, made in Settings → Workspace → API tokens. Reading needs read, clipping and exporting need edit, scheduling needs publish. The account needs a Heavenly plan. A token makes 60 calls a minute (upload chunks have their own 600), and an account starts 30 clip jobs and 120 exports an hour. Clipping spends Clips minutes for the length of the video, exactly as in the app: held when the job starts, settled to the real length, returned if it fails. Exports are free.
| Method | Path | Permission | What it does |
|---|---|---|---|
| POST | /jobs | edit | Clip a video from a public link |
| GET | /jobs | read | Your 30 most recent jobs |
| GET | /jobs/{job_id} | read | A job's status and progress |
| GET | /jobs/{job_id}/clips | read | Its clips, best first, with scores and reasons |
| GET | /jobs/{job_id}/downloads | read | Fresh download links (?clip_id= for one) |
| PATCH | /jobs/{job_id}/clips/{clip_id} | edit | Change one clip's crop, captions or headline |
| GET | /backgrounds | read | The loops a 9:16 clip can be stacked over |
| PATCH | /jobs/{job_id} | edit | Change the frame or captions of every clip |
| POST | /jobs/{job_id}/reanalyze | edit | Look again in the same video |
| POST | /jobs/{job_id}/cancel | edit | Stop a job |
| POST | /jobs/{job_id}/retry | edit | Start a failed or cancelled job again |
| POST | /jobs/{job_id}/clips/{clip_id}/export | edit | Export a clip at 1080p |
| GET | /jobs/{job_id}/clips/{clip_id}/export | read | That export's status and link |
| POST | /jobs/{job_id}/clips/{clip_id}/schedule | publish | Put a clip on the calendar |
| POST | /uploads | edit | Start uploading a local file |
| POST | /uploads/{upload_id}/chunks?offset=N | edit | Send one chunk |
| GET | /uploads/{upload_id} | edit | What has arrived |
| POST | /uploads/{upload_id}/complete | edit | Start the job once every chunk is in |
| POST | /captions | edit | Caption a whole video (when available) |
| GET | /accounts | read | Connected social accounts |
| GET | /posts | read | Scheduled and published posts |
| POST | /posts/{post_id}/ready | publish | Release a held post |
| POST | /posts/{post_id}/cancel | publish | Cancel a post |
| GET | /usage | read | Clips minutes and AI credits left, and the caps |
| POST | /quote | read | What a clip or export will spend, without starting it |
Every path is under https://lenslab.dev/api/heavenly/v1.
Clip a link
POST /jobs returns 202 and the job. Unknown fields are refused. Send an Idempotency-Key (1–255 characters) when you might retry: the same key returns the first job and holds minutes once.
| Field | Type | Meaning |
|---|---|---|
| aspect_ratio | 9:16 · 1:1 · 16:9 | Frame of the clips: 9:16 (default), 1:1 or 16:9. |
| caption_style | viral-bold · clean-creator · high-energy · editorial · none · highlighted · scale · minimalist · box · staticbox · glow · hormozi · vlog · classic · pop-box · beast · karaoke · bold-pop · one-word · comic · neon · read-along · reveal · highlighter · clean · podcast · label · impact · cinema · elegant · marker · bubble · typewriter | Burned-in caption look; none turns captions off. Default: Heavenly's highlight look. |
| caption_color | string | Accent colour of the spoken word, #RRGGBB. |
| clip_count | integer | How many clips to return. Default: as many as the video holds. |
| min_seconds | number | Shortest clip. Default 30. |
| max_seconds | number | Longest clip. Default 60. |
| mode | auto · high-retention · story · insight · funny · controversial · emotional · quotes · product · custom | What to look for: auto (default), funny, story, insight, quotes and more. |
| platform | TikTok · Shorts · Reels · LinkedIn · X | Where the clips will be posted. Default Shorts. |
| instructions | string | Plain words about what to find, e.g. "every moment about pricing". |
| video_url | string, required | Public https link to the video (YouTube, TikTok, Instagram, X, Twitch, Kick, Vimeo, a file URL…). |
| start_seconds | number | Only clip from here (seconds). |
| end_seconds | number | Only clip up to here (seconds). |
| title | string | Name for the project. |
{
"id": "6c7a8bcb-c595-4c71-934c-08be77502832",
"kind": "clips",
"status": "queued",
"stage": "queued",
"progress": 0,
"message": "Waiting in queue",
"clips_ready": 0,
"poll_after_seconds": 15,
"app_url": "https://lenslab.dev/heavenly/dashboard/clips?project=6c7a8bcb-…"
}Follow it and read the clips
GET /jobs/{job_id} until status is completed (or failed, with error). Wait poll_after_seconds between calls. Then GET /jobs/{job_id}/clips:
{
"clips": [{
"id": "c-12-53",
"rank": 1,
"title": "The best moment",
"start_seconds": 12, "end_seconds": 53, "duration_seconds": 41,
"score": 93,
"scores": { "hook": 95, "payoff": 90 },
"reasons": ["Opens on a question", "Laughter at 0:31"],
"hook": "Nobody tells you this about launching",
"preview": { "url": "https://lenslab.dev/api/heavenly-clipping/jobs/…/media/c-12-53?ticket=…&download=1",
"expires_at": "2026-10-15T10:17:51.000Z" }
}]
}Download and export
Every link is signed and works for about an hour without a token. Store the clip, not the link: GET /jobs/{job_id}/downloads always signs fresh ones. A preview is ready to post; POST /jobs/{job_id}/clips/{clip_id}/export renders the 1080p MP4 with its captions and look. Calling it again never renders twice: poll it (or its GET) until status is completed, then download.url is the file and file its SHA-256 and size.
Adjust what Heavenly found
Heavenly crops, captions and trims each clip itself. Everything it chose can be changed afterwards without paying for the video again. Only the fields you send change, the clip's preview redraws (poll the job until completed), and the next export is a new one with the change in it. Clips list their current look.
One clip
PATCH /jobs/{job_id}/clips/{clip_id} takes a headline, caption look and colour, who the 9:16 crop follows, a hand-placed crop, or a trim: start_seconds and end_seconds in seconds of the source video, inside the clip Heavenly found (each clip lists that as trim_room) and at least 3 seconds. reset_trim puts the found clip back. It costs nothing.
| Field | Type | Meaning |
|---|---|---|
| headline | string | The line over the clip's first seconds. An empty string removes it. |
| caption_style | viral-bold · clean-creator · high-energy · editorial · none · highlighted · scale · minimalist · box · staticbox · glow · hormozi · vlog · classic · pop-box · beast · karaoke · bold-pop · one-word · comic · neon · read-along · reveal · highlighter · clean · podcast · label · impact · cinema · elegant · marker · bubble · typewriter | This clip's caption look; none turns its captions off. |
| caption_color | string | Accent colour of the spoken word on this clip, #RRGGBB. |
| framing | single · active · group · split · gaming | Who the 9:16 crop follows: single (one person), active (whoever speaks), group (everyone in frame), split (two people stacked, one above the other, as in a podcast; needs two people on screen) or gaming (stream camera above the game). |
| crop_x | object | Place the crop by hand: 0 is the left edge of the picture, 1 the right, 0.5 the centre. null goes back to the automatic crop. Holds one crop for the whole clip. |
| start_seconds | number | Trim: start the clip here, in seconds of the source video (as list_clips gives them). Inside the clip Heavenly found: trim_room says how far. |
| end_seconds | number | Trim: end the clip here, in seconds of the source video. |
| reset_trim | boolean | Put the clip back to the start and end Heavenly found. |
| background_id | object | Stack this clip over a looping background (an id from list_backgrounds), in the bottom half of a 9:16 frame. null turns the background off for this clip, even when the job has one. |
A background under the clip
A 9:16 clip can sit in the top half of its frame with a looping video in the bottom half, muted, the split-screen look many feeds use. GET /backgrounds lists what can go there: the loops Heavenly makes, and footage this account added in the Heavenly app, where its rights are stated. Send one as background_id; null turns it off. On one clip it overrides the job's.
Every clip
PATCH /jobs/{job_id} changes the frame or the caption look of every clip at once. A clip's own look still wins over it. It counts as one spending call for the token's day.
| Field | Type | Meaning |
|---|---|---|
| aspect_ratio | 9:16 · 1:1 · 16:9 | Frame of the clips: 9:16 (default), 1:1 or 16:9. |
| caption_style | viral-bold · clean-creator · high-energy · editorial · none · highlighted · scale · minimalist · box · staticbox · glow · hormozi · vlog · classic · pop-box · beast · karaoke · bold-pop · one-word · comic · neon · read-along · reveal · highlighter · clean · podcast · label · impact · cinema · elegant · marker · bubble · typewriter | Burned-in caption look; none turns captions off. Default: Heavenly's highlight look. |
| caption_color | string | Accent colour of the spoken word, #RRGGBB. |
| background_id | object | Stack every clip over a looping background (an id from list_backgrounds), in the bottom half of a 9:16 frame. null takes it off every clip that has not chosen its own. |
Look again
POST /jobs/{job_id}/reanalyze looks for something different in the video already analysed, for example {"instructions":"every moment about pricing"}. It reuses the transcript, so no Clips minutes are held, and it replaces the job's clips, including any change made to them. It counts as one spending call.
| Field | Type | Meaning |
|---|---|---|
| mode | auto · high-retention · story · insight · funny · controversial · emotional · quotes · product · custom | What to look for: auto (default), funny, story, insight, quotes and more. |
| platform | TikTok · Shorts · Reels · LinkedIn · X | Where the clips will be posted. Default Shorts. |
| clip_count | integer | How many clips to return. Default: as many as the video holds. |
| min_seconds | number | Shortest clip. Default 30. |
| max_seconds | number | Longest clip. Default 60. |
| instructions | string | Plain words about what to find, e.g. "every moment about pricing". |
POST /jobs/{job_id}/cancel stops a queued or running job and POST /jobs/{job_id}/retry starts a failed or cancelled one again. Until Heavenly's worker is updated these answer adjust_unavailable.
Upload a file
Start the upload with the file's name and exact size, send each offset in missing_offsets as raw bytes (chunk_bytes each, in any order, at most 100 MB per request), then complete it. Repeating a start with the same Idempotency-Key resumes the same upload. The CLI does all of this for you.
| Field | Type | Meaning |
|---|---|---|
| aspect_ratio | 9:16 · 1:1 · 16:9 | Frame of the clips: 9:16 (default), 1:1 or 16:9. |
| caption_style | viral-bold · clean-creator · high-energy · editorial · none · highlighted · scale · minimalist · box · staticbox · glow · hormozi · vlog · classic · pop-box · beast · karaoke · bold-pop · one-word · comic · neon · read-along · reveal · highlighter · clean · podcast · label · impact · cinema · elegant · marker · bubble · typewriter | Burned-in caption look; none turns captions off. Default: Heavenly's highlight look. |
| caption_color | string | Accent colour of the spoken word, #RRGGBB. |
| clip_count | integer | How many clips to return. Default: as many as the video holds. |
| min_seconds | number | Shortest clip. Default 30. |
| max_seconds | number | Longest clip. Default 60. |
| mode | auto · high-retention · story · insight · funny · controversial · emotional · quotes · product · custom | What to look for: auto (default), funny, story, insight, quotes and more. |
| platform | TikTok · Shorts · Reels · LinkedIn · X | Where the clips will be posted. Default Shorts. |
| instructions | string | Plain words about what to find, e.g. "every moment about pricing". |
| filename | string, required | The file's name, with its extension. |
| size_bytes | integer, required | The file's exact size in bytes. |
| captions | object | Caption the whole video instead of finding clips (needs Caption a video). |
$ curl -X POST https://lenslab.dev/api/heavenly/v1/uploads -H "Authorization: Bearer hv_pat_YOUR_TOKEN" -H "Content-Type: application/json" \
-d '{"filename":"podcast.mp4","size_bytes":734003200}'
$ curl -X POST "https://lenslab.dev/api/heavenly/v1/uploads/UPLOAD_ID/chunks?offset=0" -H "Authorization: Bearer hv_pat_YOUR_TOKEN" \
-H "Content-Type: application/octet-stream" --data-binary @chunk-0
$ curl -X POST https://lenslab.dev/api/heavenly/v1/uploads/UPLOAD_ID/complete -H "Authorization: Bearer hv_pat_YOUR_TOKEN" -H "Content-Type: application/json" -d '{}'Schedule a clip
POST /jobs/{job_id}/clips/{clip_id}/schedule exports the clip if needed (answering export_pending until it is ready), then puts it on the calendar as a video post to your connected accounts. The post is held: nothing goes out until POST /posts/{post_id}/ready. It must be at least 10 minutes ahead, and TikTok posts are made in Heavenly's composer.
| Field | Type | Meaning |
|---|---|---|
| caption | string, required | The post's text. |
| networks | array, required | Connected networks to post to (from list_connected_accounts), e.g. x, youtube-shorts. |
| run_at | string, required | When it goes out, ISO 8601, at least 10 minutes ahead. |
| title | string | Title, for networks that take one (YouTube). |
| timezone | string | IANA timezone the time was chosen in. |
Caption a whole video
POST /captions burns captions into a whole video from a link and returns a job; once it completes, captioned_video.url is the MP4. It answers captions_unavailable until Heavenly's caption worker is live. It spends Clips minutes like a clip job.
| Field | Type | Meaning |
|---|---|---|
| shape | source · 9:16 · 1:1 | Frame of the result: source keeps the video's own (default), or 9:16 / 1:1. |
| caption_style | viral-bold · clean-creator · high-energy · editorial · highlighted · scale · minimalist · box · staticbox · glow · hormozi · vlog · classic · pop-box · beast · karaoke · bold-pop · one-word · comic · neon · read-along · reveal · highlighter · clean · podcast · label · impact · cinema · elegant · marker · bubble · typewriter | Caption look. Default: Heavenly's highlight look. |
| caption_color | string | Accent colour, #RRGGBB. |
| video_url | string, required | Public https link to the video (YouTube, TikTok, Instagram, X, Twitch, Kick, Vimeo, a file URL…). |
| start_seconds | number | Only clip from here (seconds). |
| end_seconds | number | Only clip up to here (seconds). |
| title | string | Name for the project. |
Errors
A refusal is { "error": "a sentence", "code": "a_code" } with the HTTP status, and Retry-After when waiting helps.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A field is missing or wrong; the message names it. |
| 401 | unauthorized | No token, or one that is unknown, expired or revoked. |
| 402 | no_plan | The account has no Heavenly plan. |
| 402 | insufficient_credits | Not enough Clips minutes left this month for this video. |
| 403 | token_scope | The token lacks the permission the endpoint needs. |
| 404 | not_found | No such job, clip or upload on this account. |
| 409 | idempotency_conflict | That Idempotency-Key was used with other settings. |
| 400 | invalid_trim | A trim stays inside the clip Heavenly found and is at least 3 seconds. |
| 404 | background_not_found | That background isn't on this account's shelf. |
| 409 | background_needs_vertical | A background goes under a 9:16 clip. |
| 409 | no_split | Heavenly didn't find two people to stack in this video. |
| 409 | not_a_clips_job | That job captions a whole video: it has no clips to change. |
| 409 | job_state | The job isn't in a state that allows it (still rendering, not failed): poll it and ask again. |
| 409 | export_pending | Scheduling waits for the 1080p export: call again. |
| 429 | rate_limited | Too many calls; wait Retry-After seconds. |
| 429 | token_daily_cap | The token's spending calls for today are used. |
| 429 | export_busy | Another export of yours is rendering; retry shortly. |
| 503 | clips_api_unavailable | Clips over the API is switched off right now. |
| 503 | background_unavailable | Backgrounds over the API aren't live yet; the app still can. |
| 503 | split_unavailable | Stacking two people over the API isn't live yet; the app still can. |
| 503 | trim_unavailable | Trimming over the API isn't live yet; the app still can. |
| 503 | adjust_unavailable | Changing clips over the API isn't live yet; the app still can. |
Agents and the terminal
The same Clips is in the Heavenly MCP as clip_video, get_clip_job, list_clip_jobs, list_clips, get_clip_downloads, export_clip and schedule_clip, with adjust_clip, restyle_clips, find_more_clips, cancel_clip_job and retry_clip_job for access tokens once the worker is updated, and in the CLI:
$ node heavenly.mjs clip https://www.youtube.com/watch?v=VIDEO_ID --confirm --wait
$ node heavenly.mjs clip ./podcast.mp4 --confirm --aspect 9:16
$ node heavenly.mjs download JOB_ID CLIP_ID --out best.mp4The agent
Gemini can propose edits using the current project context. Timeline operations include cutting, trimming, splitting, speed, framing, and captions. Proposals are validated before application. If an operation fails, the whole batch leaves the previous sequence intact. Unsupported finishing operations are identified before export.
Operations the timeline can apply include cut_range, trim_clip, razor, set_aspect, set_look, and set_captions.
Jobs
A connected media service accepts uploads, persists jobs, transcribes speech, selects candidates, and renders previews with FFmpeg. The workspace shows progress, failures, cancellation, and downloadable results. Low-confidence or speechless input may produce no candidates instead of an invented result.
Keep/reject feedback and imported style references inform personalization within your account.
Captions
Styles
These presets are in the editor. Asking for one sets it on the project.
- Pop. One phrase at a time, bold with an outline. Keyword highlights pop in.
- Karaoke. Every word lights up as it is spoken.
- Boxed. Text on a solid rounded box, brand colour behind the words.
- Bounce. Words bounce in one by one. Loud, short-form energy.
- Minimal. Small, clean sans, no animation. Talking heads and interviews.
- Outline. Thick outline, no box. Readable over busy footage.
Generation, rendering, editing
The media service generates timed captions from a processed source transcript. Automatic transcription requires a configured transcription backend. You can also edit caption settings in the timeline or ask the assistant to change them.
Supported caption styles are burned into preview and final MP4 files. Editor and renderer share caption state. Bounce animation and oversized captions are blocked at MP4 export until supported, rather than silently omitted. Translation is not part of this workflow.
Final MP4 currently supports one processed source, contiguous cuts, speed, framing, and captions. Extra video layers, titles, effects, and an additional music mix are not yet composited. Export explains these limits. Undo restores the previous caption settings.
Automations
What an automation does
An automation watches one YouTube channel, or your own Twitch channel. Each new long-form upload, or each finished stream, is clipped with your Clips Template, the best clips are exported at 1080p, and they are posted to the accounts you choose at your posting times. Shorts, and videos published before the automation was set up, are left alone. A premiere is clipped once it has been published.
Setting one up
In Clips, open Automations and choose New automation. Paste the channel's link or @handle, then choose the clip length and focus, how many clips to post per video and the minimum score, the accounts, your posting times and timezone, and whether clips post automatically or wait for your approval. The Creator plan runs 1 automation at a time and Arena runs 5. Twitch automations and YouTube stream replays come with Arena. A paused automation doesn't count.
Twitch streams
Connect your Twitch account on the Integrations page, then choose My Twitch streams in New automation. Twitch doesn't let anyone download a whole stream, so Heavenly works from the clips Twitch shares with you: half an hour after a stream ends, it takes the clips your viewers made during it, most watched first, and the markers you placed, which Twitch cuts into clips of their own. Each one, at most a minute long, is downloaded through Twitch, framed and captioned with your Clips Template, and posted or held for your approval like any clip. A moment Clips finds no clean clip in is kept whole. A stream with no viewer clips and no markers is skipped. Only your own channel can be watched, and Twitch clips run up to 60 seconds, so clip lengths stop at 30–60s. Keep Store past broadcasts on in Twitch's stream settings: a stream is found by its video.
Clipping hours
Each upload is charged in Clips hours exactly like a pasted link: its length is held when clipping starts and settled when it finishes. Each Twitch clip is charged like an upload of its own length. Exporting and posting cost nothing. A video longer than Clips takes is clipped from its start, up to that length. When the month's hours run out, the automation pauses and emails you, keeping the video it found; resume it when you have hours again. Videos published while it is paused are not clipped.
Posting times and approval
Each clip takes the next free posting time on your clock, at least 15 minutes ahead, and two clips never share a time. With Hold for my approval, clips wait under their upload until you approve or skip each one. TikTok posts always wait for approval, because TikTok asks you to choose who sees each post. YouTube's made-for-kids answer and visibility, and a Pinterest board, are chosen once when the automation is set up. The calendar's daily limits per account apply: a full day moves the clip to the next.
From Claude and other agents
With an access token, the MCP tools find_youtube_channel, create_automation, list_automations, list_automation_runs, update_automation, pause_automation, resume_automation and delete_automation manage automations. Approving a held clip stays in Heavenly.
Scheduling
Status
A network that is not switched on yet shows Coming soon in Social accounts.
The calendar posts each scheduled cut at its time to the accounts you chose. Social accounts, opened from the rail, connects them with each network’s own sign-in.
From code or an agent, the Clipping API schedules a clip as a held video post; it goes out once it is marked ready.
Credits and usage
Allowance
Hobby is USD 20 a month. Creator is USD 50. Arena is USD 100, and Arena 2× is USD 200. The published monthly allowances are 50, 300, 800, and 1,600.
Meter
The editor does not subtract credits when you cut. Clips is paid in minutes of source video: a job holds them when it starts, settles to the video's real length, and gives them back if it fails; exporting a clip is free. GET /api/heavenly/v1/usage returns the Clips minutes and AI credits left. Motion designs are paid in credits: a job reserves them when it is accepted, charges them when it completes, and returns them if it fails (see the Motion API). Rollover is off. Credits are not cash.
MCP reference
Heavenly MCP
Drive Heavenly from Claude, ChatGPT, Cursor, Claude Code, and any other MCP-capable client. Read projects, preview edits, and save approved changes through one secure connection.
Connect Heavenly MCP · Get an API key
Introduction
The Heavenly MCP server lets assistants work with your saved projects and timeline. The server validates commands and returns a proposal; edits and publishing still require your approval. Footage stays on your device.
Base URL:
https://lenslab.dev/api/heavenly-mcp
Listing is public for discovery. Project, edit, credit, and publish tools require the correct account connection and scope.
Quickstart
Three steps: connect, list tools, then ask Heavenly to work on a project.
1. Connect your client
For OAuth clients, paste the URL above and sign in. For Claude Code and other token-based clients, create a scoped Heavenly access token first.
2. Check the connection
Run this read-only request to see the tools available to an unauthenticated client:
$ curl --fail-with-body --silent --show-error 'https://lenslab.dev/api/heavenly-mcp' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json, text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'3. Ask Heavenly
Try: “List my Heavenly projects.” Approve any edit or publishing proposal before it is saved.
Authentication
OAuth is the default for hosted clients such as Claude and ChatGPT. Claude Code and compatible terminal clients can use a scoped, revocable token prefixed withhv_pat_.
$ claude mcp add --transport http heavenly https://lenslab.dev/api/heavenly-mcp --header "Authorization: Bearer hv_pat_YOUR_TOKEN"Never commit a token to source control. Publishing requires the publish scope and an explicit approval step.
Tools
Tool visibility depends on the client connection and its granted scopes.
list_projectsget_projectcreate_projectget_timelinelist_edit_commandsedit_timelineask_editor_agentapply_editor_proposaldiscard_editor_proposalget_creditslist_connected_accountslist_postsschedule_postmark_post_readymove_postcancel_postcreate_motion_designcreate_motion_followupget_motion_designlist_motion_designsupload_motion_assetfinish_motion_uploadclip_videoget_clip_joblist_clip_jobslist_clipsget_clip_downloadsexport_clipschedule_clipadd_footageindex_footageget_footage_indexsearch_footagecreate_variant_batchgenerate_hooksplan_variantsset_hookrender_batchedit_variantcapture_framesexport_variantfind_youtube_channellist_automationslist_automation_runscreate_automationupdate_automationpause_automationresume_automationdelete_automation
Errors
Errors are returned as JSON-RPC errors with a stable code and a human-readable message. A missing or revoked token returns an authentication challenge; a missing scope explains which permission is required.
MCP and Heavenly
MCP is the connection layer that lets an AI assistant send approved commands to Heavenly. Running on Heavenly means using Heavenly's editor and workspace directly.
Claude Code
Use a scoped token to connect Claude Code to Heavenly. It can read projects, inspect timelines, propose edits, check credits, and schedule posts within the permissions you grant.
$ claude mcp add --transport http heavenly https://lenslab.dev/api/heavenly-mcp --header "Authorization: Bearer hv_pat_YOUR_TOKEN"See the interactive MCP setup for every client and tool.
CLI
Install
Clip from your terminal. Or let your agent do it.
npm install -g @heavenly/cli@1.0.0. The commands are heavenly and hv. Node.js 20 or newer. There are no runtime dependencies.
Clip
heavenly login then heavenly clip <url> --wait --download ./clips --json. JSON is the default when stdout is not a terminal. Progress is written to stderr. Exit codes run from 0 to 8.
Agents
Skill
Clip from your terminal. Or let your agent do it.
heavenly skill install --agent claude-code --scope user copies the skill. The same command takes cursor or codex. Always pass --json. Do not schedule or publish unless the person explicitly approved that post.
MCP
claude mcp add --transport http heavenly https://lenslab.dev/api/mcp. heavenly mcp install writes that entry for Claude Code, Cursor, Codex, or Claude Desktop and does not write a token into a project file.
Changelog
Editor
From the repository, not a launch list.
- The video agent landed on /video, then the public door moved to /heavenly.
- Revisions: each agent turn is numbered. Undo and redo move the head.
- Gemini chat proposes edits beside the workspace, with preview, apply, and undo.
- Private media jobs produce transcript-linked clips and supported MP4 downloads.
- Export checks capabilities before writing MP4, EDL, FCPXML, or a shot sheet.
- Plans are Hobby, Creator, Arena, and Arena 2×. Enterprise goes to sales.
Hosted media processing requires deployment and provider configuration. Full color grading, layered composition, multicam, and a public clipping API remain unfinished.
