HTTP status codes, rate limits, retry semantics
TL;DR Retry
408,429, and every5xx(including529 Overloaded) with exponential backoff, honoringretry-after-msthenretry-after. Do not retry400,401,403,404, or422— fix the request or the key. Both SDKs do this by default: 2 retries, 500 ms initial backoff doubling to a 5,000 ms cap, 25% jitter.
The table
| HTTP status | Meaning | Python exception (typesafe_sdk) |
JS error class (@typesafe-ai/sdk) |
Retryable by default? | Recommended action |
|---|---|---|---|---|---|
400 Bad Request |
The request was invalid. Not listed in raw/docs/api.md; both SDKs map it. | TypeSafeBadRequestError |
BadRequestError |
No | Fix the request. Inspect body / err.body. |
401 Unauthorized |
"Missing or invalid API key. Check the Authorization header." |
TypeSafeAuthenticationError |
AuthenticationError |
No | Check TYPESAFE_API_KEY and the Bearer prefix. Never retry — it will not succeed. |
403 Forbidden |
Access was denied. Not listed in raw/docs/api.md; both SDKs map it. | TypeSafePermissionDeniedError |
PermissionDeniedError |
No | The key is valid but lacks access (e.g. a model or feature not enabled). Contact sales@typesafe.ai. |
404 Not Found |
The resource was not found. Not listed in raw/docs/api.md; both SDKs map it. | TypeSafeNotFoundError |
NotFoundError |
No | Check the path (/v1/systemone, /v1/models) and TYPESAFE_BASE_URL. |
408 Request Timeout |
Not described in the docs; present only in the SDK retry defaults. | TypeSafeAPIError (no dedicated subclass; TypeSafeInternalServerError only applies at ≥ 500) |
APIError (base; fromResponse has no 408 branch) |
Yes | Retry with backoff. |
422 Unprocessable Entity |
"The request body failed validation — for example a missing required field or a malformed question. The body details the offending field." | TypeSafeUnprocessableEntityError |
UnprocessableEntityError |
No | Read detail[] (loc, msg, type) from the HTTPValidationError body and fix the field. |
429 Too Many Requests |
"You have exceeded your rate limit. Back off and retry after a short delay." Returned when either the 250,000 tok/s or the 1,200 rpm limit is exceeded. | TypeSafeRateLimitError (adds retry_after_ms) |
RateLimitError (adds retryAfterMs) |
Yes | Sleep for the server delay if present, else exponential backoff. Reduce concurrency. |
500–528, 530+ (5xx) |
"The server failed to process the request (5xx)." | TypeSafeInternalServerError |
InternalServerError |
Yes | Retry with backoff; escalate with the x-typesafe-request-id. |
529 Overloaded |
"TypeSafe is temporarily overloaded. Retry after a short delay." | TypeSafeInternalServerError (matches the status >= 500 branch) |
InternalServerError (matches the status >= 500 branch) |
Yes | Retry with backoff. Note neither SDK has a dedicated "overloaded" class. |
| Any other non-2xx | — | TypeSafeAPIError |
APIError |
No (unless in httpStatuses) |
Inspect status and body. |
| No HTTP response (DNS, TLS, connection closed) | "A request failed without an HTTP response." | TypeSafeAPIConnectionError (also a builtin ConnectionError) |
APIConnectionError |
Yes (api_connection_error / apiConnectionError, default true) |
Retry with backoff; check network/proxy. |
| Timeout | "A request exceeded its configured timeout." | TypeSafeAPITimeoutError (also a builtin TimeoutError; carries timeout) |
APITimeoutError (carries timeoutMs) |
Yes (api_timeout_error / apiTimeoutError, default true) |
Retry, or raise the client timeout (Python default 10.0 s; JS default 10,000 ms). |
| Caller cancellation | The caller cancelled through an AbortSignal. |
— (no equivalent class in raw/docs/sdk__python__api__exceptions.md) | APIUserAbortError |
No | Do not retry; the caller asked to stop. |
| 2xx with an invalid body | "A successful HTTP response whose body was missing or structurally invalid required data." | TypeSafeAPIResponseValidationError (carries field_path, e.g. answers.tone.confidence) |
— (no equivalent class in the JS SDK reference) | No | Log the field_path and the request id; report to support@typesafe.ai. |
Sources for the mapping: statuses and meanings from raw/docs/api.md and raw/docs/models.md; Python classes from raw/docs/sdk__python__api__exceptions.md and the STATUS_ERROR_TYPES map in raw/github/typesafe-sdk-python/src/typesafe_sdk/_core/errors.py; JS classes from the raw/docs/sdk__javascript__api__classes__*Error*.md pages and APIError.fromResponse in raw/github/typesafe-sdk-js/src/errors.ts; retryability from the default retry policies (below).
Exception hierarchies
Python (raw/docs/sdk__python__api__exceptions.md):
Exception
└── TypeSafeError
├── TypeSafeAPIError (status, body, headers, endpoint, request_id)
│ ├── TypeSafeBadRequestError 400
│ ├── TypeSafeAuthenticationError 401
│ ├── TypeSafePermissionDeniedError 403
│ ├── TypeSafeNotFoundError 404
│ ├── TypeSafeUnprocessableEntityError 422
│ ├── TypeSafeRateLimitError 429 (+ retry_after_ms)
│ ├── TypeSafeInternalServerError 5xx
│ └── TypeSafeAPIResponseValidationError (+ field_path)
└── TypeSafeAPIConnectionError (also ConnectionError)
└── TypeSafeAPITimeoutError (also TimeoutError, + timeout)
JavaScript (raw/docs/sdk__javascript__api__classes__*.md):
Error
└── TypeSafeError
├── APIError (status, body, headers, requestId; static fromResponse)
│ ├── BadRequestError 400
│ ├── AuthenticationError 401
│ ├── PermissionDeniedError 403
│ ├── NotFoundError 404
│ ├── UnprocessableEntityError 422
│ ├── RateLimitError 429 (+ retryAfterMs)
│ └── InternalServerError 5xx
├── APIConnectionError (default message "Connection error.")
│ └── APITimeoutError (+ timeoutMs)
└── APIUserAbortError (default message "Request was aborted.")
Asymmetries worth knowing: Python has TypeSafeAPIResponseValidationError with no JS counterpart; JS has APIUserAbortError with no Python counterpart. In Python, TypeSafeAPIError also exposes endpoint ("the request method and URL, without credentials, query parameters, or fragment, when available"), which JS does not.
Error metadata available on an API error
| Datum | Python | JavaScript | Notes |
|---|---|---|---|
| HTTP status | status |
status |
number |
| Body | body — "The server's JSON error body, plain response text, or None for an empty body." |
body — "Parsed JSON, response text, or undefined for an empty body." |
For 422, this is the HTTPValidationError object. |
| Headers | headers |
headers (Headers) |
|
| Request id | request_id (property) |
requestId |
Both read the x-typesafe-request-id response header; None/undefined if absent. Quote it in support requests. |
| Endpoint | endpoint |
— | Method and URL, credentials stripped. |
| Server retry delay | TypeSafeRateLimitError.retry_after_ms |
RateLimitError.retryAfterMs |
Milliseconds, or None/undefined if unavailable. |
Both SDKs derive an error message from the body, preferring error, then error.message, message, detail, detail.message, and finally a formatted join of validation errors as path: message entries; a raw body is truncated at 200 characters (raw/github/typesafe-sdk-js/src/errors.ts, raw/github/typesafe-sdk-python/src/typesafe_sdk/_core/errors.py).
Rate limits
| Limit | Value | Breach |
|---|---|---|
| Throughput | 250,000 tokens per second | 429 |
| Requests | 1,200 requests per minute | 429 |
"A request over either limit returns 429 Too Many Requests" (raw/docs/models.md). These limits are explicitly declared unstable: "the limits above can change without notice." Higher limits are available on custom and enterprise plans (sales@typesafe.ai). See Models, aliases, pricing, rate limits, context.
Guidance for direct HTTP callers (verbatim, raw/docs/api.md): "When you receive a 429 Too Many Requests or 529 Overloaded response, retry the request with exponential backoff instead of retrying immediately. Our client SDKs handle this automatically, so no extra handling is needed if you use one of our SDKs with its default retry policy."
Retry-after headers
| Header | Read by | Precedence | Interpretation |
|---|---|---|---|
retry-after-ms |
Both SDKs | Preferred | Milliseconds. |
retry-after |
Both SDKs | Fallback | Seconds, or an HTTP-date; a date is converted to a delay from now (clamped at 0). |
raw/docs/models.md only mentions retry-after; the SDK implementations read retry-after-ms first (raw/github/typesafe-sdk-js/src/retry.ts, raw/github/typesafe-sdk-python/src/typesafe_sdk/_core/errors.py). A negative or unparseable value yields no delay and the client falls back to backoff. In the JS SDK a server delay is only honored when it is <= maxRetryAfterMs (default 60,000 ms); otherwise backoff is used.
Default retry policies
| Setting | Python RetryPolicy |
JS RetryPolicy |
|---|---|---|
Max retries after the initial attempt (0 disables) |
max_retries = 2 |
maxRetries = 2 |
| First backoff delay | backoff_initial = 0.5 (seconds) |
backoffInitialMs = 500 |
| Backoff cap | backoff_max = 5.0 (seconds) |
backoffMaxMs = 5000 |
| Jitter (fraction subtracted) | backoff_jitter = 0.25 |
backoffJitter = 0.25 |
| Retryable statuses | http_statuses = {408, 429, *range(500, 600)} |
httpStatuses = {408, 429, 500–599} |
| Honor retry-after headers | respect_retry_after = True |
respectRetryAfter = true |
| Max honored server delay | — (not present) | maxRetryAfterMs = 60000 |
| Retry connection errors | api_connection_error = True |
apiConnectionError = true |
| Retry timeouts | api_timeout_error = True |
apiTimeoutError = true |
| Extra retryable exception types | exceptions, a set of exception types, default empty |
— |
| Custom predicate | predicate, a callable taking the raised exception and returning bool, default None |
— |
| Overall retry deadline | timeout: float | None = 30.0 |
— |
| Per-request HTTP timeout | DEFAULT_TIMEOUT = 10.0 seconds (raw/docs/sdk__python__api__constants.md) |
DEFAULT_TIMEOUT_MS = 10_000 (raw/github/typesafe-sdk-js/src/retry.ts) |
Both defaults cover 529 because it falls inside 500–599.
Delay formula used by the JS SDK (raw/github/typesafe-sdk-js/src/retry.ts), for a zero-based attempt index: if a server delay is present and within maxRetryAfterMs, use it; otherwise round(min(backoffInitialMs * 2**attempt, backoffMaxMs) * (1 - random() * backoffJitter)). That gives roughly 375–500 ms, then 750–1000 ms, then 1500–2000 ms.
Overriding the policy (Python, verbatim from raw/docs/sdk__python__api__retries.md)
from typesafe_sdk import RetryPolicy, TypeSafeClient
client = TypeSafeClient(
retry=RetryPolicy(
max_retries=3, timeout=10.0, http_statuses={429, 500, 502, 503, 504}
)
)
Note this override drops 408 and the rest of the 5xx range, including 529. Add them back if you want the documented overload behavior.
Recommended client behavior if you are not using an SDK
- Retry only
408,429, and5xx. Everything else is a permanent failure for that request. - Read
retry-after-msfirst, thenretry-after; cap any honored delay (60 s is the SDK default cap). - Otherwise back off exponentially from 500 ms, doubling to a 5 s cap, subtracting up to 25% jitter.
- Stop after 2–3 retries and surface the error with the
x-typesafe-request-id. - On
422, parsedetail[]and loglocjoined by.withmsg— this is exactly what both SDKs do to build the error message. See OpenAPI component schemas. - On
401, do not retry and do not rotate through keys automatically.
Version notes
- Statuses documented in raw/docs/api.md:
401,422,429,529only. The SDKs additionally map400,403,404, and the whole5xxrange;408appears only in the retry defaults. - The OpenAPI document (raw/site/openapi.json,
info.version: 0.2.0) declares only200and422responses per path — it is not an exhaustive status list. - Both SDK versions referenced here are 0.6.0.
Related
- HTTP API: POST /v1/systemone and GET /v1/models — the endpoints and the error table as documented
- OpenAPI component schemas —
HTTPValidationErrorandValidationErrorfield by field - Models, aliases, pricing, rate limits, context — the numeric rate limits and the dynamic-limits warning
- Python SDK retries, exceptions, constants — the Python retry and exception API in full
- JavaScript SDK error classes, RetryPolicy, RequestOptions — the JS error classes and
RetryPolicyin full - TYPESAFE_* environment variables across SDKs —
TYPESAFE_BASE_URL,TYPESAFE_LOG_LEVEL
Sources
- raw/docs/api.md (https://docs.typesafe.ai/api)
- raw/docs/models.md (https://docs.typesafe.ai/models)
- raw/docs/sdk__python__api__exceptions.md (https://docs.typesafe.ai/sdk/python/api/exceptions)
- raw/docs/sdk__python__api__retries.md (https://docs.typesafe.ai/sdk/python/api/retries)
- raw/docs/sdk__python__api__constants.md (https://docs.typesafe.ai/sdk/python/api/constants)
- raw/docs/sdk__javascript__api__classes__APIError.md and the sibling
*Error*pages (https://docs.typesafe.ai/sdk/javascript/api/classes/APIError) - raw/docs/sdk__javascript__api__interfaces__RetryPolicy.md (https://docs.typesafe.ai/sdk/javascript/api/interfaces/RetryPolicy)
- raw/github/typesafe-sdk-js/src/errors.ts, raw/github/typesafe-sdk-js/src/retry.ts
- raw/github/typesafe-sdk-python/src/typesafe_sdk/_core/errors.py