Cast Swap
Replace the people or the location in a 4 to 30 second video with your own reference images over REST, the SDK or MCP, keeping the motion, framing and sound.
Available on Nodaro Cloud
The Cast Swap API replaces the people, the location, or both in a short video with your own reference images. The video keeps its motion, framing, timing and sound. You quote a run for free, start it, and follow its job like any other.
Cast Swap runs on Nodaro Cloud only. On a self-hosted install, both routes answer 404. The routes take a bearer token. See Authentication.
Endpoints
| Method | Path | What it does | Cost |
|---|---|---|---|
POST | /v1/cast-swap/estimate | Quote a run. Nothing is created or reserved. | Free |
POST | /v1/cast-swap | Start a run. Answers 202 { jobId }. Follow the job with GET /v1/jobs/:id. | The quoted run |
The request body
Both routes take the same body.
| Field | What it does |
|---|---|
videoUrl | Required. The source clip, from 4 to 30 seconds. |
roles | Required. Up to 6 person roles and 1 location role. See below. |
postPrompt | Optional. Your own words for the model, up to 4,000 characters. Mention a role as {role:N}, where N counts from 1 over roles, empty roles included. |
preview | Optional. true renders 4 seconds of the clip, from previewStart. |
previewStart | Optional. Where the preview starts, in seconds. The default is 0. |
quality | Optional. "draft", the only value today. |
sessionId, origin | Optional. The session to file the result in, and your app's origin label. |
A role names someone, or the setting, in the video, and takes 0 to 3 images of one person or one place:
- A person:
{ kind: "person", name, images, body?, outfit?, hair? }.namesays who in the video, for example"the woman dancing". Each ofbody,outfitandhairis"new", the default, which takes it from your images, or"original", which keeps it from the video. - The location:
{ kind: "location", name?, images }. The default name is"the setting".
A role with no images, empty or null, keeps that person or place as in the video. At least one role with images, or a postPrompt, is required.
{
"videoUrl": "https://example.com/clip.mp4",
"roles": [
{ "kind": "person", "name": "the woman dancing", "images": ["https://example.com/me.jpg"], "outfit": "original" }
],
"preview": true
}The quote
POST /v1/cast-swap/estimate answers { totalCredits, breakdown: { castSwap, seedance }, settlesLower }.
totalCreditsisbreakdown.castSwap, the run itself, plusbreakdown.seedance, the video model it calls.settlesLower: truemeans the length of the result is left to the model, so the final charge is expected to be lower than the quote.
Repeats
Send the same Idempotency-Key header, 8 to 200 characters, when you retry a run. A repeat within about 10 seconds answers 200 { jobId, deduped: true } with an X-Dedup-Hit: 1 header. A later repeat answers 202 { jobId } with the first run's job. Either way, nothing new is started or charged. Without a key, every request is a new run.
Errors
Every refusal comes before anything is charged. The body is { error: { code, message } }.
| Status | code | When |
|---|---|---|
400 | validation_error | The body is not valid: no role with images and no postPrompt, too many roles, more than 3 images on a role, a postPrompt over 4,000 characters, or an invalid origin. |
400 | raw_reference_token | A role's name or the postPrompt contains a raw reference token: {image:, {video: or {audio:, in any case. |
400 | invalid_role_mention | postPrompt mentions {role:N} with N outside 1 to the number of roles. |
400 | unsafe_url | videoUrl or a role image is not a public http or https address. |
400 | video_unreadable | The length of the clip could not be read. It is not a playable video. |
400 | clip_out_of_range | The clip is shorter than 4 seconds or longer than 30 seconds. |
400 | preview_out_of_range | A preview runs past the end of the clip. previewStart plus 4 must be at most the clip's length. |
401 | unauthorized | No authenticated user. |
402 | insufficient_credits | Your credits cannot cover the run. POST /v1/cast-swap only. In a workspace, the limit that refused it can answer instead: budget_exceeded, member_cap_exceeded or user_allowance_exceeded. |
404 | not_found | sessionId names a session you cannot use. POST /v1/cast-swap only. |
422 | job_blocked | The request was blocked by the deployment's job policy. POST /v1/cast-swap only. |
503 | host_too_old | This platform version cannot run Cast Swap. |
503 | estimate_unavailable | The video model's price cannot be quoted right now. Try again shortly. /estimate only. |
From the SDK
client.castSwap wraps both routes. It is also importable as @nodaro/sdk/cast-swap.
run(params: CastSwapParams, options?: { idempotencyKey?: string }): Promise<{ jobId: string; deduped?: boolean }>
estimate(params: CastSwapParams): Promise<CastSwapEstimate>CastSwapParams is the request body above. CastSwapEstimate is the quote. run() sends idempotencyKey unchanged as the Idempotency-Key header. Without it, no header is sent.
const params: CastSwapParams = {
videoUrl,
roles: [{ kind: "person", name: "the woman dancing", images: [myPhotoUrl], outfit: "original" }],
preview: true,
}
const quote = await client.castSwap.estimate(params)
const { jobId } = await client.castSwap.run(params, { idempotencyKey: crypto.randomUUID() })Follow the job with client.jobs.get(jobId). A preview's job keeps every input in its input_data, so you can run it again with preview: false to render the full clip with the same choices.
From MCP
The MCP tool is cast_swap. Without confirm, it returns the quote and charges nothing. With confirm: true, after the user accepts the price, it starts the run and returns a job id.
Frequently asked questions
What does Cast Swap do?
Cast Swap replaces the people, the location, or both in a short video with your own reference images. The motion, the framing, the timing and the sound of the video stay as they are.
How long can the source video be?
From 4 to 30 seconds. A shorter or longer clip is refused with clip_out_of_range before anything is charged.
How do I know what a Cast Swap run costs?
Call POST /v1/cast-swap/estimate with the same body you will run. It is free and returns totalCredits, with a breakdown of the run itself and the video model it calls.
Can I try the look before I render the whole clip?
Yes. Send preview set to true to render 4 seconds of the clip, from previewStart. A preview costs less, and its job keeps every input, so you can run it again with preview set to false.
Does Cast Swap work on a self-hosted install?
No. Cast Swap runs on Nodaro Cloud only. On a self-hosted install, both routes answer 404.
Related
Jobs
Recast
Video
Face Swap
Last updated on
Recast
Recast an analyzed video with your own cast over REST: quote and buy a run, answer interactive gates, remix its audio, or import an authored script.
Studio productions
Read and edit Studio productions over REST: apply atomic operations, generate stills and clips, land finished jobs, plan the export and share or copy a film.