Video shot endpoints
Every /v1/videos/shots, /v1/videos/joins and /v1/videos/projects endpoint — render shots from a prompt with tagged characters and scenes, join finished shots into one video for free, and keep storyboards as projects.
A shot is a few seconds of video made from a prompt and pictures: tag your saved characters and scenes in the prompt — @Mia_Wong, @[Night Café] or @chr_… — and each tagged one becomes a reference picture. The people keep their faces, hair and clothes from their pictures, the place its look from its picture, and the shot comes with its own sound and spoken dialogue. Render each shot on its own, then join finished shots into one video at no charge.
The endpoints live under https://lovelyrouter.com/v1/videos. Authenticate with your API key as Authorization: Bearer $LOVELY_API_KEY (Authentication). The model is minimax-h3-video, powered by MiniMax H3 and under the MiniMax H3 terms: every shot sets consent: true. Try it without code in the playground.
Tags and pictures#
The prompt picks the pictures. List the characters and scenes the shot may use in characters and scenes (a project's whole library is fine); every one the prompt tags becomes a picture, in the order it is first mentioned.
@handle— the name with spaces as_:Mia Wong→@Mia_Wong. Case doesn't matter.@[Full Name]— any name as written:@[Night Café].@chr_…/@scn_…— the id, when two have the same name.
Words in double quotes are dialogue, spoken by the character tagged last before them: @Leo looks up: "You came.". Don't describe clothes or faces — the pictures carry them. A shot takes 1 to 9 pictures; a prompt without tags uses the only listed character (and the only listed scene), if there is exactly one.
Voices#
A tagged character with a voice brings it: their lines are spoken in that voice (its timbre, pitch and manner; not its words or background sound). The first 3 voiced characters of a shot, in picture order, bring their voices — the model takes at most 3 audio references; the others are voiced by the model. An inline character can carry voice too: a saved voice id (voice_…) or an http(s) / data: URL of a sample. Voices don't change the price.
Who is who: the prompt sweep#
The video model needs to know who every word is about, so pronouns — he, she, his, her, they (and 他, 她, 彼, 彼女) — outside the dialogue never reach it. Before a shot renders:
- One character: every pronoun becomes that character's tag (
her coat→@Mia's coat), at once and free. - Two or more: a chat model (
sweep_model, defaultgemini-3.8-flash) rewrites the prompt so every person is named by their tag every time. The answer is checked — the same tags, the same dialogue word for word, no pronoun left — and tried once more if it fails the check; if it still does, the prompt renders as you sent it. The shot never fails because of the sweep.
The prompt rendered is the shot's prompt_rendered (null when nothing changed); prompt stays as you sent it. The chat call is billed as an ordinary chat request on the same key and shown as other_usd; it is not part of the shot's price. Send sweep: false to render your prompt exactly as written. Words inside double quotes are never changed.
Pricing#
A shot is billed per rendered second: the model renders on a grid of 17k+5 frames at 24 fps (at least 124 frames), so a 5 s shot renders 5.17 s and a 10 s shot 10.13 s — rendered_seconds in the shot object. The price is $0.13 per rendered second with one picture, plus $0.06 per second for each extra picture: a 5 s shot with two characters and a scene costs ($0.13 + 2 × $0.06) × 5.17 s. The price is reserved when you submit and charged when the shot completes; failed and cancelled shots cost nothing. Joins and projects are free.
The shot object#
Attributes
idstringshot_ followed by 24 characters.
objectstringAlways video.shot.
statusstringqueued, in_progress, completed, failed or cancelled.
progressinteger0–100, estimated while rendering.
promptstringThe prompt as you sent it.
prompt_renderedstring | nullThe prompt after the sweep, when it changed anything.
secondsnumberThe length you asked for, 5–15.
rendered_secondsnumberThe length rendered and billed.
sizestring16:9, 9:16 or 1:1; width and height are the pixels once it is done.
referencesarrayThe tagged characters and scenes in picture order: {picture, kind, name, id} (id is null for inline ones). Then the voices that came with them, in order: {audio, kind, name, id} (audio = 1–3).
project_idstring | nullThe project it belongs to.
urlstring | nullThe mp4, once completed. Kept for 7 days.
hold_usdcharge_usd` numberThe amount reserved, and the charge once settled.
other_usdnumberThe sweep's chat calls, billed as chat on the same key (not part of hold_usd / charge_usd).
errorobject | null{code, message} when it failed.
{
"id": "shot_8c1f0a7d3b9e4c2a6f5d1e0b",
"object": "video.shot",
"model": "minimax-h3-video",
"status": "completed",
"progress": 100,
"project_id": "prj_2a9e7c1b5d3f4e6a8c0b1d2e",
"prompt": "In @[Night Café], @Leo looks up as @Mia_Wong sits down: \"You came.\"",
"prompt_rendered": null,
"seconds": 5,
"rendered_seconds": 5.167,
"size": "16:9",
"width": 1344,
"height": 768,
"seed": null,
"language": "en",
"style": null,
"references": [
{ "picture": 1, "kind": "scene", "name": "Night Café", "id": "scn_5b2d9e0c1a7f4b3e8d6c2a1f" },
{ "picture": 2, "kind": "character", "name": "Leo", "id": "chr_1e4b7d0a9c3f2e5b8a6d0c1f" },
{ "picture": 3, "kind": "character", "name": "Mia Wong", "id": "chr_3b9e1f0c7a2d4e58b6c1a9f2" },
{ "audio": 1, "kind": "character", "name": "Mia Wong", "id": "chr_3b9e1f0c7a2d4e58b6c1a9f2" }
],
"url": "https://lovelyrouter.com/v1/videos/shots/shot_8c1f0a7d3b9e4c2a6f5d1e0b/content",
"hold_usd": 1.291667,
"charge_usd": 1.291667,
"other_usd": 0,
"error": null,
"powered_by": "MiniMax H3"
}Create a shot#
/v1/videos/shotsStarts a shot and returns 202 with the shot object (200 if it finished within wait). Send JSON, or multipart with characters / scenes as JSON text fields.
Body parameters
modelstringRequiredminimax-h3-video.
promptstringRequiredWhat happens, with characters and scenes tagged. Up to 4,000 characters.
consentbooleanRequiredtrue accepts the MiniMax H3 terms.
charactersarraySaved character ids (chr_…), or inline ones: {name, image, description?} with image an http(s) or data: URL — or, in multipart, the name of a file part. Inline characters may add voice: a saved voice id or an http(s) / data: URL of a sample (see Voices).
scenesarraySaved scene ids (scn_…), or inline {name, image, description?}.
secondsnumberDefault 55–15.
sizestringDefault 16:916:9, 9:16 or 1:1.
languagestringThe language the dialogue is spoken in, e.g. en, zh, ja. Default English.
stylestringThe look, e.g. warm 35mm film. Default live action.
seedinteger0–2147483647, for repeatable results.
sweepbooleanDefault truefalse renders the prompt as written, without the sweep.
sweep_modelstringDefault gemini-3.8-flashThe chat model that names everyone when two or more characters are in the shot. Any chat model your key can use.
project_idstringA project of yours to file it under.
waitinteger0–300 seconds to hold the request until it finishes (also ?wait=).
curl https://lovelyrouter.com/v1/videos/shots \
-H "Authorization: Bearer $LOVELY_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "minimax-h3-video",
"consent": true,
"prompt": "In @[Night Café], @Leo looks up as @Mia_Wong sits down: \"You came.\"",
"characters": ["chr_3b9e1f0c7a2d4e58b6c1a9f2", "chr_1e4b7d0a9c3f2e5b8a6d0c1f"],
"scenes": ["scn_5b2d9e0c1a7f4b3e8d6c2a1f"],
"seconds": 5
}'Retrieve, list and delete shots#
/v1/videos/shots/{id}The shot object. ?wait=1–300 holds until it finishes or the time runs out. GET /v1/videos/shots/{id}/content is the mp4 (Range requests work). GET /v1/videos/shots lists your shots, newest first: ?limit=1–100, ?before=<shot id> for the next page, ?project_id= for one project's.
DELETE /v1/videos/shots/{id} removes a shot from your list and deletes its file. A queued shot is cancelled for free; one already rendering still finishes and is charged.
curl "https://lovelyrouter.com/v1/videos/shots/SHOT_ID?wait=60" \
-H "Authorization: Bearer $LOVELY_API_KEY"
curl https://lovelyrouter.com/v1/videos/shots/SHOT_ID/content \
-H "Authorization: Bearer $LOVELY_API_KEY" -o shot.mp4Join shots#
/v1/videos/joinsJoins your completed shots into one video, in the order given, each with its own sound. Free. The shots must share one size and be less than 7 days old. Returns 202 with the join object: {id: "join_…", object: "video.join", status, shots, size, seconds, width, height, url, charge_usd: 0}. GET /v1/videos/joins/{id} (with ?wait=), /content, GET /v1/videos/joins and DELETE work like shots.
Body parameters
shotsarrayRequired2–50 shot ids, in the order they play.
project_idstringA project of yours to file it under.
curl https://lovelyrouter.com/v1/videos/joins \
-H "Authorization: Bearer $LOVELY_API_KEY" \
-H "content-type: application/json" \
-d '{"shots": ["shot_8c1f0a7d3b9e4c2a6f5d1e0b", "shot_0d2e4f6a8b1c3e5d7f9a0b2c"]}'Projects#
/v1/videos/projectsA project keeps a storyboard: a name and a JSON document of your own (doc) — the playground stores its size, style, language, shots, characters and scenes there. Free; up to 100 per account. POST takes {name?, doc?} and returns 201 with {id: "prj_…", object: "video.project", name, doc, created_at, updated_at}.
| Request | What it does |
|---|---|
GET /v1/videos/projects | Your projects, last changed first, without doc (?limit, ?after). |
GET /v1/videos/projects/{id} | One project, with doc. |
PATCH /v1/videos/projects/{id} | {name?, doc?}; doc is replaced whole, up to 200 KB. |
DELETE /v1/videos/projects/{id} | Deletes the project; its shots stay. |
curl https://lovelyrouter.com/v1/videos/projects \
-H "Authorization: Bearer $LOVELY_API_KEY" \
-H "content-type: application/json" \
-d '{"name": "Night café", "doc": {"size": "16:9", "shots": []}}'Errors#
Errors use the standard error body. Prompt errors carry a code and the tags concerned.
| Status | code / type | Why |
|---|---|---|
400 | unknown_tags | The prompt tags a name that isn't in characters or scenes; error.tags lists them. |
400 | ambiguous_tag | Two listed characters or scenes share the tagged name: tag one by its id. |
400 | no_pictures / too_many_pictures | No picture, or more than 9. |
400 | consent_required | consent wasn't true. |
400 | mixed_sizes | A join of shots of different sizes. |
400 | invalid_request_error | A field out of range, an unreadable picture, pictures over 24 MB in total, a join of shots that haven't completed or have expired. |
402 | insufficient_quota | The balance doesn't cover the reserve. |
404 | not_found_error | An unknown model, shot, join, project, character or scene, or one of another account. |
429 | rate_limit_exceeded | Five shots or joins are already queued or rendering on your account. |