Errors
Every Nodaro API error has an HTTP status and a stable code in one envelope. Learn what each code means, which errors to retry, and how failed jobs report why.
Every Nodaro API error returns an HTTP status and one JSON envelope with a stable code, so your client can dispatch on the code instead of parsing messages. There are two kinds of failure: a request error, when the call itself is refused and nothing starts, and a job failure, when a generation that started fails later and reports why on the job.
The error envelope
{ "error": { "code": "rate_limited", "message": "Too many requests. Max 30 per minute." } }codeis a stable slug. Dispatch on it.messageis text for people. It can change, so never match on it.- Some errors add fields, listed with each code below. Most sit inside
error;already_runningputsexecutionIdbesideerror, and a failed pre-run allowance check putsrequiredandremainingat the top level.
The OAuth token endpoint is the one exception: it answers in the OAuth format, for example { "error": "invalid_grant", "error_description": "…" }. See OAuth apps.
Which errors to retry
| Status | What to do |
|---|---|
5xx | Usually temporary. Retry with exponential backoff. |
429 | Wait, then retry with backoff: for example 5, then 10, then 20 seconds. Honor a Retry-After header when there is one. |
402 | Add credits, or ask a workspace admin for budget, then retry. |
Other 4xx | Fix the request first. The same request fails again. |
Three server errors need care:
503 price_not_configureddoes not succeed on retry: the model has no price on this deployment until its operator sets one.503 provider_unavailableon the asynchronous structured LLM route is permanent on an install that sends its language-model calls to nodaro.ai.- When publishing to social networks,
503 publish_retryablemeans nothing was posted and the same request is safe to send again, but500 publish_failedmeans the outcome is unknown, and sending again can post twice.
Error codes
400 Bad Request
| Code | Meaning |
|---|---|
validation_error | A malformed body, a bad UUID, an invalid field or a malformed cursor. |
limit_reached | You already have 10 API tokens, active or not. Delete one first. |
invalid_workflow | A token's workflowIds entry is not a workflow in your personal space. |
token_workspace_mismatch | A token bound to one workspace was sent with an X-Nodaro-Workspace header naming another. |
locked_field | A run tried to change where an outbound node sends or fetches. See What a run cannot change. |
image_required | POST /v1/generate-video without a start frame, on a model that cannot make video from text. The message says whether references would work instead. |
sequence_execution_required | The run includes Studio production nodes that must be generated through the Studio production API. |
not_workspace_scoped | You changed visibility on a workflow that is not in a workspace. |
advanced_mode_unsupported | advancedMode: true on a model that has no direct lane. |
401 Unauthorized
| Code | Meaning |
|---|---|
unauthorized | The token is missing, invalid, expired or revoked. |
402 Payment Required
These codes come from Nodaro Cloud and from deployments that bill.
| Code | Extra fields | Meaning |
|---|---|---|
insufficient_credits | required, balance | The account is out of credits. On a deployment with one billing account, it means the deployment's pool is empty: the error then carries required but never balance, and only the billing account can add credits. |
insufficient_app_credits | A published app's run: the account is out of credits. | |
budget_exceeded | Work paid by a workspace: the workspace budget cannot cover the run. Ask a workspace admin for more. | |
member_cap_exceeded | Work paid by a workspace: your own spending cap in that workspace is reached. | |
user_allowance_exceeded | required, remaining | A deployment with one billing account that enforces per-user allowances: your allowance cannot cover the run. Only the billing account can raise it. |
instance_cap_reached | An OAuth token of a connected self-hosted install: the install has spent its monthly cap on the account that connected it. Raise or remove the cap under Connected Instances. |
403 Forbidden
| Code | Extra fields | Meaning |
|---|---|---|
forbidden | The token's workflow scope does not include this workflow, or the route needs a signed-in session. See Authentication. | |
insufficient_scope | missingScope | An OAuth token lacks a scope the route needs. Send the user through consent again with the wider scope. |
in_app_only | The route exists only for the Nodaro web app, such as the Workflow Copilot. | |
edition_required | required_edition | The route needs a higher edition: cloud for pipeline branching, business for API token management. |
not_a_member | The X-Nodaro-Workspace header names a workspace you are not an active member of. | |
member_suspended | Your membership in the workspace is suspended. | |
personal_space_disabled | Your organization lets you create work only inside a workspace. Send the workspace header. | |
project_create_not_allowed | Only the workspace's admins may create projects. | |
workspace_archived | A write into an archived workspace, such as sharing a workflow. Creating work there answers 409 instead. | |
not_permitted | The workflow is not yours to move, or not yours to move there. | |
sso_required | The deployment restricts sign-in to its identity provider, and this session's account was not created through it. Tokens are not affected. | |
subscription_required | A pay-as-you-go account spent from the web editor. Pay-as-you-go credits work through the API, the SDK, the CLI and MCP, so token calls never get this code. | |
api_tokens_payer_only | On a deployment with one billing account, only that account can create API tokens. | |
payer_balance_jwt_only | On such a deployment, the billing account read the pool's balance with a token. Only its own browser session may. | |
payer_required | A deployment billing route was called by anything but the billing account's browser session. |
404 Not Found
| Code | Meaning |
|---|---|
not_found | The resource does not exist, or you cannot see it. The two are deliberately the same answer, so an id never reveals whether something exists. |
workspace_not_found | Work paid by a workspace names a workspace that does not exist. |
409 Conflict
| Code | Extra fields | Meaning |
|---|---|---|
already_running | executionId | The workflow already has an active run. Poll that run instead. |
workflow_conflict | currentVersion, currentUpdatedAt, currentRecord | The workflow changed since the version you sent. Merge onto currentRecord and save again. |
workspace_archived | You tried to create or move work into an archived workspace. | |
workspace_has_no_default_project | A create in a workspace named no project, and the workspace has none. Name a project. | |
move_blocked | The work was created for an assignment and cannot be moved. | |
production_capability_required | A generic workflow save touched a Studio production that needs its own API. | |
retained_image_in_use | A delete would remove protected images, or a production still has jobs using them. Finish or cancel those jobs first. |
413, 422 and 429
| Status | Code | Meaning |
|---|---|---|
| 413 | Your account is over its storage limit. The SDK raises StorageExceededError with limitBytes. | |
| 422 | job_blocked | The deployment's job policy refused the generation before it ran. No job was created and nothing was charged. Show message to your user as it is, and do not retry the same request. |
| 422 | upload_blocked | The deployment's upload policy refused a file before it was stored. Show message as it is. |
| 429 | rate_limited | The per-token limit of a personal API token. |
| 429 | rate_limit_exceeded | Any other limit: per address, per route, or a published app's daily runs. |
| 429 | too_many_downloads | You already have 4 video imports running. |
The 422 codes happen only on deployments that register such a policy. See Rate limits for the 429 codes.
500 and 503
| Status | Code | Meaning |
|---|---|---|
| 500 | internal_error | A server error or a failure of a service it depends on. Retry with backoff. |
| 503 | price_not_configured | The requested model has no price on this deployment, so the server refuses instead of billing wrongly. Retrying does not help. |
| 503 | rate_limit_unavailable | A spending route could not check its rate limit. Retry later. |
| 503 | provider_unavailable | The model's provider is not available. |
| 503 | billing_unavailable | Usage reporting is not available on this instance yet. |
When a job fails later
A request that succeeded can still produce a job that fails. The job then carries error_message, and for two kinds of failure a structured error_hint:
{ "kind": "safety-block", "class": "copyright" | "likeness" | "safety", "retried": boolean, "suggestedProvider"?: string }when a model's safety filter blocked the request.{ "kind": "policy-block", "policyId": string, "reason": string, "hookPoint": "request" | "result" }when a deployment's policy rejected it.
Both are always refunded, and credit_status on the job shows it. Read Why a job failed for what each field means and when to try another model.
Errors in the SDK
@nodaro/sdk throws a typed error for every failed response. Catch the most specific class first:
| Class | Status | Extra fields |
|---|---|---|
UnauthorizedError | 401 | |
ForbiddenError | 403 | missingScope when the code is insufficient_scope |
NotFoundError | 404 | |
InsufficientCreditsError | 402 | required, available |
StorageExceededError | 413 | limitBytes |
WorkflowConflictError | 409 | currentVersion, currentUpdatedAt, currentRecord. Also used for the Studio production_busy conflict. |
RateLimitedError | 429 | |
JobBlockedError | 422 | |
StudioOpError | 4xx | opIndex: which operation of a Studio batch was refused |
NodaroError | any | The base class: code, status and message |
import {
NodaroError,
ForbiddenError,
InsufficientCreditsError,
RateLimitedError,
} 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 ForbiddenError && err.missingScope) {
requestConsent([err.missingScope])
} else if (err instanceof RateLimitedError) {
await new Promise((r) => setTimeout(r, 5_000))
} else if (err instanceof NodaroError) {
console.error(`API error ${err.status} (${err.code}): ${err.message}`)
} else {
throw err // a network failure, not an API error
}
}The polling helpers add their own errors, such as JobFailedError and JobTimeoutError. See Run a single node.
Errors in the CLI
| Exit code | Meaning |
|---|---|
0 | Success. |
1 | Unauthorized, not found, a wrong argument or a network error. |
2 | --watch ended and the run failed. |
3 | --watch stopped because the job is held for review. It is not a failure: check back later. |
130 | --watch ended and the run was cancelled. |
With --json, the CLI prints the payload and exits normally instead of using codes 2, 3 and 130, so check .status yourself.
Frequently asked questions
Related
Jobs
Rate limits
Authentication
Credits
TypeScript SDK
Last updated on
Credits
Read your Nodaro credit balance and history from the API, price models and runs before you start them, and see how reservations, refunds and pay-as-you-go work.
Rate limits
A Nodaro API token allows 30 run requests per minute by default, up to 120. See the per-route limits, the two 429 codes, batch sizes and how to back off.