Lovely RouterDocs
Get a key
API reference

Video replacement endpoints

Every /v1/videos/replacements endpoint — upload a whole video, get its shots and the people in it, map each person to a character, and get the video back with them swapped.

Swap people in a whole video — cuts, several shots, several people. Upload it once: its shots and everyone in it are found ($0.01 per second of video). Then say who becomes who — a saved character, a picture, or nobody — and whether to swap the whole person or only the head (face, hair and anything worn on it). Every shot with a swapped person is re-rendered by MiniMax H3; the other shots stay exactly as they are; the shots are joined back over the original sound.

The endpoints live under https://lovelyrouter.com/v1/videos/replacements. Authenticate with your API key as Authorization: Bearer $LOVELY_API_KEY (Authentication). The models are minimax-h3-character-swap and minimax-h3-aftermidnight, under the MiniMax H3 terms: every upload sets consent: true. Try it without code in the playground.

How it works#

  1. Upload (POST /v1/videos/replacements): the video (or the part between start and end, up to 3 minutes) is cut into its shots and everyone in it is grouped into a cast, each with a face and a half-body picture; every person is tracked through every shot, so people seen from behind or turned away are found too. $0.01 per second analysed (at least 5 s), held from your balance at upload and charged when it's ready; nothing if the analysis fails. Status analyzing → ready.
  2. Render (POST …/{id}/render): map cast members to characters. Shots without a mapped person are kept as they are; the others are re-rendered piece by piece (rendering) and joined back (stitching → succeeded). Every piece starts and ends on a cut and is at least about 5 s long: quick shots in a row render together with their cuts kept (a short shot with nobody to swap may be taken along — it stays as it is and isn't billed); only a shot longer than 15 s is split, into near-equal pieces.
  3. Download …/{id}/content. Pieces can be watched as they finish (pieces[].url).

Before the first shot renders, a vision model looks at each character's picture and writes down what it shows — hair, anything worn on the head, glasses, and in whole-person mode the clothes — and what it doesn't ("no glasses, no headphones"), so every shot paints that look and adds nothing else. It is billed as a chat call on your key (about $0.003 per character).

Before each shot renders, a model watches and listens to it and notes who speaks, and stages the shot: where each swapped person is, which way they face and how they move, arms included, so the new person keeps the original's pose and motion. The shot's description names the speaker, and a swapped person who doesn't speak keeps their mouth still instead of mouthing someone else's lines. It is billed as a call on your key (about $0.03 per shot).

Who is who comes from faces: in each shot, the people the tracker follows are matched to the cast by face, so a person seen from behind for a whole shot may not be found there — that shot then comes back unchanged and isn't charged.

Pricing#

Billed per second of video swapped: the seconds of the pieces where someone was actually replaced — minimax-h3-character-swap $0.20 per second, each swapped piece counting at least 5 seconds (it renders on FastH3 Omni, whose shortest render is 5 s); minimax-h3-aftermidnight $0.12 per second, each swapped piece at least 2 seconds — every video at least 3 seconds. Finding the shots and the people is $0.01 per second (charged once, at upload; run again and templates don't analyse again); shots that stay unchanged are free. The most it can cost (every piece with a mapped person) is reserved when you render and the rest returned when it's done.

The replacement object#

Attributes

idstring

rpl_ followed by 24 characters.

objectstring

Always video.replacement.

statusstring

analyzing, ready, rendering, stitching, succeeded, failed or cancelled.

secondssize` number

The part worked on and its size, once analysed.

shotsarray

{index, start, end, people} — seconds, and the cast ids seen in the shot, left to right.

castarray

{id, seconds, shots, appearances, face_url, body_url} — one per person found (p1, p2, …, most screen time first).

modebackground

What the render asked for.

piecesarray | null

Once rendering: {index, shot, shots, start, seconds, people, status, swapped, missed, last_retry, url} — shot is the piece's first camera shot, shots every camera shot in it, kept those taken along that stay as they are; status is pending, rendering, replaced, unchanged (nobody mapped was found in it), kept (nobody mapped was in it), failed or retrying. Once a piece is done, swapped and missed list its mapped people who were and weren't found — retry a piece that missed someone (below).

progressinteger | null

0–100 while rendering.

replaced_secondsnumber | null

Seconds swapped (planned while rendering, billed once done).

holdcharge` number | null
analysis_chargenumber — what finding the people cost (0 for a run again)

Dollars reserved, and charged once settled.

source_urlstring | null

The video as you uploaded it (GET …/{id}/source, your key), kept 7 days after the upload; null after that.

urlstring | null

The finished mp4, kept 7 days.

rerun_untilinteger | null

Unix time until which POST …/{id}/rerun can run a finished replacement again (2 days after the upload); null otherwise.

errorobject | null

{type, message} when it failed.

A replacement waiting for its cast
{
  "id": "rpl_5e0c2b9a7f1d4e3b8a6c0d2f",
  "object": "video.replacement",
  "model": "minimax-h3-character-swap",
  "status": "ready",
  "seconds": 32.04,
  "size": "768x1344",
  "shots": [
    { "index": 0, "start": 0, "end": 5.04, "people": ["p1"] },
    { "index": 1, "start": 5.04, "end": 10.04, "people": ["p3", "p2"] }
  ],
  "cast": [
    { "id": "p1", "seconds": 14.2, "shots": [0, 2, 6], "face_url": "https://lovelyrouter.com/v1/videos/replacements/rpl_5e0c…/cast/p1/face", "body_url": "…/cast/p1/body", "appearances": [] },
    { "id": "p2", "seconds": 10, "shots": [1, 4], "face_url": "…/cast/p2/face", "body_url": "…/cast/p2/body", "appearances": [] }
  ],
  "pieces": null,
  "url": null,
  "error": null,
  "powered_by": "MiniMax H3"
}

Upload a video#

POST/v1/videos/replacements

Returns 202 with the replacement object, status analyzing. Poll it (?wait=) until ready.

Body parameters

modelstringRequired

minimax-h3-character-swap or minimax-h3-aftermidnight.

videofile | string

MP4, MOV or WebM up to 95 MB: a multipart file, or an http(s) / data: URL.

templatestring

A template (rvt_…) instead of video: no upload and no analysis, free; the replacement is ready within seconds.

consentbooleanRequired

true accepts the MiniMax H3 terms.

startend` number

Seconds: the part to work on, up to 3 minutes. Default: the whole video (up to 3 minutes).

waitinteger

0–300 seconds to hold the request while it is analysed.

cURL
curl https://lovelyrouter.com/v1/videos/replacements \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -F model=minimax-h3-character-swap -F consent=true \
  -F video=@party.mp4 -F start=12 -F end=72

Render#

POST/v1/videos/replacements/{id}/render

Starts the swap of a ready replacement and returns it, status rendering. Send JSON, or multipart with cast as a JSON text field and the pictures as files.

Body parameters

castobjectRequired

Cast id → {"character_id": "chr_…"} (a saved character), {"image": "<http(s) or data: URL>"} (a picture — in multipart, "file:<part name>"), or "keep". People not listed stay as they are. Up to 8 people.

modestringDefault person

person: the whole person is swapped — face, hair, body, clothes. face: only the head — face, hair and anything worn on it (glasses, headphones, hats); body and clothes stay.

backgroundstringDefault keep

keep: every shot keeps its place. new (mode person only): every shot is re-imagined in scene.

scenestring

With background: "new": where it happens. Up to 2,000 characters.

promptstring

Extra words for every shot. Up to 2,000 characters.

modelstring

Render with the other replacement model (the upload's otherwise).

seedinteger

For repeatable results.

addedarray

People the analysis missed (seen only from behind, too small): [{shot, x, y, time}] — the shot's index, where they are (fractions of the frame) and when (seconds, as shots count them; default: the middle of the shot). They become x1, x2, … in order: map them in cast like anyone else. Find them in GET …/{id}/preview.

skipobject

Shots to leave out per person: {"p1": [3, 7]} — shot indices where the analysis put someone else under that cast member (check each person's appearances). Those shots stay as they are for them and aren't billed for them.

curl https://lovelyrouter.com/v1/videos/replacements/RPL_ID/render \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "cast": {"p1": {"character_id": "chr_3b9e1f0c7a2d4e58b6c1a9f2"}, "p2": "keep"},
    "mode": "face"
  }'

Retry a shot#

POST/v1/videos/replacements/{id}/pieces/{n}/retry

A finished replacement, one piece again — for a piece that missed someone (pieces[].missed) or swapped the wrong person. Up to 2 days after the upload (retry_until).

  • search string, optional Who to find, in a few plain words ("man in a black suit", at most 60 characters), instead of any person. Whole-person mode only.
  • points array, optional [{person, x, y, time}] — a tap on a mapped person: x, y fractions of the frame, time seconds into the video (default: the middle of the piece). The person under the point is followed through the piece.

The piece's price is reserved; it is charged only if someone is swapped. The video stays downloadable while the piece renders (status rendering, then succeeded with the piece in it). A retry that finds nobody, or fails, keeps the earlier version and isn't charged (pieces[].last_retry.outcome: swapped, none or failed).

cURL
curl https://lovelyrouter.com/v1/videos/replacements/RPL_ID/pieces/0/retry \
  -H "Authorization: Bearer $LOVELY_API_KEY" -H "Content-Type: application/json" \
  -d '{"search": "man in a black suit", "points": [{"person": "p2", "x": 0.18, "y": 0.43, "time": 1.4}]}'

Run it again#

POST/v1/videos/replacements/{id}/rerun

A new replacement from a finished one (succeeded, failed or cancelled): the same upload, cast mapping, pictures, mode, background, scene and prompt, with a new seed — new takes of every piece. Nothing is uploaded or analysed again; it starts rendering at once and is priced, held and charged like any render. Up to 2 days after the first upload (rerun_until). No body.

Answers with the new replacement object (status rendering). The first one is left as it is. A finished job whose analysed video is no longer kept answers 410: upload the video again.

cURL
curl -X POST https://lovelyrouter.com/v1/videos/replacements/RPL_ID/rerun \
  -H "Authorization: Bearer $LOVELY_API_KEY"

Save as a template#

POST/v1/videos/replacements/{id}/save

Save an analysed video as a template to make more replacements from it later — other people swapped, other characters — without uploading or paying for the analysis again. Saving copies its shots, cast and people tracks; it's free and kept until you delete it, up to 20 per account. Save within 2 days of the upload (the analysed video is kept that long). Body: name (optional, up to 80 characters; default: the file name).

Answers 202 with the template, status saving, then ready (a few seconds to a minute). Use it with template on POST /v1/videos/replacements.

The template object

idstring

rvt_….

namestatus` string

status: saving, ready or failed (with error.message).

fromstring

The replacement it was saved from.

secondswidth
castarray

[{id, seconds, shots, face_url, body_url}] — the same cast ids as the replacement it came from.

  • GET …/templates — your templates, newest first.
  • GET …/templates/{id} — one; PATCH with {"name"} renames it; DELETE deletes it (replacements made from it keep their own copy).
  • GET …/templates/{id}/cast/{person}/face (or /body) — the cast pictures.
cURL
curl -X POST https://lovelyrouter.com/v1/videos/replacements/RPL_ID/save \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "Content-Type: application/json" -d '{"name": "Party, take 1"}'

curl https://lovelyrouter.com/v1/videos/replacements \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "minimax-h3-character-swap", "consent": true, "template": "RVT_ID"}'

Retrieve, list and delete#

GET/v1/videos/replacements/{id}

The replacement object; ?wait=1–300 holds until it changes (a step, or a piece finishing). GET /v1/videos/replacements lists yours, newest first (?limit=1–100, ?after=<id>).

  • GET …/{id}/content — the finished mp4 (Range requests work).
  • GET …/{id}/pieces/{n}/content — one swapped piece, while the rest renders.
  • GET …/{id}/source — the video as you uploaded it (the part you sent, under its file name), for 7 days.
  • GET …/{id}/cast/{p}/face and …/body — a cast member's pictures (JPEG).
  • GET …/{id}/preview — the analysed part as a small video (640 px, with its sound) on the same timeline as shots, to find someone the analysis missed; kept 2 days.
  • POST …/{id}/outfit — what one person wears, cut out of a frame of the analysed video (free; for drawing your character in their outfit). Body {"person": "p3"} (optionally "shot": one of their shots) or {"added": {"shot", "x", "y", "time"}} → 202 {"call", "shot", "shots"}: the shot used and every shot that shows them, best first (people change clothes between shots). Then poll GET …/{id}/outfit/{call}: 202 while it runs (~10 s; ~40 s when the GPU has to start), then the JPEG — only them, by their own mask, on white, head removed (a hat kept). 422: they can't be seen clearly in that shot — try another.
  • DELETE …/{id} — before rendering: discards it (nothing more is charged; a finished analysis stays paid). While rendering: stops it; pieces already swapped are charged. Finished: deletes the file.
cURL
curl "https://lovelyrouter.com/v1/videos/replacements/RPL_ID?wait=60" \
  -H "Authorization: Bearer $LOVELY_API_KEY"

curl https://lovelyrouter.com/v1/videos/replacements/RPL_ID/content \
  -H "Authorization: Bearer $LOVELY_API_KEY" -o swapped.mp4

Getting good results#

  • People are told apart by their faces: a cast member split in two (a profile in one shot, a frontal face in another) can be mapped to the same character twice.
  • For mode: face, a picture of the character's head works best; for mode: person, a full-body picture or a turnaround sheet.
  • Shots shorter than 2 seconds still work (they are rendered a little longer and trimmed back).