Nodaro Docs
DocumentationNode ReferenceModelsAI Agents (MCP)DevelopersSelf-hostingResearch
REST API

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

MethodPathWhat it does
GET/v1/objectsList your objects.
GET/v1/objects/:idGet one object with its jobs in progress.
POST/v1/objectsCreate an object, or update one when the body has an id.
DELETE/v1/objects/:idArchive an object. It can be restored.
DELETE/v1/objects/:id?permanent=trueDelete an archived object and its files for good.
POST/v1/objects/:id/restoreRestore an archived object.
POST/v1/generate-objectGenerate candidate main images.
POST/v1/generate-object-assetGenerate one angle, material, variation or custom variant.
POST/v1/generate-object-motionAnimate the main image into a motion clip.
POST/v1/objects/:id/approve-main-imageApprove a candidate as the main image and write the object's description.
POST/v1/objects/:id/llm-captionWrite the description again from the current main image.

What an object holds

FieldWhat it holds
id, name, descriptionThe identifier, the display name and identity notes.
categoryfurniture, vehicle, weapon, food, clothing, electronics, nature, tool, animal or other.
stylerealistic, anime, 3d-pixar or illustration.
sourceImageUrlThe anchor main image, set when you approve a candidate.
canonicalDescriptionA visual description of about 80 to 120 words, written by Nodaro when the main image is approved. It is an empty string until then.
styleLockWhether variants are generated from the main image. true by default.
angles, materials, variations, motionClipsThe asset buckets. Each entry is { name, url }; motionClips holds videos.
referencePhotosUp to 20 mood-board photos, each { kind, url }.
pendingJobsOn GET /v1/objects/:id only: the variant jobs still running for this object.

The asset buckets

BucketWhat it showsPreset variants
anglesThe object from another viewpointfront, side, top, back, three-quarter, detail, in-context, exploded, perspective
materialsThe object in another materialwood, metal, glass, plastic, fabric, stone, ceramic, leather, paper, gold, silver, copper, marble
variationsAnother condition or styleclean, weathered, damaged, ornate, minimal, broken, antique, futuristic, holographic, dirty, polished
motionClipsLooping camera-move clipsrotate-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.

kindWhat it is for
frontA clean front view.
sideA side profile, useful for vehicles, furniture and weapons.
detailA close-up of a defining feature, such as an engraving or a hinge.
contextThe object in place, held or mounted, for scale.
moodBoardThe palette or the feel.
otherAnything 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 --archived

GET /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 false

A 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"
  ]
}
FieldWhat it does
nameRequired. The object name, 1 to 200 characters.
descriptionIdentity notes, up to 2,000 characters.
category, styleThe object's category and visual style.
countCandidates to generate, 1 to 10. The default is 1.
providerThe image model id. Omit it for the default model.
seedPromptHintA prompt fragment to fold into the prompt, up to 2,000 characters, for example antique brass from the Material picker.
sourceImageUrlAn image to start from.
aspectRatioThe frame of the main image.
attachToObjectId, expectedUpdatedAtAttach 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
FieldWhat it does
nameRequired. The object name.
assetTypeRequired. angles, materials, variations or custom.
variantRequired. The variant to generate, 1 to 100 characters.
descriptionA 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, aspectRatioThe image model, the image to vary and the frame.
attachToObjectId, attachToColumn, attachNameWhere 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
FieldWhat it does
name, motionPromptRequired. The object name and the movement to create.
sourceImageUrlRequired. The start frame.
providerkling-turbo (the default), kling, kling-3.0, minimax, hailuo-2.3, wan-i2v, seedance or bytedance-lite.
aspectRatio1:1 (the default, a centered product frame), 3:4, 16:9, 9:16 or 4:3.
refineFromVideoUrlAn 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, attachNameThe object and the clip's name in motionClips.

The price of a clip is the model's image-to-video price:

ModelMakerModesCreditsDetails
Kling 2.5 Turbo ProKuaishouImage to video, Text to videofrom 110Faster Kling — good quality at lower cost. Supports end frame.
Kling 2.6KuaishouImage to video, Text to videofrom 138Kling 2.6 I2V — strong motion realism. 5s/10s, optional native audio.
Kling 3.0KuaishouImage to video, Text to videofrom 270Premium Kling 3.0 — variable 3-15s duration, native audio, 720P/1080P.
Hailuo 02 I2V ProMiniMaxImage to video, Text to video143Hailuo 02 Pro — strong photoreal motion, fixed 5-second clips. Supports end frame.
Hailuo 2.3 StandardMiniMaxImage to videofrom 75Cheaper Hailuo 2.3 tier — good baseline quality.
Wan 2.6 I2VAlibabaImage to videofrom 175Wan 2.6 image-to-video — 5/10/15s at 720p/1080p.
Bytedance Lite I2VBytedanceImage to video, Text to video57Cheapest 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

ActioncurlTypeScript SDKCLI
ArchiveDELETE /v1/objects/:idclient.objects.delete(id)nodaro objects delete <id>
RestorePOST /v1/objects/:id/restoreclient.objects.restore(id)nodaro objects restore <id>
Delete for goodDELETE /v1/objects/:id?permanent=trueclient.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 returns 400 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

ToolWhat it does
list_objects, get_objectFind an object and read its variant URLs.
generate_objectGenerate a main image or a variant.
approve_object_main_image, recaption_objectApprove a main image, or write its description again.
generate_object_motionAnimate 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

RoutePrice on Nodaro Cloud
POST /v1/generate-objectThe image model's price times count, reserved for every candidate before the first job starts.
POST /v1/generate-object-assetThe image model's price, per variant.
POST /v1/generate-object-motionThe video model's image-to-video price, per clip.
approve-main-image, llm-captionFree.

Errors

StatusCodeMeaning
400validation_errorA field is missing or invalid.
400not_archivedA permanent delete was sent for an object that is not archived.
400main_image_requiredllm-caption was called before the object has a main image.
401unauthorizedThe token is missing, invalid or revoked.
402insufficient_creditsNodaro Cloud only. The account cannot cover the reservation.
404not_foundNo active object with that id belongs to you.
409concurrent_modificationexpectedUpdatedAt no longer matches. Read the object again, merge and retry.
502—The canonical description could not be written. Try again.

Frequently asked questions

Last updated on

On this page