# Cast Swap

> Substitua as pessoas ou o lugar de um vídeo de 4 a 30 segundos pelas suas imagens via REST, SDK ou MCP, mantendo o movimento, o enquadramento e o som.

Source: https://nodaro.ai/pt-BR/docs/developers/api/cast-swap

A **API do Cast Swap** substitui as pessoas, o lugar ou os dois em um vídeo curto pelas suas próprias imagens de referência. O vídeo mantém o movimento, o enquadramento, o ritmo e o som. Você faz a cotação de uma execução de graça, inicia a execução e acompanha a tarefa dela como qualquer outra.

O Cast Swap só roda no Nodaro Cloud. Em uma instalação auto-hospedada, as duas rotas respondem `404`. As rotas recebem um bearer token. Veja [Autenticação](https://nodaro.ai/docs/developers/api/authentication).

## Endpoints
| Método | Caminho | O que faz | Custo |
| --- | --- | --- | --- |
| `POST` | `/v1/cast-swap/estimate` | Faz a cotação de uma execução. Nada é criado nem reservado. | Grátis |
| `POST` | `/v1/cast-swap` | Inicia uma execução. Responde `202 { jobId }`. Acompanhe a tarefa com `GET /v1/jobs/:id`. | A execução cotada |

## O corpo da requisição
As duas rotas recebem o mesmo corpo.

| Campo | O que faz |
| --- | --- |
| `videoUrl` | Obrigatório. O clipe de origem, de 4 a 30 segundos. |
| `roles` | Obrigatório. Até 6 papéis de pessoa e 1 papel de lugar. Veja abaixo. |
| `postPrompt` | Opcional. As suas próprias palavras para o modelo, até 4.000 caracteres. Mencione um papel como `{role:N}`, em que N conta a partir de 1 sobre `roles`, incluindo os papéis vazios. |
| `preview` | Opcional. `true` renderiza 4 segundos do clipe, a partir de `previewStart`. |
| `previewStart` | Opcional. Onde a prévia começa, em segundos. O padrão é 0. |
| `quality` | Opcional. `"draft"`, o único valor hoje. |
| `sessionId`, `origin` | Opcionais. A sessão em que o resultado é arquivado, e o rótulo de origem do seu app. |

Um papel indica alguém, ou o cenário, no vídeo, e recebe de 0 a 3 imagens de uma pessoa ou de um lugar:

- **Uma pessoa:** `{ kind: "person", name, images, body?, outfit?, hair? }`. `name` diz quem é no vídeo, por exemplo `"the woman dancing"`. Cada um de `body`, `outfit` e `hair` é `"new"`, o padrão, que vem das suas imagens, ou `"original"`, que mantém o do vídeo.
- **O lugar:** `{ kind: "location", name?, images }`. O nome padrão é `"the setting"`.

Um papel sem imagens, vazio ou `null`, mantém essa pessoa ou esse lugar como no vídeo. É preciso pelo menos um papel com imagens, ou um `postPrompt`.

```json
{
"videoUrl": "https://example.com/clip.mp4",
"roles": [
{ "kind": "person", "name": "the woman dancing", "images": ["https://example.com/me.jpg"], "outfit": "original" }
],
"preview": true
}
```

## A cotação
`POST /v1/cast-swap/estimate` responde `{ totalCredits, breakdown: { castSwap, seedance }, settlesLower }`.

- `totalCredits` é `breakdown.castSwap`, a execução em si, mais `breakdown.seedance`, o modelo de vídeo que ela chama.
- `settlesLower: true` significa que a duração do resultado fica a cargo do modelo, então a cobrança final deve ser menor que a cotação.

## Repetições
Envie o mesmo cabeçalho `Idempotency-Key`, de 8 a 200 caracteres, quando tentar uma execução de novo. Uma repetição em cerca de 10 segundos responde `200 { jobId, deduped: true }` com um cabeçalho `X-Dedup-Hit: 1`. Uma repetição posterior responde `202 { jobId }` com a tarefa da primeira execução. Em ambos os casos, nada novo é iniciado nem cobrado. Sem uma chave, cada requisição é uma nova execução.

## Erros
Toda recusa acontece antes de qualquer cobrança. O corpo é `{ error: { code, message } }`.

| Status | `code` | Quando |
| --- | --- | --- |
| `400` | `validation_error` | O corpo não é válido: nenhum papel com imagens e nenhum `postPrompt`, papéis demais, mais de 3 imagens em um papel, um `postPrompt` com mais de 4.000 caracteres, ou um `origin` inválido. |
| `400` | `raw_reference_token` | O `name` de um papel ou o `postPrompt` contém um token de referência bruto: `{image:`, `{video:` ou `{audio:`, em maiúsculas ou minúsculas. |
| `400` | `invalid_role_mention` | `postPrompt` menciona `{role:N}` com N fora do intervalo de 1 ao número de papéis. |
| `400` | `unsafe_url` | `videoUrl` ou uma imagem de papel não é um endereço http ou https público. |
| `400` | `video_unreadable` | Não foi possível ler a duração do clipe. Ele não é um vídeo reproduzível. |
| `400` | `clip_out_of_range` | O clipe tem menos de 4 segundos ou mais de 30 segundos. |
| `400` | `preview_out_of_range` | Uma prévia passa do fim do clipe. `previewStart` mais 4 deve ser no máximo a duração do clipe. |
| `401` | `unauthorized` | Nenhum usuário autenticado. |
| `402` | `insufficient_credits` | Os seus créditos não cobrem a execução. Só em `POST /v1/cast-swap`. Em um espaço de trabalho, o limite que a recusou pode responder no lugar: `budget_exceeded`, `member_cap_exceeded` ou `user_allowance_exceeded`. |
| `404` | `not_found` | `sessionId` indica uma sessão que você não pode usar. Só em `POST /v1/cast-swap`. |
| `422` | `job_blocked` | A requisição foi bloqueada pela política de tarefas da implantação. Só em `POST /v1/cast-swap`. |
| `503` | `host_too_old` | Esta versão da plataforma não consegue executar o Cast Swap. |
| `503` | `estimate_unavailable` | O preço do modelo de vídeo não pode ser cotado agora. Tente de novo em instantes. Só em `/estimate`. |

## Pelo SDK
`client.castSwap` encapsula as duas rotas. Ele também pode ser importado como `@nodaro/sdk/cast-swap`.

```ts
run(params: CastSwapParams, options?: { idempotencyKey?: string }): Promise<{ jobId: string; deduped?: boolean }>
estimate(params: CastSwapParams): Promise<CastSwapEstimate>
```

`CastSwapParams` é o corpo da requisição acima. `CastSwapEstimate` é a cotação. `run()` envia `idempotencyKey` sem alteração como o cabeçalho `Idempotency-Key`. Sem ele, nenhum cabeçalho é enviado.

```ts
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() })
```

Acompanhe a tarefa com `client.jobs.get(jobId)`. A tarefa de uma prévia guarda todas as entradas no `input_data` dela, então você pode executá-la de novo com `preview: false` para renderizar o clipe inteiro com as mesmas escolhas.

## Pelo MCP
A ferramenta MCP é [`cast_swap`](https://nodaro.ai/docs/mcp/tools/video#cast_swap). Sem `confirm`, ela retorna a cotação e não cobra nada. Com `confirm: true`, depois que o usuário aceita o preço, ela inicia a execução e retorna um ID de tarefa.

## Perguntas frequentes

### O que o Cast Swap faz?

O Cast Swap substitui as pessoas, o lugar ou os dois em um vídeo curto pelas suas próprias imagens de referência. O movimento, o enquadramento, o ritmo e o som do vídeo continuam como estão.

### Qual pode ser a duração do vídeo de origem?

De 4 a 30 segundos. Um clipe mais curto ou mais longo é recusado com clip_out_of_range antes de qualquer cobrança.

### Como sei quanto uma execução do Cast Swap custa?

Chame POST /v1/cast-swap/estimate com o mesmo corpo que você vai executar. A chamada é gratuita e retorna totalCredits, com um detalhamento da execução em si e do modelo de vídeo que ela chama.

### Posso testar o visual antes de renderizar o clipe inteiro?

Sim. Envie preview como true para renderizar 4 segundos do clipe, a partir de previewStart. Uma prévia custa menos, e a tarefa dela guarda todas as entradas, então você pode executá-la de novo com preview como false.

### O Cast Swap funciona em uma instalação auto-hospedada?

Não. O Cast Swap só roda no Nodaro Cloud. Em uma instalação auto-hospedada, as duas rotas respondem 404.
