NodaroAccount
Nodaro Docs
DocumentationNode ReferenceModelsTemplatesAI Agents (MCP)DevelopersSelf-hostingResearchCompare
REST API

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." } }
  • code is a stable slug. Dispatch on it.
  • message is 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_running puts executionId beside error, and a failed pre-run allowance check puts required and remaining at 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

StatusWhat to do
5xxUsually temporary. Retry with exponential backoff.
429Wait, then retry with backoff: for example 5, then 10, then 20 seconds. Honor a Retry-After header when there is one.
402Add credits, or ask a workspace admin for budget, then retry.
Other 4xxFix the request first. The same request fails again.

Three server errors need care:

  • 503 price_not_configured does not succeed on retry: the model has no price on this deployment until its operator sets one.
  • 503 provider_unavailable on 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_retryable means nothing was posted and the same request is safe to send again, but 500 publish_failed means the outcome is unknown, and sending again can post twice.

Error codes

400 Bad Request

CodeMeaning
validation_errorA malformed body, a bad UUID, an invalid field or a malformed cursor. With reason: "llm_model_unpriced", an LLM model that has no price yet. See A model with no price yet.
limit_reachedYou already have 10 API tokens, active or not. Delete one first.
invalid_workflowA token's workflowIds entry is not a workflow in your personal space.
token_workspace_mismatchA token bound to one workspace was sent with an X-Nodaro-Workspace header naming another.
locked_fieldA run tried to change where an outbound node sends or fetches. See What a run cannot change.
image_requiredPOST /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_requiredThe run includes Studio production nodes that must be generated through the Studio production API.
not_workspace_scopedYou changed visibility on a workflow that is not in a workspace.
advanced_mode_unsupportedadvancedMode: true on a model that has no direct lane.
advanced_mode_unavailableadvancedMode: true while the model's own provider cannot take requests. Nothing is charged. Turn Advanced mode off to run the model. The same code can arrive as 502 mid-call. It is then refunded unless the provider billed the attempt.
quote_unsupportedPOST /v1/quote for a node type the platform cannot quote. The message lists the types it can. See Run quotes.
continuation_subset_requiredcontinueFromExecutionId without nodeIds or continueMode. See Continue a run.
continuation_workflow_mismatchThe run named in continueFromExecutionId ran another workflow.
continuation_version_mismatchThe run named in continueFromExecutionId ran a published app version of the workflow.
render_final_node_not_foundrenderFinal.renderNodeId names no node of the workflow.
media_unreadableA use-case run, such as Tighten Episode, Chapters, Trailer, Clip Pack or Speaker Layout, whose recording could not be read. Check that the link opens and is a video or audio file. Nothing is created or charged.
media_has_no_audioA use-case run or its estimate: a recording has no sound to transcribe. The message names the recording. Nothing is created or charged.
media_too_longA use-case run or its estimate: a recording, or the Master audio, is longer than 3 hours. The message names which. Nothing is created or charged.
master_audio_unreadableA use-case run or its estimate with masterAudioUrl: the Master audio could not be read as audio. Nothing is created or charged.
render_final_not_a_renderrenderFinal.renderNodeId names a node that is not an Apply EDL render.

401 Unauthorized

CodeMeaning
unauthorizedThe token is missing, invalid, expired or revoked.

402 Payment Required

These codes come from Nodaro Cloud and from deployments that bill.

CodeExtra fieldsMeaning
insufficient_creditsrequired, balanceThe 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_creditsA published app's run: the account is out of credits.
budget_exceededWork paid by a workspace: the workspace budget cannot cover the run. Ask a workspace admin for more.
member_cap_exceededWork paid by a workspace: your own spending cap in that workspace is reached.
user_allowance_exceededrequired, remainingA 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_reachedAn 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

CodeExtra fieldsMeaning
forbiddenThe token has a workflow scope and the request is outside it, or the route needs a signed-in session. See Limit a token to some workflows and Routes that need a signed-in session.
access_blockedOn Nodaro Cloud and the Business edition, an administrator blocked the account. Every credential of the account is refused: the session, API tokens and OAuth tokens. Its schedules and triggers stop, and its published apps answer 404 not_found. Contact support if you think this is a mistake.
insufficient_scopemissingScopeAn OAuth token lacks a scope the route needs. Send the user through consent again with the wider scope.
in_app_onlyThe route exists only for the Nodaro web app, such as the Workflow Copilot.
node_not_availableThis deployment does not offer the node, or the option you chose in it, to your account. The message names it. Nothing is charged. Every route that spends credits is checked as the node it runs, whatever its path: POST /v1/suno/generate as suno-generate, a trend run (POST /v1/trends/:slug/runs and its /estimate) as cast-swap, Nodaro Motion, POST /v1/video-director/run as video-director, the Video Director, and POST /v1/pipelines and the pipeline routes that start or continue work as film-pipeline, the film pipeline. See Pipelines.
model_not_availableThe request would run on a model that this deployment does not offer. That is the model you named, or, on a deployment that restricts models, the default the route runs when you name none: the image default for the aspect ratio, a feature's default language model, or a route's one fixed model. Nothing is created or charged. Name a model that GET /v1/models lists.
model_disabledNodaro Cloud only. An admin switched this model off. Nothing is reserved or charged. Buying credits does not help: choose another model, or try again when the model is switched back on.
edition_requiredrequired_editionThe route needs a higher edition: cloud for pipeline branching, business for API token management.
not_a_memberThe X-Nodaro-Workspace header names a workspace you are not an active member of.
member_suspendedYour membership in the workspace is suspended.
personal_space_disabledYour organization lets you create work only inside a workspace. Send the workspace header.
project_create_not_allowedOnly the workspace's admins may create projects.
workspace_archivedA write into an archived workspace, such as sharing a workflow. Creating work there answers 409 instead.
not_permittedThe workflow is not yours to move, or not yours to move there.
sso_requiredThe deployment restricts sign-in to its identity provider, and this session's account was not created through it. Tokens are not affected.
subscription_requiredA 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_onlyOn a deployment with one billing account, only that account can create API tokens.
payer_balance_jwt_onlyOn such a deployment, the billing account read the pool's balance with a token. Only its own browser session may.
payer_requiredA deployment billing route was called by anything but the billing account's browser session.
rights_confirmation_required, rights_authorization_required and the other rights_confirmation_* codesAn import of a link has no content rights confirmation that fits. Nothing was fetched. See Content rights.
import_source_disabledThe deployment does not allow imports from the link's site.
link_imports_disabledThis deployment turned imports from links to other sites off. Upload the file instead. A file of the deployment's own storage is not refused. See Content rights.
imports_blockedImporting links is blocked on the account, or the account is closed, under the deployment's repeat-infringer policy. The message says until when. See Repeat infringers.

404 Not Found

CodeMeaning
not_foundThe 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_foundWork paid by a workspace names a workspace that does not exist.
continuation_not_foundThe run named in continueFromExecutionId does not exist or is not yours.

409 Conflict

CodeExtra fieldsMeaning
already_runningexecutionIdThe workflow already has an active run, or a Retry of the app run is pending. Poll that run instead.
continuation_not_completedThe run named in continueFromExecutionId is still going, failed or was cancelled. Only a completed run can be continued without continueMode.
continuation_not_failedWith "continueMode": "from-failed": the run did not fail.
continuation_supersededexecutionId, supersededByWith "continueMode": "from-failed": a newer continuation of the run failed, so continue that one, or it completed everything that was left. See Continue a failed run.
continuation_preview_pendingexecutionId, renderNodeIds, reviewNodeIds, nextWith "continueMode": "from-failed": the newest continuation stopped at a Preview review. Send next as the body of a run first.
continuation_subset_requiredWith "continueMode": "from-failed": nothing is left to continue. On an app run's Retry: every step completed.
app_run_version_changedappVersion, runVersionA Retry of an app run made on another app version. Start the run over.
app_run_version_unavailableA Retry of an app run whose version is no longer available. Start the run over.
workflow_conflictcurrentVersion, currentUpdatedAt, currentRecordThe workflow changed since the version you sent. Merge onto currentRecord and save again.
job_cancelledA synchronous metered LLM request whose job was cancelled after its credits were held, but before its call was sent. See Metered LLM requests.
idempotency_key_reused—A Tighten Episode run or its continuation (POST /v1/tighten-episode, POST /v1/tighten-episode/continue): the Idempotency-Key already belongs to one of your jobs of another kind, such as an image generation. Nothing is created or charged. Send a new key. A key that belongs to a run of the same node answers that run, with 202 and "deduped": true.
workspace_archivedYou tried to create or move work into an archived workspace.
workspace_has_no_default_projectA create in a workspace named no project, and the workspace has none. Name a project.
move_blockedThe work was created for an assignment and cannot be moved.
production_capability_requiredA generic workflow save touched a Studio production that needs its own API.
retained_image_in_useA delete would remove protected images, or a production still has jobs using them. Finish or cancel those jobs first.
content_heldThe file is held as evidence for a complaint, so it cannot be deleted right now. See Restricted content.
not_applicableOn a deployment where one billing account pays for every user, a free signup credit route: POST /v1/credits/claim-signup-grant and the two welcome offer routes. Free signup credits do not apply there, so nothing is recorded or granted. There is nothing to retry, and a client that claims on every page load can ignore it.

413, 422, 429 and 451

StatusCodeMeaning
413Your account is over its storage limit. The SDK raises StorageExceededError.
422job_blockedThe deployment's job policy or content guard 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.
422upload_blockedThe deployment's upload policy refused a file before it was stored. Show message as it is.
422offsets_not_appliedPOST /v1/edit-plan received a raw offsets field. Write each offset onto its source as sources[].offsetMs first. See Edit Plan.
422master_offsetPOST /v1/edit-plan received an offset other than 0 on the master, or on the source that the transcript was made from. Nothing is charged.
429rate_limitedThe per-token limit of a personal API token.
429rate_limit_exceededAny other limit: per address, per route, or a published app's daily runs.
429too_many_downloadsYou already have 4 video imports running.
451content_restrictedGET /v1/download or GET /v1/image-proxy was asked for a file that Nodaro restricted. See Restricted content.

job_blocked and upload_blocked happen only on deployments that register such a policy. See Rate limits for the 429 codes.

500 and 503

StatusCodeMeaning
500internal_errorA server error or a failure of a service it depends on. Retry with backoff.
502advanced_mode_unavailableAn Advanced-mode LLM call was refused mid-call by the model's own provider. The hold is refunded unless the provider billed the attempt. Turn Advanced mode off to run the model.
503provider_key_missingA use-case run or its estimate that transcribes, on a self-hosted install with no key for that transcription provider and no Nodaro Cloud connection that covers it. The message names the key. Nothing is created.
503job_start_failedA synchronous metered LLM request could not record that its call is starting. See Metered LLM requests.
503price_not_configuredThe requested model has no price on this deployment, so the server refuses instead of billing wrongly. Retrying does not help.
503rate_limit_unavailableA spending route could not check its rate limit. Retry later.
503provider_unavailableThe model's provider is not available.
503billing_unavailableUsage reporting is not available on this instance, or billing is briefly unavailable for a workspace run of an app's Retry. Try again shortly.

Metered LLM requests

A language-model call is charged for the tokens it uses, so its credits are held before the call starts. A synchronous request of this kind, such as Generate Text, Prompt Helper or POST /v1/llm/structured, can answer two more codes:

  • 409 job_cancelled. The job was cancelled after its credits were held, but before its call was sent. The call is never sent, and the hold is refunded. Do not retry the request as it is: the job was cancelled on purpose.
  • 503 job_start_failed. The server could not record that the call is starting, so it did not send the call. The hold is refunded. Retry the request.

A model with no price yet

An LLM node is charged per model, at the rate of the lane that serves it. A model with no rate on that lane cannot be run. Every route that would price or run it answers 400:

{
  "error": {
    "code": "validation_error",
    "reason": "llm_model_unpriced",
    "modelId": "<model>",
    "message": "The model \"<model>\" has no price yet, so it cannot be run. Choose another model."
  }
}
  • Estimates: POST /v1/credits/estimate-workflow, GET /v1/credits/model-cost, POST /v1/component/estimate-credits, POST /v1/workflows/:id/render-final/estimate and the /estimate route of each use-case node. POST /v1/credits/model-costs answers per key instead: it lists the key in errors, with an errorCodes entry.
  • Publishing: POST /v1/apps/publish and POST /v1/templates/publish refuse before anything is written. A republish leaves the current version live.
  • Runs: POST /v1/workflows/:id/run, POST /v1/app/:slug/run, POST /v1/app/:slug/runs/:runId/resume, POST /v1/component/execute, POST /v1/api/run, POST /v1/present/:token/run, and the run and continue routes of each use-case node. The check runs before the run is created, with your input overrides applied. No node runs, so nothing is charged. The MCP tools run_app, run_workflow and run_component reach the same routes.
  • Triggers: a webhook fire answers the same 400. Schedule, Telegram and plugin-trigger fires are skipped. Each trigger leaves one failed execution with error_message: "llm_model_unpriced" in the workflow's history, and repeated fires add no more.
  • Inside the run: the server checks again when a run starts, before any node runs. The check covers the sub-workflows and components the run executes. A run refused here ends failed, with the message above as its error_message. A model inside a sub-workflow adds Found inside Sub-workflow node <id>., and one inside a component adds Found inside Component node <id>.

Two read-only routes still describe the workflow. GET /v1/present/:token leaves out estimatedCost and returns estimatedCostUnpriced: { reason, modelId, message }. GET /v1/api/schema leaves out estimatedCredits and returns estimatedCreditsUnpriced in the same shape.

When a job fails later

A request that succeeded can still produce a job that fails. The job then carries error_message, and for five kinds of failure a structured error_hint:

  • { "kind": "safety-block", "class": "copyright" | "likeness" | "safety", "retried": boolean, "suggestedProvider"?: string, "subject"?: "input_video" | "input_image" | "prompt" | "audio" } when a model's safety filter blocked the request. subject: "input_video" means the provider refused the job's input video itself, so send a different clip. subject: "input_image", always with class: "copyright", means the provider refused one of the job's input images as possibly copyrighted: send a different picture, because rephrasing the prompt will not help. subject: "prompt" means the provider's own words named the prompt, so change the prompt. subject: "audio" means the provider's own words blamed the generated audio, not the picture. Treat a subject you do not know as absent.
  • { "kind": "policy-block", "policyId": string, "reason": string, "hookPoint": "request" | "result" } when a deployment's policy rejected it.
  • { "kind": "credits", "code": string } when the job ran out of credits partway, for example insufficient_credits or a workspace limit such as budget_exceeded.
  • { "kind": "input-refused", "subject": "input_video" } when the provider refused the job's input video without naming a content-policy class. No other model is suggested. Send a different clip.
  • { "kind": "request-refused", "subject"?: "input_video" } when the provider refused the request as it was sent, naming a fault of it: a setting, a file's format or size, or the prompt's length. The same request is refused again, so change the input images, the video, the prompt or the settings. subject: "input_video" is there when the provider's words name the input video as the fault, such as its length or size, or no character found in it, so send a different clip. It is not a content-policy block.

A failed job also carries error_detail, the provider's own words, or null. The words go only to a caller who can open the run's workflow, and a deployment can keep them for its administrators. See The provider's own words.

The first three are refunded, and credit_status on the job shows it. A failed metered LLM call is the exception: usage the provider billed is charged. 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:

ClassStatusExtra fields
UnauthorizedError401
ForbiddenError403missingScope when the code is insufficient_scope
ModelUnavailableError403A subclass of ForbiddenError for model_not_available and model_disabled: the model is refused, not the caller
NotFoundError404
InsufficientCreditsError402required, available. code is the refusal's own 402 code.
CreditCapExceededError402A subclass of InsufficientCreditsError for budget_exceeded, member_cap_exceeded and user_allowance_exceeded: a cap someone else set
StorageExceededError413reason: storage_limit_exceeded for a full storage, file_too_large for one file over the limit
WorkflowConflictError409currentVersion, currentUpdatedAt, currentRecord. Also used for the Studio production_busy conflict.
AlreadyRunningError409executionId: the run under way, for already_running
AppRunVersionChangedError409runVersion, appVersion, for app_run_version_changed
RateLimitedError429
JobBlockedError422
StudioOpError4xxopIndex: which operation of a Studio batch was refused
NodaroErroranyThe base class: code, status, message and details, the envelope's other fields
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 codeMeaning
0Success.
1Unauthorized, 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

What does a Nodaro API error look like?

Every error has an HTTP status and the body { "error": { "code": "…", "message": "…" } }. The code is stable, so dispatch on it. The message is for people and can change.

Which Nodaro API errors should I retry?

Retry 5xx errors and 429 with exponential backoff, for example after 5, 10 and 20 seconds. Do not retry a 4xx error without changing the request, because the same request fails again. 503 price_not_configured is the exception among server errors.

What does 402 insufficient_credits mean?

The account does not have enough credits to start the run. The error carries required, the credits the run needs, and usually balance. Add credits on Nodaro Cloud, then send the request again.

What is error_hint on a failed job?

A structured reason attached to a failed job for five kinds of failure, a model's safety filter, a deployment's policy, running out of credits partway, a refused input video and a request the provider refused as it was sent. Read it instead of parsing error_message, for example to offer the suggested model.

What does llm_model_unpriced mean?

The workflow or request names an LLM model that has no price yet on the lane it would run on, so it cannot be run. The answer is 400 validation_error with reason llm_model_unpriced and the modelId. Choose another model. Nothing is charged.

Why does a route answer 403 in_app_only?

The route exists only for the Nodaro web app's own session, for example the Workflow Copilot or stored Webhook Output credentials. API tokens and OAuth tokens cannot call it. Use the workflow endpoints, the SDK or MCP instead.

Last updated on

On this page