# Errors

Source: https://docs.temprl.org/api/errors

> Simo API error format, HTTP status codes, error codes, which errors are retried and charged, and the matching SDK exceptions.

Every error returns JSON with a stable `code` and a human-readable `message`:

```json
{"error": {"code": "insufficient_balance", "message": "Your balance is $0. Contact us to add funds."}}
```

Branch on `code`, not on `message`; messages may change.

## Status codes

| Status | Code | Meaning | Charged | What to do |
|---|---|---|---|---|
| 400 | `invalid_json` | The body isn't a JSON object | No | Fix the body |
| 400 | `unknown_model` | `model` isn't a Simo model | No | Use `simo-1-pro` |
| 400 / 422 | varies | A question, `media` or option is invalid. The body names the bad field | No | Read the error detail |
| 401 | `unauthorized` | No `Authorization` header | No | Send `Bearer <key>` |
| 401 | `invalid_api_key` | The key is wrong or revoked | No | Check the key in the console |
| 402 | `insufficient_balance` | Your balance is $0 | No | Add credit |
| 403 | `account_suspended` | The account is suspended | No | Contact support |
| 403 | `model_not_available` | The model isn't open yet | No | Use `simo-1-pro` |
| 413 | `payload_too_large` | The body is over 25 MB | No | Send smaller or fewer images |
| 429 | | Too many requests | No | Retry with backoff |
| 502 | `upstream_unavailable` | The model is briefly unavailable | No | Retry with backoff |
| 503 / 504 | | The service is briefly unavailable | No | Retry with backoff |

Validation errors from the model (400 / 422) pass through with details naming the field, for example:

```json
{"detail": [{"loc": ["body", "questions", "team", "criteria"], "msg": "...", "type": "..."}]}
```

## Retrying

Retry 429, 502, 503 and 504 with exponential backoff, honoring `Retry-After` when present. Don't retry 4xx errors other than 429: the same request will fail again. The Python SDK does this for you (3 retries by default).

```javascript
async function ask(body, attempt = 0) {
  const res = await fetch("https://api.temprl.org/v1/ask", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SIMO_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if ([429, 502, 503, 504].includes(res.status) && attempt < 3) {
    const wait = Number(res.headers.get("retry-after")) * 1000 || 500 * 2 ** attempt;
    await new Promise((r) => setTimeout(r, wait));
    return ask(body, attempt + 1);
  }
  const data = await res.json();
  if (!res.ok) throw Object.assign(new Error(data.error?.message ?? res.statusText), { status: res.status, data });
  return data;
}
```

## SDK exceptions

Every failure raises a `SimoError` subclass. Each carries `status`, `code`, `detail` and `request_id`.

| Exception | When |
|---|---|
| `AuthError` | 401: missing, wrong or revoked key |
| `InsufficientBalance` | 402: no balance left (not charged) |
| `PermissionDenied` | 403: account suspended, or the model isn't open to you |
| `InvalidRequest` | 400 / 413 / 422: bad JSON or question, media not matching the state, over 25 MB |
| `ModelNotFound` | Unknown `model` |
| `QueueFull` | 429, after the automatic retries |
| `Unavailable` | 502 / 503 / 504, after the automatic retries (not charged) |
| `ServerError` | Any other 5xx |
| `ConnectionFailed` | The API is unreachable |
| `SimoTimeout` | No answer within `timeout` |

```python
from simoai import InsufficientBalance, InvalidRequest, SimoError

try:
    r = c.ask(state, questions)
except InsufficientBalance:
    top_up()                              # 402: not charged
except InvalidRequest as e:
    print(e.code, e.detail)               # names the bad field
except SimoError as e:
    print(e.status, e.code, e, e.request_id)
```
