# 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.