Errors and utilities
Handle errors
Every API, connection and job failure raises a NaceError. An error response raises a NaceAPIError subclass:
| Status | Class |
|---|---|
| 400 | NaceBadRequestError |
| 401 | NaceAuthenticationError |
| 402 | NaceInsufficientCreditError (type is insufficient_credit or payment_required) |
| 403 | NacePermissionDeniedError |
| 404, 410 | NaceNotFoundError |
| 409 | NaceConflictError |
| 422 | NaceUnprocessableEntityError |
| 429 | NaceRateLimitError |
| 529 | NaceOverloadedError (a subclass of NaceInternalServerError) |
| Other 5xx | NaceInternalServerError |
| Any other error status, such as 408 or 413 | NaceAPIError |
Every NaceAPIError has:
| Attribute | Type | Description |
|---|---|---|
status | int | The HTTP status. |
message | str | None | The server's message, without the status or request id. |
type | str | None | Drex's error.type, such as rate_limit_error. Branch on it. |
code | str | None | The exact cause (error.code), sent by the document routes. |
issues | list[dict] | Where a request failed validation (error.issues), as {"path", "message"} entries. |
detail | Any | The document service's detail (error.detail), such as detail["errors"] on a refused body. |
server_retryable | bool | None | The server's error.retryable verdict, or None when it gave none. |
request_id | str | None | The x-request-id header, else the body's request_id. Include it in support requests. |
body | Any | The decoded JSON body, plain text, or None. |
headers | httpx.Headers | The response headers. |
endpoint | str | None | The method and URL, without credentials or query. |
idempotency_key | str | None | The Idempotency-Key the failed request carried, or None when it carried none. Document creates always carry one: after 503 job_pending, send the same body with this key to resume the job. |
| Class | Raised when | Extra attributes |
|---|---|---|
NaceRateLimitError | 429 | retry_after_ms: the server's requested wait, or None. |
NaceAPIResponseValidationError | A successful response whose body doesn't match the expected type. A NaceAPIError subclass with the response's own status. | field_path: the first bad field, such as answers.topic.confidence. |
NaceAPIConnectionError | No response: the connection failed or dropped. Also a ConnectionError. | |
NaceAPITimeoutError | An attempt exceeded timeout. A NaceAPIConnectionError and a TimeoutError. | timeout: the setting that ran out. |
NaceJobFailedError | jobs.wait saw a document job end failed or cancelled. | job: the DocumentJob as last read. |
NaceJobTimeoutError | jobs.wait ran out of time; the job keeps running. | job, and timeout in seconds. |
Invalid arguments the SDK checks itself raise a plain NaceError before anything is sent. A few errors aren't NaceErrors: ValueError for both transport and http_client, or for uploading bytes without a file_name; pydantic.ValidationError for an unknown field on a question or source object; and OSError when a file can't be read or written.
from nace_sdk import NaceAPIError, NaceClient, NaceRateLimitError, Noul
with NaceClient() as client:
try:
client.system_one(state="Hello", questions={"greeting": Noul(instructions="Is this a greeting?")})
except NaceRateLimitError as error:
print("slow down", error.retry_after_ms, error.request_id)
except NaceAPIError as error:
print(error.status, error.type, error.message, error.issues, error.request_id)The document routes raise the same classes as system_one. On them, error.code is the exact cause, and error.detail carries the document service's detail, such as detail["errors"] listing the fields a refused body got wrong. It's None when there is no detail, as for unsupported_file_type.
Status and code | Class | When |
|---|---|---|
| 402 | NaceInsufficientCreditError | A create while the balance isn't positive and no card pays for usage beyond it (insufficient_credit), or while the account has an unpaid invoice (payment_required, not retryable until the invoice is paid on the Billing page). |
| 404 | NaceNotFoundError | No such job or saved schema for this account (schema_not_found). |
409 idempotency_conflict | NaceConflictError | An Idempotency-Key reused with a different body. |
409 path_conflict | NaceConflictError | Another file already has an upload's path. Upload with on_conflict="new_version" or another path. |
409 session_busy, upload_expired | NaceConflictError | An upload session call that doesn't fit the session's state. See Upload sessions. |
404 workspace_not_found, file_not_found | NaceNotFoundError | No such workspace or file for this account. |
| 409 | NaceConflictError | download of Markdown content the document service failed to prepare. |
410 result_expired, job_unavailable | NaceNotFoundError | A job file or the request echo has expired, or the job was created before 3 October 2026 and its results are no longer available. An expired job itself still reads, with result set to None. |
422 invalid_source, invalid_request, unsupported_file_type, invalid_schema | NaceUnprocessableEntityError | The body or source was refused before any job was created or billed. |
| 429 | NaceRateLimitError | Over the document routes' own per-minute limit, separate from system_one's. |
503 document_billing_not_configured, job_pending | NaceInternalServerError | Document prices aren't configured, or the job's create isn't confirmed yet. Resume a job_pending with the error's idempotency_key. |
A failed multi-part upload raises NaceError, and jobs.wait raises NaceJobFailedError and NaceJobTimeoutError from the table above (see Wait for a job). See Errors for every code and the rate limits.
from nace_sdk import NaceConflictError, NaceUnprocessableEntityError
try:
file = client.documents.upload("invoice.pdf", path="invoices/invoice.pdf")
except NaceConflictError as error:
if error.code == "path_conflict":
file = client.documents.upload("invoice.pdf", path="invoices/invoice.pdf", on_conflict="new_version")
else:
raise
try:
client.documents.parse(file.as_source(), page_ranges=[{"start": 0, "end": 1}])
except NaceUnprocessableEntityError as error:
print(error.code, error.message, error.detail)See Errors and retries for what each error means.