NodaroAccount
Nodaro Docs
DocumentationNode ReferenceModelsTemplatesAI Agents (MCP)DevelopersSelf-hostingResearchCompare
TypeScript SDK

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 Node.js 20 or newer. It also runs in any runtime with a global fetch and URL: modern browsers, React Native, Cloudflare Workers, Deno and Bun.
  • It installs two other Nodaro packages with it: @nodaro/shared for the wire types and the model catalog, and @nodaro/prompts for the prompt helpers.
  • @supabase/supabase-js and @supabase/ssr are optional peer dependencies. Install them only for a browser app that signs users in with Supabase, through supabaseAuth or @nodaro/sdk/supabase.
  • 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 callYou get backWhat 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 outputNothing. The SDK polls every 2 seconds, for up to 15 minutes.
client.nodes.runMany(type, paramsList, opts)One { jobId, output } per requestNothing. 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

ProviderUse it whenToken source
StaticTokenAuthServer code with one fixed tokenAn API token (ndr_...) or an OAuth access token (ndr_app_...)
CallbackAuthYou refresh or rotate tokens yourselfYour function, called before every request
supabaseAuthA browser app whose users sign in to the same Nodaro instanceThe 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().

For images, one call can do the same. runBatchAndWait() takes count (1 to 4 images) or models (2 to 4 image models). The batch is charged as a whole, and one failed image never discards the others.

const { results } = await client.nodes.runBatchAndWait("generate-image", {
  prompt: "A lighthouse at dusk, watercolor",
  count: 4,
})
for (const r of results) if (r.status === "completed") console.log(r.output.imageUrl)

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.

Be told when a job finishes, instead of polling

Register an https endpoint once. From then on, Nodaro sends it a signed POST for every job.completed and job.failed of your account, and retries until your server answers with a 2xx status.

const { data: endpoint, secret } = await client.webhooks.create({ url: "https://example.com/nodaro-webhook" })
// `secret` is shown only once: keep it with your server's configuration.
await client.webhooks.test(endpoint.id) // a ping; see client.webhooks.deliveries(endpoint.id)

On the receiving side, check the signature on the raw body before you trust it:

import { verifyWebhookSignature } from "@nodaro/sdk"

const event = await verifyWebhookSignature({
  payload: rawBody,
  header: request.headers.get("nodaro-signature"),
  secret: process.env.NODARO_WEBHOOK_SECRET!,
})
if (event.type === "job.completed") console.info(event.data.job.output_data)

See client.webhooks and Webhooks for the events, the body and the retry schedule.

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

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

What is @nodaro/sdk?

It is the official TypeScript client for the Nodaro REST API. It runs nodes and workflows, waits for their results, and manages characters, voices, media and more, with a typed method and a typed error for every call.

How do I authenticate the Nodaro SDK?

Create a token in Nodaro under Settings › API Tokens, store it in an environment variable, and pass it to createClient as new StaticTokenAuth(token). Browser apps and OAuth apps use supabaseAuth or CallbackAuth instead.

Which runtimes does the SDK support?

Node.js 20 or newer, and any runtime with a global fetch and URL, such as modern browsers, React Native, Cloudflare Workers, Deno and Bun.

Do I have to poll for results myself?

No. client.nodes.runAndWait starts a run, polls its job every 2 seconds, and resolves with the output URLs. Write your own loop with client.jobs.getStatus only when you need full control.

Can I use the SDK with a self-hosted Nodaro install?

Yes. Set baseUrl to the address of your instance. Some resources, such as Studio productions, Recast and organizations, exist only on Nodaro Cloud and answer 404 elsewhere.

Last updated on

On this page