Perception reference

Errors

The error envelope, statuses, codes and rate limits of the document routes.

The document routes (/v1/documents/* and /v1/extraction-schemas/*) and the Workspaces API (/v1/workspaces/*) use Drex's error envelope with a few extra fields:

{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_source",
    "message": "source.type must be \"url\", \"parse_result\" or \"workspace_file\"",
    "retryable": false
  },
  "request_id": "req_0123456789abcdef0123456789abcdef"
}
FieldWhat it holds
typeThe class of error. Branch on this.
codeThe exact cause: Drex's own code, or the document service's when it refused the request, such as invalid_request, unsupported_file_type or schema_not_found.
retryableWhether the same request can succeed later. Retry creates with the same Idempotency-Key.
detailPresent with the document service's validation errors and conflicts. detail.errors lists the fields that failed.

These routes send no issues. Responses from Drex carry their request id in the x-request-id header. Error bodies repeat it as request_id; a successful job body doesn't. A request rejected by the hosting platform before it reaches Drex, such as an oversized body above about 4.5 MB, can return 413 without this JSON envelope or a request id. Handle the HTTP status even when those fields are absent.

Statuses and codes

StatustypecodeWhen
400, 422invalid_request_errorinvalid_requestThe body isn't JSON, or the document service refused it.
413invalid_request_error when presentpayload_too_large when presentThe body is too large. A platform rejection can omit the envelope and request id.
415invalid_request_errorunsupported_media_typeA workspace file replace that isn't multipart.
422invalid_request_errorinvalid_sourceThe source is missing or isn't one of the allowed kinds.
422invalid_request_errorunsupported_file_typeThe tool doesn't read this type of file.
422invalid_request_errorwait_required_for_zero_retentionA create without wait_seconds from a zero-retention account. See Zero retention.
401authentication_errorinvalid_api_keyThe API key is missing or invalid.
402insufficient_creditA create while the balance is not positive and no card pays for usage beyond it.
402payment_requiredA create while the account has an unpaid invoice. Pay it on the Billing page. Not retryable until it is paid.
403permission_erroraccess_deniedA workspace delete by someone other than the owner account's owners and admins or the workspace's creator, a search naming a file you aren't allowed to read, or a QA turn in another member's conversation.
404not_found_errornot_foundNo such job for this account: a job ID (a UUID) the account didn't create.
404not_found_errorschema_not_foundNo such saved schema.
404not_found_errorworkspace_not_foundNo such workspace. A workspace you can't use, or one that was deleted, is also workspace_not_found.
404not_found_errorfile_not_foundNo such file.
404not_found_errorsession_not_foundNo such QA session.
409conflict_erroridempotency_conflictIdempotency-Key reused with a different body (document tools).
409conflict_errorschema_version_conflictTwo versions of one saved schema created at once.
409conflict_errorstorage_limit_exceededThe account's managed storage allowance is used up. Tenant-wide usage and allowance are not exposed through workspace-scoped errors. Not retryable. Allowances depend on deployment configuration; there are no default byte limits. Review billing options or contact support@nace.ai for more space; the paid tier follows billing paid-account status, and saving a card alone does not qualify. Deleting files also frees space. Uploads into a workspace shared with you count against its owner's account, and customer-owned storage does not count. A direct upload by grant or session comes from the document service without a type; from 32 MiB the session's completion job fails with this code instead.
409conflict_errorsession_busyA QA session is still answering its last turn.
409conflict_errorA job's full content couldn't be prepared.
410not_found_errorresult_expiredA result or file has expired. On zero retention, a result's download links close about 21 minutes after its job finishes; the file in your own storage is not deleted.
410not_found_errorjob_unavailableA job created before 3 October 2026 whose results are no longer available.
429rate_limit_errorrate_limitedOver the per-minute limit; over 60 automatic searches and QA turns per account every 10 minutes; over 150 locates; or over 600 file reads, SQL queries, folder listings and catalog queries together. Wait retry-after seconds.
500internal_errorSomething failed inside Drex. Retry once, then contact support with the request_id.
502, 504upstream_errorndi_refused_drexThe document service refused Drex's own request.
502, 504upstream_errorndi_redirectThe document service answered a job file with a redirect Drex can't serve.
502, 504upstream_errorThe document service timed out. Retry later.
503service_unavailabledocument_billing_not_configuredBilling is not configured.
503service_unavailablendi_not_configuredUploads are not configured on this deployment.
503service_unavailablejob_pendingThe job's create is not confirmed yet. The job may be running, and is billed once if it ran. Send the same body again with the same key to get it.
503service_unavailablestorage_limit_sync_unavailableThe account's storage allowance could not be checked before a workspace create or upload. Retry shortly; reads and deletes are unaffected.
503service_unavailablendi_unavailableThe document service is down.
503service_unavailableThe document service's own codeA 5xx from the document service itself also answers 503.
529overloadedservice_disabledThe API is paused. Retry after retry-after seconds.

A file that can't be read, such as a corrupt_file, is accepted but its job ends failed, with the cause in the job's error.code. See The job object.

In the SDKs

Each status raises its own error class. Each class reads type, code and detail from the envelope, and its retryable as server_retryable (Python) or serverRetryable (TypeScript), None or undefined when the body has none:

StatusPythonTypeScript
400NaceBadRequestErrorBadRequestError
401NaceAuthenticationErrorAuthenticationError
402NaceInsufficientCreditErrorInsufficientCreditError
403NacePermissionDeniedErrorPermissionDeniedError
404, 410NaceNotFoundErrorNotFoundError
409NaceConflictErrorConflictError
422NaceUnprocessableEntityErrorUnprocessableEntityError
429NaceRateLimitErrorRateLimitError
529NaceOverloadedErrorOverloadedError
Other 5xxNaceInternalServerErrorInternalServerError
413, 415NaceAPIErrorAPIError

All of them are NaceAPIError (Python) or APIError (TypeScript). A few cases need a closer look:

  • A path that's taken: an upload under 32 MiB to a path that's taken raises the 409 class with code path_conflict. A larger upload, sent in parts, fails its completion job with that code instead, and the SDK raises a plain NaceError whose message names it. See Uploads.
  • Mistakes caught before sending: the SDKs refuse these with a plain NaceError: a string source that isn't an https:// URL, a url source with no file_name whose URL doesn't end in a file extension, wait_seconds outside 0 to 60, and an empty Idempotency-Key or one over 200 characters. The SDKs infer file_name from a URL extension when you omit it. For raw HTTP, include source.file_name explicitly, even if the URL ends in .pdf.
  • Waiting for a job: jobs.wait raises its own errors for a failed job and for a wait that ran out. See Read a job.
  • After a 503 job_pending: the error carries the Idempotency-Key the request sent, as idempotency_key in Python and idempotencyKey in TypeScript. Send the same body with it to resume the job.
  • In the CLI: it prints the status, type, code, message and request_id, then up to five of the fields that failed, and exits 1. On job_pending it also prints the key to rerun with as --idempotency-key.

For more on handling errors, see the Python, TypeScript and CLI pages.

Funding cancellations

A job cancelled because its execution allowance was exhausted can return status: "cancelled" with error.code: "job_cancelled" and error.detail.budget_exhausted: true. This is a job result, rather than an HTTP error status. It is not charged. Prepaid documents have a per-document allowance even when the account has more funds; see Pricing. Resending the same oversized document does not raise that allowance.

Rate limits

  • The limits: creates, upload setup (grants and upload sessions), and job observation (get, list, events, request echoes and parse preview metadata) each have an independent requests-per-minute window per account. Creates allow 120/min free or 600/min paid; job reads allow 600/min free or 3000/min paid (5x); upload setup allows 240/min free or 1200/min paid (2x). An account RPM override scales the same way: 1x creates, 5x reads and 2x upload setup. Unlimited accounts remain uncapped (see Limits). Uploads and polls cannot spend create capacity, and all three windows are separate from inference. File downloads and individual preview parts do not count. NDI can also throttle a call with 429 and Retry-After.

  • A create refused with 429: it starts no job and is not billed, and nothing creates it for you later. Retry it yourself, with the same Idempotency-Key, within 13 minutes. A later retry of that key answers 503 job_pending until Drex has confirmed with the document service that no job exists under it, then 422 job_not_created: send the job again under a new key. If the document service did start the job after all, it is linked to your request and billed once, like any other.

  • Automatic retries: the SDKs and the CLI retry 408, 429 and 5xx answers on their own, reusing the call's key, and never one marked retryable: false. By default they allow two retries without a total retry budget, so slow failures can take several minutes to surface. An explicit retry budget can stop retries earlier. Saved-schema creates are sent once unless you pass a retry policy. To retry later yourself, pass your own idempotency_key so the retry reuses it. See Errors and retries.

  • Request limits: use a document create endpoint and JSON source, such as POST /v1/documents/parse?wait_seconds=60. For a URL source, send a JSON body with source.type set to "url", source.url set to your public HTTPS document URL, and source.file_name set to its filename including the extension (for example, "invoice.pdf"). wait_seconds is a query parameter, not a JSON body field. Keep create bodies under 1,000,000 bytes. The SDKs cap wait_seconds at 60; zero-retention HTTP creates allow up to 100 seconds.

Document batches

Start with 8 concurrent documents, keeping each worker busy through upload, create and completion. Prefer the SDK option wait_seconds=60 on creates (for raw HTTP, POST /v1/documents/parse?wait_seconds=60, with source.file_name in the JSON source and no wait_seconds body field): it holds that request open for up to 60 seconds, then returns the current job. If it is still running, use jobs.wait rather than a tight polling loop. This reduces requests without changing how the job is billed. See the Python batch example and TypeScript batch example.

A Retry-After response header is the number of seconds to wait before retrying, not a job timeout. The current SDKs honor it automatically with their default retry policy (Python nace-sdk 0.7.0 or later). For a raw HTTP client, wait that long and retry creates with the same Idempotency-Key. Larger batches can still reach the create RPM limit or NDI's own limits; keep concurrency bounded and let retries wait for the window.

On this page