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

Nodes

Run any Nodaro node with POST /v1/<node-type>, discover nodes, models and picker values, and steer prompts with references and cinematic direction ids.

A node run calls one Nodaro node directly, without building a workflow: you send POST /v1/<node-type> with the node's settings as the body. Every generation node follows this shape, from generate-image and generate-video to text-to-speech, and the discovery endpoints tell you which nodes, models and settings exist. Most node runs are asynchronous: the response is a jobId that you poll until the result is ready.

Run a node

The route for a node type is POST /v1/ followed by the type, and the body is the node's settings as JSON:

POST /v1/generate-image
Authorization: Bearer ndr_…
Content-Type: application/json

{ "prompt": "a lighthouse in a storm, oil painting", "provider": "nano-banana-pro", "aspectRatio": "3:4" }

What comes back depends on the node:

  • Generation nodes answer 200 with { "jobId": "…" }. The work runs on a worker, and you poll the job with GET /v1/jobs/:id/status until its status is completed. See Jobs.
  • Image and video generations may add adjustments, a list of the settings the server corrected for the chosen model. generate-video may also add warnings. See Parameter corrections.
  • Inline nodes, such as combine-text, return their full result at once, with no jobId.
  • Scrapers, such as web-scrape, also answer at once. Their response carries both a jobId, for your history, and the data itself.

A generation reserves credits when it starts. When the account cannot cover the run, the call answers 402 insufficient_credits.

Nodes with a longer path

Most text nodes that call a language model follow the same POST /v1/<node-type> rule: generate-script, image-critic, qa-check and describe-to-picker. A few others are registered under a longer path:

Node typeRoute
llm-chatPOST /v1/llm-chat/generate
after-effectsPOST /v1/after-effects/generate
motion-graphicsPOST /v1/motion-graphics/generate
lottie-overlayPOST /v1/lottie-overlay/generate
3d-titlePOST /v1/3d-title/generate
image-to-textPOST /v1/image-to-text/describe
video-composerPOST /v1/scene-graph/generate

The SDK's client.nodes.run(type, params) posts to /v1/<type>, so call these with client.request('POST', '/v1/llm-chat/generate', { body }).

Language-model routes accept two optional fields:

  • reasoningEffort: none, low, medium, high, xhigh or max, depending on the model. Leave it out, or choose a level the model does not support, for the model's own default. xhigh and max bill one credit tier up.
  • advancedMode: true: Gemini models only. The request runs on the model maker's own API, the only lane where temperature, maxTokens and the full reasoning range take effect. It bills one credit tier up, independently of reasoningEffort. A model without that lane answers 400 advanced_mode_unsupported.

Example: generate an image

Start the job

curl -s -X POST https://app.nodaro.ai/v1/generate-image \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "prompt": "a knight on a hill at dawn, cinematic",
        "provider": "nano-banana-pro",
        "aspectRatio": "16:9",
        "resolution": "2K"
      }'
{ "jobId": "0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10" }
import { createClient, StaticTokenAuth } from '@nodaro/sdk'

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

const result = await client.nodes.run('generate-image', {
  prompt: 'a knight on a hill at dawn, cinematic',
  provider: 'nano-banana-pro',
  aspectRatio: '16:9',
  resolution: '2K',
})
nodaro nodes run generate-image \
  --param prompt="a knight on a hill at dawn, cinematic" \
  --param provider=nano-banana-pro \
  --param aspectRatio=16:9 \
  --param resolution=2K

Poll the job

Ask for the job's status every 2 to 5 seconds until it is completed or failed:

curl -s https://app.nodaro.ai/v1/jobs/0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10/status \
  -H "Authorization: Bearer $NODARO_API_KEY" | jq -r .data.status

Read the result

A completed image job carries the picture's URL in output_data.imageUrl. A video job uses videoUrl, an audio job audioUrl, and many jobs also carry a thumbnailUrl.

{
  "data": {
    "id": "0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10",
    "status": "completed",
    "progress": 100,
    "output_data": { "imageUrl": "https://…/0f1a9c2e.png" },
    "error_message": null
  }
}

The SDK and the CLI can do the polling for you:

const output = await client.nodes.runAndWait('generate-image', {
  prompt: 'a knight on a hill at dawn, cinematic',
  provider: 'nano-banana-pro',
})
console.log(output.imageUrl)

// Several candidates at once, in input order:
const results = await client.nodes.runMany('generate-image', [
  { prompt: 'a knight on a hill, sunrise' },
  { prompt: 'a knight on a hill, golden hour' },
  { prompt: 'a knight on a hill, blue hour' },
])
for (const { jobId, output } of results) console.log(jobId, output.imageUrl)
nodaro nodes run generate-image \
  --param prompt="a knight on a hill at dawn, cinematic" \
  --param provider=nano-banana-pro \
  --watch --json | jq -r '.output_data.imageUrl'

runAndWait polls every 2,000 ms for up to 15 minutes by default. Change that with pollMs and maxMs, stop it with an AbortSignal in signal, and follow progress with onProgress. It throws typed errors you can catch with instanceof:

ErrorWhen
InsufficientCreditsError, StorageExceededError, JobBlockedErrorThe run was refused before any job started.
JobFailedErrorThe job ended failed or cancelled. It carries jobId and the error message.
JobTimeoutErrormaxMs passed. The job is not cancelled and usually still completes: fetch it later with client.jobs.get(jobId).
JobAbortedErrorYour signal fired.
JobHeldErrorThe job entered pending_review on a deployment that reviews results. The job is not cancelled: check back later.

With the CLI, pass complex bodies, such as arrays or nested objects, in a file with --params-file body.json. Flag values override the file for the same key, and true, false, null and numbers are converted from text.

Generate Image settings

The fields below are the most used settings of POST /v1/generate-image. Read Generate Image for what each setting does in the editor, and the image models for what each model supports.

Prop

Type

A masked edit or a refine costs the same as a new generation on that model.

Generate a video

Two routes make video. POST /v1/generate-video animates from pictures: a start frame in imageUrl, an optional last frame in endFrameUrl, or references alone on the models that accept them. POST /v1/text-to-video makes a clip from a prompt alone, so its prompt is required. When you leave out provider, the platform's default video model is used.

{
  "imageUrl": "https://…/frame.png",
  "provider": "seedance-2",
  "prompt": "she turns toward the window",
  "duration": 8,
  "resolution": "720p",
  "direction": { "cameraMotion": "dolly-in", "timeOfDay": "dawn" }
}

The response is { "jobId": "…" }, with warnings and adjustments when they apply. The finished job carries output_data.videoUrl.

Prop

Type

A few rules differ by model:

  • resolution and aspectRatio on the Seedance 2 family are passed through: a value the model does not support is ignored, never refused. Seedance 2 is the only one with 4k and adaptive. Seedance 2.5 makes up to 30 seconds in one call and, with a start frame, always renders at that frame's aspect ratio.
  • MiniMax Hailuo 3 takes resolution 2K or 768P, written exactly like that. GET /v1/nodes/:type lists the value to send in providerResolutionWire.
  • Frames and references can be combined on Seedance 2 and MiniMax Hailuo 3. When you send a reference beside a start or end frame, the frames become numbered references in the prompt instead of fixed endpoints. Wan 3.0 cannot send both, so the frame is added to the end of the reference list instead, and the call still succeeds.
  • Reference videos cost more. On models that bill them, a reference clip is priced by its own duration plus the output duration, so a longer source clip reserves more credits. Wan 3.0 bills output seconds only.

For every model's durations, resolutions and credit prices, read the video models or call GET /v1/models.

Use references

References are pictures, and on video also clips and audio, that the model should match. You can send them as a flat list of URLs, or as structured references that the route numbers, labels and writes into the prompt for you, exactly as the editor does for wired nodes.

Structured references

connectedReferences is accepted by POST /v1/generate-image, POST /v1/generate-video, POST /v1/text-to-video and POST /v1/extend-video. Each entry describes one picture:

Prop

Type

On the video routes, the route turns these entries into numbered references:

  • Every reference you do not mention is attached. Its URL joins the reference list, with duplicates removed, and gets a line such as @image_1 (reference): <label>. A wired-character entry becomes part of a "Use these characters:" instruction instead.
  • The list is capped at the model's limit before numbering, so a reference number in the prompt never points at a picture that was not sent.
  • {image:N:label} in the prompt becomes "the label from @image_N", numbered against the attached references.
  • {ref:<id>} and {ref:<id>:label} address a reference by the id you gave it. The platform replaces the token with the reference's number after it has numbered the list, so you never compute a number yourself. The list is numbered in this order: the flat referenceImageUrls first, then characters you did not mention, then the other entries in your order. A token whose reference was not attached, because it was over the cap or the model takes no references, falls back to its label, then to defaultName, then to nothing. It never reaches the model as raw text.
  • referenceOrder, a list of reference ids, reorders the references and renumbers them to match. POST /v1/generate-image accepts it too.

connectedReferences feeds pictures only. referenceVideoUrls and referenceAudioUrls stay flat lists. Leave connectedReferences out and the routes behave as before: your prompt and referenceImageUrls are sent as they are.

Which video models accept picture references

Model familyPicture references
Seedance 2 familyUp to 9
HappyHorse Ref2VUp to 9
Gemini Omni, Kling 3 Omni, Grok Imagine image-to-videoUp to 7
VEO 3.1 Fast and VEO 3.1 LiteUp to 3

On any other model, the {image:N} tokens are reduced to their labels and nothing is attached. VEO 3.1 Quality is not on the list: references sent with veo3 are ignored, and the run uses its frames.

Video from references alone

On POST /v1/generate-video, the start frame is optional when at least one kind of reference you send is supported by the model, for example Kling 3 Omni with referenceImageUrls alone. A references-only run on VEO 3.1 Fast or Lite switches to reference mode by itself, so you do not need generationType. A reference kind the model cannot use does not count: audio-only references on a model that takes only pictures are refused with 400.

An end frame alone is accepted on the models that fold it into their references: the Seedance 2 family, MiniMax Hailuo 3 and Wan 3.0. Send endFrameUrl once, without repeating the picture in referenceImageUrls. The @nodaro/shared package exports videoProviderFoldsLoneEndFrame(provider) so your interface can use the same rule.

When a request has no start frame, no reference mode and no reference the model can use, the answer depends on the model:

  • A model that cannot make video from text alone, such as Kling 3 Omni, HappyHorse Ref2V or Hailuo 2.3, answers 400 image_required. The message says whether references would work instead. GET /v1/models is the authority on which models these are.
  • Every other model answers 400 validation_error and tells you to use POST /v1/text-to-video for a prompt-only clip.

Extend Video

POST /v1/extend-video accepts connectedReferences and referenceImageUrls only with provider: "seedance-2-extend". Any other extend model refuses them with 400. The limit is 8 of your pictures, because the source clip's last frame takes one reference seat, after yours, so your numbers never shift. The source's last 2 seconds travel as @video_1 and are already priced into the extend rate: reference pictures add no credits. Extend Video has no direction and no subject, because its prompt continues a clip that already has a look.

Described references

describedReferences names a subject you can describe but have no picture for yet, such as a role in a script. POST /v1/generate-image, /v1/generate-video, /v1/text-to-video and /v1/extend-video accept up to 10 entries of { name, description }. The name can be up to 80 characters and the description up to 2,000.

  • Nothing is attached. A described reference uses no reference seat, and the numbering of your other references does not change.
  • It reaches the model as a line of text: <Name> — <description>. Keep the name in your prompt, for example Natalie walks down the pier., and the line tells the model who Natalie is. Do not write an @ mention for it: that grammar addresses an attached picture.
  • It works on its own. Send describedReferences without any connectedReferences, the usual case for a story written before any character exists.
  • Entries without a name or a description are dropped, and a repeated name is written once. Every extend model accepts described references, because they carry no URL.

Per-use descriptions and captions

  • descriptionOverride on a connectedReferences entry says what the reference is for this run only, up to 2,000 characters. It fills the description the reference's instruction already has, ahead of the stored description. When the instruction has no description, it adds one line, so the model is told once, never twice.
  • referenceVideoCaptions and referenceAudioCaptions on POST /v1/generate-video and /v1/text-to-video describe the reference clips and audio, up to 500 characters each. They are index-aligned: referenceVideoCaptions[0] describes referenceVideoUrls[0]. Each becomes a line such as @video_1: <caption>., and a blank entry skips one clip without breaking the alignment.

Mention a reference in the prompt

On POST /v1/generate-image, you can place a reference inside your sentence instead of leaving it in the list at the end. Write @<name-slug>:<index>, or @<name-slug>:<index>:<role> to say what to take from it:

{
  "prompt": "a wide shot of @nessie:1 rising beside @dock:2:material",
  "connectedReferences": [
    { "id": "cr-1", "defaultName": "Nessie", "source": "wired-creature", "url": "https://…/nessie.png" },
    { "id": "ob-1", "defaultName": "Dock", "source": "wired-object", "url": "https://…/dock.png" }
  ]
}

The model receives "a wide shot of the creature from reference image A rising beside the material from reference image B".

  • The slug comes from defaultName, in lower case, with every run of other characters turned into one -: Old Town becomes old-town. There is no slug field to set.
  • The index only matches the mention to a reference. It is never written into the prompt, and the platform numbers the references itself.
  • Roles for pictures (manual and wired-image) are object, person, face, clothes, background, style, pose and texture. Creatures take creature, anatomy, markings, pose, color and style. Objects take object, shape, material, color, texture and style. Any other single word passes through as written. Without a role, the entry's defaultRole applies.
  • ~lock and ~nolock after a mention, as in @town:1:background~lock, turn that reference's identity lock on or off for the mention.
  • Names resolve in this order: character, location, image, creature, object. A name shared by a character and a picture means the character, and within one kind the first match wins.
  • A name whose slug starts with a digit cannot be mentioned, for example 3D Render, which becomes 3d-render. Rename the reference to mention it. A mention of a reference that was over the model's cap stays as literal text.
  • Mentioning a reference moves it from the list at the end to the place you typed it, and the letters of the references after it change to follow the sentence. For a creature or an object, a mention also replaces the line it would otherwise add at the end.

Reference lock

referenceLock on POST /v1/generate-image adds Nodaro's tested reference-fidelity wording ahead of the scene:

ValueAddsUse it for
standardInstructions to use only what the references show, keep likeness, and compose them togetherCompositions from several references
multi-personThe same, plus rules never to change or blend facesTwo or more faces in one shot

You send the id, not text: the wording belongs to the platform and improves without a client update. Leave the field out and no lock is added.

Cinematic direction by id

direction describes the camera, the light and the look with picker ids instead of prose. POST /v1/generate-image, POST /v1/generate-video and POST /v1/text-to-video accept it. Nodaro writes its own tested wording for each id into the prompt, so a saved request picks up improved wording over time instead of freezing the text your client wrote.

{
  "prompt": "a knight on a hill",
  "provider": "nano-banana-pro",
  "direction": {
    "shotSize": "wide-shot",
    "lens": "wide-24mm",
    "lightingStyle": "rembrandt",
    "style": "anime",
    "mood": ["happy", "joyful"]
  }
}

Keys and where their ids come from

Each key is a field of a Creative Controls picker. Get the valid ids from GET /v1/picker-catalogs/<picker>, the same catalogs the editor's pickers use.

KeysPickerImageVideo
shotSize, angle, coverage, composition, vantageFramingYesYes
posePoseYesYes
compositionEffectComposition EffectsYesYes
cameraFormatCamera FormatYesYes
lensLensYesYes
aperture, shutterSpeed, isoValueExposure SettingsYesNo
timeOfDay, lightingStyle, lightingDirection, lightingRatio, colorTemperatureLightingYesYes
colorLookColor / LookYesYes
atmosphereAtmosphereYesYes
postProcessPost-Process EffectsYesNo
styleStyleYesYes
moodMoodYesYes
aestheticAestheticYesYes
photoGenrePhoto GenreYesNo
photographerPhotographerYesNo
renderQualityRender QualityYesNo
settingSettingYesYes
eraEraYesYes
backdropBackdropYesYes
cameraMotionCamera MotionNoYes
actionFxAction FXNoYes
temporalSpeed, temporalFreeze, temporalDirection, temporalShutterTemporalNoYes
transitionTransitionNoYes
loopSubjectLoop SubjectNoYes

A key that does not apply to a route is accepted and simply adds nothing, so one map of look ids can be sent to both the image and the video routes unchanged.

Values and limits

  • One id or a list. The multi-pick keys, mood, aesthetic, photographer, atmosphere, postProcess, composition and lightingStyle, take up to their own limit, and extra ids are dropped. A single-pick key given a list uses the first entry.
  • Two limits are refused with 400 validation_error: more than 8 entries for one key, and an id longer than 100 characters.
  • Absent is not empty. A missing key means no hint, never a default. An empty string or an empty list adds nothing.
  • Unknown keys and unknown ids are skipped, not refused. A newer client on an older server gets fewer hints instead of an error, so update the server before a client that sends new keys.
  • Custom catalog packs. On a deployment that registers its own catalog packs, GET /v1/picker-catalogs lists the ids a pack adds. Those ids are accepted but add no wording to direction.

Where the words go

The clauses are added after your prompt, in a [style] section. The film line carries cameraFormat, colorLook, style and era, and the scene line carries the other look keys:

a knight on a hill

[style]:
<film line>
<scene line>
  • The order inside a line is the platform's fixed order, not the order of your keys. A clause repeated by two keys is written once.
  • A line with nothing on it is left out. When no key adds anything, there is no section at all, and your prompt reaches the model unchanged.
  • On the video routes, motion keys work differently. They add a short professional term, such as cross-dissolve, and stay in the body, after your prose, because motion is part of the shot. cameraMotion comes first. Only look keys go into the [style] section.

When the prompt is too long

Each model accepts a prompt up to its own length. A full direction can exceed a small limit on its own, for example 3,000 characters on Seedream on the image side and 1,000 characters on Kling on the video side. When that happens, Nodaro removes direction clauses one at a time, starting from the end of its fixed order, until the prompt fits. Nothing else is removed before them:

  • Subject clauses are removed only after every direction clause.
  • Your prose, your references and the phrases that bind them, @ mentions, and the Style: and Avoid: lines always stay.
  • Only when the prompt still does not fit with no hints left is the end of the text cut, with ... added.

On the video routes, the budget also counts the reference instructions the route adds, which are never removed. For a model with no negative-prompt setting, your negativePrompt is added as an Avoid: line whose room is reserved first, so a long negative prompt costs you hint clauses, not prose. The optional injectCharacterContext text is added after this step and is not part of the budget.

The job records what happened. input_data.prompt is what the model received, input_data.userPrompt is the text you sent (an empty string when you sent only direction), and input_data.direction is your ids as sent.

Direction stored on a node

A Generate Image node in a saved workflow can carry the same direction object in its data, written by the API, MCP or an app that creates workflows. The editor honors it on every run and in the final-prompt preview. Stored ids add to any wired Framing, Lighting or Style picker: the wired hint comes first, then the stored ids. Presets and workflow exports keep the ids with the rest of the node.

Describe the subject by id

subject is the same idea for who is in the shot: the person, how they are styled, and the props in the frame. POST /v1/generate-image, POST /v1/generate-video and POST /v1/text-to-video accept it:

{
  "prompt": "on the seawall at dusk",
  "provider": "nano-banana-pro",
  "subject": {
    "type": "woman",
    "age": "age-30s",
    "ethnicity": "east-asian",
    "hairBase": "base-short-straight",
    "makeup": "makeup-smoky",
    "outerwear": "outerwear-trench",
    "heldProp": "smartphone"
  }
}
  • The keys are the fields of the Person and Styling pickers, such as type, age, ethnicity, faceShape, hairColor, skinTone, makeup, outfit, outerwear and footwear, plus three prop keys: heldProp (Held Prop), material (Material) and animal (Animal). GET /v1/picker-catalogs/person and /styling list every field and id.
  • subject and direction never overlap. Their keys are separate, so one choice never adds two clauses. pose belongs to direction.
  • customAge is the one number. Send "age": "age-custom" with "customAge": 34 for an exact age in years. It is rounded and kept between 0 and 120.
  • Lists have limits per key. jewelry, wardrobeState and distinctiveFeature take 3 ids. ethnicity, regionalAesthetic, hairColor, eyeColor, lipState, eyeState, skinTexture, hairState, heldProp and material take 2. Every other key, animal included, takes 1. Extra ids are dropped.
  • Refused limits: more than 8 entries for one key, an id longer than 100 characters, more than 128 keys, or a key longer than 64 characters answer 400 validation_error.
  • Unknown keys are removed and unknown ids are skipped. input_data.subject on the job records exactly the ids that were used.
  • Subject clauses are part of your prose, ahead of the direction clauses and never in the [style] section. Person becomes one clause and Styling another, and overlapping choices are written once. On the image route each choice adds its full clause; the video routes add the short term, because the start frame already shows who the subject is.

Discover nodes

GET /v1/nodes lists every node type the server knows, and GET /v1/nodes/:type returns one. Both are public, need no token, and are cached for 5 minutes. An unknown type answers 404 not_found.

curl -s https://app.nodaro.ai/v1/nodes/generate-image | jq .data
const { data: nodes } = await client.nodes.list()
const imageNodes = nodes.filter((n) => n.category === 'ai-image')

const { data: generateImage } = await client.nodes.get('generate-image')
console.log(generateImage.providers)
nodaro nodes list --category ai-image
nodaro nodes get generate-image
{
  "data": {
    "type": "generate-image",
    "label": "Generate Image",
    "category": "ai-image",
    "description": "Generate an image from a text prompt using an AI provider.",
    "outputType": "image",
    "creditCost": "2-620",
    "providers": ["nano-banana-pro", "gpt-image-2", "gpt-image-2-5-flare", "seedream-5-pro", "z-image"],
    "capabilities": ["supports-reference-image", "supports-aspect-ratio"],
    "inputSchema": {
      "fields": [
        { "key": "prompt", "type": "text", "required": true },
        { "key": "provider", "type": "select", "options": ["nano-banana-pro", "gpt-image-2"] },
        { "key": "aspectRatio", "type": "select" },
        { "key": "promptPrefix", "type": "text" },
        { "key": "promptSuffix", "type": "text" }
      ]
    }
  }
}
FieldMeaning
typeThe node type, also the route: POST /v1/<type>.
label, category, descriptionHow the editor names and groups the node.
outputTypetext, image, video, audio, data or none.
creditCostThe node's credit cost, or its range. Nodaro Cloud only: editions without credits leave it out.
providersThe model ids the node accepts in provider.
capabilitiesFeature flags, such as supports-reference-image.
inputSchema.fieldsThe node's settings, with their type, whether they are required, and their options.

Every node that takes a prompt also lists promptPrefix and promptSuffix: text added before and after the prompt. See Prompt pre and post text. Nodes with per-model limits carry extra fields:

FieldMeaning
maxDurationSecThe longest duration the node accepts.
sparseProvidersModels with only a few segment durations. A value between them snaps to the nearest one.
providerResolutionsEach model's resolutions, for example { "minimax-h3": ["2K", "768P"] }.
providerResolutionWireThe exact value to send for each resolution, for example 768P, not 768p, for MiniMax Hailuo 3's cheaper tier.
soundtrackOn Generate Video Pro: the server accepts the original-audio soundtrack input.

The descriptor grows over time, so ignore fields you do not know. The same data drives the node reference.

Discover models

GET /v1/models returns the model catalog, grouped by kind and by maker. It is public and cached for 5 minutes, and it is the same data the MCP list_models tool returns.

curl -s "https://app.nodaro.ai/v1/models?kind=video&mode=i2v" | jq '.totalModels'
const catalog = await client.models.list({ kind: 'video', mode: 'i2v' })
for (const section of catalog.sections)
  for (const family of section.families)
    for (const m of family.models) console.log(m.id)
nodaro models list --kind video --mode i2v

The response is { sections, recommendations, totalModels }. Each model carries its capabilities (modes, features, aspectRatios, resolutions, durations), its credit pricing per variant on Nodaro Cloud, short promptTips, and doctrineCovered, which is true only when a sourced prompting guide exists for the model's family.

QueryValuesFilters to
kindimage, video or audioOne kind of media
modeFor example t2i, i2v, t2v, tts, video-analysisOne operation
familyA maker, for example Google or BytedanceOne maker
featuredOnlytrueFeatured models

The model pages show the same catalog.

Discover picker values

The Creative Controls pickers have public catalogs of valid ids, the values direction and subject take:

MethodPathWhat it returns
GET/v1/picker-catalogsEvery picker: nodeType, label, kind, its field or fields, optionCount and imageCount.
GET/v1/picker-catalogs/:nodeTypeOne picker's options. ?detail=full adds each option's description and promptHint, ?category= filters a single-field picker, and ?field= returns one field of a multi-field picker.
GET/v1/catalogsEvery catalog in one call, as the deployment curated it. data is present only when the deployment registered catalog packs.
POST/v1/text-to-picker"AI Fill": picks ids for many pickers from a free-text scene description. Costs credits.

Every option carries id, label and term, the short phrase to use in a prompt, plus imageUrl when the option has a picture. The catalogs also ship as data in the @nodaro/shared npm package. Read Picker catalogs for the full shapes. From the terminal: nodaro pickers list, nodaro pickers get mood --full and nodaro pickers analyze "<text>".

Structured LLM output

POST /v1/llm/structured runs one language-model call whose answer is forced into a JSON Schema you supply, validated, and returned as an object. It is billed in credits by model tier.

{
  "system": "You write production plans.",
  "input": "A rainy chase through Rome.",
  "jsonSchema": {
    "type": "object",
    "properties": { "title": { "type": "string" } },
    "required": ["title"]
  }
}

The answer is { jobId, output, usage: { inputTokens, outputTokens } }, where output has your schema's shape.

  • Fields: system, input, jsonSchema, and optionally schemaName (up to 64 characters), llmModel, reasoningEffort, maxRetries, origin, advancedMode, temperature and maxTokens. system and input take up to 100,000 characters each, and input needs at least one.
  • Model: without llmModel, the call runs on Gemini 3.6 Flash.
  • Schema: the root must be a plain object schema, up to 64 KB and 20 levels deep. It may use properties, required, additionalProperties, items, the basic types, enum, const, anyOf and oneOf below the root, the numeric and length bounds, multipleOf, exclusiveMinimum and description. not, if, then, else, the dependent keywords, external $ref, and combinators at the root answer 400. An anyOf of required branches below the root is accepted but not enforced, so check rules across fields yourself.
  • Retries: maxRetries, from 0 to 3 with a default of 2, is how many times an invalid answer goes back to the model with its validation error.
  • Sampling: maxTokens applies on every call and may not exceed the model's own limit. temperature is ignored unless you also send advancedMode: true, which bills one credit tier up.
  • Duration: the call is synchronous and may run several minutes. Each attempt may take up to 240 seconds on each of two lanes, so the worst case is 24 minutes at the default maxRetries and 32 minutes at the maximum. Raise your HTTP client's timeout, or use the job form below. The SDK's default timeoutMs of 60 seconds is too short.
  • Errors: 400 validation_error, 401, 402, 500 internal_error, 502 llm_error once the retries are spent, and 503 provider_unavailable.

In the SDK, call client.llm.structured(body).

As a job

POST /v1/llm/structured/jobs takes the same body and answers { jobId } at once. Poll GET /v1/jobs/:id/status: on completed, output_data is { output, inputTokens, outputTokens }, and on failed, error_message says why. Find your drafts again with GET /v1/jobs?type=llm-structured&origin=<your app>. Three extra fields are accepted:

  • label: a display name for the job, up to 120 characters.
  • videoUrl: draft from a video. The video is analyzed first, as a separate job you also own, at the video analysis price, and the analysis is added to your input. While it runs, output_data.stage is analyzing, then drafting.
  • videoAnalysis: { llmModel?, selectionMode? } for that analysis.

Cancelling the draft with POST /v1/jobs/:id/cancel also cancels a running analysis. A refused analysis releases every credit reserved for the draft. On an install that sends its language-model calls to nodaro.ai, the job form answers 503 provider_unavailable: use the synchronous call there. In the SDK, call client.llm.structuredJob(body).

Frequently asked questions

Last updated on

On this page