Nodaro Docs
DocumentationNode ReferenceModelsAI Agents (MCP)DevelopersSelf-hostingResearch
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 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 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().

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

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

Last updated on

On this page