Errors
Every uniqOS error response uses one envelope, so you can handle them uniformly across endpoints. The SDKs map each one to a typed error class.
The error envelope
A non-2xx response carries a single error object:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 12 seconds.",
"type": "rate_limit",
"request_id": "req_...",
"details": null,
"documentation_url": "https://docs.uniqos.ai/errors"
}
}
code— the machine-readable error code. This is authoritative: two errors can share an HTTP status but differ bycode(see rate-limit vs quota below).message— a human-readable description.type— the error family.request_id— include this when contacting support; it traces the exact call.details— optional structured context (e.g. the offending fields on a validation error).documentation_url— a pointer to the relevant docs.
The request_id is on every response, success or error. In the SDKs it is err.requestId
(TypeScript) / err.request_id (Python) on a thrown error, and available via
.withResponse() on a successful call.
Status codes
| HTTP | code | Meaning |
|---|---|---|
400 / 422 | validation_error | The request payload was invalid; details lists the offending fields. |
401 | authentication_error | Missing, malformed, or expired credentials. |
402 | payment_required | Billing issue — e.g. an unpaid invoice or suspended subscription. |
403 | permission_denied | Authenticated, but not allowed to perform the operation. |
404 | not_found | The addressed resource does not exist. |
409 | conflict | Conflict with current state (e.g. a duplicate). |
429 | rate_limit_exceeded | Too many requests. Retryable — honor Retry-After. |
429 | quota_exhausted | Plan quota used up. Not retryable — upgrade or wait. |
4xx | guardrail_blocked | A personality/engine guardrail blocked the request. |
500 | internal_error | An unexpected server error. |
502 | upstream_llm_error | An upstream LLM provider failed. |
503 | service_unavailable | Temporarily unavailable. |
504 | turn_timeout | The turn exceeded the engine budget and was aborted. Not billed. |
Two 429s, told apart by code
A 429 can mean two very different things, distinguished by code:
rate_limit_exceeded— you are going too fast. It is retryable; the response carries aRetry-Afterheader, which the SDKs surface asretryAfterSeconds(TypeScript) /retry_after_seconds(Python) and honor automatically on retry.quota_exhausted— your plan's allotment is used up. The SDKs never retry it; upgrade your tier or wait for the next cycle.
504 turn_timeout is not auto-retried
When a turn exceeds the server's engine budget, uniqOS aborts it and returns
504 turn_timeout. That aborted turn is not billed — no turn is consumed and no
active relationship is counted (this is the server-side guarantee in ADR-0009).
The SDKs do not auto-retry a 504, on purpose. A budget timeout means the turn was
already too slow; a blind retry risks another long turn — and, if a client gives up while
the server is still finishing, it can leave the original turn billable and start a second
one. To keep that from happening, the SDKs also default their request timeout above the
server's turn ceiling (~75s), so the client receives the server's clean, unbilled 504
rather than abandoning the turn early. Retrying a 504 is therefore your explicit choice,
never the default.
The SDKs map this to a TurnTimeoutError. See the
SDK error handling sections.
What the SDKs retry
Both SDKs retry network errors, 429 rate_limit_exceeded, and 500 / 502 /
503 with exponential backoff and jitter, honoring Retry-After. They never retry
429 quota_exhausted, 401, 403, other 4xx, or 504 turn_timeout.