# Creatures

> Create and manage animals and creatures over REST: generate main images, angle, pose and variation variants and motion clips, and give a creature a voice.

Source: https://nodaro.ai/docs/developers/api/creatures

The **Creatures API** manages animals and other non-human beings with a locked look: a pet, a dragon, a mascot. A creature has an approved main image, a written description and variant images, and image and video nodes reuse it so the creature looks the same in every shot. Creatures work like [objects](https://nodaro.ai/docs/developers/api/objects), with three additions: a free-text `species`, named boards, and an optional voice.

The routes work on every edition and take a bearer token: a personal API token (`ndr_…`), an OAuth app token (`ndr_app_…`), or your session token on Community edition. See [Authentication](https://nodaro.ai/docs/developers/api/authentication). The CLI has no creature commands; use REST, the SDK or MCP.

## Endpoints

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/v1/creatures` | List your creatures. |
| `GET` | `/v1/creatures/:id` | Get one creature with its jobs in progress. |
| `POST` | `/v1/creatures` | Create a creature, or update one when the body has an `id`. |
| `DELETE` | `/v1/creatures/:id` | Archive a creature. It can be restored. |
| `DELETE` | `/v1/creatures/:id?permanent=true` | Delete an archived creature and its files for good. |
| `POST` | `/v1/creatures/:id/restore` | Restore an archived creature. |
| `POST` | `/v1/generate-creature` | Generate 1 to 10 candidate main images. |
| `POST` | `/v1/generate-creature-asset` | Generate one angle, pose, variation or custom variant. |
| `POST` | `/v1/generate-creature-motion` | Animate the main image into a motion clip. |
| `POST` | `/v1/creatures/:id/approve-main-image` | Approve a candidate as the main image and write the creature's description. |
| `POST` | `/v1/creatures/:id/llm-caption` | Write the description again from the current main image. |

## What a creature holds

| Field | What it holds |
| --- | --- |
| `id`, `name`, `description` | The identifier, the display name and identity notes. |
| `species` | Free text, for example `dragon`, `wolf` or `tabby cat`. It is the subject of the main-image prompt. |
| `category`, `style` | Free-text category, and the visual style: `realistic`, `anime`, `3d-pixar` or `illustration`. |
| `sourceImageUrl` | The anchor main image, set when you approve a candidate. |
| `canonicalDescription` | A visual description of about 80 to 120 words, written by Nodaro when the main image is approved. |
| `styleLock` | Whether variants are generated from the main image. `true` by default. |
| `angles`, `poses`, `variations`, `motionClips` | The asset buckets. Each entry is `{ name, url }`; `motionClips` holds videos. |
| `boards` | Up to 24 named boards: dense reference sheets, one per look or mood. |
| `voice` | The creature's voice, or `null`. |
| `referencePhotos` | Up to 20 mood-board photos, each `{ kind, url }`. `kind` is `front`, `side`, `detail`, `context`, `moodBoard` or `other`. |
| `pendingJobs` | On `GET /v1/creatures/:id` only: the variant jobs still running. |

## List and read creatures

`GET /v1/creatures` returns your active creatures. It takes the same parameters as the object list: `archived=true`, `projectId`, and an optional `limit` (at most 500) with `cursor`. Without `limit` you get the full list; with it, one page and a `nextCursor` to pass back until it is `null`.

`GET /v1/creatures/:id` returns one creature. An archived creature returns `404 not_found`.

**curl**

```bash
curl "https://app.nodaro.ai/v1/creatures?limit=50" \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

**TypeScript SDK**

```ts

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

const { creatures, nextCursor } = await client.creatures.list({ limit: 50 })
const { creatures: archived } = await client.creatures.listArchived()
```

## Create or update a creature

`POST /v1/creatures` creates a creature when the body has no `id`, and updates it when it has one. A create needs `nodeId` and `name`; use any label for `nodeId`, such as `"scripted"`, when there is no canvas node. A create returns `{ id }`, and an update returns `{ id, updatedAt }`.

On an update, only the fields you send are written. The asset buckets are never written by an update, but `boards` is yours to set: send the whole list to replace it. Send `expectedUpdatedAt` to refuse the update with `409 concurrent_modification` when someone changed the creature since you read it.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/creatures \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"nodeId": "scripted",
"name": "Ember",
"species": "red dragon",
"description": "Young dragon with copper scales and a chipped left horn",
"style": "realistic"
}'
```

**TypeScript SDK**

```ts
const { id } = await client.creatures.create({
nodeId: 'scripted',
name: 'Ember',
species: 'red dragon',
description: 'Young dragon with copper scales and a chipped left horn',
style: 'realistic',
})

await client.creatures.update(id, {
voice: { voiceId: 'Callum', voiceName: 'Callum', traits: 'gravelly, slow', voiceType: 'premade' },
})
```

<TypeTable
type={{
id: { type: 'string (uuid)', description: 'The creature to update. Omit it to create a creature.' },
nodeId: { type: 'string', description: 'Required on create. The canvas node the creature belongs to, or any label when there is none.' },
name: { type: 'string', description: 'Required on create.' },
species: { type: 'string', description: 'What the creature is, in free text.' },
description: { type: 'string', description: 'What makes the creature distinctive.' },
category: { type: 'string', description: 'Free text.' },
style: { type: 'string', description: 'realistic, anime, 3d-pixar or illustration.' },
styleLock: { type: 'boolean', description: 'Generate variants from the approved main image.', default: 'true' },
referencePhotos: { type: 'array', description: 'Up to 20 { kind, url } mood-board photos.' },
voice: { type: 'object | null', description: '{ voiceId, voiceName, traits, voiceType?, ttsProvider? }. Send null on update to clear it.' },
boards: { type: 'array', description: 'Update only. Up to 24 { name, url } boards. Replaces the whole list.' },
canonicalDescription: { type: 'string', description: 'Replace the written description.' },
projectId: { type: 'string (uuid)', description: 'The project to file the creature in.' },
expectedUpdatedAt: { type: 'string (ISO 8601)', description: 'On update: refuse the write with 409 when the creature changed since this timestamp.' },
}}
/>

### Boards

A board is a dense reference sheet of the creature for one look or mood. Render one with the `generate-image/creature-board` preset of [Generate Image](https://nodaro.ai/docs/nodes/image/generate-image), then save its URL in `boards`. See [Presets](https://nodaro.ai/docs/developers/api/presets) and [Reference boards](https://nodaro.ai/docs/guides/reference-boards).

## Generate main images and variants

`POST /v1/generate-creature` starts one job per candidate and returns `jobIds`; a single-candidate request also returns `jobId`. With `attachToCreatureId` and a `count` of 1, the result becomes the main image when the job completes. With several candidates nothing is attached; approve the one you prefer.

`POST /v1/generate-creature-asset` generates one variant and returns `{ jobId }`. `assetType` is `angles`, `poses`, `variations` or `custom`. Send `attachToCreatureId`, `attachToColumn` (`angles`, `poses` or `variations`) and `attachName` to append the result to a bucket; a `custom` variant must name its column.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/generate-creature \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"name": "Ember",
"species": "red dragon",
"count": 1,
"attachToCreatureId": "0b8d6f4a-2c1e-4b9d-8f3a-7e5c1d9b3f2a"
}'

curl -X POST https://app.nodaro.ai/v1/generate-creature-asset \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"name": "Ember",
"assetType": "poses",
"variant": "wings spread",
"attachToCreatureId": "0b8d6f4a-2c1e-4b9d-8f3a-7e5c1d9b3f2a",
"attachToColumn": "poses",
"attachName": "wings spread"
}'
```

**TypeScript SDK**

```ts
const { jobIds } = await client.creatures.generate({
name: 'Ember',
species: 'red dragon',
count: 4,
})

await client.creatures.generateAsset({
name: 'Ember',
assetType: 'poses',
variant: 'wings spread',
attachToCreatureId: id,
attachToColumn: 'poses',
attachName: 'wings spread',
})
```

| Field | Route | What it does |
| --- | --- | --- |
| `name` | both | Required. The creature name. |
| `species`, `description`, `category`, `style` | both | The creature's identity. |
| `count` | main image | Candidates to generate, 1 to 10. The default is 1. |
| `assetType`, `variant` | variant | Required. The kind of variant and its name. |
| `provider` | both | The image model id. Omit it for the default model. |
| `sourceImageUrl` | both | An image to start from or to vary. |
| `seedPromptHint` | both | A prompt fragment to fold into the prompt, for example a choice from the [Animal](https://nodaro.ai/docs/nodes/creative-controls/animal) picker. |

## Animate the main image

`POST /v1/generate-creature-motion` turns an image of the creature into a clip, such as an idle loop, a prowl or an attack, and returns `{ jobId }`. `sourceImageUrl` is required. With `attachToCreatureId` and `attachName`, the clip is appended to `motionClips`.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/generate-creature-motion \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"name": "Ember",
"motionPrompt": "slow idle breathing, tail sways, smoke curls from the nostrils",
"sourceImageUrl": "https://cdn.nodaro.ai/creatures/ember-main.png",
"attachToCreatureId": "0b8d6f4a-2c1e-4b9d-8f3a-7e5c1d9b3f2a",
"attachName": "idle"
}'
```

**TypeScript SDK**

```ts
await client.creatures.generateMotion({
name: 'Ember',
motionPrompt: 'slow idle breathing, tail sways, smoke curls from the nostrils',
sourceImageUrl: ember.sourceImageUrl!,
provider: 'kling-turbo',
duration: 5,
attachToCreatureId: id,
attachName: 'idle',
})
```

| Field | What it does |
| --- | --- |
| `name`, `motionPrompt`, `sourceImageUrl` | Required. The creature name, the movement and the start frame. |
| `provider` | `kling-turbo` (the default), `kling`, `kling-3.0`, `minimax`, `hailuo-2.3`, `wan-i2v`, `seedance` or `bytedance-lite`. |
| `duration` | The clip length in seconds. It must be a length the model offers; omit it for the model's default. |
| `aspectRatio` | `1:1` (the default), `3:4`, `16:9`, `9:16` or `4:3`. |
| `refineFromVideoUrl` | An existing clip to refine with the new prompt instead of starting again from the image. |
| `attachToCreatureId`, `attachName` | The creature and the clip's name in `motionClips`. |

| Model | Maker | Modes | Credits | Details |
| --- | --- | --- | --- | --- |
| [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 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 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. |
| [Hailuo 02 I2V Pro](https://nodaro.ai/docs/models/video/hailuo-02-i2v-pro) | MiniMax | Image to video, Text to video | 143 | Hailuo 02 Pro — strong photoreal motion, fixed 5-second clips. Supports end frame. |
| [Hailuo 2.3 Standard](https://nodaro.ai/docs/models/video/hailuo-2-3-standard) | MiniMax | Image to video | from 75 | Cheaper Hailuo 2.3 tier — good baseline quality. |
| [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. |
| [Bytedance Lite I2V](https://nodaro.ai/docs/models/video/bytedance-lite-i2v) | Bytedance | Image to video, Text to video | 57 | Cheapest Bytedance video tier with end-frame support. |

## Approve a main image

`POST /v1/creatures/:id/approve-main-image` with `{ candidateJobId, expectedUpdatedAt? }` sets a completed candidate as the main image and writes `canonicalDescription` in the same call. It returns `{ sourceImageUrl, canonicalDescription }`. When writing the description fails, the main image is still set and the description is empty; the SDK returns `null`.

`POST /v1/creatures/:id/llm-caption` writes the description again and returns `{ canonicalDescription }`. It answers `502` when the description cannot be written and `400 main_image_required` when there is no main image yet. Both routes are free and safe to repeat.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/creatures/0b8d6f4a-2c1e-4b9d-8f3a-7e5c1d9b3f2a/approve-main-image \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "candidateJobId": "1c9e7a5b-3d2f-4c8e-9a4b-8f6d2e1a4c3b" }'
```

**TypeScript SDK**

```ts
const ember = await client.creatures.get(id)
const approved = await client.creatures.approveMainImage(id, jobIds[0], ember.updatedAt)
if (approved.canonicalDescription === null) await client.creatures.recaption(id)
```

## Archive, restore and delete a creature

| Action | curl | TypeScript SDK |
| --- | --- | --- |
| Archive | `DELETE /v1/creatures/:id` | `client.creatures.delete(id)` |
| Restore | `POST /v1/creatures/:id/restore` | `client.creatures.restore(id)` |
| Delete for good | `DELETE /v1/creatures/:id?permanent=true` | `client.creatures.permanentDelete(id)` |

Archive returns `{ success: true, archived: true }`, and repeating it changes nothing. Restore returns `{ id, name }`, with a `(restored)` suffix when an active creature has the same name. Delete for good works only on an archived creature (`400 not_archived` otherwise) and removes the creature and every file it references.

## Make a creature talk

No creature-specific route is needed. Render speech with the creature's voice, then lip-sync it onto the main image:

### Render the speech

Run [Text to Speech](https://nodaro.ai/docs/nodes/audio/text-to-speech) with the creature's `voice.voiceId` as `voice`, and its `voice.ttsProvider` and `voice.voiceType` when they are set.

### Lip-sync the main image

Run [Lip Sync](https://nodaro.ai/docs/nodes/video/lip-sync) with the creature's `sourceImageUrl` as `imageUrl` and the speech as `audioUrl`.

```ts
const speech = await client.nodes.runAndWait('text-to-speech', {
text: 'I knocked the vase off the shelf. I regret nothing.',
voice: ember.voice!.voiceId,
provider: ember.voice!.ttsProvider,
voiceType: ember.voice!.voiceType,
})

const clip = await client.nodes.runAndWait('lip-sync', {
imageUrl: ember.sourceImageUrl!,
audioUrl: speech.audioUrl,
provider: 'kling-avatar',
})
```

See [Run a single node](https://nodaro.ai/docs/developers/api/nodes) for `nodes.runAndWait` and the raw `POST /v1/<node-type>` routes.

## Put a creature in a shot

On [Generate Image](https://nodaro.ai/docs/nodes/image/generate-image), pass the creature as a structured reference with `source: "wired-creature"`. The creature is attached automatically, with wording that keeps its anatomy, markings and coloring. You can also name it in the prompt: `@ember:1` places it where you type it. A role picks what to take from the image, for example `@ember:1:markings`. The roles are `creature`, `anatomy`, `markings`, `pose`, `color` and `style`.

```json
{
"prompt": "a wide shot of @ember:1 landing on the castle wall",
"connectedReferences": [
{ "id": "cr-1", "defaultName": "Ember", "source": "wired-creature", "url": "https://cdn.nodaro.ai/creatures/ember-main.png" }
]
}
```

In a workflow, wire the [Animal/Creature Asset](https://nodaro.ai/docs/nodes/assets/creature) node into the image or video node instead.

## Use it from MCP

| Tool | What it does |
| --- | --- |
| `list_creatures`, `get_creature` | Find a creature and read its variant URLs and voice. |
| `generate_creature` | Generate a main image (`kind: "main"`) or a variant (`kind: "asset"`). |
| `approve_creature_main_image`, `recaption_creature` | Approve a main image, or write its description again. |
| `generate_creature_motion` | Animate the main image. |

See the [MCP tools reference](https://nodaro.ai/docs/mcp/tools).

## Credits

On Nodaro Cloud, a main-image request costs the image model's price times `count`, reserved before the first job starts. A variant costs the image model's price, and a motion clip the video model's image-to-video price. Approval and captioning are free.

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `validation_error` | A field is missing or invalid, or `duration` is not a length the model offers. |
| `400` | `not_archived` | A permanent delete was sent for a creature that is not archived. |
| `400` | `main_image_required` | `llm-caption` was called before the creature has a main image. |
| `401` | `unauthorized` | The token is missing, invalid or revoked. |
| `402` | `insufficient_credits` | Nodaro Cloud only. The account cannot cover the reservation. |
| `404` | `not_found` | No active creature with that id belongs to you. |
| `409` | `concurrent_modification` | `expectedUpdatedAt` no longer matches. Read the creature again, merge and retry. |
| `502` | — | The canonical description could not be written. Try again. |

## Frequently asked questions

### What is the difference between a creature and an object?

A creature is an animal or a non-human being. It has a free-text species, a poses bucket instead of materials, named boards and an optional voice. Otherwise creatures work like objects, with the same routes for creating, generating, approving and archiving.

### How do I make a creature talk?

Give the creature a voice, render speech with the Text to Speech node using that voice, then run Lip Sync with the creature's main image and the speech. No creature-specific route is needed.

### Which models can animate a creature?

The same eight video models as objects, by provider id kling-turbo (the default), kling, kling-3.0, minimax, hailuo-2.3, wan-i2v, seedance and bytedance-lite. Each clip costs that model's image-to-video price.

### Is there a CLI command for creatures?

No. Use the REST routes, the TypeScript SDK (client.creatures) or the creature tools over MCP.
