# Errors

layahost uses standard HTTP status codes and the same error bodies as the Jev API, so the Jev SDKs raise their usual exceptions.

## Error format

Most errors have a `detail` object with a machine-readable `error_type` and a human-readable `message`:

Response · 401

```json
{
  "detail": {
    "error_type": "authentication_error",
    "message": "Invalid or revoked API key."
  }
}
```

Validation errors (`422`) use the FastAPI format instead: `detail` is a list with one entry per problem, and `loc` is the path to the field. See [Validation errors](#validation).

Branch on the status code and `error_type`. Messages are for people and may change.

## Status codes

| Status | `error_type` | Meaning | What to do |
| --- | --- | --- | --- |
| `401` | `authentication_error` | The API key is missing, invalid or revoked. | Check the `Authorization` header and the key. Don't retry. |
| `402` | `insufficient_credits` | Your prepaid balance doesn't cover this request. | [Top up](https://layahost.com/billing). Don't retry until you have. |
| `404` | `not_found_error` | There is no endpoint at this path. | Check the URL: paths start with `/v1/`, with no `/api` prefix. |
| `405` | `invalid_request_error` | The endpoint exists but not for this HTTP method. | Use `POST` for `/v1/decide` and `/v1/systemone`, `GET` for the lists. |
| `422` | (list) | The request body is invalid. | Fix the request. Don't retry. |
| `429` | `rate_limit_error` | This key sent too many requests this minute. | Wait for the number of seconds in `retry-after`, then retry. |
| `500` | `api_error` | An unexpected error on our side. | Retry with backoff. If it persists, [contact support](mailto:support@layahost.com) with the request ID. |
| `503` | `api_error` | The inference backend is unavailable or failed. | Retry after `retry-after` seconds, with backoff. |
| `529` | `overloaded_error` | All inference capacity is busy. | Retry after `retry-after` seconds, with backoff. |

Requests rejected with `401`, `402`, `404`, `405`, `422`, `429`, `503` or `529` are not billed: if a request fails after it was charged, the charge is returned to your balance.

## Authentication errors

A missing header and a bad key get different messages:

Response · 401

```json
{
  "detail": {
    "error_type": "authentication_error",
    "message": "Missing API key. Send it as \"Authorization: Bearer <API_KEY>\"."
  }
}
```

See [Authentication](https://layahost.com/docs/authentication).

## Insufficient credits

layahost is prepaid. When your balance can't cover a request, you get `402` with `error_type` `insufficient_credits` and a message with a link to the Billing page. A request is charged as a whole, so a `/v1/systemone` call with 10 questions needs credit for 10 decisions. We email you before you run out; set the threshold in Settings → Data & alerts. See [Pricing & billing](https://layahost.com/docs/pricing).

## Unknown endpoints and methods

A path that doesn't exist gets `404`, and a known path with the wrong method gets `405`:

Response · 404

```json
{
  "detail": {
    "error_type": "not_found_error",
    "message": "Unknown endpoint. See https://layahost.com/docs."
  }
}
```

Response · 405

```json
{
  "detail": {
    "error_type": "invalid_request_error",
    "message": "Method not allowed for this endpoint."
  }
}
```

## Validation errors

A body that is missing fields or has values of the wrong kind gets `422`. Each entry has `loc` (`"body"` followed by the path to the field), `msg` and `type`:

Response · 422

```json
{
  "detail": [
    {
      "loc": ["body", "type"],
      "msg": "The type field is required when template is not present.",
      "type": "value_error"
    },
    {
      "loc": ["body", "question"],
      "msg": "The question field is required when template is not present.",
      "type": "value_error"
    }
  ]
}
```

Problems inside a `/v1/systemone` question also carry the offending `input` and a `ctx` object with details, and `type` names the rule that failed:

Response · 422

```json
{
  "detail": [
    {
      "type": "too_short",
      "loc": ["body", "questions", "q", "choice", "criteria"],
      "msg": "Dictionary should have at least 2 items after validation, not 1",
      "input": {"yes": null},
      "ctx": {
        "field_type": "Dictionary",
        "min_length": 2,
        "actual_length": 1
      }
    }
  ]
}
```

Option lists that don't fit the model's option budget (192 tokens for all names and descriptions of one question) also get `422`, on `["body", "questions", "<name>", "criteria"]` for `/v1/systemone` and on `["body", "options"]` for `/v1/decide`. See [Limits](https://layahost.com/docs/systemone#limits).

## Rate limit errors

Each key may send 600 requests per minute. Over that, you get `429` with a `retry-after` header: the number of seconds until the window resets.

Response · 429

```http
HTTP/1.1 429 Too Many Requests
content-type: application/json
retry-after: 38
x-request-id: req_0t9rn7vlp9c6tl5rgaeetoc8
x-typesafe-request-id: req_0t9rn7vlp9c6tl5rgaeetoc8

{
  "detail": {
    "error_type": "rate_limit_error",
    "message": "Rate limit of 600 requests per minute exceeded."
  }
}
```

See [Rate limits](https://layahost.com/docs/rate-limits).

## Server and capacity errors

`503` (`api_error`) means the inference backend was unavailable or failed to answer. `529` (`overloaded_error`, message “Inference capacity is saturated, retry shortly.”) means all inference capacity is busy. Both are temporary and both come with `retry-after: 1`:

Response · 503

```http
HTTP/1.1 503 Service Unavailable
content-type: application/json
retry-after: 1
x-request-id: req_guglin9qhthimpisgz5umdhp
x-typesafe-request-id: req_guglin9qhthimpisgz5umdhp

{
  "detail": {
    "error_type": "api_error",
    "message": "Inference backend is unavailable, retry shortly."
  }
}
```

An unexpected error on our side gets `500` with `error_type` `api_error`, in the same format.

## Retries

- Retry `429`, `503` and `529` after the number of seconds in `retry-after`, and back off exponentially if the error repeats. Retry `500` with backoff.
- Don't retry `401`, `402`, `404`, `405` or `422`: the same request will fail again.
- Retrying `429`, `503` and `529` is safe. Those requests were not billed and changed nothing.

The Jev SDKs do this for you. By default, both retry `408`, `429` and every `5xx` status up to twice, and honour `retry-after`. There is a fetch-based retry loop in [Code examples](https://layahost.com/docs/code-examples).

## SDK exceptions

| Status | Python (`typesafe_sdk`) | JavaScript (`@typesafe-ai/sdk`) |
| --- | --- | --- |
| `401` | `TypeSafeAuthenticationError` | `AuthenticationError` |
| `402`, `405` | `TypeSafeAPIError` | `APIError` |
| `404` | `TypeSafeNotFoundError` | `NotFoundError` |
| `422` | `TypeSafeUnprocessableEntityError` | `UnprocessableEntityError` |
| `429` | `TypeSafeRateLimitError` | `RateLimitError` |
| `500`, `503`, `529` | `TypeSafeInternalServerError` | `InternalServerError` |

Every class in the table extends the SDK's base API error, `TypeSafeAPIError` or `APIError`, which has the HTTP `status`, the parsed `body` and the request ID (`request_id` in Python, `requestId` in JavaScript).

## Request IDs

Every response from `/v1/`, successful or not, carries a request ID such as `req_3rmqhpttolpzez0quj0bt42x` in two headers with the same value: `x-request-id`, and `x-typesafe-request-id` for the Jev SDKs. Successful decisions also include it in the body, as `request_id` on `/v1/decide` and `meta.request_id` on `/v1/systemone`.

Requests that reach the model are listed in [Logs](https://layahost.com/logs) under the same ID. Include the ID when you [contact support](mailto:support@layahost.com).
