TypeScript SDK
Install @nodaro/sdk, authenticate with an API token, and run Nodaro nodes and workflows from TypeScript in Node.js, the browser and edge runtimes.
The Nodaro TypeScript SDK is the typed client for the Nodaro REST API, published on npm as @nodaro/sdk. It runs single nodes and whole workflows, waits for their results, and manages characters, voices, media and more. It works in Node.js, in the browser and on edge runtimes, and every API error arrives as a typed error class you can catch.
Install
npm install @nodaro/sdk- The package is open source under the Apache-2.0 license. See @nodaro/sdk on npm and its source on GitHub.
- It ships ES module and CommonJS builds with TypeScript type definitions.
- It needs only a global
fetch, so it runs on Node.js 20 or newer, in modern browsers, in React Native, on Cloudflare Workers, on Deno and on Bun. - The Nodaro CLI is built on this SDK, so a CLI command and an SDK call reach the same endpoints.
Get an API token
Create the token
Sign in to Nodaro, open Settings › API Tokens and click Create Token.
Copy it once
The token starts with ndr_ and is shown only once. Nodaro stores only a hash of it, so a lost token cannot be shown again. Create a new one instead.
Keep it out of your code
Store the token in an environment variable, such as NODARO_TOKEN, or in your secret manager. Never put it in client-side code.
A personal token acts as you. To act for other people, for example in an app that many users connect to, use OAuth instead. Authentication compares every option.
Make your first call
import { createClient, StaticTokenAuth } from "@nodaro/sdk"
const client = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
})
const { data: nodes } = await client.nodes.list()
console.log(`${nodes.length} node types available`)client.nodes.list() costs nothing and needs no scopes, so it is a good first test of the connection. On a self-hosted install, set baseUrl to the address of your instance. In a browser app that is served from the same origin as Nodaro, use an empty string. The client lists every option of createClient.
Generate an image, then a video
const image = await client.nodes.runAndWait("generate-image", {
prompt: "A snow leopard resting on a rock at sunrise",
provider: "nano-banana-2",
})
console.log(image.imageUrl)
const video = await client.nodes.runAndWait("generate-video", {
prompt: "The snow leopard slowly turns its head toward the camera",
imageUrl: image.imageUrl,
provider: "seedance-2-fast",
duration: 4,
})
console.log(video.videoUrl)runAndWait starts the run, polls it, and resolves with the job's output: imageUrl for images, videoUrl for video and audioUrl for audio. The first call runs the Generate Image node on Nano Banana 2. The second call animates that image with the Generate Video node on Seedance 2 Fast.
Leave out provider to use the node's default model. Each node page lists the models it can run and their credit prices, and client.models.list() returns the same catalog from code. See Models and credits.
How runs work
Generation is asynchronous. A request starts a job on a worker and returns at once. The result arrives seconds or minutes later.
| You call | You get back | What to do next |
|---|---|---|
client.nodes.run(type, params) | { jobId } | Poll client.jobs.getStatus(jobId) until the status is completed, failed or cancelled. |
client.nodes.runAndWait(type, params, opts) | The job's output | Nothing. The SDK polls every 2 seconds, for up to 15 minutes. |
client.nodes.runMany(type, paramsList, opts) | One { jobId, output } per request | Nothing. The runs start together and resolve in input order. |
client.workflows.run(id) | { executionId, status } | Poll client.executions.get(executionId) until the run ends. |
A few node types, such as Combine Text, run inline and return their result directly, without a jobId. Run nodes explains every run method, and Jobs and executions explains the statuses.
Show progress and let the user stop waiting
import { JobAbortedError } from "@nodaro/sdk"
const controller = new AbortController()
stopButton.onclick = () => controller.abort()
try {
const clip = await client.nodes.runAndWait(
"generate-video",
{ prompt: "Waves roll onto a black sand beach", provider: "seedance-2-fast", duration: 4 },
{
signal: controller.signal,
onProgress: (status) => setProgressBar(status.progress ?? 0),
},
)
showVideo(clip.videoUrl)
} catch (err) {
if (err instanceof JobAbortedError && err.jobId) {
await client.jobs.cancel(err.jobId)
} else {
throw err
}
}onProgress receives the job status on every poll, with progress from 0 to 100 when the model reports it. Aborting the signal only stops the waiting. The job keeps running until you cancel it with client.jobs.cancel(jobId), which also refunds the credits it had reserved.
Show each result as soon as it arrives. In a two-step flow, display the image while the video step is still running.
Choose how to authenticate
| Provider | Use it when | Token source |
|---|---|---|
StaticTokenAuth | Server code with one fixed token | An API token (ndr_...) or an OAuth access token (ndr_app_...) |
CallbackAuth | You refresh or rotate tokens yourself | Your function, called before every request |
supabaseAuth | A browser app whose users sign in to the same Nodaro instance | The user's live session |
Every request asks the provider for a token and sends it as Authorization: Bearer <token>. When the provider returns null, the request goes out without the header. See Authentication for each provider and for browser-specific rules.
Handle errors
Every method throws a typed subclass of NodaroError when the API answers with an error. Catch the specific classes first and NodaroError last.
import {
InsufficientCreditsError,
NodaroError,
RateLimitedError,
UnauthorizedError,
} from "@nodaro/sdk"
try {
await client.workflows.run(workflowId)
} catch (err) {
if (err instanceof InsufficientCreditsError) {
showPaywall({ required: err.required, available: err.available })
} else if (err instanceof UnauthorizedError) {
askForANewToken()
} else if (err instanceof RateLimitedError) {
retryLater()
} else if (err instanceof NodaroError) {
console.error(`API error ${err.status} (${err.code}): ${err.message}`)
} else {
throw err // a network failure, not an API answer
}
}Every NodaroError has a message, a stable code such as insufficient_credits, and the HTTP status. Errors lists every class and when it is thrown.
Common recipes
Run a workflow and wait for it
client.workflows.run() starts an execution, one run of every node in the workflow, and returns at once. Poll client.executions.get() until the status is terminal.
const { executionId } = await client.workflows.run(workflowId)
for (;;) {
const { data } = await client.executions.get(executionId)
console.log(`${data.completedNodes}/${data.totalNodes} nodes done`)
if (["completed", "failed", "cancelled", "timed_out"].includes(data.status)) {
if (data.status !== "completed") throw new Error(data.errorMessage ?? data.status)
console.log(`Done. Used ${data.totalCreditsUsed} credits.`)
break
}
await new Promise((resolve) => setTimeout(resolve, 2_000))
}Pass { nodeIds: [...] } as the second argument to run only some nodes. See Workflows and projects.
Generate several candidates at once
const results = await client.nodes.runMany("generate-image", [
{ prompt: "A lighthouse at dawn, watercolor" },
{ prompt: "A lighthouse at dusk, watercolor" },
{ prompt: "A lighthouse in a storm, watercolor" },
])
for (const { jobId, output } of results) console.log(jobId, output.imageUrl)runMany rejects when any run fails. To let a model choose the best result, pass the URLs to client.reduce.run().
Upload a file and use it
const upload = await client.uploads.upload(file) // a File, in the browser or Node.js
const portrait = await client.nodes.runAndWait("generate-image", {
prompt: "The same person as a watercolor portrait",
referenceImageUrls: [upload.url],
})The upload returns a public url you can pass to any node that takes an image, a video or an audio URL. See Media and uploads.
Check the price before you run
const { total } = await client.credits.balance()
const { data: prices } = await client.credits.modelCosts(["nano-banana-pro", "nano-banana-pro:4K"])
if (total < prices["nano-banana-pro:4K"]) showPaywall()Prices and balances exist on Nodaro Cloud. See Models and credits and Credits.
Improve a prompt before you generate
const { prompt } = await client.promptHelper.enhance({
nodeType: "generate-image",
prompt: "snow leopard on a rock",
})The Prompt Wizard rewrites a rough idea into a detailed prompt for the node you name. Each call costs credits.
Use the SDK with an AI coding assistant
- Claude Code plugin. Run
/plugin marketplace add nodaroai/app.nodaro.ai, then/plugin install nodaro. The plugin adds a skill that knows the SDK's patterns, models and credits, and connects Nodaro's hosted MCP server. See Agent skills. - Other assistants. The package README on npm starts with a short primer written for coding assistants. Paste it into Cursor or any other assistant together with your request.
- No code at all. To let an assistant run Nodaro for you, connect it over MCP.
Reference
Client
createClient, every option, workspaces, and the full list of resources.
Authentication
StaticTokenAuth, CallbackAuth, supabaseAuth and shared browser sessions.
Errors
Every error class, its status and code, and how to recover.
Run nodes
run, runAndWait and runMany, with typed parameters.
Workflows and projects
Create, update, share, export and run workflows.
Jobs and executions
Poll, list, cancel and delete runs.
The SDK wraps the same endpoints as the REST API. For an endpoint that has no SDK method yet, call it with client.request(), which keeps the same authentication and typed errors.
Frequently asked questions
Related
Client
Authentication
Run nodes
Errors
REST API overview
Last updated on
OpenAPI spec
Download the Nodaro OpenAPI 3.1 spec from /v1/openapi.json, see which endpoints it covers, and generate typed clients in Go, Rust, Python and other languages.
Client
Create a Nodaro SDK client with createClient, set its base URL, auth, timeout and workspace, and find every resource the client exposes.