Objects
Script props and products over REST: create and archive objects, generate main images, material and angle variants and motion clips, and approve the main image.
The Objects API scripts everything Object Studio does for props and products. You create an object, generate candidate main images, approve one, and add angle, material and variation variants and motion clips. Image and video nodes then reuse the object, so the same lantern, car or chair looks the same in every shot.
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. Every route is scoped to the caller. See Authentication.
Endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v1/objects | List your objects. |
GET | /v1/objects/:id | Get one object with its jobs in progress. |
POST | /v1/objects | Create an object, or update one when the body has an id. |
DELETE | /v1/objects/:id | Archive an object. It can be restored. |
DELETE | /v1/objects/:id?permanent=true | Delete an archived object and its files for good. |
POST | /v1/objects/:id/restore | Restore an archived object. |
POST | /v1/generate-object | Generate candidate main images. |
POST | /v1/generate-object-asset | Generate one angle, material, variation or custom variant. |
POST | /v1/generate-object-motion | Animate the main image into a motion clip. |
POST | /v1/objects/:id/approve-main-image | Approve a candidate as the main image and write the object's description. |
POST | /v1/objects/:id/llm-caption | Write the description again from the current main image. |
What an object holds
| Field | What it holds |
|---|---|
id, name, description | The identifier, the display name and identity notes. |
category | furniture, vehicle, weapon, food, clothing, electronics, nature, tool, animal or other. |
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. It is an empty string until then. |
styleLock | Whether variants are generated from the main image. true by default. |
angles, materials, variations, motionClips | The asset buckets. Each entry is { name, url }; motionClips holds videos. |
referencePhotos | Up to 20 mood-board photos, each { kind, url }. |
pendingJobs | On GET /v1/objects/:id only: the variant jobs still running for this object. |
The asset buckets
| Bucket | What it shows | Preset variants |
|---|---|---|
angles | The object from another viewpoint | front, side, top, back, three-quarter, detail, in-context, exploded, perspective |
materials | The object in another material | wood, metal, glass, plastic, fabric, stone, ceramic, leather, paper, gold, silver, copper, marble |
variations | Another condition or style | clean, weathered, damaged, ornate, minimal, broken, antique, futuristic, holographic, dirty, polished |
motionClips | Looping camera-move clips | rotate-360, hover, spin-slow, parallax, pulse, drift, dolly-around, push-in, drone-orbit |
Reference photos
A mood board travels with the object. Every node that uses the object receives these photos as extra references, even without a wired image, and each photo's kind tells the model what it is for.
kind | What it is for |
|---|---|
front | A clean front view. |
side | A side profile, useful for vehicles, furniture and weapons. |
detail | A close-up of a defining feature, such as an engraving or a hinge. |
context | The object in place, held or mounted, for scale. |
moodBoard | The palette or the feel. |
other | Anything else. |
You can add up to 20 photos, and any number of each kind. For a hero prop or a signature product, add three to six photos before the first generation: the first results are then much more faithful.
List objects
GET /v1/objects returns your active objects, newest first. Add archived=true for the archive, or projectId for one project. Without limit, the route returns the full list. With limit (at most 500), it returns one page and a nextCursor; pass it back as cursor until it is null.
curl "https://app.nodaro.ai/v1/objects?limit=100" \
-H "Authorization: Bearer $NODARO_API_KEY"import { createClient, StaticTokenAuth } from '@nodaro/sdk'
const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})
const page = await client.objects.list({ limit: 100 })
const next = await client.objects.list({ limit: 100, cursor: page.nextCursor! })
const { objects: archived } = await client.objects.listArchived()nodaro objects list --json
nodaro objects list --archivedGET /v1/objects/:id returns one object with pendingJobs. An archived object returns 404 not_found, the same answer as an object that does not exist.
Create or update an object
POST /v1/objects creates an object 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.
On an update, only the fields you send are written. The asset buckets are never written by an update, so a save cannot overwrite a variant that a job is adding. Send expectedUpdatedAt, the object's current updatedAt, to refuse the update when someone changed the object since you read it: the route then answers 409 concurrent_modification.
curl -X POST https://app.nodaro.ai/v1/objects \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"nodeId": "scripted",
"name": "Antique Lantern",
"description": "Weathered brass lantern with hand-engraved filigree",
"category": "tool",
"style": "realistic"
}'const { id } = await client.objects.create({
nodeId: 'scripted',
name: 'Antique Lantern',
description: 'Weathered brass lantern with hand-engraved filigree',
category: 'tool',
style: 'realistic',
})
const object = await client.objects.get(id)
await client.objects.update(id, { styleLock: false, expectedUpdatedAt: object.updatedAt })nodaro objects create "Antique Lantern" --node-id scripted \
--description "Weathered brass lantern with hand-engraved filigree" \
--category tool --style realistic
nodaro objects update <id> --style-lock falseA create returns { id }. An update returns { id, updatedAt }.
Prop
Type
What Style Lock changes
- On (default). Every angle, material and variation is generated from the approved main image. The variant keeps the proportions, the silhouette and the defining details. Nodes that use the object also receive its canonical description. Use it for anything that must look like the same item in every shot.
- Off. Variants are generated from text only. Nodes still receive the canonical description, but only as guidance, so the model may reinterpret the design. Use it to explore alternatives or compare looks.
Generate candidate main images
POST /v1/generate-object starts one job per candidate and returns jobIds at once, one id per candidate. A single-candidate request also returns jobId. Poll the jobs with the Jobs API.
With attachToObjectId and a count of 1, the result becomes the object's main image when the job completes. With several candidates nothing is attached; approve the one you prefer.
curl -X POST https://app.nodaro.ai/v1/generate-object \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Antique Lantern", "count": 4 }'const { jobIds } = await client.objects.generate({
name: 'Antique Lantern',
description: 'Weathered brass lantern with hand-engraved filigree',
count: 4,
})nodaro objects generate --name "Antique Lantern" --count 1 \
--attach-to-object-id <id> --watch{
"jobIds": [
"5e2a8c1f-3b7d-4f9a-a6c2-8d1e4b7f0a3c",
"6f3b9d2a-4c8e-4a1b-b7d3-9e2f5c8a1b4d",
"7a4c1e3b-5d9f-4b2c-c8e4-1f3a6d9b2c5e",
"8b5d2f4c-6e1a-4c3d-d9f5-2a4b7e1c3d6f"
]
}| Field | What it does |
|---|---|
name | Required. The object name, 1 to 200 characters. |
description | Identity notes, up to 2,000 characters. |
category, style | The object's category and visual style. |
count | Candidates to generate, 1 to 10. The default is 1. |
provider | The image model id. Omit it for the default model. |
seedPromptHint | A prompt fragment to fold into the prompt, up to 2,000 characters, for example antique brass from the Material picker. |
sourceImageUrl | An image to start from. |
aspectRatio | The frame of the main image. |
attachToObjectId, expectedUpdatedAt | Attach a single candidate to this object, optionally only when the object is unchanged. |
seedPromptHint is also accepted by generate-object-asset and generate-object-motion. It lets you compose a catalog choice, such as a vehicle or a material, into the prompt without wiring a picker node.
Generate a variant
POST /v1/generate-object-asset generates one variant and returns { jobId }. Send attachToObjectId, attachToColumn and attachName to append { name: attachName, url } to the bucket when the job completes.
curl -X POST https://app.nodaro.ai/v1/generate-object-asset \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Antique Lantern",
"assetType": "materials",
"variant": "gold",
"attachToObjectId": "2c7e9a4b-1d3f-4e8a-9b6c-5f0d2a7e3b1c",
"attachToColumn": "materials",
"attachName": "gold"
}'await client.objects.generateAsset({
name: 'Antique Lantern',
assetType: 'variations',
variant: 'weathered',
attachToObjectId: id,
attachToColumn: 'variations',
attachName: 'weathered',
})nodaro objects generate-asset --asset-type materials --variant gold \
--attach-to-object-id <id> --attach-to-column materials --watch| Field | What it does |
|---|---|
name | Required. The object name. |
assetType | Required. angles, materials, variations or custom. |
variant | Required. The variant to generate, 1 to 100 characters. |
description | A description of this variant, up to 1,000 characters. When you attach to an object and omit it, Nodaro drafts one from the object's canonical description and the variant name. Send your own to skip the draft. |
provider, sourceImageUrl, aspectRatio | The image model, the image to vary and the frame. |
attachToObjectId, attachToColumn, attachName | Where the result goes. attachToColumn is angles, materials or variations, and a custom variant must name it. |
Animate the main image
POST /v1/generate-object-motion turns an image of the object into a short camera-move clip, such as a slow rotation, a hover or a drone orbit. Use the clips as B-roll or as the start of a longer video. The route returns { jobId }.
sourceImageUrl is required; there is no fallback, so pass the approved main image. With attachToObjectId and attachName, the clip is appended to motionClips when it completes; you do not send a column.
curl -X POST https://app.nodaro.ai/v1/generate-object-motion \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Antique Lantern",
"motionPrompt": "slow 360 rotation, soft golden rim light",
"sourceImageUrl": "https://cdn.nodaro.ai/objects/lantern-main.png",
"provider": "kling-turbo",
"attachToObjectId": "2c7e9a4b-1d3f-4e8a-9b6c-5f0d2a7e3b1c",
"attachName": "rotate-360"
}'const lantern = await client.objects.get(id)
await client.objects.generateMotion({
name: 'Antique Lantern',
motionPrompt: 'slow 360 rotation, soft golden rim light',
sourceImageUrl: lantern.sourceImageUrl!,
provider: 'kling-turbo',
attachToObjectId: id,
attachName: 'rotate-360',
})nodaro objects generate-motion --name "Antique Lantern" \
--motion-prompt "slow 360 rotation, soft golden rim light" \
--source-image-url "https://cdn.nodaro.ai/objects/lantern-main.png" \
--provider kling-turbo --attach-to-object-id <id> --attach-name "rotate-360" --watch| Field | What it does |
|---|---|
name, motionPrompt | Required. The object name and the movement to create. |
sourceImageUrl | Required. The start frame. |
provider | kling-turbo (the default), kling, kling-3.0, minimax, hailuo-2.3, wan-i2v, seedance or bytedance-lite. |
aspectRatio | 1:1 (the default, a centered product frame), 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. The composition stays; use a model that supports video-to-video, such as wan-i2v. |
attachToObjectId, attachName | The object and the clip's name in motionClips. |
The price of a clip is the model's image-to-video price:
| Model | Maker | Modes | Credits | Details |
|---|---|---|---|---|
| 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 | Kuaishou | Image to video, Text to video | from 138 | Kling 2.6 I2V — strong motion realism. 5s/10s, optional native audio. |
| 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 | 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 | MiniMax | Image to video | from 75 | Cheaper Hailuo 2.3 tier — good baseline quality. |
| 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 | Bytedance | Image to video, Text to video | 57 | Cheapest Bytedance video tier with end-frame support. |
Approve a main image
POST /v1/objects/:id/approve-main-image sets a completed candidate as the main image and, in the same call, writes canonicalDescription: the text that later prompts use to describe the object. The body is { candidateJobId, expectedUpdatedAt? }, and the candidate must be a completed job that belongs to you.
The route returns { sourceImageUrl, canonicalDescription }. When writing the description fails, the main image is still set and canonicalDescription is an empty string; the SDK returns null instead. Call POST /v1/objects/:id/llm-caption to try again. That route returns { canonicalDescription }, answers 502 when the description cannot be written, and 400 main_image_required when there is no main image yet. It does not take expectedUpdatedAt: it is always safe to repeat.
curl -X POST https://app.nodaro.ai/v1/objects/2c7e9a4b-1d3f-4e8a-9b6c-5f0d2a7e3b1c/approve-main-image \
-H "Authorization: Bearer $NODARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "candidateJobId": "6f3b9d2a-4c8e-4a1b-b7d3-9e2f5c8a1b4d" }'const approved = await client.objects.approveMainImage(id, jobIds[1])
if (approved.canonicalDescription === null) {
await client.objects.recaption(id)
}nodaro objects approve-main-image <id> --candidate-job-id <jobId>
nodaro objects recaption <id>Archive, restore and delete an object
| Action | curl | TypeScript SDK | CLI |
|---|---|---|---|
| Archive | DELETE /v1/objects/:id | client.objects.delete(id) | nodaro objects delete <id> |
| Restore | POST /v1/objects/:id/restore | client.objects.restore(id) | nodaro objects restore <id> |
| Delete for good | DELETE /v1/objects/:id?permanent=true | client.objects.permanentDelete(id) | nodaro objects delete <id> --permanent |
- Archive returns
{ success: true, archived: true }. Archiving an archived object changes nothing. - Restore returns
{ id, name }. When an active object has the same name, ignoring case, Nodaro adds a(restored)suffix and returns the new name. - Delete for good returns
{ success: true, permanent: true }and removes the object and every file it references: the main image, the variants, the clips and the reference photos. It works only on an archived object; an active one returns400 not_archived. Archive first, then delete.
Pick a variant when you run an app
When a workflow with an Object/Props Asset node is published as an app, the object becomes one of the app's inputs. Pass "<bucket>/<variant>" to use a variant as the object's main image for that run, for example "materials/gold". Write the variant name in lowercase with hyphens for spaces: polished-brass matches a variant named Polished Brass. An unknown bucket or variant falls back to the main image. See Workflows for running apps.
Use the object in other generations
Pass the object's asset URLs as reference images to Generate Image or Generate Video; explicit URLs are the simplest choice for code. In a workflow, wire the object node into the image node, or mention a variant in the prompt, for example @lantern:1:materials/gold for an object named Lantern. When you wire an object without a mention, Nodaro also looks for variant names in your prompt: "gold finish" selects materials/gold. See Objects.
Use it from MCP
| Tool | What it does |
|---|---|
list_objects, get_object | Find an object and read its variant URLs. |
generate_object | Generate a main image or a variant. |
approve_object_main_image, recaption_object | Approve a main image, or write its description again. |
generate_object_motion | Animate the main image. |
There are no MCP tools to create, update, archive, restore or delete objects: generate_object creates them, and the other changes go through REST, the SDK or the CLI. See the MCP tools reference.
Credits
| Route | Price on Nodaro Cloud |
|---|---|
POST /v1/generate-object | The image model's price times count, reserved for every candidate before the first job starts. |
POST /v1/generate-object-asset | The image model's price, per variant. |
POST /v1/generate-object-motion | The video model's image-to-video price, per clip. |
approve-main-image, llm-caption | Free. |
Errors
| Status | Code | Meaning |
|---|---|---|
400 | validation_error | A field is missing or invalid. |
400 | not_archived | A permanent delete was sent for an object that is not archived. |
400 | main_image_required | llm-caption was called before the object 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 object with that id belongs to you. |
409 | concurrent_modification | expectedUpdatedAt no longer matches. Read the object again, merge and retry. |
502 | — | The canonical description could not be written. Try again. |
Frequently asked questions
Related
Objects and props
Object/Props Asset
Characters
Locations
Jobs
Last updated on
Characters
Create, update, archive and restore characters over REST, generate portrait candidates, expressions, angles and motion clips, and approve the anchor portrait.
Locations
Script locations over REST: create and archive places, generate establishing shots, time-of-day, weather and angle variants, 360-degree views and motion clips.