Lovely RouterDocs
Get a key
API reference

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, default gemini-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

idstring

shot_ followed by 24 characters.

objectstring

Always video.shot.

statusstring

queued, in_progress, completed, failed or cancelled.

progressinteger

0–100, estimated while rendering.

promptstring

The prompt as you sent it.

prompt_renderedstring | null

The prompt after the sweep, when it changed anything.

secondsnumber

The length you asked for, 5–15.

rendered_secondsnumber

The length rendered and billed.

sizestring

16:9, 9:16 or 1:1; width and height are the pixels once it is done.

referencesarray

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

The project it belongs to.

urlstring | null

The mp4, once completed. Kept for 7 days.

hold_usdcharge_usd` number

The amount reserved, and the charge once settled.

other_usdnumber

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

The shot object
{
  "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#

POST/v1/videos/shots

Starts 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

modelstringRequired

minimax-h3-video.

promptstringRequired

What happens, with characters and scenes tagged. Up to 4,000 characters.

consentbooleanRequired

true accepts the MiniMax H3 terms.

charactersarray

Saved 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).

scenesarray

Saved scene ids (scn_…), or inline {name, image, description?}.

secondsnumberDefault 5

5–15.

sizestringDefault 16:9

16:9, 9:16 or 1:1.

languagestring

The language the dialogue is spoken in, e.g. en, zh, ja. Default English.

stylestring

The look, e.g. warm 35mm film. Default live action.

seedinteger

0–2147483647, for repeatable results.

sweepbooleanDefault true

false renders the prompt as written, without the sweep.

sweep_modelstringDefault gemini-3.8-flash

The chat model that names everyone when two or more characters are in the shot. Any chat model your key can use.

project_idstring

A project of yours to file it under.

waitinteger

0–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#

GET/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
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.mp4

Join shots#

POST/v1/videos/joins

Joins 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

shotsarrayRequired

2–50 shot ids, in the order they play.

project_idstring

A project of yours to file it under.

cURL
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#

POST/v1/videos/projects

A 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}.

RequestWhat it does
GET /v1/videos/projectsYour 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
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.

Statuscode / typeWhy
400unknown_tagsThe prompt tags a name that isn't in characters or scenes; error.tags lists them.
400ambiguous_tagTwo listed characters or scenes share the tagged name: tag one by its id.
400no_pictures / too_many_picturesNo picture, or more than 9.
400consent_requiredconsent wasn't true.
400mixed_sizesA join of shots of different sizes.
400invalid_request_errorA field out of range, an unreadable picture, pictures over 24 MB in total, a join of shots that haven't completed or have expired.
402insufficient_quotaThe balance doesn't cover the reserve.
404not_found_errorAn unknown model, shot, join, project, character or scene, or one of another account.
429rate_limit_exceededFive shots or joins are already queued or rendering on your account.