# キャラクター

> REST API でキャラクターを作成、更新、アーカイブ、復元します。ポートレートの候補、表情、アングル、モーションクリップを生成し、基準となるポートレートを承認できます。

Source: https://nodaro.ai/ja/docs/developers/api/characters

**キャラクター API** を使うと、キャラクタースタジオでできることを、すべてスクリプトから実行できます。キャラクターを作成し、ポートレートの候補を生成して、そのうち 1 つを基準となるポートレートとして承認し、表情、アングル、ポーズ、ライティングのバリエーション、モーションクリップを追加します。その後は画像ノードと動画ノードがそのキャラクターを再利用するので、同じ人物がどのショットでも同じ見た目になります。

これらのルートは、すべてのエディションで使えます。認証には Bearer トークンを使います。個人用 API トークン（`ndr_…`）、OAuth アプリのトークン（`ndr_app_…`）、Community エディションではセッショントークンのいずれかです。すべてのルートは呼び出し元に限定されており、表示や変更ができるのは自分のキャラクターだけです。[認証](https://nodaro.ai/docs/developers/api/authentication)を参照してください。

## エンドポイント
| メソッド | パス | 説明 |
| --- | --- | --- |
| `GET` | `/v1/characters` | 自分のキャラクターを、1 ページずつ一覧表示します。 |
| `GET` | `/v1/characters/:id` | 1 つのキャラクターを、進行中のジョブとともに取得します。 |
| `POST` | `/v1/characters` | キャラクターを作成します。ボディに `id` がある場合は、そのキャラクターを更新します。 |
| `POST` | `/v1/characters/:id/duplicate` | キャラクターを、名前の末尾に `(copy)` が付いた新しいキャラクターとしてコピーします。 |
| `DELETE` | `/v1/characters/:id` | キャラクターをアーカイブします。アーカイブしたキャラクターは復元できます。 |
| `POST` | `/v1/characters/:id/restore` | アーカイブしたキャラクターを復元します。 |
| `GET` | `/v1/characters/:id/usage` | キャラクターを使っているワークフローの数と一覧を返します。 |
| `POST` | `/v1/generate-character` | ポートレートの候補を 1〜10 個生成します。 |
| `POST` | `/v1/generate-character-asset` | 表情、ポーズ、アングル、ライティングのバリエーションを 1 つ生成します。 |
| `POST` | `/v1/generate-character-motion` | キャラクターをアニメーション化して、モーションクリップを作ります。 |
| `POST` | `/v1/characters/:id/approve-portrait` | 候補をポートレートとして承認し、キャラクターの説明を作成します。 |
| `POST` | `/v1/characters/:id/llm-caption` | 現在のポートレートから、説明を作成し直します。 |

キャラクターで専用のモデルを学習させるのは Nodaro Cloud の機能で、専用のルートがあります。[キャラクター学習](https://nodaro.ai/docs/developers/api/character-training)を参照してください。

## キャラクターが持つ情報
キャラクターは、保存された 1 つのアイデンティティです。次のフィールドは、`GET /v1/characters/:id` からキャメルケース（camelCase）で返されます。

| フィールド | 説明 |
| --- | --- |
| `id`, `name` | 識別子と表示名です。名前は、大文字と小文字を区別せずに、アカウント内で一意です。 |
| `description`, `gender`, `style`, `baseOutfit` | 人物像についてのメモです。キャラクターの生成画像すべてに反映されます。 |
| `seedPrompt` | ポートレートの方向性を決める短いプロンプトで、最大 4,000 文字です。 |
| `sourceImageUrl` | 基準となるポートレートです。候補を承認すると設定されます。 |
| `canonicalDescription` | ポートレートの承認時に Nodaro が作成する、80〜120 語程度の見た目の説明です。キャラクターを参照するプロンプトには、この説明が含まれます。 |
| `expressions`, `poses`, `angles`, `bodyAngles`, `lightingVariations`, `motions` | アセットバケットです。各エントリーは `{ name, url }` です。 |
| `referencePhotos` | 最大 20 枚の実際の写真です。それぞれにフレーミングのタグが付きます。 |
| `realLifeRefsByVariant`, `referenceVideosByVariant` | 1 つのバリエーション（たとえば `smile` の表情）のための、追加のリファレンス写真やクリップです。 |
| `person`, `wardrobe` | キャラクタースタジオの「ピッカー」ページで設定する、外見と衣装の構造化された選択内容です。 |
| `voice`, `personality` | キャラクターのボイスと性格です。 |
| `identityLock` | 生成するアセットで、顔をどれだけ厳密に保つかです。`off`、`soft`、`strict` のいずれかで、デフォルトは `off` です。 |
| `deletedAt` | キャラクターをアーカイブすると設定されます。 |

### アセットバケット
各バケットには、基準となるポートレートのバリエーションが入ります。バリエーションには自由に名前を付けられます。次の表は、プリセットの名前です。

| バケット | 内容 | プリセットのバリエーション |
| --- | --- | --- |
| `expressions` | 頭と肩。別の感情の表情 | neutral, smile, angry, surprised, sad, talking, laughing, disgusted, fearful, smirk, crying |
| `angles` | 頭と肩を、別のカメラアングルから | front, 3/4 left, left profile, right profile, 3/4 right |
| `bodyAngles` | 全身を別のアングルから。腕は自然に下ろした状態 | front, 3/4 left, left profile, right profile, 3/4 right, back |
| `poses` | 別の姿勢の全身 | standing, walking, sitting, running, crouching, pointing, fighting stance, jumping, turning |
| `lightingVariations` | 同じポーズを、別の光のもとで | daylight, night, dramatic |
| `motions` | キャラクターが動く動画クリップ | 任意のラベル（たとえば walking や head turn） |

## キャラクターを一覧表示する
`GET /v1/characters` は、自分のキャラクターを新しい順に 1 ページ分返します。`nextCursor` が `null` になるまで、ページのリクエストを続けてください。キャラクターの数がページサイズを超えるアカウントでは、1 回のレスポンスが「すべてのキャラクター」になることはありません。

| クエリパラメーター | 説明 |
| --- | --- |
| `limit` | 1 ページあたりの行数です。デフォルトは 100、最大は 500 です。 |
| `cursor` | 前のページの `nextCursor` です。 |
| `projectId` | 1 つのプロジェクトのキャラクターだけを返します。 |
| `archived` | `true` にすると、代わりにアーカイブしたキャラクターを一覧表示します。 |

**curl**

```bash
CURSOR=""
while :; do
PAGE=$(curl -s "https://app.nodaro.ai/v1/characters?limit=100${CURSOR:+&cursor=$CURSOR}" \
    -H "Authorization: Bearer $NODARO_API_KEY")
echo "$PAGE" | jq -r '.characters[] | "\(.id) \(.name)"'
CURSOR=$(echo "$PAGE" | jq -r '.nextCursor // empty')
[ -z "$CURSOR" ] && break
done
```

**TypeScript SDK**

```ts

const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})

const all = []
let cursor: string | undefined
do {
const page = await client.characters.list({ limit: 100, cursor })
all.push(...page.characters)
cursor = page.nextCursor ?? undefined
} while (cursor)
```

**CLI**

```bash
nodaro characters list --limit 100 --json
nodaro characters list --archived
```

```json
{
"characters": [
{
"id": "3f6c2a9e-8d41-4b7a-9c35-1e2f7a6b0d94",
"name": "Kira",
"description": "young protagonist with auburn hair",
"sourceImageUrl": "https://cdn.nodaro.ai/characters/kira-portrait.png",
"expressions": [{ "name": "smile", "url": "https://cdn.nodaro.ai/characters/kira-smile.png" }]
}
],
"nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTA5LTIwVDEwOjAwOjAxWiJ9"
}
```

カーソルは不透明な値です。サーバーから受け取った `nextCursor` だけを渡し、リリースをまたいで保存しないでください。形式が正しくないカーソルは、黙って最初のページに戻るのではなく、`400 validation_error` になります。ページをたどっている間に作成されたキャラクターは含まれません。それらを表示するには、カーソルなしで最初からやり直してください。

## キャラクターを作成または更新する
`POST /v1/characters` は、ボディに `id` がない場合はキャラクターを作成し、`id` がある場合はそのキャラクターを更新します。作成には `nodeId` と `name` が必要です。`nodeId` は、キャラクターをキャンバスのノードに関連付けます。コードから作成する場合は、`"scripted"` のような任意のラベルを使います。

更新では、送信したフィールドだけが書き込まれます。省略したフィールドは値が保たれるので、保存によって、実行中のジョブが書き込んでいるアセットバケットが上書きされることはありません。アセットバケットは、置き換えるつもりのときだけ送信してください。

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/characters \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"nodeId": "scripted",
"name": "Kira",
"description": "young protagonist with auburn hair",
"style": "realistic",
"seedPrompt": "kira portrait, warm natural lighting"
}'
```

**TypeScript SDK**

```ts
const { id } = await client.characters.create({
nodeId: 'scripted',
name: 'Kira',
description: 'young protagonist with auburn hair',
style: 'realistic',
seedPrompt: 'kira portrait, warm natural lighting',
})

await client.characters.update(id, { gender: 'female', identityLock: 'soft' })
```

**CLI**

```bash
nodaro characters create --name "Kira" \
  --description "young protagonist with auburn hair" \
  --style realistic --seed-prompt "kira portrait, warm natural lighting"

nodaro characters update <id> --gender female
```

```json
{ "id": "3f6c2a9e-8d41-4b7a-9c35-1e2f7a6b0d94", "name": "Kira" }
```

<TypeTable
type={{
id: { type: 'string (uuid)', description: '更新するキャラクターです。キャラクターを作成する場合は省略します。' },
nodeId: { type: 'string', description: '作成時は必須です。キャラクターが属するキャンバスのノードです。ノードがない場合は、任意のラベルを指定します。' },
name: { type: 'string', description: '作成時は必須です。1〜200 文字です。ほかの有効なキャラクターがすでに使っている名前を指定すると、409 name_taken が返されます。' },
description: { type: 'string', description: '人物像についてのメモで、最大 2,000 文字です。' },
gender: { type: 'string', description: '最大 50 文字です。' },
style: { type: 'string', description: 'ビジュアルスタイルです。たとえば realistic、anime、3d-pixar、illustration です。' },
baseOutfit: { type: 'string', description: '普段の衣装で、最大 1,000 文字です。' },
seedPrompt: { type: 'string', description: 'ポートレートの方向性を決める短いプロンプトで、最大 4,000 文字です。これより長い値を指定すると、400 が返されます。' },
identityLock: { type: "'off' | 'soft' | 'strict'", description: '生成するアセットで、顔をどれだけ厳密に保つかです。', default: 'off' },
referencePhotos: { type: 'array', description: '最大 20 枚の { url, kind } 形式の写真です。kind は frontFace、sideLeft、sideRight、threeQuarterLeft、threeQuarterRight、frontBody、other のいずれかです。other 以外の kind は、それぞれ 1 回だけ使えます。' },
realLifeRefsByVariant: { type: 'object', description: 'バリエーションごとのリファレンス写真の URL です。たとえば { "smile": [url] } のように指定します。キーは最大 20 個、1 つのキーにつき URL は最大 5 個です。キーは前後の空白が除かれ、小文字に変換されます。' },
referenceVideosByVariant: { type: 'object', description: 'ラベルごとのリファレンスクリップの URL で、上限は同じです。読み出した値は、動画生成（Generate Video）の referenceVideoUrls に渡せます。' },
person: { type: 'object', description: "外見の構造化された選択内容（髪、目、体格、年齢など）です。キャラクター自身のポートレートとアセットのプロンプトに加えられます。" },
wardrobe: { type: 'object', description: '衣装の構造化された選択内容（トップス、ボトムス、靴、配色、時代など）です。同じプロンプトに加えられます。' },
voice: { type: 'object | null', description: 'ボイスです。{ voiceId, voiceName, traits, voiceType?, ttsProvider? } の形式で指定します。null を送信すると、ボイスが解除されます。' },
personality: { type: 'object | null', description: '{ mood, speechStyle, movementStyle, behavioralNotes } の形式です。' },
canonicalDescription: { type: 'string', description: '作成された説明を置き換えます。最大 4,000 文字です。' },
projectId: { type: 'string (uuid)', description: 'キャラクターを所属させるプロジェクトです。' },
}}
/>

`person` と `wardrobe` が影響するのは、キャラクター自身のポートレートとアセットの生成だけです。これらは [**人物**](https://nodaro.ai/docs/nodes/creative-controls/person)（Person）ピッカーと同じカタログを使います。[ピッカーカタログ](https://nodaro.ai/docs/developers/picker-catalogs)を参照してください。

ワークフローの実行時には、キャラクターの下流に接続された [**テキストから音声**](https://nodaro.ai/docs/nodes/audio/text-to-speech)（Text to Speech）ノードに、キャラクターの `voice` から、ボイス、ボイスの種類、推奨モデルが設定されます。テキストから音声ノード自体で設定した値が優先されます。

## ポートレートの候補を生成する
`POST /v1/generate-character` は、候補ごとに 1 つのジョブを開始し、それらの ID をすぐに返します。各ジョブが `completed` になるまで、[ジョブ API](https://nodaro.ai/docs/developers/api/jobs) でポーリングしてください。

`attachToCharacterId` を指定すると、最初に完了した候補がキャラクターのポートレートになります。候補が 1 つだけなら、これで完了です。候補が複数ある場合は、気に入ったものを承認してください。承認すると、ポートレートが置き換わります。

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/generate-character \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"name": "Kira",
"seedPrompt": "kira portrait, warm natural lighting",
"count": 4,
"attachToCharacterId": "3f6c2a9e-8d41-4b7a-9c35-1e2f7a6b0d94"
}'
```

**TypeScript SDK**

```ts
const { jobIds } = await client.characters.generate({
name: 'Kira',
seedPrompt: 'kira portrait, warm natural lighting',
count: 4,
attachToCharacterId: id,
})
```

**CLI**

```bash
nodaro characters generate <id> --count 4 \
  --seed-prompt "kira portrait, warm natural lighting" --watch
```

```json
{
"jobId": "a1c4e8f2-5b3d-4e6a-8f7c-2d9b1e0a3c5f",
"jobIds": [
"a1c4e8f2-5b3d-4e6a-8f7c-2d9b1e0a3c5f",
"b7d2f9a1-6c4e-4a8b-9d3f-3e0c2f1b4d6a",
"c3e5a7b9-8d1f-4c2e-a6b8-4f1d3a2c5e7b",
"d9f1b3c5-2e4a-4d6f-b8c1-5a2e4b3d6f8c"
]
}
```

<TypeTable
type={{
name: { type: 'string', description: 'キャラクターの名前で、1〜200 文字です。', required: true },
seedPrompt: { type: 'string', description: 'ポートレートのプロンプトです。seedPrompt、description、referencePhotos のいずれかを送信するか、説明を持つキャラクターに関連付けてください。' },
description: { type: 'string', description: '人物像についてのメモで、最大 2,000 文字です。' },
referencePhotos: { type: 'array', description: 'ポートレートの元にする、最大 20 枚の { url, kind } 形式の写真です。' },
gender: { type: 'string', description: '最大 50 文字です。' },
style: { type: 'string', description: 'ビジュアルスタイルです。' },
baseOutfit: { type: 'string', description: '最大 1,000 文字です。' },
count: { type: 'integer', description: '生成する候補の数で、1〜10 です。', default: '1' },
provider: { type: 'string', description: '画像モデルの ID です。省略すると、デフォルトのモデルを使います。' },
quality: { type: "'basic' | 'medium' | 'high'", description: '品質の段階です。段階があるモデルで使えます。料金が変わります。' },
resolution: { type: 'string', description: '1K、2K、4K、0.5 MP、1 MP、2 MP、4 MP のいずれかで、対応しているモデルで使えます。料金が変わります。' },
aspectRatio: { type: "'1:1' | '3:4' | '16:9' | '9:16'", description: 'ポートレートのフレームです。', default: '3:4' },
sourceImageUrl: { type: 'string', description: '元にする画像です。' },
attachToCharacterId: { type: 'string (uuid)', description: '結果をポートレートとして受け取るキャラクターです。' },
}}
/>

`quality` と `resolution` による料金は、[**画像生成**](https://nodaro.ai/docs/nodes/image/generate-image)（Generate Image）とまったく同じです。4K や高品質での実行は、同じモデルの基本の段階よりも高くなります。モデルが対応していない値は、拒否されずに、対応している最も近い値に変更されます。クレジットも、変更後の値に従います。これらのルートは `adjustments` の一覧を返しません。実際に使われた値は、`GET /v1/jobs/:id` で取得したジョブの `input_data` で確認してください。

## 表情、アングル、ポーズ、ライティングのバリエーションを生成する
`POST /v1/generate-character-asset` は、基準となるポートレートのバリエーションを 1 つ生成し、`{ jobId }` を返します。結果をキャラクターに追加するには、`attachToCharacterId`、`attachToColumn`、`attachName` の 3 つの attach フィールドをすべて送信します。ジョブが完了すると、ワーカーがそのバケットに `{ name: attachName, url }` を追加します。

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/generate-character-asset \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"name": "Kira",
"assetType": "expressions",
"variant": "smile",
"attachToCharacterId": "3f6c2a9e-8d41-4b7a-9c35-1e2f7a6b0d94",
"attachToColumn": "expressions",
"attachName": "smile"
}'
```

**TypeScript SDK**

```ts
await client.characters.generateAsset({
name: 'Kira',
assetType: 'bodyAngles',
variant: 'front',
attachToCharacterId: id,
attachToColumn: 'body_angles',
attachName: 'front',
})
```

**CLI**

```bash
nodaro characters generate-asset <id> --asset-type expressions --variant smile --watch
```

| フィールド | 説明 |
| --- | --- |
| `name` | 必須。キャラクターの名前です。 |
| `assetType` | 必須。`expressions`、`poses`、`lighting`、`headAngles`、`angles`（`headAngles` と同じ）、`bodyAngles`、`custom` のいずれかです。 |
| `variant` | 必須。生成するバリエーションで、1〜100 文字です。たとえば `smile` や `3/4 left` です。 |
| `description` | このバリエーションを 1 文で説明したもので、最大 1,000 文字です。キャラクターに関連付けてこれを省略すると、キャラクターの基準となる説明をもとに、Nodaro が自動で作成します。 |
| `sourceImageUrl` | バリエーションの元にする画像です。通常は、承認済みのポートレートです。 |
| `provider`, `quality`, `resolution` | 画像モデルと、その出力の段階です。料金は画像生成と同じです。 |
| `aspectRatio` | `1:1`、`3:4`、`16:9`、`9:16` のいずれかです。デフォルトは種類によって異なり、表情は `1:1`、ポーズと全身のアングルは `9:16`、それ以外は `3:4` です。 |
| `attachToCharacterId`, `attachToColumn`, `attachName` | 結果の保存先です。`attachToColumn` は `expressions`、`poses`、`angles`、`body_angles`、`lighting_variations` のいずれかです。`custom` のバリエーションでは、列を必ず指定します。 |

バリエーションをキャラクターに関連付けると、`realLifeRefsByVariant` でそのバリエーションのキーに保存されている実際の写真が、リクエストと一緒に自動で送信されます。

## キャラクターをアニメーション化する
`POST /v1/generate-character-motion` は、キャラクターの静止画を動画クリップにして、`{ jobId }` を返します。`attachToCharacterId` と `attachName` を指定すると、完成したクリップが `motions` バケットに追加されます。

キャラクターに関連付けて `sourceImageUrl` を省略した場合、Nodaro は次の順で開始フレームを選びます。

1. 送信した `sourceImageUrl`。常にこれが優先されます。
2. `bodyAngles` の `front` のエントリー。全身のフレームは、頭と肩のポートレートよりもはるかにうまくアニメーション化できます。
3. `bodyAngles` のほかのエントリー。新しいものから順に使います。
4. 基準となるポートレート。

最良のクリップを得るには、最初のモーションの前に、`front` の全身のアングルを生成してください。

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/generate-character-motion \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"name": "Kira",
"motionPrompt": "slow head turn left, soft smile",
"provider": "kling",
"attachToCharacterId": "3f6c2a9e-8d41-4b7a-9c35-1e2f7a6b0d94",
"attachName": "head turn"
}'
```

**TypeScript SDK**

```ts
await client.characters.generateMotion({
name: 'Kira',
motionPrompt: 'slow head turn left, soft smile',
provider: 'kling',
attachToCharacterId: id,
attachName: 'head turn',
})
```

**CLI**

```bash
nodaro characters generate-motion <id> \
  --motion-prompt "slow head turn left, soft smile" --attach-name "head turn" --watch
```

| フィールド | 説明 |
| --- | --- |
| `name` | 必須。キャラクターの名前です。 |
| `motionPrompt` | 必須。何がどのように動くかで、1〜2,000 文字です。 |
| `provider` | 動画モデルです。`kling`（デフォルト）、`kling-turbo`、`kling-3.0`、`wan-i2v`、`wan-2.7-i2v` のいずれかです。 |
| `sourceImageUrl` | 開始フレームです。キャラクターに関連付けない場合は必須です。 |
| `description`, `motionDescription` | 見た目の説明（最大 1,000 文字）と、動きの説明（最大 500 文字）です。キャラクターに関連付けてこれらを省略すると、Nodaro が両方を自動で作成します。 |
| `aspectRatio` | `1:1`、`3:4`、`16:9`、`9:16` のいずれかです。デフォルトは `9:16` で、全身が入る縦長のクリップになります。 |
| `attachToCharacterId`, `attachName` | キャラクターと、`motions` でのクリップの名前です。 |

キャラクターをアニメーション化できるモデルは、次のとおりです。料金は、そのモデルで画像から動画を生成する料金です。

| Model | Maker | Modes | Credits | Details |
| --- | --- | --- | --- | --- |
| [Kling 2.6](https://nodaro.ai/docs/models/video/kling-2-6) | Kuaishou | Image to video, Text to video | from 138 | Kling 2.6 I2V — strong motion realism. 5s/10s, optional native audio. |
| [Kling 2.5 Turbo Pro](https://nodaro.ai/docs/models/video/kling-2-5-turbo-pro) | Kuaishou | Image to video, Text to video | from 110 | Faster Kling — good quality at lower cost. Supports end frame. |
| [Kling 3.0](https://nodaro.ai/docs/models/video/kling-3-0) | Kuaishou | Image to video, Text to video | from 270 | Premium Kling 3.0 — variable 3-15s duration, native audio, 720P/1080P. |
| [Wan 2.6 I2V](https://nodaro.ai/docs/models/video/wan-2-6-i2v) | Alibaba | Image to video | from 175 | Wan 2.6 image-to-video — 5/10/15s at 720p/1080p. |
| [Wan 2.7 I2V](https://nodaro.ai/docs/models/video/wan-2-7-i2v) | Alibaba | Image to video | 188 | Wan 2.7 image-to-video — 2–15s at 720p/1080p, supports start+end frame. |

## ポートレートを承認する
`POST /v1/characters/:id/approve-portrait` は、完了した候補を基準となるポートレートに設定します。同じ呼び出しの中で、Nodaro はポートレートを見て `canonicalDescription` を作成します。これは、以降のプロンプトがキャラクターを説明するために使うテキストです。この説明がないと、シーンごとにキャラクターの見た目がずっと大きく変わってしまいます。

候補は、自分の `completed` のジョブである必要があります。説明の作成に失敗した場合でもポートレートは設定され、`canonicalDescription` は `null` になります。その場合は、`POST /v1/characters/:id/llm-caption` を呼び出して、もう一度試してください。どちらのルートも、繰り返し呼び出して問題ありません。承認は無料です。llm-caption は呼び出し 1 回につき 7 クレジットかかり、失敗した呼び出し（`502`）の分は返還されます。

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/characters/3f6c2a9e-8d41-4b7a-9c35-1e2f7a6b0d94/approve-portrait \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "candidateJobId": "b7d2f9a1-6c4e-4a8b-9d3f-3e0c2f1b4d6a" }'
```

**TypeScript SDK**

```ts
const { portraitUrl, canonicalDescription } =
await client.characters.approvePortrait(id, jobIds[1])

if (canonicalDescription === null) {
await client.characters.recaption(id)
}
```

**CLI**

```bash
nodaro characters approve-portrait <id> --job <jobId>
nodaro characters recaption <id>
```

```json
{
"portraitUrl": "https://cdn.nodaro.ai/characters/kira-portrait-2.png",
"canonicalDescription": "Kira is a woman in her mid-twenties with shoulder-length auburn hair, green eyes and light freckles..."
}
```

`llm-caption` は `{ canonicalDescription }` を返します。キャラクターにまだポートレートがない場合は `400 no_portrait` を、説明を作成できなかった場合は `502` を返します。

## キャラクターのアーカイブ、復元、コピー
- **アーカイブ**：`DELETE /v1/characters/:id` はキャラクターをアーカイブし、`{ success: true, archived: true }` を返します。キャラクターはデフォルトの一覧から外れますが、`GET /v1/characters/:id` では引き続き返されるので、そのキャラクターを使うワークフローはそのまま動作します。Nodaro Cloud では、アーカイブすると、進行中の学習もキャンセルされてそのクレジットが返還され、学習済みモデルが削除されます。
- **復元**：`POST /v1/characters/:id/restore` は `{ id, name }` を返します。同じ名前の有効なキャラクターがその時点で存在する場合、Nodaro は名前の末尾に `(restored)` を付け、新しい名前を返します。
- **完全な削除**：キャラクターを完全に削除する API ルートはありません。エディターのライブラリにある、アーカイブの表示を使ってください。
- **コピー**：`POST /v1/characters/:id/duplicate` は、名前の末尾に `(copy)` が付いた新しいキャラクターの `{ id, name }` を返します。コピーは、アセットを再生成するまで、元のキャラクターのアセットの URL を共有します。
- **使用状況**：`GET /v1/characters/:id/usage` は `{ workflowCount, workflows: [{ id, name }] }` を返します。キャラクターをアーカイブする前に確認してください。

| 操作 | curl | TypeScript SDK | CLI |
| --- | --- | --- | --- |
| アーカイブ | `DELETE /v1/characters/:id` | `client.characters.delete(id)` | `nodaro characters delete <id>` |
| 復元 | `POST /v1/characters/:id/restore` | `client.characters.restore(id)` | `nodaro characters restore <id>` |
| コピー | `POST /v1/characters/:id/duplicate` | `client.characters.duplicate(id)` | `nodaro characters duplicate <id>` |
| 使用状況 | `GET /v1/characters/:id/usage` | `client.characters.usage(id)` | `nodaro characters usage <id>` |

## 一連の流れ：作成、生成、承認、バリエーションの追加
### キャラクターを作成する
`nodeId`、`name`、`seedPrompt` を指定して、`POST /v1/characters` を呼び出します。返された `id` を控えておきます。

### ポートレートの候補を生成する
`count: 4` と `attachToCharacterId` を指定して、`POST /v1/generate-character` を呼び出します。各ジョブ ID を、`completed` か `failed` になるまでポーリングします。

### 気に入った候補を承認する
その候補のジョブ ID を指定して、`POST /v1/characters/:id/approve-portrait` を呼び出します。ポートレートと基準となる説明が設定されます。

### バリエーションとモーションを追加する
`POST /v1/generate-character-asset` で、まず `front` の全身のアングルを生成し、続けて表情とポーズを生成します。クリップは、`POST /v1/generate-character-motion` で生成します。

## ほかの生成でキャラクターを使う
アセットができたら、その URL をリファレンス画像として、[画像生成](https://nodaro.ai/docs/nodes/image/generate-image)や [**動画生成**](https://nodaro.ai/docs/nodes/video/generate-video)（Generate Video）に渡します。たとえば、`expressions` の `smile` のエントリーを読み取り、その URL をプロンプトと一緒にリファレンスとして送信します。コードでは、URL を明示的に指定するのが最も簡単です。ワークフローの接続のしかたに左右されないためです。リクエストのフィールドについては、[単体のノードを実行する](https://nodaro.ai/docs/developers/api/nodes)を参照してください。

ワークフローでは、[**キャラクターアセット**](https://nodaro.ai/docs/nodes/assets/character)（Character Asset）ノードを画像ノードや動画ノードに接続するか、プロンプトで `@` を使ってキャラクターをメンションします（たとえば `@kira:1:smile`）。メンションの記法は[リファレンスの役割](https://nodaro.ai/docs/guides/reference-roles)で、全体の手法は[キャラクターの一貫性](https://nodaro.ai/docs/guides/consistent-characters)で説明しています。

## MCP から使う
AI アシスタントは、次のツールを通じて同じルートを使います。アーカイブと復元は、意図的に MCP では使えないようにしています。

| ツール | 説明 |
| --- | --- |
| `list_characters`, `get_character` | キャラクターを探し、そのアセットの URL を読み取ります。 |
| `create_character`, `update_character` | キャラクターを作成するか、その基本情報のフィールドを変更します。 |
| `generate_character` | ポートレート（`kind: "main"`）またはバリエーション（`kind: "asset"`）を生成します。 |
| `generate_character_motion` | キャラクターをアニメーション化して、クリップを作ります。 |
| `approve_portrait`, `recaption_character` | ポートレートを承認するか、その説明を作成し直します。 |

[MCP ツールリファレンス](https://nodaro.ai/docs/mcp/tools)を参照してください。

## クレジット
Nodaro Cloud では、キャラクターの生成に、対応するノードと同じクレジット料金がかかります。

| ルート | 料金 |
| --- | --- |
| `POST /v1/generate-character` | 画像モデルの料金 × `count` です。最初のジョブが始まる前に、すべての候補の分が確保されます。 |
| `POST /v1/generate-character-asset` | バリエーション 1 つにつき、画像モデルの料金です。 |
| `POST /v1/generate-character-motion` | クリップ 1 本につき、動画モデルで画像から動画を生成する料金です。 |
| `approve-portrait` | 無料です。 |
| `llm-caption` | 呼び出し 1 回につき 7 クレジットです。 |

各モデルの正確な料金は、それぞれのモデルのページに記載されています。[クレジット](https://nodaro.ai/docs/concepts/credits)を参照してください。

## エラー
エラーは、標準のエンベロープ `{ "error": { "code", "message" } }` で返されます。[エラー](https://nodaro.ai/docs/developers/api/errors)を参照してください。

| ステータス | コード | 説明 |
| --- | --- | --- |
| `400` | `validation_error` | フィールドが不足しているか、無効です。または、カーソルの形式が正しくないか、生成リクエストに `seedPrompt`、`description`、`referencePhotos` のいずれもありません。 |
| `400` | `no_portrait` | キャラクターにポートレートがない状態で、`llm-caption` が呼び出されました。 |
| `401` | `unauthorized` | トークンがないか、無効か、取り消されています。 |
| `402` | `insufficient_credits` | Nodaro Cloud のみ。アカウントの残高では、確保に必要なクレジットが足りません。 |
| `404` | `not_found` | その ID を持つ自分のキャラクターまたはジョブがありません。 |
| `409` | `name_taken` | ほかの有効なキャラクターが、すでにその名前を使っています。 |
| `502` | — | 基準となる説明を作成できませんでした。ポートレートは変更されていません。もう一度試してください。 |

## Frequently asked questions

### API で、すべての生成に同じキャラクターを登場させるにはどうすればよいですか？

キャラクターを作成し、ポートレートを生成して承認します。すると Nodaro が、そのキャラクターの基準となる説明を作成します。キャラクターのアセットの URL をリファレンス画像として画像生成（Generate Image）や動画生成（Generate Video）に渡すか、キャラクターをワークフローに接続してください。

### 1 回のリクエストで、ポートレートの候補をいくつ生成できますか？

POST /v1/generate-character は、1〜10 の count を受け付けます。すべての候補のクレジットが、最初のジョブが始まる前に確保されます。バッチの途中で失敗した場合は、その確保がすべて取り消されます。

### DELETE /v1/characters/:id で、キャラクターは完全に削除されますか？

いいえ。キャラクターはアーカイブされ、POST /v1/characters/:id/restore で元に戻せます。キャラクターを完全に削除できるのは、エディターのアーカイブの表示だけです。

### ポートレートを承認した後、canonicalDescription が null になるのはなぜですか？

ポートレートは設定されましたが、説明の作成に失敗したためです。POST /v1/characters/:id/llm-caption を呼び出して、もう一度試してください。承認と、承認時に作成される説明は無料です。llm-caption は呼び出し 1 回につき 7 クレジットかかり、失敗した呼び出しの分は返還されます。

### キャラクターをアニメーション化できるモデルはどれですか？

Kling 2.6（デフォルト。プロバイダー ID は kling）、Kling 2.5 Turbo Pro、Kling 3.0、Wan 2.6 I2V、Wan 2.7 I2V です。クリップ 1 本の料金は、そのモデルで画像から動画を生成する料金と同じです。
