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

Client

Create a Nodaro SDK client with createClient, set its base URL, auth, timeout and workspace, and find every resource the client exposes.

The client is the object createClient() returns: a NodaroClient that holds your base URL, your auth provider and your settings, and exposes every part of the Nodaro API as a resource, such as client.workflows or client.nodes. You create it once and reuse it for every call.

Create a client

import { createClient, StaticTokenAuth } from "@nodaro/sdk"

const client = createClient({
  baseUrl: "https://app.nodaro.ai",
  auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
  timeoutMs: 120_000,
})
createClient(options: ClientOptions): NodaroClient

Prop

Type

NodaroClient is also exported as a class, so you can type a function that receives a client:

import type { NodaroClient } from "@nodaro/sdk"

async function countWorkflows(client: NodaroClient, projectId: string) {
  const { data } = await client.workflows.list({ projectId })
  return data.length
}

Resources on the client

Every resource is created by createClient and reached as client.<resource>.

ResourceWhat it coversReference
client.workflowsWorkflows: create, update, share, export, import and runWorkflows and projects
client.projectsThe projects that hold workflowsWorkflows and projects
client.sessionsGeneration sessions inside projectsWorkflows and projects
client.executionsRuns of a whole workflowJobs and executions
client.jobsSingle generation jobs, with ratings and filtersJobs and executions
client.webhooksEndpoints Nodaro calls when a job endsJobs and executions
client.videoProStop or continue a Generate Video Pro runJobs and executions
client.nodesThe node catalog and single-node runsRun nodes
client.appsPublished apps and their runsApps and templates
client.templatesThe template marketplaceApps and templates
client.tutorialsTutorial videos and tutorial workflowsApps and templates
client.llmStructured output from a language modelLLM and Reduce
client.reduceChoose the best of many results, or combine themLLM and Reduce
client.uploadsFile uploadsMedia and uploads
client.libraryYour stored mediaMedia and uploads
client.galleryThe public gallery and your favoritesMedia and uploads
client.mediaDownload, trim, caption, overlay and collage mediaMedia and uploads
client.voicesVoices, voice changer, voice design and dubbingVoices and audio
client.audioSeparate, isolate, mix, trim and transcribe audioVoices and audio
client.editPodcast editing in one call (tighten, chapters, trailer, clips, speaker layout), silence detection, audio sync, camera switching, edit plans and EDL rendersEditing
client.scene3dEditable 3D scenes and 3D Render Pro3D scenes
client.charactersCharactersCharacters
client.locationsLocationsLocations
client.objectsObjects and propsObjects and creatures
client.creaturesAnimals and creaturesObjects and creatures
client.communityThe shared community library of assetsCommunity library
client.studioStudio productionsStudio productions
client.shotsShared shot records behind share linksStudio productions
client.recastRecast runs and authored scriptsRecast
client.castSwapReplace the people, pets, products or place in a short video, Nodaro Cloud onlyNodaro Motion
client.trendsReady-made Nodaro Motion recipes run with your own pictures, Nodaro Cloud onlyTrends
client.pipelinesStory-to-video pipelinesPipelines
client.copilotCopilot threads, inside the Nodaro app onlyCopilot
client.modelsThe model catalogModels and credits
client.creditsYour balance and model pricesModels and credits
client.billingPlans, prices and your subscriptionModels and credits
client.pickerCatalogsValid options of each pickerPickers, presets and prompts
client.catalogsEvery picker catalog in one callPickers, presets and prompts
client.presetsSaved and built-in node presetsPickers, presets and prompts
client.palettesYour color palettesPickers, presets and prompts
client.moodboardsYour moodboardsPickers, presets and prompts
client.promptHelperThe Prompt WizardPickers, presets and prompts
client.organizationsOrganizations, members and invitationsOrganizations and workspaces
client.workspacesWorkspaces, members and join codesOrganizations and workspaces
client.userSettingsYour own settings: language, gallery visibility, node menu and canvas preferences, and the monthly statement emailUser settings
client.surfaceWhat this deployment offers you: node types, models and worldsSurface
client.developerAppsThe OAuth apps you ownOAuth and developer apps
client.oauthCode exchange, token revocation and consent-screen dataOAuth and developer apps

The client itself has five more methods: me(), connectedApps(), revokeConnectedApp(), withWorkspace() and request().

Entry points per resource

The root @nodaro/sdk exports everything. Each resource is also published as its own entry point, @nodaro/sdk/<entry>, which loads only that resource and the types it takes and returns. For example, @nodaro/sdk/jobs, @nodaro/sdk/nodes and @nodaro/sdk/workflows.

  • @nodaro/sdk/client holds createClient, NodaroClient and the auth providers.
  • @nodaro/sdk/core holds NodaroClientCore and the same auth providers.
  • @nodaro/sdk/errors holds the error classes and throwFromResponse.
  • Every other entry is named after its resource. @nodaro/sdk/node-presets is client.presets, and @nodaro/sdk/webhooks also holds verifyWebhookSignature.
import { NotFoundError } from "@nodaro/sdk/errors"
import type { Job, JobStatus } from "@nodaro/sdk/jobs"

A value imported from an entry is the same as the one imported from the root, so instanceof NotFoundError works whichever path each side used. createClient builds every resource, so @nodaro/sdk/client loads all of them. The older subpaths @nodaro/sdk/supabase and @nodaro/sdk/signup-signals are unchanged.

A client with only the resources you use

NodaroClientCore is the client without its resources: the options createClient takes, the request methods, and me(), connectedApps() and revokeConnectedApp(). NodaroClient extends it with every resource. Extend it yourself with only the resources you call, and the other resource modules are never loaded:

import { NodaroClientCore, StaticTokenAuth } from "@nodaro/sdk/core"
import { JobsResource } from "@nodaro/sdk/jobs"
import { NodesResource } from "@nodaro/sdk/nodes"

class AppClient extends NodaroClientCore {
  readonly jobs = new JobsResource(this)
  readonly nodes = new NodesResource(this) // polls through `jobs`
}

const client = new AppClient({ baseUrl: "https://app.nodaro.ai", auth: new StaticTokenAuth(token) })
await client.nodes.runAndWait("generate-image", { prompt: "a lighthouse at dusk" })

Every resource class takes a NodaroClientCore. NodesResource needs jobs on the same client, and Scene3DResource needs nodes: their constructor types say so, so leaving one out fails to compile. withWorkspace is a NodaroClient method. To work in a workspace, pass workspaceId to the constructor.

What methods return

  • Envelopes are kept. When an endpoint answers { "data": ... }, the method resolves to that envelope, so you write const { data } = await client.workflows.get(id). Paginated lists add a cursor next to data, such as nextCursor.
  • Some resources return the payload. A few methods unwrap their response, for example client.characters.list() resolves to { characters, nextCursor } and client.credits.balance() to the balance itself. Each reference page shows the exact return type.
  • Deletes and cancels usually resolve to { success: true }.
  • Field names follow the wire format. A Job uses snake_case fields, such as output_data and created_at, because the API sends them that way. A Workflow and a WorkflowExecution use camelCase.

Every response and input type is exported, so you can import it with import type. See Types.

me()

me(): Promise<UserIdentity & MeOrganizations>

Returns the identity behind the current token (GET /v1/me). Any valid token resolves to its owner, whether it is an API token, an OAuth access token or a browser session. A missing or invalid token throws UnauthorizedError.

const me = await client.me()
console.log(me.email, me.tier)
FieldTypeDescription
idstringThe Nodaro user id.
emailstringThe user's email address.
displayNamestring | nullThe display name, or null when it is not set.
avatarUrlstring | nullThe avatar URL, or null when it is not set.
tierstringThe stored subscription tier, such as "free" or "pro". For the tier that is actually enforced, including pay-as-you-go, read effectiveTier from client.credits.balance().
isAdminbooleanWhether the user is an administrator. Use it only to decide what to show. The server checks every permission itself.

On a Nodaro Cloud instance with organizations, the result also carries organizations, workspaces, lastWorkspaceId and organizationsUnavailable. Treat their three states differently:

What you seeWhat it meansWhat to do
The fields are absentThe instance has no organizationsDo not show a workspace switcher.
The fields are present and emptyThe account belongs to no organizationOffer to create or join one.
organizationsUnavailable: trueThe lookup failedKeep the selection you already had. Do not tell the user they lost access.

connectedApps()

connectedApps(): Promise<ConnectedApp[]>

Returns every app with access to the signed-in user's account (GET /v1/me/connected-apps), newest grant first. The list covers developer apps, the MCP connectors of AI assistants, and self-hosted installs. It is the data behind a Connected apps settings screen. See OAuth for what a grant is.

This method needs a signed-in browser session. It accepts a session token from supabaseAuth() or createSharedSupabaseClient. It refuses an API token (ndr_…) or an app token (ndr_app_…) with UnauthorizedError, so an app cannot list or revoke the grants it uses. A server-side integration cannot call it.

const apps = await client.connectedApps()
// [{ authorizationId, name, kind, homepageUrl, scopes, connectedAt, lastUsedAt }]
FieldTypeDescription
authorizationIdstringThe grant. Pass it to revokeConnectedApp().
namestring | nullThe app's name. An MCP client that registered itself chose its own name.
kindDeveloperAppKinduser (a developer app), first_party_mcp, dynamic_mcp (an MCP client that registered itself) or community_instance (a self-hosted install).
homepageUrlstring | nullThe app's homepage, when it gave one.
scopesDeveloperAppScope[]The scopes the grant holds.
connectedAtstringWhen the user granted access, in ISO 8601.
lastUsedAtstring | nullThe latest use of any token issued under the grant, or null if it was never used.

revokeConnectedApp(authorizationId)

revokeConnectedApp(authorizationId: string): Promise<void>

Ends an app's access (POST /v1/me/connected-apps/:authorizationId/revoke). It revokes the grant and every token issued under it at once, so the app's next call fails with 401. Like connectedApps(), it needs a signed-in browser session. It throws NotFoundError when the grant is not the user's or is already revoked.

await client.revokeConnectedApp(app.authorizationId)

client.userSettings

client.userSettings reads and changes the signed-in person's own settings: the interface language, the gallery visibility, the node menu and canvas preferences, and the monthly statement email. It is also importable as @nodaro/sdk/user-settings. An app shows the settings it needs, and sends back only the fields it changes.

MethodEndpointNotes
get()GET /v1/user/settingsReturns UserSettings: { tier, publicOutputs, promptTemplates, textTemplates, preferredLocale, mcpPreferences, showRecentNodes, showMostUsedNodes, variableDisplayMode, nodeDoubleClickAction, monthlyStatementEmail }. Throws UnauthorizedError when nobody is signed in.
update(patch)PATCH /v1/user/settingsChanges only the fields you send, and returns the settings as saved, without tier.

Rules for update(patch):

  • promptTemplates and textTemplates replace the stored value whole, so send the full map or list. A template key the server does not know, or a value equal to the default, is dropped.
  • mcpPreferences is merged one axis at a time. null clears one of its fields, for example update({ mcpPreferences: { image: { model: null } } }), and the MCP tools then use their own default for it. The patch type is UserMcpPreferencesPatch.
  • publicOutputs: false needs the Standard plan or above on Nodaro Cloud. Otherwise it throws ForbiddenError.
  • preferredLocale is one of the languages Nodaro ships (UserSettingsLocale).
  • An invalid value, or a patch with nothing to change, throws a NodaroError with status 400 (validation_error). A missing profile throws NotFoundError.
// The monthly statement toggle on a billing screen.
const { monthlyStatementEmail } = await client.userSettings.get()
await client.userSettings.update({ monthlyStatementEmail: !monthlyStatementEmail })

The types are UserSettings, UserSettingsPatch, UserSettingsSaved, UserSettingsLocale, UserTextTemplate, UserMcpPreferences, UserMcpPreferencesPatch, VariableDisplayMode and NodeDoubleClickAction.

On a deployment where one billing account pays for every user, tier is the deployment's plan, the billing account's tier, and turning publicOutputs off follows that plan.

client.surface

client.surface says what this deployment offers the caller: the node types, models, Web Scrape sources and worlds an app should show the person on screen. The answer depends on who asks, so it is never shared-cached. Signed out, the answer is a user's. It is also importable as @nodaro/sdk/surface.

MethodEndpointNotes
availability()GET /v1/surface/availabilityReturns SurfaceAvailability: { nodes: { denied, hiddenFromUsers }, models: { denied, restricted }, webScrapeSources: { denied, hiddenFromUsers }, worlds }. hiddenFromUsers is empty for anyone but an administrator. models.restricted is true when the deployment limits which models are offered. A model an administrator switched off is denied, but it is not a restriction. An older server omits restricted: read that as false. worlds lists every world (SurfaceAvailabilityWorld: { id, enabled }) in the deployment's display order, and a newer server may add one. A world is enabled when the deployment lets it through and every node type it runs is available to the caller.
// The shared header shows only the worlds this person can use.
const { worlds } = await client.surface.availability()
const tabs = worlds.filter((w) => w.enabled).map((w) => w.id)

The fields are described in What this deployment offers you.

withWorkspace(workspaceId)

withWorkspace(workspaceId: string | null): NodaroClient

Returns a new client that acts in workspaceId. The new client shares the auth, base URL, timeout and fetch of the original. Pass null for your personal space.

const classroom = client.withWorkspace(workspaceId)

await classroom.workflows.run(workflowId) // runs in the workspace
await client.workflows.run(workflowId)    // runs in the personal space

The method returns a new client instead of changing the current one. Two operations that run at the same time on one client can therefore never mix up their workspaces.

The workspace decides scope, never access. It chooses which workspace a list reads from and where a new item is created. Reading, changing, deleting or running an item you name by id depends on that item's own workspace. A forgotten workspace cannot hide your work, and a wrong one cannot reach anyone else's.

Workspaces belong to organizations on Nodaro Cloud. See Organizations and workspaces and Workspaces.

request(method, path, options)

request<T>(method: string, path: string, options?: {
  body?: unknown
  query?: Record<string, string | number | boolean | undefined>
  headers?: Record<string, string>
  signal?: AbortSignal
}): Promise<T>

Sends a request to any endpoint, for the few that have no resource method yet. It adds your auth header and your workspace, sends body as JSON, applies timeoutMs, and throws the same typed errors as the resource methods.

// The same request that client.jobs.list() sends
const page = await client.request<{ data: unknown[]; next: string | null }>("GET", "/v1/jobs", {
  query: { type: "llm-structured", limit: 20 },
})

A FormData body is sent as a multipart upload. Query values that are undefined are left out. The REST API reference lists every endpoint and its fields.

Timeouts and a custom fetch

timeoutMs aborts a request that takes longer than the limit, 60 seconds by default. Most generation takes longer than any sensible HTTP timeout, so start it and poll the job instead: client.nodes.runAndWait() does both for you. The streaming methods, client.copilot.stream() and client.media.downloadVideoProgress(), do not apply the timeout, because they are meant to stay open for minutes.

Pass your own fetch to change how requests travel:

  • Tests. Return canned Response objects from a mock.
  • Retries. Wrap the global fetch in a helper that retries on a 5xx answer.
  • Tracing. Wrap it with your tracing or monitoring library.
const client = createClient({
  baseUrl: "https://app.nodaro.ai",
  auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
  fetch: (input, init) => tracedFetch(input, init),
})

Runtimes and browsers

The SDK uses only fetch and URL, which are global in Node.js 20 and newer, in modern browsers, in React Native, on Cloudflare Workers, on Deno and on Bun. No polyfill is needed.

  • CORS with OAuth tokens. A browser app that calls Nodaro with an OAuth access token must run on an origin listed in the developer app's allowedOrigins. See OAuth and developer apps.
  • CORS with a session. A browser app that uses supabaseAuth is not checked against that list.
  • The client label. On a server, the SDK sends X-Nodaro-Client: sdk/<version>, and Nodaro records it as the origin of each job. In a browser the default label is not sent, because the browser's Origin header already names your app. A clientLabel you set yourself is always sent.

Frequently asked questions

What base URL should I use with the Nodaro SDK?

Use https://app.nodaro.ai for Nodaro Cloud and the address of your instance for a self-hosted install. In a browser app served from the same origin as Nodaro, use an empty string.

What is the default request timeout?

60 seconds. Change it with the timeoutMs option. Generation runs longer than any request, so start it and poll the job instead of raising the timeout.

How do I make requests in a workspace?

Call client.withWorkspace(workspaceId). It returns a new client that sends every request in that workspace, and the original client keeps working in your personal space.

How do I call an endpoint that has no SDK method?

Use client.request(method, path, options). It sends the same authentication header, applies the same timeout, and throws the same typed errors as every resource method.

Last updated on

On this page