Nodaro Docs
DocumentationNode ReferenceModelsTemplatesAI Agents (MCP)DevelopersSelf-hostingResearchCompare
REST API

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

MethodPathWhat it doesCost
POST/v1/cast-swap/estimateQuote a run. Nothing is created or reserved.Free
POST/v1/cast-swapStart 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.

FieldWhat it does
videoUrlRequired. The source clip, from 4 to 30 seconds.
rolesRequired. Up to 6 person roles and 1 location role. See below.
postPromptOptional. 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.
previewOptional. true renders 4 seconds of the clip, from previewStart.
previewStartOptional. Where the preview starts, in seconds. The default is 0.
qualityOptional. "draft", the only value today.
sessionId, originOptional. 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? }. name says who in the video, for example "the woman dancing". Each of body, outfit and hair is "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 }.

  • totalCredits is breakdown.castSwap, the run itself, plus breakdown.seedance, the video model it calls.
  • settlesLower: true means 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 } }.

StatuscodeWhen
400validation_errorThe 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.
400raw_reference_tokenA role's name or the postPrompt contains a raw reference token: {image:, {video: or {audio:, in any case.
400invalid_role_mentionpostPrompt mentions {role:N} with N outside 1 to the number of roles.
400unsafe_urlvideoUrl or a role image is not a public http or https address.
400video_unreadableThe length of the clip could not be read. It is not a playable video.
400clip_out_of_rangeThe clip is shorter than 4 seconds or longer than 30 seconds.
400preview_out_of_rangeA preview runs past the end of the clip. previewStart plus 4 must be at most the clip's length.
401unauthorizedNo authenticated user.
402insufficient_creditsYour 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.
404not_foundsessionId names a session you cannot use. POST /v1/cast-swap only.
422job_blockedThe request was blocked by the deployment's job policy. POST /v1/cast-swap only.
503host_too_oldThis platform version cannot run Cast Swap.
503estimate_unavailableThe 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.

Last updated on

On this page