# Errors
Every failed API call returns the same JSON shape and a standard HTTP status. The SDK turns each into a Python exception, so your code handles a meaning, not a number.
## The error body [#the-error-body]
Errors follow RFC 9457 and are sent as `application/problem+json`:
```json
{
"type": "https://klarety.ai/problems/too-many-requests",
"title": "Too many requests",
"status": 429,
"detail": "slow down and retry in 12s",
"instance": "/v1/sql"
}
```
| Field | Meaning |
| --- | --- |
| `type` | A stable address for this kind of problem. Safe to branch on. |
| `title` | A short summary. |
| `status` | The HTTP status. |
| `detail` | What happened, in words you can show or log. |
| `instance` | The path that was called. |
| `credits` | When present: what the refused action would cost, so a person can agree to the price and ask again. |
| `limit` | When present: the plan limit that refused it, so a person can ask for it to be raised. |
## Status codes [#status-codes]
| Status | Meaning | Retry? |
| --- | --- | --- |
| `401` | Sign-in failed: no token, a wrong or expired token, or a rejected key. The body is the same for every cause. | No. Fix the key or get a new token. |
| `402` | The workspace cannot pay for this work. | After adding credits. |
| `403` | You are signed in, but this is out of scope for your key or role. | No. |
| `404` | Not found, or not yours to see. | No. |
| `409` | It changed since you read it, or is changing. | Read again, then retry. |
| `413` | The request body is too large (over 1 MiB). | No. Send less, or upload a file. |
| `422` | The request is not one you may make, or is invalid. | No. Fix the request. |
| `429` | Too many requests, or every slot is busy. Nothing ran. | Yes, after `Retry-After` seconds. |
| `500` | Something failed on our side. The body carries no detail. | Yes, a little later. |
| `503` | Temporarily unavailable, for example while Nexus starts. | Yes, after `Retry-After` when present. |
## How the SDK raises them [#how-the-sdk-raises-them]
All SDK exceptions inherit from `KlaretyError`, and you import them from `klarety`.
| Exception | Raised for |
| --- | --- |
| `AuthError` | `401`, and a key rejected at `Nexus.connect()`. |
| `RefusedError` | `403` and `422`. |
| `TableRuleError` | A change that breaks a rule of the table (a kind of `RefusedError`). Never resend it unchanged. |
| `NotFoundError` | `404`. |
| `ConflictError` | `409`. |
| `InsufficientCreditsError` | `402`. |
| `OverBudgetError` | `413`, `429`, and a query that would read more than your plan allows. |
| `RateLimitedError` | More calls per minute than your plan allows (a kind of `OverBudgetError`). |
| `ServiceError` | `500`, `503` and anything else on our side. |
| `AnswerNotReadyError` | No answer in the time allowed, often while Nexus starts (a kind of `ServiceError`). |
| `SparkStartingError` | Spark is starting for your workspace (a kind of `AnswerNotReadyError`). |
| `WaitingForOwnerError` | A change is held for the owner's OK. Carries `job_id`. Never resend it. |
### A careful handler [#a-careful-handler]
```python
from klarety import (
AuthError,
ConflictError,
InsufficientCreditsError,
KlaretyError,
Nexus,
OverBudgetError,
RefusedError,
ServiceError,
)
try:
with Nexus.connect() as nexus:
result = nexus.sql("SELECT COUNT(*) AS n FROM orders")
print(result.rows)
except AuthError:
print("Sign-in failed: check or replace the API key.")
except InsufficientCreditsError:
print("Out of credits.")
except OverBudgetError as over:
print(f"Over a plan limit or rate: {over}")
except RefusedError as refused:
print(f"Not allowed: {refused}")
except ConflictError:
print("Changed since read: read again, then retry.")
except ServiceError as failed:
print(f"Klarety could not answer, try later: {failed}")
except KlaretyError as other:
print(f"Klarety error: {other}")
```
Order matters: catch the more specific exceptions first.
## What the SDK retries for you [#what-the-sdk-retries-for-you]
You rarely need your own retry loop.
- **Reads** (`GET`) are retried up to three times on `502`, `503` and `504`, with a short backoff.
- **Busy and rate-limited** answers are waited out on any call, honouring `Retry-After`, for up to five minutes. Nothing ran, so this is safe.
- **SQL statements** carry an ID, so a resent statement runs once.
- **Other writes** (`POST`, `PUT`, `PATCH`, `DELETE`) are never retried, because replaying one could spend credits twice.
## Retrying with raw HTTP [#retrying-with-raw-http]
If you call the API directly:
1. Retry only on `429`, `503` and `500`.
2. Wait the `Retry-After` header's seconds when it is present; otherwise back off with random jitter.
3. Retry a write only if it is safe to run twice, or carries its own ID (`statement_id` on `/v1/sql`, `write_id` on row edits).
## Next steps [#next-steps]
- [Limits and rate limits](/docs/developers/limits) to see what triggers `429` and `413`.
- [Authentication](/docs/developers/authentication) for `401`.
- [API reference](/docs/developers/api-reference) for every endpoint.