Nodaro Docs
DocumentationNode ReferenceModelsAI Agents (MCP)DevelopersSelf-hostingResearch
TypeScript SDK

Run nodes

Run any Nodaro node from TypeScript without a workflow. Discover node types, start runs, wait for results, and pass references and camera direction.

client.nodes lists the node types a Nodaro server supports and runs any one of them directly, without building a workflow. A run posts your parameters to the node's endpoint, POST /v1/<type>, and returns a job you can wait for. It is the same path the CLI uses for nodaro nodes run and the one Nodaro's MCP tools use. See Run a single node for the REST view.

Methods

MethodWhat it does
nodes.list()List every node type, with its models and credit cost
nodes.get(type)Read one node type
nodes.run(type, params?, options?)Start a node and return its job id at once
nodes.runAndWait(type, params?, opts?)Start a node, poll its job, and return the output
nodes.runMany(type, paramsList, opts?)Start several runs of one node at once and wait for all of them

client.nodes

list()

Lists every node type the server supports. The answer can be cached by the server for 5 minutes. The call costs nothing and needs no scopes.

list(): Promise<{ data: NodeDescriptor[] }>
const { data: nodes } = await client.nodes.list()

const imageGenerators = nodes.filter((n) => n.category === "ai-image")
const takesReferences = nodes.filter((n) => n.capabilities?.includes("supports-reference-image"))

Each NodeDescriptor has these fields:

FieldTypeDescription
typestringThe API type, such as generate-image. Pass it to run().
labelstringThe node's name in the editor.
categorystringThe category, such as ai-image, ai-video, ai-audio, ai-text, processing or parameter.
descriptionstringA one-line description.
outputTypestringtext, image, video, audio, data or none.
creditCostnumber | stringThe credit cost: a number when fixed, or a range such as "2-620" when it depends on the model. Nodaro Cloud only.
providersstring[]The model ids the node can run, for the provider parameter.
capabilitiesstring[]Flags such as supports-reference-image or supports-end-frame.
inputSchema{ fields }The input fields you can set, each with key, type, required and options.
maxDurationSecnumberThe longest duration the node accepts, where it has one.
providerResolutionsRecord<string, string[]>The resolutions each model accepts, where they differ.

Self-hosted Community and Business installs have no credit system, so their descriptors omit creditCost.

get(type)

Reads the descriptor of one node type.

get(type: string): Promise<{ data: NodeDescriptor }>

Prop

Type

const { data: node } = await client.nodes.get("generate-video")
console.log(node.providers)  // every video model id
console.log(node.creditCost) // Nodaro Cloud only

To show prices next to the models, pass node.providers to client.credits.modelCosts().

run(type, params?, options?)

Starts one node and returns at once. The body is sent to POST /v1/<type>, the route every generation node uses. Field names match the node's input fields.

run(type: string, params?: Record<string, unknown>, options?: { idempotencyKey?: string }): Promise<RunNodeResult>

Prop

Type

const result = await client.nodes.run("generate-image", {
  prompt: "A snow leopard in the mountains",
  provider: "nano-banana-2",
})

if ("jobId" in result) {
  const { data: job } = await client.jobs.getStatus(result.jobId)
  console.log(job.status)
}

What comes back. Most node types are asynchronous: the result carries a jobId, and a worker does the generation. Poll client.jobs.getStatus(jobId) until it ends, or use runAndWait(). A few inline node types, such as combine-text, return their full result at once, without a jobId. Branch on the presence of jobId.

Parameter corrections. On the image node types (generate-image, image-to-image and edit-image), the server may correct a value the chosen model does not accept. The run goes ahead with the corrected value, and the credits reserved match it. The result then carries adjustments, one entry per corrected field:

const result = await client.nodes.run("generate-image", {
  prompt: "A snow leopard",
  provider: "gpt-image-2",
  aspectRatio: "3:2",
})
if ("adjustments" in result && result.adjustments?.length) {
  for (const a of result.adjustments) {
    console.warn(`${a.field}: ${a.from} -> ${a.to ?? "(dropped)"} (${a.reason})`)
  }
}

Each adjustment has field (aspectRatio, resolution, quality or duration), from, to and reason. adjustments is absent when nothing changed.

Throws InsufficientCreditsError when the account cannot pay, StorageExceededError when storage is full, and JobBlockedError when a content policy of the deployment refuses the request. See Errors.

runAndWait(type, params?, opts?)

Runs one asynchronous node to completion. It calls run(), takes the jobId, and polls client.jobs.getStatus() until the job ends. It resolves with the job's output when the status is completed.

runAndWait(type: string, params?: Record<string, unknown>, opts?: RunAndWaitOptions): Promise<NodeJobOutput>

Prop

Type

const output = await client.nodes.runAndWait(
  "generate-video",
  { prompt: "Rain falls on a neon street at night", provider: "seedance-2-fast", duration: 4 },
  { onProgress: (s) => console.log(`${s.progress ?? 0}%`) },
)
console.log(output.videoUrl, output.thumbnailUrl)

The output is a NodeJobOutput: imageUrl for image nodes, videoUrl and thumbnailUrl for video nodes, audioUrl for audio nodes, plus any other fields the node writes. Audio Separation, for example, adds one URL per stem, such as vocalUrl and instrumentalUrl.

It throws these typed errors:

ErrorWhen
InsufficientCreditsError, StorageExceededError, JobBlockedErrorThe run request was refused, before any polling.
JobFailedErrorThe job ended as failed or cancelled. It carries jobId and the job's error message.
JobTimeoutErrormaxMs passed. The job is not cancelled.
JobAbortedErrorYour signal fired. The job is not cancelled.
JobHeldErrorThe job is held for human review, on deployments with a content policy.

Slow recoveries. Sometimes a model delivers its result after the worker gave up on it. The job then stays processing with recovering: true while the platform recovers it, which can take tens of minutes for slow models. If JobTimeoutError ends your wait, fetch the job later with client.jobs.get(jobId), or raise maxMs.

runMany(type, paramsList, opts?)

Starts several runs of one node type at the same time and waits for all of them, for example to generate a grid of candidates. Each entry runs through runAndWait().

runMany(type: string, paramsList: Record<string, unknown>[], opts?: RunAndWaitOptions): Promise<RunManyResult[]>

Prop

Type

const results = await client.nodes.runMany("generate-image", [
  { prompt: "A snow leopard at sunrise" },
  { prompt: "A snow leopard at golden hour" },
  { prompt: "A snow leopard at blue hour" },
])
for (const { jobId, output } of results) console.log(jobId, output.imageUrl)

It resolves when every run has finished, to one { jobId, output } per entry, in input order. It rejects as soon as any run fails, with the same errors as runAndWait(). To choose the best result afterwards, pass the URLs to client.reduce.run().

Typed parameters

Four node types have typed parameters, so your editor completes and checks their fields. Every other node type takes a plain object; its fields are the node's input fields, listed in inputSchema and on its page in the Node Reference. The 3D scene nodes also have typed parameters: see 3D scenes.

Node typeParameter typeNode page
generate-imageGenerateImageParamsGenerate Image
generate-videoGenerateVideoParamsGenerate Video
text-to-videoTextToVideoParamsGenerate Video
assemble-narrated-videoAssembleNarratedVideoParamsAssemble Narrated Video

Every typed parameter object also accepts other fields of the route. The server validates the full body.

GenerateImageParams

Prop

Type

GenerateVideoParams

The image-to-video lane: a start frame, an optional end frame, and references.

Prop

Type

TextToVideoParams

The prompt-only video lane, POST /v1/text-to-video. The prompt is required, and start and end frames belong to generate-video instead. A model without a text-to-video mode answers 400 image_required.

Prop

Type

AssembleNarratedVideoParams

Joins video blocks with narration into one video. The run costs 3 + ceil(blocks / 6) credits. See Assemble Narrated Video for how each block is fitted to its narration.

Prop

Type

References

generate-image, generate-video and text-to-video accept references the way the editor wires them. The server turns them into numbered directives such as @image_1 in the prompt, so you do not write "Image 1 is..." yourself.

  • connectedReferences is a list of ConnectedReference entries, the editor's wired-reference shape, exported from the SDK. The server removes duplicates and keeps as many as the model accepts. referenceOrder sets their order by id.
  • Identity lock. An entry may carry identityLock: { enabled: true, text? }. The server then adds a short line that tells the model to keep that reference's identity. text replaces the built-in wording, and {ref} in it stands for the reference's name. It is off by default.
  • describedReferences takes up to 10 { name, description } entries for subjects you can name but have no picture of, such as a role in a script. Each becomes a line Name — description. in the prompt. Keep the name in your prompt text, so the model knows who it is.
  • descriptionOverride on a reference entry replaces its stored description for this run only.
  • Captions for video and audio references. referenceVideoCaptions and referenceAudioCaptions follow the order of referenceVideoUrls and referenceAudioUrls. Each caption appears in the prompt as @video_1: caption. or @audio_1: caption..
  • Mention an image reference in the prompt. On generate-image, name a reference and mention it as @<name>:<index> or @<name>:<index>:<role>, for example @town:1:background. The mention renders that reference, or its role, at that point of the prompt. ~lock and ~nolock work as on character mentions.

See Reference roles for the roles you can use and Consistent characters for identity work.

Direction

generate-image, generate-video and text-to-video accept a direction object of picker ids instead of words. Nodaro writes tested wording for each id into the prompt, so your code sends ids and the wording stays up to date.

await client.nodes.runAndWait("generate-image", {
  prompt: "A detective waits under a street lamp",
  direction: { shotSize: "wide-shot", timeOfDay: "golden-hour", mood: "suspicious" },
})
  • Keys are picker dimensions, such as shotSize, lightingStyle, style, mood, photographer or era. The video routes add motion keys, such as cameraMotion, actionFx, transition, loopSubject and the temporal keys.
  • Values are one id or an array of ids. A dimension that takes several values keeps at most its own limit and drops the rest. The request is refused only above 8 values per key or 100 characters per id.
  • Unknown keys and ids are skipped, not refused. An empty direction leaves your prompt unchanged.
  • One map serves images and video. A still-only key sent to a video run is accepted and adds nothing.
  • extend-video takes no direction, because its prompt continues an existing clip.

Read the valid ids from client.pickerCatalogs. See Picker catalogs for every dimension.

Language-model nodes

run(type, params) posts to /v1/<type>. For language-model nodes that route exists only for generate-script, image-critic, qa-check and describe-to-picker. The other language-model nodes use a longer path, so call them with client.request():

Node typeEndpoint
llm-chat/v1/llm-chat/generate
after-effects/v1/after-effects/generate
motion-graphics/v1/motion-graphics/generate
lottie-overlay/v1/lottie-overlay/generate
3d-title/v1/3d-title/generate
image-to-text/v1/image-to-text/describe
video-composer/v1/scene-graph/generate
await client.nodes.run("generate-script", {
  prompt: "A 3-scene product launch script for a smart water bottle",
  reasoningEffort: "high",
})
  • reasoningEffort is "none", "low", "medium", "high", "xhigh" or "max", depending on the model. Omit it, or send a level the model does not support, for the model's default. xhigh and max bill one credit tier higher. See Prompt for the models and their tiers.
  • advancedMode: true runs a Gemini model directly on its maker's API. Only there do temperature, maxTokens and the full effort range take full effect. It bills one credit tier higher, on top of any effort increase. A model without that option answers 400 advanced_mode_unsupported.
  • Streaming is not wrapped. The SDK does not read the streaming answer of the Prompt node. Use fetch with a readable stream for it.

Model-specific rules

Some video models accept values the others do not. Each model page lists the full options and prices.

  • Seedance 2 accepts resolution: "4k" and aspectRatio: "adaptive" or "21:9". Seedance 2 Fast and Seedance 2 Mini render at 480p or 720p only.
  • Seedance 2.5 renders at 480p, 720p or 1080p, runs up to 30 seconds in one call, and takes 30 image, 10 video and 10 audio references. With a start frame it uses that frame's aspect and refuses an explicit aspectRatio.
  • MiniMax Hailuo 3 (minimax-h3) takes 9 image, 3 video and 3 audio references at resolution: "2K" (the default) or "768P". Any other value renders and bills as 2K.
  • Wan 3.0 (wan-3 and the faster wan-3-prime) takes 10 image, 5 video and 5 audio references. The reference lists cannot be combined with imageUrl or endFrameUrl. duration is a whole number from 2 to 30, and resolution is 480p, 720p or 1080p.
  • Gemini Omni Flash takes the same request as Gemini Omni: durations of 4, 6, 8 or 10 seconds, 720p to 4K, and 16:9 or 9:16 only.

Text to Speech. When you omit provider, Text to Speech uses ElevenLabs v3 for text up to 3,000 characters. Longer text falls back to ElevenLabs Turbo v2.5, whose limit is 40,000 characters, so it is not cut short. A provider you name is always used.

Scrapers and other input nodes

Input nodes run the same way. A scraper, such as Web Scrape, answers at once: the result carries a jobId for your history and the data itself, so you can use it without polling. The request fields are the node's input fields, listed in inputSchema.

Frequently asked questions

Last updated on

On this page