Objects and creatures
Create objects and creatures, generate main images and variants, animate them, and make a creature talk from TypeScript with the Nodaro SDK.
client.objects scripts everything Object Studio does for props, products and vehicles, and client.creatures does the same for animals and creatures. Both create and edit items, generate main-image candidates, approve one, add variants and motion clips, and write a caption that keeps the item consistent in later prompts. The methods call the Objects and Creatures REST APIs. See Objects and Creatures for the editor view.
Methods
The two resources have the same methods.
| Method | What it does |
|---|---|
objects.list(params?), creatures.list(params?) | List your items |
objects.listArchived(params?) | List your archived items |
objects.get(id) | Read one item, with its jobs in progress |
objects.create(input), creatures.create(input) | Create an item |
objects.update(id, input), creatures.update(id, input) | Change an item |
objects.delete(id) and restore(id) | Archive an item, or bring it back |
objects.permanentDelete(id) | Destroy an archived item and its files |
objects.generate(input), creatures.generate(input) | Generate main-image candidates |
objects.generateAsset(input), creatures.generateAsset(input) | Generate a variant |
objects.generateMotion(input), creatures.generateMotion(input) | Animate the main image into a clip |
objects.approveMainImage(id, candidateJobId, expectedUpdatedAt?) | Make a candidate the main image |
objects.recaption(id) | Write the description again |
client.objects
An object has its main image in sourceImageUrl, four collections (angles, materials, variations and motionClips, each a list of { name, url }), boards, referencePhotos, canonicalDescription and styleLock. category is one of furniture, vehicle, weapon, food, clothing, electronics, nature, tool, animal or other.
Object shares its name with the JavaScript global. When you need both, import it under another name: import type { Object as NodaroObject } from "@nodaro/sdk".
objects.list(params?)
Lists your objects. By default it returns active objects only. Paging is optional: without limit you get the whole list; with limit, at most 500, you get one page and a nextCursor.
list(params?: { archived?: boolean; projectId?: string; limit?: number; cursor?: string }): Promise<{
objects: Object[]
nextCursor?: string | null
}>Prop
Type
const { objects } = await client.objects.list()
const page = await client.objects.list({ limit: 100 })objects.listArchived(params?)
Lists your archived objects, as a shortcut for list({ archived: true }). creatures.listArchived() works the same way.
listArchived(params?: { projectId?: string; limit?: number; cursor?: string }): Promise<{ objects: Object[]; nextCursor?: string | null }>Prop
Type
const { objects: archived } = await client.objects.listArchived()objects.get(id)
Reads one object, with pendingJobs, the variants still generating. An archived object is not returned: it throws NotFoundError. creatures.get() works the same way.
get(id: string): Promise<ObjectDetail>Prop
Type
const object = await client.objects.get(objectId)
console.log(object.sourceImageUrl, object.materials)objects.create(input)
Creates an object. name and nodeId are required. A script without a canvas node can pass "mcp-managed" as nodeId.
create(input: CreateObjectInput): Promise<{ id: string }>Prop
Type
const { id: objectId } = await client.objects.create({
nodeId: "mcp-managed",
name: "Antique Lantern",
description: "Weathered brass lantern with hand-engraved filigree",
category: "tool",
style: "realistic",
})objects.update(id, input)
Changes an object. Only the fields you send are written. The variant collections are not part of this call, because generation jobs add to them while you work.
update(id: string, input: UpdateObjectInput): Promise<{ id: string; updatedAt: string }>Prop
Type
await client.objects.update(objectId, {
canonicalDescription: "A weathered brass lantern with engraved filigree and a glass chimney",
expectedUpdatedAt: object.updatedAt,
})The 409 concurrent_modification arrives as a plain NodaroError. Read the object again, merge, and retry.
objects.delete(id) and restore(id)
delete() archives an object. Repeating it on an archived object changes nothing. restore() brings it back; when the name now matches an active object's name, ignoring case, the server adds (restored) and returns the name it used.
delete(id: string): Promise<{ success: true; archived: true }>
restore(id: string): Promise<{ id: string; name: string }>Prop
Type
await client.objects.delete(objectId)
const { name } = await client.objects.restore(objectId)objects.permanentDelete(id)
Destroys an archived object and every stored file it references. It works on archived objects only: an active object fails with 400 not_archived. Archive first with delete(). creatures.permanentDelete() works the same way.
permanentDelete(id: string): Promise<{ success: true; permanent: true }>Prop
Type
await client.objects.delete(objectId)
await client.objects.permanentDelete(objectId)Nodaro's MCP tools do not offer this operation, so an AI assistant cannot destroy your objects.
objects.generate(input)
Generates main-image candidates (POST /v1/generate-object). With count above 1, every job is reserved before any starts, so a failure part way through rolls the batch back.
generate(input: GenerateObjectInput): Promise<{ jobIds: string[]; jobId?: string }>Prop
Type
const { jobIds } = await client.objects.generate({ name: "Antique Lantern", count: 4 })
for (const jobId of jobIds) {
// poll each candidate with client.jobs.getStatus(jobId)
}jobIds is always present, with one id per candidate. jobId is an older alias for a single candidate; use jobIds. With attachToObjectId and one candidate, the result becomes the main image when the job completes. Otherwise, pick one with approveMainImage().
objects.generateAsset(input)
Generates one variant (POST /v1/generate-object-asset). With attachToObjectId, attachToColumn and attachName, the result is added to that collection when the job completes.
generateAsset(input: GenerateObjectAssetInput): Promise<{ jobId: string }>Prop
Type
const { jobId } = await client.objects.generateAsset({
name: "Antique Lantern",
assetType: "materials",
variant: "gold",
attachToObjectId: objectId,
attachToColumn: "materials",
attachName: "gold",
})For angles, materials, variations and motion, the collection follows from the asset type. A custom variant needs attachToColumn.
objects.generateMotion(input)
Animates the object's image into a clip (POST /v1/generate-object-motion), with Generate Video in image-to-video mode. The clip always goes to motionClips. The defaults suit product shots: the model is kling-turbo and the frame is 1:1.
generateMotion(input: GenerateObjectMotionInput): Promise<{ jobId: string }>Prop
Type
const { jobId } = await client.objects.generateMotion({
name: "Antique Lantern",
motionPrompt: "Slow 360-degree rotation, soft golden rim light",
sourceImageUrl: object.sourceImageUrl!,
attachToObjectId: objectId,
attachName: "rotate-360",
})objects.approveMainImage(id, candidateJobId, expectedUpdatedAt?)
Makes a completed candidate from generate() the object's main image. A vision model then writes the object's description, and the method returns both.
approveMainImage(id: string, candidateJobId: string, expectedUpdatedAt?: string): Promise<{
sourceImageUrl: string
canonicalDescription: string | null
}>Prop
Type
const { sourceImageUrl, canonicalDescription } = await client.objects.approveMainImage(objectId, jobIds[0])canonicalDescription is null when the description could not be written. The main image is still set; call recaption() to try again.
objects.recaption(id)
Writes the object's description again from its current main image. The call is safe to repeat and takes no concurrency token.
recaption(id: string): Promise<{ canonicalDescription: string }>Prop
Type
const { canonicalDescription } = await client.objects.recaption(objectId)Fails with 400 main_image_required when the object has no main image, and with a 502 when the vision model fails.
client.creatures
A creature is an animal or a fantasy being. It works like an object, with four differences:
speciesis a free-text type, such asdragonorwolf, and it is the subject of the main-image prompt.categoryis free text too.posesreplacesmaterials, so the collections areangles,poses,variationsandmotionClips. The variant types areangles,poses,variationsandcustom.boardsholds up to 24 named Creature Boards, dense reference sheets made with the Creature Board preset of Generate Image. You own this list:create()andupdate()replace it as a whole.voicemakes the creature a talking creature. It has the same shape as a character's voice:{ voiceId, voiceName, traits, voiceType?, previewUrl?, ttsProvider? }. Passvoice: nullto remove it.
creatures.listArchived(), get(), delete(), restore(), permanentDelete(), approveMainImage() and recaption() take the same arguments and behave like the object methods above.
creatures.list(params?)
list(params?: { archived?: boolean; projectId?: string; limit?: number; cursor?: string }): Promise<{
creatures: Creature[]
nextCursor?: string | null
}>Prop
Type
const { creatures } = await client.creatures.list()creatures.create(input)
create(input: CreateCreatureInput): Promise<{ id: string }>Prop
Type
const { id: creatureId } = await client.creatures.create({
nodeId: "mcp-managed",
name: "Biscuit",
species: "ginger cat",
style: "realistic",
})creatures.update(id, input)
Takes the fields of create() except nodeId, plus boards, selectedAssetByVariant and expectedUpdatedAt. Only the fields you send are written.
update(id: string, input: UpdateCreatureInput): Promise<{ id: string; updatedAt: string }>Prop
Type
await client.creatures.update(creatureId, {
voice: { voiceId: chosenVoiceId, voiceName: "Aria", traits: "smug, unhurried" },
})creatures.generate(input)
Generates main-image candidates. It takes the fields of objects.generate(), plus species, and returns { jobIds }.
generate(input: GenerateCreatureInput): Promise<{ jobIds: string[]; jobId?: string }>Prop
Type
const { jobIds } = await client.creatures.generate({ name: "Biscuit", species: "ginger cat", count: 4 })creatures.generateAsset(input)
Generates one variant. It works like objects.generateAsset(), with attachToCreatureId.
generateAsset(input: GenerateCreatureAssetInput): Promise<{ jobId: string }>Prop
Type
await client.creatures.generateAsset({
name: "Biscuit",
assetType: "poses",
variant: "sitting",
attachToCreatureId: creatureId,
attachToColumn: "poses",
attachName: "sitting",
})creatures.generateMotion(input)
Animates the creature's image into a clip. It works like objects.generateMotion(), with the same defaults, kling-turbo and 1:1, and adds the clip to motionClips.
generateMotion(input: GenerateCreatureMotionInput): Promise<{ jobId: string }>Prop
Type
await client.creatures.generateMotion({
name: "Biscuit",
motionPrompt: "The cat stretches, then yawns",
sourceImageUrl: creature.sourceImageUrl!,
attachToCreatureId: creatureId,
})Make a creature talk
Speech needs no creature-specific method. Render the line with the creature's voice, then lip-sync it onto the creature's image:
const creature = await client.creatures.get(creatureId)
// 1. Speak the line in the creature's voice
const speech = await client.nodes.runAndWait("text-to-speech", {
text: "I knocked the vase off the shelf. I regret nothing.",
voice: creature.voice!.voiceId,
provider: creature.voice!.ttsProvider,
voiceType: creature.voice!.voiceType,
})
// 2. Lip-sync the audio onto the creature's main image
const clip = await client.nodes.runAndWait("lip-sync", {
imageUrl: creature.sourceImageUrl!,
audioUrl: speech.audioUrl,
provider: "kling-avatar",
})
console.log(clip.videoUrl)The Lip Sync node also dubs an existing video: pass videoUrl and a model that takes video. volcengine-lipsync is the lowest-priced option for dubbing and the only one that handles several speakers:
const dub = await client.nodes.runAndWait("lip-sync", {
videoUrl: "https://example.com/scene.mp4",
audioUrl: "https://example.com/new-vocal.mp3",
provider: "volcengine-lipsync",
mode: "basic", // for complex scenes
openScenedet: true, // several speakers: scene and speaker detection
audioDurationSec: 42, // sets the per-second price; without it, you pay for 5 minutes
})To bind a creature into a shot as a reference, build the reference with toConnectedReference({ kind: "creature", id, name, url, description }) from @nodaro/shared. Generate Image then adds a line that keeps the creature's anatomy, markings and colors. See References.
Frequently asked questions
Related
Objects and props
Animals and creatures
Objects
Creatures
Characters
Last updated on
Locations
Create locations, generate establishing shots, approve one, and add time-of-day, weather, season, angle and motion variants from TypeScript.
Community library
Browse the Nodaro community library from TypeScript, clone shared characters, locations and objects into your account, favorite them, and report a listing.