# Cast Swap

> REST、SDK、MCP で、4〜30 秒の動画の人物や場所を自分のリファレンス画像に置き換えます。動き、構図、音声はそのまま残ります。

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

**Cast Swap API** は、短い動画の人物、場所、またはその両方を、あなた自身のリファレンス画像に置き換えます。動画の動き、構図、タイミング、音声はそのまま残ります。実行の見積もりを無料で出し、実行を開始して、ほかのジョブと同じようにそのジョブを追跡します。

Cast Swap は Nodaro Cloud でのみ動作します。セルフホスティング環境では、どちらのルートも `404` を返します。これらのルートは Bearer トークンを受け取ります。[認証](https://nodaro.ai/docs/developers/api/authentication)を参照してください。

## エンドポイント
| メソッド | パス | 内容 | 料金 |
| --- | --- | --- | --- |
| `POST` | `/v1/cast-swap/estimate` | 実行の見積もりを出します。何も作成も確保もされません。 | 無料 |
| `POST` | `/v1/cast-swap` | 実行を開始します。`202 { jobId }` を返します。`GET /v1/jobs/:id` でジョブを追跡します。 | 見積もった実行の料金 |

## リクエストボディ
どちらのルートも同じボディを受け取ります。

| フィールド | 内容 |
| --- | --- |
| `videoUrl` | 必須。元のクリップで、4〜30 秒です。 |
| `roles` | 必須。人物のロールを最大 6 個と、場所のロールを 1 個です。下記を参照してください。 |
| `postPrompt` | 任意。モデルに渡すあなた自身の言葉で、最大 4,000 文字です。ロールは `{role:N}` で指定します。N は `roles` を 1 から数えた番号で、空のロールも数えます。 |
| `preview` | 任意。`true` にすると、クリップの `previewStart` から 4 秒をレンダリングします。 |
| `previewStart` | 任意。プレビューの開始位置で、単位は秒です。デフォルトは 0 です。 |
| `quality` | 任意。`"draft"` で、現在はこの値だけです。 |
| `sessionId`、`origin` | 任意。結果を保存するセッションと、あなたのアプリの origin ラベルです。 |

ロールは、動画の中の誰か、または舞台を指し、1 人の人物か 1 か所の場所の画像を 0〜3 枚受け取ります。

- **人物：** `{ kind: "person", name, images, body?, outfit?, hair? }`。`name` は動画の中の誰かを示します。たとえば `"the woman dancing"`（踊っている女性）です。`body`、`outfit`、`hair` はそれぞれ、デフォルトの `"new"` ならあなたの画像から取り、`"original"` なら動画のまま残します。
- **場所：** `{ kind: "location", name?, images }`。デフォルトの名前は `"the setting"`（舞台）です。

画像のないロール（空または `null`）は、その人物や場所を動画のまま残します。画像のあるロールが少なくとも 1 個、または `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
}
```

## 見積もり
`POST /v1/cast-swap/estimate` は `{ totalCredits, breakdown: { castSwap, seedance }, settlesLower }` を返します。

- `totalCredits` は、実行そのものである `breakdown.castSwap` と、呼び出す動画モデルである `breakdown.seedance` の合計です。
- `settlesLower: true` は、結果の長さがモデルに任されることを意味します。そのため、最終的な請求は見積もりより低くなる見込みです。

## 繰り返し
実行を再試行するときは、同じ `Idempotency-Key` ヘッダー（8〜200 文字）を送ります。約 10 秒以内の繰り返しは、`X-Dedup-Hit: 1` ヘッダー付きで `200 { jobId, deduped: true }` を返します。それより後の繰り返しは、最初の実行のジョブで `202 { jobId }` を返します。どちらの場合も、新しく開始や課金されるものはありません。キーがない場合、リクエストはすべて新しい実行になります。

## エラー
拒否はすべて、何かが課金される前に行われます。ボディは `{ error: { code, message } }` です。

| ステータス | `code` | 条件 |
| --- | --- | --- |
| `400` | `validation_error` | ボディが有効ではありません。画像のあるロールも `postPrompt` もない、ロールが多すぎる、ロールの画像が 3 枚を超える、`postPrompt` が 4,000 文字を超える、または `origin` が無効な場合です。 |
| `400` | `raw_reference_token` | ロールの `name` か `postPrompt` に、生のリファレンストークン（`{image:`、`{video:` または `{audio:`、大文字小文字を問わず）が含まれています。 |
| `400` | `invalid_role_mention` | `postPrompt` が、1 からロールの数までの範囲外の N で `{role:N}` を指定しています。 |
| `400` | `unsafe_url` | `videoUrl` かロールの画像が、公開された http または https のアドレスではありません。 |
| `400` | `video_unreadable` | クリップの長さを読み取れませんでした。再生できる動画ではありません。 |
| `400` | `clip_out_of_range` | クリップが 4 秒より短いか、30 秒より長いです。 |
| `400` | `preview_out_of_range` | プレビューがクリップの終わりを越えています。`previewStart` に 4 を足した値は、クリップの長さ以下でなければなりません。 |
| `401` | `unauthorized` | 認証されたユーザーがいません。 |
| `402` | `insufficient_credits` | クレジットが実行の料金に足りません。`POST /v1/cast-swap` のみです。ワークスペースでは、代わりに拒否した上限が返ることがあります：`budget_exceeded`、`member_cap_exceeded` または `user_allowance_exceeded`。 |
| `404` | `not_found` | `sessionId` が、あなたが使えないセッションを指しています。`POST /v1/cast-swap` のみです。 |
| `422` | `job_blocked` | リクエストが、デプロイのジョブポリシーによってブロックされました。`POST /v1/cast-swap` のみです。 |
| `503` | `host_too_old` | このプラットフォームのバージョンでは Cast Swap を実行できません。 |
| `503` | `estimate_unavailable` | 現在、動画モデルの料金を見積もれません。少し後でもう一度試してください。`/estimate` のみです。 |

## SDK から
`client.castSwap` は両方のルートをラップします。`@nodaro/sdk/cast-swap` としてインポートすることもできます。

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

`CastSwapParams` は上記のリクエストボディです。`CastSwapEstimate` は見積もりです。`run()` は `idempotencyKey` をそのまま `Idempotency-Key` ヘッダーとして送ります。指定しない場合、ヘッダーは送られません。

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

`client.jobs.get(jobId)` でジョブを追跡します。プレビューのジョブは、すべての入力を `input_data` に保持します。そのため、`preview: false` でもう一度実行すれば、同じ選択でクリップ全体をレンダリングできます。

## MCP から
MCP ツールは [`cast_swap`](https://nodaro.ai/docs/mcp/tools/video#cast_swap) です。`confirm` を指定しない場合は、見積もりを返し、何も課金しません。ユーザーが料金を承認した後に `confirm: true` で呼び出すと、実行を開始してジョブ ID を返します。

## よくある質問

### Cast Swap では何ができますか？

Cast Swap は、短い動画の人物、場所、またはその両方を、あなた自身のリファレンス画像に置き換えます。動画の動き、構図、タイミング、音声はそのまま残ります。

### 元の動画は、どのくらいの長さまで使えますか？

4〜30 秒です。これより短いクリップや長いクリップは、何も課金される前に clip_out_of_range で拒否されます。

### Cast Swap の実行にかかる料金は、どうすればわかりますか？

実行するのと同じボディで POST /v1/cast-swap/estimate を呼び出します。この呼び出しは無料で、totalCredits と、実行そのものと呼び出す動画モデルの内訳が返されます。

### クリップ全体をレンダリングする前に、仕上がりを試せますか？

はい。preview を true にして送ると、クリップの previewStart から 4 秒をレンダリングします。プレビューは料金が安く、そのジョブはすべての入力を保持するので、preview を false にしてもう一度実行できます。

### Cast Swap は、セルフホスティング環境で使えますか？

いいえ。Cast Swap は Nodaro Cloud でのみ動作します。セルフホスティング環境では、どちらのルートも 404 を返します。
