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 own route, POST /v1/<type> for most node types, 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
| Method | What 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.quote(type, params?) | Price a run before you start it |
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 |
nodes.runBatchAndWait(type, params, opts?) | Make several images in one Generate Image call and wait for every one |
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:
| Field | Type | Description |
|---|---|---|
type | string | The API type, such as generate-image. Pass it to run(). |
label | string | The node's name in the editor. |
category | string | The category, such as ai-image, ai-video, ai-audio, ai-text, processing or parameter. |
description | string | A one-line description. |
outputType | string | text, image, video, audio, data or none. |
creditCost | number | string | The credit cost a run is charged, the same figure as the node's Run button: a number when fixed, or a range such as "3-682" when it depends on the model. For Trim Video, Loop Video and Combine Videos, it is the price of one 5-second step instead, and a run costs a number of steps. Nodaro Cloud only. |
estimate | boolean | true when creditCost is an estimate: a metered LLM node's typical call on its default model. Show it as ≈N. A metered LLM node whose default model has no price yet lists creditCost: "unpriced". |
creditRate | NodeCreditRate | For a node charged per unit of its own measure, such as Speaker Frames (creditCost "per-1000-frames"): { identifier, unit, creditsPerUnit, minUnits, rounding, note }. A run of N units is charged Math.ceil(Math.max(minUnits, N) * creditsPerUnit), rounded once. |
providers | string[] | The model ids the node can run, for the provider parameter. |
capabilities | string[] | 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. |
maxDurationSec | number | The longest duration the node accepts, where it has one. |
providerResolutions | Record<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 onlyTo 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 the node's route, POST /v1/<type> for most node types. 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)
}Node types with a route of their own. Some node types run on a different route, and run() posts there for you:
| Node types | Route |
|---|---|
generate-3d-scene, edit-3d-scene | /v1/3d-scene/generate, /v1/3d-scene/edit |
llm-chat, after-effects, motion-graphics, lottie-overlay, 3d-title | /v1/<type>/generate |
image-to-text | /v1/image-to-text/describe |
video-composer | /v1/scene-graph/generate |
telegram-channel-feed | /v1/telegram-channel/fetch |
Every suno-* node, such as suno-generate and suno-cover | /v1/suno/<name>. suno-voice uses /v1/suno/voice/generate. |
instagram-post, tiktok-post, youtube-upload, linkedin-post, x-post, facebook-post, telegram-post, publish-social | /v1/social/publish |
ugc-creator, ugc-script, ugc-clips, ugc-clip, ugc-cards | /v1/ugc/creator, /v1/ugc/script and so on. Nodaro Cloud only. |
render-video with a planType | /v1/render-video/plan |
The publish route needs a platform. A social node for one platform, such as x-post, fills it in from its type when you leave it out. publish-social takes the platform you send.
Node types that do not run on their own. A node type with no route of its own rejects with a NodaroError whose code is node_not_runnable, without calling the API. The message names what to run instead:
modify-imageruns throughimage-to-image, oredit-imagefor thenano-banana-editmodel.upscale-imageandremove-backgroundrun throughedit-image.image-to-videoruns throughgenerate-video.composite,manual-editandpaint-maskrun only inside a workflow.sceneandgenerative-pipelinerun only inside a pipeline.
Node types that run only inside a workflow, such as inputs, triggers, utilities and pickers (upload-image, combine-text), reject the same way once the API answers that it has no route for them. A 404 about a missing resource, such as a character id that does not exist, stays a NotFoundError.
A model this deployment does not offer, or one an admin switched off, is refused before any credits are held. The call rejects with ModelUnavailableError, with code set to model_not_available or model_disabled. Choose another model: a retry changes nothing.
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 synchronous node types, such as meta-ads-scrape, 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.
quote(type, params?)
Prices a run before you start it, with POST /v1/quote. Pass exactly the body you would pass to run. The platform prices it with the run route's own code: the validation, the measuring of reference clips, the credit id and the amount. Nothing is started and nothing is charged. Nodaro Cloud only.
quote(type: string, params?: Record<string, unknown>): Promise<NodeQuote>The method is typed for text-to-video, generate-video, video-to-video, video-upscale (a Seedance 2.5 draft's 1080p finalize is priced from the draft) and edit-image. split-into-layers is quotable too, with the body client.media.splitIntoLayers takes. These are the types the platform can quote today. Any other type rejects with 400 quote_unsupported. A body, model or node the run would refuse rejects with the run's own 400 or 403.
const q = await client.nodes.quote("video-to-video", {
videoUrl, prompt: "make it black and white", provider: "seedance-2-5", resolution: "480p",
})
q.reserve // credits held when the run starts
q.expected // what the run should actually cost, when knownA settles_to_actual warning marks a reserve that is the most the run can cost: the run is charged for what it delivers, and the rest is refunded. Without it, reserve is what the run is charged when it completes.
// Nodaro Image Upscale: the exact price of this picture at this factor.
// The platform reads the picture to measure it, and stores nothing.
const up = await client.nodes.quote("edit-image", {
provider: "nodaro-image-upscale", imageUrl: "https://example.com/photo-1024.png", upscaleFactor: "2",
})
up.identifier // "nodaro-image-upscale:2K" for a 1024 × 1024 picture
// Split into Layers: the hold, the most the run can cost (warning settles_to_actual).
const split = await client.nodes.quote("split-into-layers", { imageUrl: "https://example.com/poster.png", maxLayers: 5 })
split.identifier // "split-into-layers:1k:6": 5 layers and the base at 1KWhere Nodaro Image Upscale is not offered, the quote rejects with the run's 403 model_not_available (ModelUnavailableError) before the picture is read. A link to another site follows the deployment's link-import setting.
NodeQuote is { identifier, reserve, expected?, adjustments, warnings }. See Run quotes for every field and code.
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)On an image job, NodeJobOutput also carries the image's pixel size, so a client can lay out a grid before the images load:
| Field | Type | Description |
|---|---|---|
imageUrl | string | The image, or the first one when the job made several. |
imageUrls | readonly string[] | Every image, when the job made more than one. |
width, height | number | The pixel size of imageUrl as stored, with EXIF orientation applied. A free-tier watermark is included. |
images | readonly JobOutputImage[] | One { url, width?, height? } per entry of imageUrls, in the same order. An entry without a size could not be measured. |
The four size fields are optional. Older jobs do not have them, and the server never guesses a size it could not read.
const output = await client.nodes.runAndWait("generate-image", { prompt: "a snow leopard in the mountains" })
console.log(output.imageUrl, output.width, output.height)
for (const image of output.images ?? []) console.log(image.url, image.width ?? "?", image.height ?? "?")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:
| Error | When |
|---|---|
NodaroError with code batch_requires_run_batch_and_wait | A generate-image request with count above 1 or with models makes several jobs. runAndWait() refuses it before anything is sent. Use runBatchAndWait(). |
InsufficientCreditsError, StorageExceededError, JobBlockedError | The run request was refused, before any polling. |
ModelUnavailableError | The model is not offered by this deployment, or an admin switched it off. The run is refused before credits are held. Choose another model. |
JobFailedError | The job ended as failed or cancelled. It carries jobId and the job's error message. |
JobTimeoutError | maxMs passed. The job is not cancelled. |
JobAbortedError | Your signal fired. The job is not cancelled. |
JobHeldError | The 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(). An entry that is itself a batch, with count above 1 or with models, is refused before anything is sent. To choose the best result afterwards, pass the URLs to client.reduce.run().
runBatchAndWait(type, params, opts?)
Makes several images in one Generate Image call and waits for every one. Use count for 1 to 4 images, each its own job. Use models to send the same request to 2 to 4 image models instead of one provider. With both, count applies per model, up to 8 images in all.
runBatchAndWait(
type: "generate-image",
params: GenerateImageParams,
opts?: RunAndWaitOptions,
): Promise<GenerateImageBatchOutput>The method submits the batch once, then waits for every job the way runAndWait() waits. The batch is checked against your balance and charged as a whole or not at all. With opts.idempotencyKey, image number i is keyed <key>:<i>, so a retry returns the same jobs and charges nothing again. A presetId combines with both levers: count images of the preset's request, or the preset applied once per model.
const { batch, results } = await client.nodes.runBatchAndWait(
"generate-image",
{ prompt: "a lighthouse at dusk", models: ["gpt-image-2", "flux-2-max"], count: 2, seed: 41 },
{ idempotencyKey: crypto.randomUUID() },
)
console.log(batch.batchId, batch.totalCredits)
for (const r of results) {
if (r.status === "completed") console.log(r.model, r.output.imageUrl)
else console.warn(r.model, r.jobId, r.error.message)
}It resolves to { batch, results }:
batchis the submit's answer, aGenerateImageBatchResult. It hasjobId(the first job),jobIds(model by model, then image by image),batchId,groups({ model, jobIds, credits?, adjustments? }per model) andtotalCredits.nodes.run("generate-image", …)returns the same object when the params setcountabove 1 ormodels.resultsholds oneBatchJobOutcomeper job, in the order ofbatch.jobIds. Each is{ jobId, model, status: "completed", output }or{ jobId, model, status: "error", error }. Theerroris theJobFailedError,JobHeldErrororJobTimeoutErrorthat ended the wait. One failed image never discards the others.
It rejects only before any job exists, with the submit's own errors, such as InsufficientCreditsError with the batch's total as required. It also rejects with JobAbortedError when your signal fires. A request for one image is refused before anything is sent, with code not_a_batch: wait for it with runAndWait(). With a seed, image i uses seed + i. Find the jobs of a batch later with client.jobs.list({ batchId }).
Typed parameters
These 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 type | Parameter type | Node page |
|---|---|---|
generate-image | GenerateImageParams | Generate Image |
image-to-image | ImageToImageParams | Modify Image |
edit-image | EditImageParams | Upscale Image |
split-image | SplitImageParams | |
generate-mask | GenerateMaskParams | Generate Mask |
generate-video | GenerateVideoParams | Generate Video |
text-to-video | TextToVideoParams | Generate Video |
video-to-video | VideoToVideoParams | Video to Video |
assemble-narrated-video | AssembleNarratedVideoParams | Assemble Narrated Video |
video-depth | VideoDepthParams | Video Depth |
video-upscale | VideoUpscaleParams | Upscale Video |
Every typed parameter object also accepts other fields of the route. The server validates the full body.
GenerateImageParams
Prop
Type
strength and guidanceScale are typed on GenerateImageParams too.
Image file formats
GenerateImageParams and ImageToImageParams type outputFormat (ImageOutputFormat). It is "png" by default, or "jpeg", "webp", "gif" or "svg" on the models whose outputFormats list it in client.models.list(). Today those are GPT Image 2.6 Flare and Sunburst.
- A GIF takes
gifFrames(2 to 16) andgifFrameDurationMs(20 to 2,000 ms, in steps of 10). A GIF is square, 512×512 per frame. ItsaspectRatioandresolutionare ignored, andadjustmentssays so. - An SVG takes
svgLevel(ImageSvgLevel):"standard","detailed"or"max". - A transparent background is kept only in PNG and WebP.
- A mask cannot be used with a GIF or an SVG. The request is refused with
400.
The finished job's output names the file's type in imageMimeType. For a GIF or an SVG, it also holds a PNG still in thumbnailUrl. A GIF is priced by its quality and by the band its number of frames falls in: 2 to 8, 9 to 12 or 13 to 16. The Generate Image node shows the price before it runs.
await client.nodes.runAndWait("generate-image", {
prompt: "a waving flag",
provider: "gpt-image-2-6-flare",
outputFormat: "gif",
gifFrames: 12,
} satisfies GenerateImageParams)Presets on Generate Image
presetId applies a node preset exactly as the editor does: the prompt, the pre and post text, placeholder defaults, the style, the negative prompt and the settings. The job then matches the editor's Run. Use a built-in id from presets.listFactory("generate-image") or one of your own presets. Fields you pass override the preset.
A placeholder without a default needs a value in presetVariables. Without one, the call fails with 400 preset_variables_required, which names the placeholder.
await client.nodes.runAndWait("generate-image", {
presetId: "generate-image/logo-wordmark",
presetVariables: { BRAND: "Acme" },
})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
Copyright refusals
TextToVideoParams takes contentPolicyRewrite?: boolean. When it is absent or true, a copyright refusal is retried once with a reworded prompt. With false, a copyright refusal fails the job instead.
Seedance 2.5 reference task type
GenerateVideoParams and TextToVideoParams take referenceTaskType?: "auto" | "reference" | "edit" | "extend". It tells seedance-2-5 what the reference media is for, instead of leaving it to the prompt. Every other model ignores it, and so does a run with no reference media. A start or end frame does not count as reference media.
"auto", the default, declares nothing."reference"keeps youraspectRatioandduration. With an Autoduration(-1), the run is reserved at the longest output the model can make, and charged for what it makes."edit"edits the reference video in its own shape and length:aspectRatiobecomes"adaptive"anddurationAuto. Every reference video must be 4 to 30 seconds long. Otherwise nothing is declared, and the run keeps the ratio and length you sent."extend"continues the reference video in its own shape, at a set length. An Auto or missingdurationbecomes the model's default. You get the continuation only, not the source joined to it. For both as one video, runextend-video.editandextendneed a reference video. A draft (draft: true) declares the type exactly as the same run withoutdraft, at 480p.
What they change comes back in result.adjustments, and nodes.quote prices the body the same way the run does. See Seedance 2.5 reference task type.
await client.nodes.run("generate-video", {
provider: "seedance-2-5",
prompt: "remove the sign on the wall",
referenceVideoUrls: [clipUrl],
referenceTaskType: "edit",
} satisfies GenerateVideoParams)Seedance 2.5 drafts
GenerateVideoParams and TextToVideoParams take draft: true. On seedance-2-5, the run is a 480p draft at the price of a 480p run. Finalize the take you like with run("video-upscale", { provider: "seedance-2-5-hd", sourceJobId }): the same take, rendered again at 1080p, priced from the draft's length. A source that is not your own finished draft is refused with a 4xx before anything is charged. See Seedance 2.5 Draft mode.
const draft = await client.nodes.run("generate-video", { prompt, provider: "seedance-2-5", draft: true, duration: 8 })
// Wait for the draft and review it. When you like the take, finalize it at 1080p.
if ("jobId" in draft) {
const final = await client.nodes.runAndWait("video-upscale", { provider: "seedance-2-5-hd", sourceJobId: draft.jobId })
console.log(final.videoUrl)
}Topaz video upscale. VideoUpscaleParams takes targetResolution ("720p", "1080p" or "4k", the frame's short side, default 1080p) and targetFps (15 to 60, default the source's own rate): run("video-upscale", { videoUrl, targetResolution: "4k", targetFps: 60 }). Topaz is metered. See Upscale Video › Credits. upscaleFactor is deprecated. It is still accepted and applied to the source's short side.
Nodaro Video Upscale. provider: "nodaro-video-upscale" takes targetResolution "1080p" (default), "2k" or "4k", the output frame. A value it does not take, such as "720p", runs at 1080p: run("video-upscale", { provider: "nodaro-video-upscale", videoUrl, targetResolution: "2k" }).
- It takes clips of up to 60 seconds and 50 MB. It is priced per second of the clip, and the amount held is the amount charged.
- It is offered only where
models.listlists it. Elsewhere the request is refused with403 model_not_available, aForbiddenError. - Its other refusals:
400for a clip past a limit or one that cannot be read (video_too_long,video_too_large,video_unreadable), and403 link_imports_disabledfor a link to another site where the deployment turned such links off. - A clip the deployment's upload policy refuses answers
422 upload_blocked. A clip that could not be fetched just now answers429 input_busy, or503 input_timed_outorinput_failed: send the same request again. - Every refusal comes before anything is charged.
"2k"is this provider's target only: Topaz and VEO still answer it with a400.
VideoToVideoParams
The video edit lane, POST /v1/video-to-video. A seedance-2-5 edit is built on the server, so you send the same body as for any other edit model. See Video to Video.
Prop
Type
The type also carries audio, multiShots, videoEditDuration, audioSetting and promptExtend for the models that take them, plus sessionId and origin. Any other model refuses the three reference lists. The numbering of the references and the limits are in Video to Video.
Typed image routes
image-to-image, edit-image, split-image and generate-mask have typed overloads on run. The three that start a job also have them on runAndWait. Each params type takes the platform's own vocabularies, such as ModifyImageProvider, ImageEditProvider, ImageAspectRatio and ImageResolution, which the SDK re-exports. Any other route field still passes through.
The compile-time check applies when the body is typed as the params type: const params: EditImageParams = …, or { … } satisfies EditImageParams inline. A body written straight into the call that the typed overload rejects still compiles. It falls through to the untyped run(type: string, …) overload, and the route answers 400. Use satisfies to get the check.
image-to-image,edit-imageandgenerate-mask.runanswersImageJobStarted, that is{ jobId: string; adjustments? }, soresult.jobIdneeds no cast.ImageToImageParamsrequiresimageUrlandprompt. ItsmaskUrl(white is the area to change) is sent to the model as its own mask. Onlyideogram-edit,flux-fill,gpt-image-2-6-flare-i2iandgpt-image-2-6-sunburst-i2iuse it. On GPT Image 2.6, the rest of the picture is kept exactly as it was, and the result has the source's size. The run keeps the photo's shape, and is rendered and priced at the size nearest the photo.adjustmentssays so. A mask costs nothing extra. It takesoutputFormatand the GIF and SVG fields asGenerateImageParamsdoes.EditImageParamsis one of two forms. It takesimageUrlfor every model except the Grok task operations. For those, it takestaskIdwithprovider: "grok-2-edit" | "grok-2-segment": the task id of an earlier Grok Imagine 2 generation, stored in that job's output data. A typed body that sends both, or ataskIdwith another model, does not compile. The model defaults torecraft-upscale.promptis required fornano-banana-editandgrok-2-edit.maskIndexesis forgrok-2-editonly.upscaleFactor(UpscaleFactor:"1","2","4"or"8") is 1, 2 or 4 ontopaz-image-upscale, and 1, 2, 4 or 8 onnodaro-image-upscale. A factor the model does not take is a400 validation_error.nodaro-image-upscaleis priced by its output's size. It refuses an output past 67,108,864 pixels, or 16,384 pixels on a side, with400 image_too_largebefore anything is charged. See Nodaro Image Upscale.split-imageis free and answers at once. It creates no job, sorunAndWaitdoes not apply.runanswersSplitImageResult, that is{ images: { name, url }[] }, one PNG per tile innamesorder.namesmust hold exactlygridCols × gridRowsentries, andgridColsandgridRowsare each 1 to 6.generate-maskis the SDK's way to start a Generate Mask job.runAndWait("generate-mask", …)resolvesGenerateMaskJobOutput:maskUrl, the mask PNG where white is the subject, andimageUrl, the source passed through unchanged.
import type { EditImageParams, GenerateImageParams, GenerateMaskParams } from "@nodaro/sdk"
const { maskUrl } = await client.nodes.runAndWait("generate-mask", { imageUrl, prompt: "the red car" } satisfies GenerateMaskParams)
await client.nodes.runAndWait("generate-image", { prompt: "a blue car", baseImageUrl: imageUrl, maskUrl } satisfies GenerateImageParams)
const { jobId } = await client.nodes.run("edit-image", { imageUrl, provider: "topaz-image-upscale", upscaleFactor: "4" } satisfies EditImageParams)
await client.nodes.runAndWait("edit-image", { imageUrl, provider: "nodaro-image-upscale", upscaleFactor: "8" } satisfies EditImageParams)VideoDepthParams
Turns a clip into a depth video, near bright and far dark (POST /v1/video-depth). The finished job has videoUrl and thumbnailUrl. See Video Depth for the prices and length limits.
Prop
Type
A setting the chosen provider does not take is ignored, so switching providers never fails a request. A value outside the lists above is a 400 validation_error. The clip is measured before any credits are reserved. A clip that is too long answers 400 video_too_long: more than 2,400 frames or 300 seconds on video-depth-anything, and more than 80 seconds on depth-anything-frames. A clip that cannot be read answers 400 video_unreadable.
const { videoUrl } = await client.nodes.runAndWait("video-depth", {
videoUrl: "https://cdn.example.com/clip.mp4",
colormap: "turbo",
resolution: "720p",
})AssembleNarratedVideoParams
Joins video blocks with narration into one video. The run is priced by the number of blocks and costs 44–143 credits: 3 units, plus one unit for every 6 blocks or part of 6. 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.
connectedReferencesis a list ofConnectedReferenceentries, the editor's wired-reference shape, exported from the SDK. The server removes duplicates and keeps as many as the model accepts.referenceOrdersets 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.textreplaces the built-in wording, and{ref}in it stands for the reference's name. It is off by default. describedReferencestakes 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 lineName — description.in the prompt. Keep the name in your prompt text, so the model knows who it is.descriptionOverrideon a reference entry replaces its stored description for this run only.- Captions for video and audio references.
referenceVideoCaptionsandreferenceAudioCaptionsfollow the order ofreferenceVideoUrlsandreferenceAudioUrls. 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.~lockand~nolockwork 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,photographerorera. The video routes add motion keys, such ascameraMotion,actionFx,transition,loopSubjectand thetemporalkeys. - 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
directionleaves your prompt unchanged. - One map serves images and video. A still-only key sent to a video run is accepted and adds nothing.
extend-videotakes nodirection, because its prompt continues an existing clip.- Your own palettes and moodboards work on
generate-image. SetcolorLooktopaletteDirectionId(id)orstyletomoodboardDirectionId(id). See palettes and moodboards.
Read the valid ids from client.pickerCatalogs. See Picker catalogs for every dimension.
Subject
GenerateImageParams and TextToVideoParams type an optional subject, with the exported Subject type. It says who is in the shot: the person, how they are styled and the props in the frame, as a flat map of picker field to catalog id. You never write prompt text for it. Nodaro writes the wording and places it right after your prompt, before any direction.
import type { Subject } from "@nodaro/sdk"
const subject: Subject = { type: "woman", age: "age-30s", hairBase: "base-short-straight", heldProp: "smartphone" }
await client.nodes.run("generate-image", { prompt: "on the seawall at dusk", subject })- Field names and ids come from the picker catalogs.
client.pickerCatalogs.get("person")andget("styling")list each dimension'sfieldwith itsoptions[].id.get("held-prop"),get("material")andget("animal")write their ids to the catalog'svalueField:heldProp,materialandanimal. - Values are one id or a list of ids. Ids past a dimension's own limit are dropped, not refused.
customAgeis the one number, in years, read only withage: "age-custom". - Limits. Without a preset, the route checks the map. More than 128 keys, a key over 64 characters, more than 8 ids on a key or an id over 100 characters is a
400 validation_error. Unknown keys and ids within those limits are skipped, and the job'sinput_data.subjectkeeps exactly the ids that were used. - With
presetId, yoursubjectreplaces the preset's own and is added to the preset's prompt, asdirectionis. Values that are too long are dropped rather than refused, andinput_datadoes not record the subject.
See Nodes for every key and the order in which the wording is added.
Language-model nodes
run(type, params) posts each language-model node type to its own route: /v1/<type> for generate-script, image-critic, qa-check and describe-to-picker, and a longer path for the others. You do not need client.request() for them:
| Node type | Endpoint |
|---|---|
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",
})reasoningEffortis"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. Effort has no price of its own. Every LLM call is charged for the tokens it used, at the model's rate on the lane that served it, so a higher effort costs more only through the tokens it writes. On a Claude model, any effort runs the call on the maker's own API, at that lane's rate. See Generate Text - Any LLM for the models, and How LLM calls are charged. One exception:describe-to-pickerand its text versiontext-to-pickerrun at"high"when you omit the field andllmModelisclaude-opus-5.5. That run is on Anthropic's own API, at that lane's rate. Send another level to change it. Any other model keeps its default, including their default model, Gemini 3.8 Flash.advancedMode: trueruns a Gemini or Claude model directly on its maker's API. Only there dotemperature,maxTokensand the full effort range take full effect. The call is charged at that lane's per-token rate. A model without that option answers400 advanced_mode_unsupported. While the model's own provider cannot take requests, the call answersadvanced_mode_unavailableinstead of running on the default lane:400up front, or502midway, refunded unless the provider billed the attempt. Turn Advanced mode off to run the model. Describe to Picker is the exception: a Claude run there that the provider refuses falls back to Gemini 3.8 Flash, as it does with Advanced mode off.- Quoting a call.
client.credits.modelCosts([llmPriceKey(feature, model)])returns an estimate of a typical call, listed inestimates. Show it as≈N.llmPriceKeycomes from@nodaro/shared/llm-models. See Models and credits. - Streaming is not wrapped. The SDK does not read the streaming answer of the Generate Text - Any LLM node. Use
fetchwith 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"andaspectRatio: "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 H3 (
minimax-h3) takes 9 image, 3 video and 3 audio references atresolution: "2K"(the default) or"768P". Any other value renders and bills as 2K. - Wan 3.0 (
wan-3and the fasterwan-3-prime) takes 10 image, 5 video and 5 audio references. The reference lists cannot be combined withimageUrlorendFrameUrl.durationis a whole number from 2 to 30, andresolutionis480p,720por1080p. - 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.
- Gemini Omni Flash 1.2 (
gemini-omni-flash-1-2) is priced per second. It takes any whole-seconddurationfrom 3 to 15,360p,720p,1080por4k, and anaspectRatioof16:9,9:16orauto. It takes up to 6 reference images, or 5 beside a source video. A start frame alone, or a start and end frame up to 10 seconds, are exact frames. With references, or past 10 seconds, the frames join the reference list and the prompt names them. A source video is edited and charged by its own length, up to 15 seconds, andvideoTrimStartandvideoTrimEndpick the stretch. The model takes nocharacterReferences. See Edit a clip. - Kling 4.0 (
kling-4) runs from text alone, or from reference materials. It takesreferenceImageUrls(up to 10),referenceVideoUrls(up to 5, each 2 to 30 seconds),characterReferences(up to 7, as named subjects) andreferenceAudioUrls(up to 3). More than 15 materials in all is a400before anything is reserved. A sentimageUrlorendFrameUrljoins the reference images. Onkling-4andkling-4-flash, each character reference is built fromimageUrl,bodyImageUrland up to 3extraImageUrls, a start frame is allowed, andvoiceis refused.keepOriginalSound: truekeeps a reference video's own sound. The first audio clip plays as the soundtrack by default. It is sent as one more reference video, so the run bills as a reference-video run, and the other clips stay named voices.audioSoundtrack: falsekeeps every clip a voice sample. A reference-video run bills the output plus the longer of the output and the clips, at the reference rate. Kling 4.0 Keyframes (kling-4-keyframes) takesimageUrl, up to 8 in-between frames asreferenceImageUrls, in order, andendFrameUrl. The frames in all are capped byduration, from 4 at 3 seconds to 10 from 9 seconds, and more is a400. Both take adurationof 3 to 30 (default 5) and aresolutionof720por1080p; anything else renders 720p.aspectRatioisauto,16:9,9:16,1:1or21:9. Audio is always on, and there is no seed. - Kling 4.0 Flash (
kling-4-flash) is the faster, lower-cost Kling 4.0. AnimageUrlalone is the exact opening frame. Anything else runs in reference mode: a prompt alone,referenceImageUrls(up to 10),referenceVideoUrls(up to 5, 2 to 20 seconds together),characterReferences(up to 7), or a start frame beside them. Reference mode takes 15 materials in all.referenceAudioUrlsclip N (up to 7) becomes the voice ofcharacterReferencesentry N. A character needs 2 to 4 images to carry a voice. Otherwise its clip is dropped, as is any clip past the last character.durationis 3 to 20 (default 5), and the output is 720p only; anything else renders 720p. There is no end frame, no seed and noaudioSoundtrack. - PixVerse V6 Max (
pixverse-v6-max) runsrun("generate-video")with animageUrlas the exact first frame, and noaspectRatiois sent. It runsrun("text-to-video")withaspectRatio16:9(the default),4:3,1:1,3:4,9:16,2:3,3:2or21:9.durationis 1 to 15 (default 5),resolutionis360p,540p,720p(default) or1080p(anything else renders 720p), andseedis 0 to 2147483647. Native audio is on unlesssound: false(or, with nosound,generateAudio: false), and it changes the price. It takes no end frame and no references. - Grok Imagine 1.5 (
grok-imagine-video-1.5) needsimageUrl, the exact first frame.endFrameUrlis the exact last frame, and the same URL as both makes a seamless loop. Up to 4 in-between frames go inreferenceImageUrls, in order, spread over the clip at whole seconds. A D-second clip takes at most D − 1, and the rest are left out.durationis 1 to 15 (default 8),resolutionis480p(default),720por1080p(anything else renders 480p), and there is no seed. At1080pit takesimageUrlalone: with an end frame, a loop or in-between frames, the run is refused with400 start_frame_only_resolutionbefore any credits are reserved. Use720pfor those. - Gemini Omni and Gemini Omni Flash (
gemini-omni-videoandgemini-omni-flash) keep a real person's face withcharacterReferences. Pass up to 3 entries of{ imageUrl, description, bodyImageUrl?, name?, voice? }.voiceis{ preset, description?, exampleLine? }, wherepresetis one of theGEMINI_OMNI_VOICE_PRESETSids from@nodaro/shared. It keeps one voice for that character across clips. Only these models,kling-4andkling-4-flashacceptcharacterReferences. Any other model answers400. On Gemini Omni,characterReferencescannot be combined with a start frame (imageUrl). A character uses 1 of the model's 7 input units, or 2 with abodyImageUrl, and reference images and a source video share those units. Character references add no credits. Use them instead ofreferenceImageUrlswhen the face must stay the same.
Text to Speech. When you omit provider, Text to Speech uses ElevenLabs v4 for text up to 10,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. Send provider: "elevenlabs-v3" to keep ElevenLabs v3, whose limit is 5,000 characters.
Continuity across clips. Text to Speech also takes previousText and nextText: the lines spoken just before and after the clip, up to 1,000 characters each. They are not spoken. ElevenLabs v4, v4 Turbo, Turbo v2.5 and Multilingual v2 use them to keep one intonation across clips made separately. ElevenLabs v3 ignores them.
Timings and the audio file. With withTimestamps: true, the finished job's output_data.transcript carries per-word timings (words[] with startMs and endMs), at no extra credits. outputFormat picks the audio file: mp3_44100_128 (the default), mp3_44100_192 for a higher MP3 bitrate, or wav_24000 for an uncompressed WAV. The credits are the same for every format. The job's audioUrl ends in .mp3 or .wav by the file that came back, and output_data.audioMimeType names it (audio/mpeg or audio/wav). Name a download from that field, not from the format you asked for. The values are TTS_OUTPUT_FORMATS in @nodaro/shared/models.
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
How do I generate an image with the Nodaro SDK?
Call client.nodes.runAndWait with the node type generate-image and a prompt. It resolves with the job output, whose imageUrl is the generated picture.
What is the difference between run and runAndWait?
run starts the node and returns a jobId at once. runAndWait starts it, polls the job every 2 seconds, and resolves with the output when the job completes.
How long does runAndWait wait?
Up to 15 minutes by default. Change it with the maxMs option. A timeout does not cancel the job, which usually still completes on the server.
Which node types can I run?
Call client.nodes.list() for every type the server supports, with its category, output type, models and credit cost. Every node in the Node Reference has an API type.
How do I generate several images in one call?
Call client.nodes.runBatchAndWait with generate-image and count (1 to 4 images) or models (2 to 4 image models). The batch is charged as a whole or not at all, and one failed image never discards the others.
How do I avoid paying twice when I retry a request?
Pass an idempotencyKey in the options of run or runAndWait, and reuse the same key when you retry. The platform returns the first run instead of starting a second one.
Related
Jobs and executions
Models and credits
Nodes
Node reference
CLI
Last updated on
Jobs and executions
Poll, list, rate, cancel and delete Nodaro runs from TypeScript, track workflow runs, and get signed webhook calls when a job ends.
Apps and templates
Browse and run published Nodaro apps from TypeScript, read their run history, clone workflow templates into a project, and list the tutorials.