Skip to content

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.

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

MethodPathPermissionWhat it does
POST/jobseditClip a video from a public link
GET/jobsreadYour 30 most recent jobs
GET/jobs/{job_id}readA job's status and progress
GET/jobs/{job_id}/clipsreadIts clips, best first, with scores and reasons
GET/jobs/{job_id}/downloadsreadFresh download links (?clip_id= for one)
PATCH/jobs/{job_id}/clips/{clip_id}editChange one clip's crop, captions or headline
GET/backgroundsreadThe loops a 9:16 clip can be stacked over
PATCH/jobs/{job_id}editChange the frame or captions of every clip
POST/jobs/{job_id}/reanalyzeeditLook again in the same video
POST/jobs/{job_id}/canceleditStop a job
POST/jobs/{job_id}/retryeditStart a failed or cancelled job again
POST/jobs/{job_id}/clips/{clip_id}/exporteditExport a clip at 1080p
GET/jobs/{job_id}/clips/{clip_id}/exportreadThat export's status and link
POST/jobs/{job_id}/clips/{clip_id}/schedulepublishPut a clip on the calendar
POST/uploadseditStart uploading a local file
POST/uploads/{upload_id}/chunks?offset=NeditSend one chunk
GET/uploads/{upload_id}editWhat has arrived
POST/uploads/{upload_id}/completeeditStart the job once every chunk is in
POST/captionseditCaption a whole video (when available)
GET/accountsreadConnected social accounts
GET/postsreadScheduled and published posts
POST/posts/{post_id}/readypublishRelease a held post
POST/posts/{post_id}/cancelpublishCancel a post
GET/usagereadClips minutes and AI credits left, and the caps
POST/quotereadWhat 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.

FieldTypeMeaning
aspect_ratio9:16 · 1:1 · 16:9Frame of the clips: 9:16 (default), 1:1 or 16:9.
caption_styleviral-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 · typewriterBurned-in caption look; none turns captions off. Default: Heavenly's highlight look.
caption_colorstringAccent colour of the spoken word, #RRGGBB.
clip_countintegerHow many clips to return. Default: as many as the video holds.
min_secondsnumberShortest clip. Default 30.
max_secondsnumberLongest clip. Default 60.
modeauto · high-retention · story · insight · funny · controversial · emotional · quotes · product · customWhat to look for: auto (default), funny, story, insight, quotes and more.
platformTikTok · Shorts · Reels · LinkedIn · XWhere the clips will be posted. Default Shorts.
instructionsstringPlain words about what to find, e.g. "every moment about pricing".
video_urlstring, requiredPublic https link to the video (YouTube, TikTok, Instagram, X, Twitch, Kick, Vimeo, a file URL…).
start_secondsnumberOnly clip from here (seconds).
end_secondsnumberOnly clip up to here (seconds).
titlestringName for the project.
202 Accepted
{
  "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:

200 OK
{
  "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.

FieldTypeMeaning
headlinestringThe line over the clip's first seconds. An empty string removes it.
caption_styleviral-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 · typewriterThis clip's caption look; none turns its captions off.
caption_colorstringAccent colour of the spoken word on this clip, #RRGGBB.
framingsingle · active · group · split · gamingWho 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_xobjectPlace 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_secondsnumberTrim: 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_secondsnumberTrim: end the clip here, in seconds of the source video.
reset_trimbooleanPut the clip back to the start and end Heavenly found.
background_idobjectStack 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.

FieldTypeMeaning
aspect_ratio9:16 · 1:1 · 16:9Frame of the clips: 9:16 (default), 1:1 or 16:9.
caption_styleviral-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 · typewriterBurned-in caption look; none turns captions off. Default: Heavenly's highlight look.
caption_colorstringAccent colour of the spoken word, #RRGGBB.
background_idobjectStack 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.

FieldTypeMeaning
modeauto · high-retention · story · insight · funny · controversial · emotional · quotes · product · customWhat to look for: auto (default), funny, story, insight, quotes and more.
platformTikTok · Shorts · Reels · LinkedIn · XWhere the clips will be posted. Default Shorts.
clip_countintegerHow many clips to return. Default: as many as the video holds.
min_secondsnumberShortest clip. Default 30.
max_secondsnumberLongest clip. Default 60.
instructionsstringPlain 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.

FieldTypeMeaning
aspect_ratio9:16 · 1:1 · 16:9Frame of the clips: 9:16 (default), 1:1 or 16:9.
caption_styleviral-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 · typewriterBurned-in caption look; none turns captions off. Default: Heavenly's highlight look.
caption_colorstringAccent colour of the spoken word, #RRGGBB.
clip_countintegerHow many clips to return. Default: as many as the video holds.
min_secondsnumberShortest clip. Default 30.
max_secondsnumberLongest clip. Default 60.
modeauto · high-retention · story · insight · funny · controversial · emotional · quotes · product · customWhat to look for: auto (default), funny, story, insight, quotes and more.
platformTikTok · Shorts · Reels · LinkedIn · XWhere the clips will be posted. Default Shorts.
instructionsstringPlain words about what to find, e.g. "every moment about pricing".
filenamestring, requiredThe file's name, with its extension.
size_bytesinteger, requiredThe file's exact size in bytes.
captionsobjectCaption the whole video instead of finding clips (needs Caption a video).
Terminal
$ 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.

FieldTypeMeaning
captionstring, requiredThe post's text.
networksarray, requiredConnected networks to post to (from list_connected_accounts), e.g. x, youtube-shorts.
run_atstring, requiredWhen it goes out, ISO 8601, at least 10 minutes ahead.
titlestringTitle, for networks that take one (YouTube).
timezonestringIANA 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.

FieldTypeMeaning
shapesource · 9:16 · 1:1Frame of the result: source keeps the video's own (default), or 9:16 / 1:1.
caption_styleviral-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 · typewriterCaption look. Default: Heavenly's highlight look.
caption_colorstringAccent colour, #RRGGBB.
video_urlstring, requiredPublic https link to the video (YouTube, TikTok, Instagram, X, Twitch, Kick, Vimeo, a file URL…).
start_secondsnumberOnly clip from here (seconds).
end_secondsnumberOnly clip up to here (seconds).
titlestringName for the project.

Errors

A refusal is { "error": "a sentence", "code": "a_code" } with the HTTP status, and Retry-After when waiting helps.

StatusCodeMeaning
400invalid_requestA field is missing or wrong; the message names it.
401unauthorizedNo token, or one that is unknown, expired or revoked.
402no_planThe account has no Heavenly plan.
402insufficient_creditsNot enough Clips minutes left this month for this video.
403token_scopeThe token lacks the permission the endpoint needs.
404not_foundNo such job, clip or upload on this account.
409idempotency_conflictThat Idempotency-Key was used with other settings.
400invalid_trimA trim stays inside the clip Heavenly found and is at least 3 seconds.
404background_not_foundThat background isn't on this account's shelf.
409background_needs_verticalA background goes under a 9:16 clip.
409no_splitHeavenly didn't find two people to stack in this video.
409not_a_clips_jobThat job captions a whole video: it has no clips to change.
409job_stateThe job isn't in a state that allows it (still rendering, not failed): poll it and ask again.
409export_pendingScheduling waits for the 1080p export: call again.
429rate_limitedToo many calls; wait Retry-After seconds.
429token_daily_capThe token's spending calls for today are used.
429export_busyAnother export of yours is rendering; retry shortly.
503clips_api_unavailableClips over the API is switched off right now.
503background_unavailableBackgrounds over the API aren't live yet; the app still can.
503split_unavailableStacking two people over the API isn't live yet; the app still can.
503trim_unavailableTrimming over the API isn't live yet; the app still can.
503adjust_unavailableChanging 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:

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

The 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.