> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mireye.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Structured error codes returned by /v1/* endpoints.

All errors use standard HTTP status codes plus a structured `detail`
object. The body shape is consistent:

```jsonc theme={null}
{
  "detail": {
    "error": "<machine-readable error code>",
    "message": "<human-readable explanation>",
    /* …optional context fields per error code */
  }
}
```

Every response except unhandled 500s carries `X-Request-ID`. You can
also send your own `X-Request-ID` request header — the server echoes it
and binds it to its log lines, which is the reliable way to correlate
even the responses that lack the header.

## Error code reference

| Error code                  | HTTP | Meaning                                                   | How to recover                                                         |
| --------------------------- | ---- | --------------------------------------------------------- | ---------------------------------------------------------------------- |
| `coord_out_of_bounds`       | 400  | `lat` or `lng` is outside the US envelope.                | Validate caller-side against `us_envelope` from `/v1/meta/fields`.     |
| `no_fields_requested`       | 400  | `/v1/fetch` called with neither `fields` nor `preset`.    | Provide at least one of the two.                                       |
| `fields_unknown`            | 400  | One or more requested field names are not in the catalog. | Hit `/v1/meta/fields`, pick valid names.                               |
| `fields_too_many`           | 400  | Resolved field set (post-preset-expansion) exceeds 50.    | Drop the preset, split into multiple calls, or remove explicit fields. |
| `ask_upstream_rate_limited` | 429  | `/v1/ask`: the LLM API rate-limited the call.             | Retry with backoff (`retryable: true`).                                |
| `ask_upstream_unreachable`  | 502  | `/v1/ask`: could not reach the LLM API.                   | Retry with backoff (`retryable: true`).                                |
| `ask_upstream_error`        | 502  | `/v1/ask`: the LLM API returned an error.                 | Retry only if `retryable: true` (upstream 5xx).                        |
| `ask_timeout`               | 504  | `/v1/ask` exceeded the 110 s end-to-end deadline.         | Retry; persistent timeouts mean a degraded upstream source.            |

Authentication and account-route failures (401 `auth_*`, 403, 429
`rate_limited`) use the same `detail` shape — the full table lives on the
[Authentication](/authentication#auth-error-codes) page.

Unhandled server exceptions return a plain `500 Internal Server Error`
(no structured `detail` body, and no `X-Request-ID` response header —
the framework default, since the error bypasses the header middleware).
The server-side log line still carries the request ID, so send your own
`X-Request-ID` request header if you need to correlate a 500.

## Worked examples

The validation examples below need a valid token — authentication runs
before request validation, so an unauthenticated call returns
`401 auth_missing` rather than the 400s shown.

### `coord_out_of_bounds`

```bash theme={null}
curl -s -i https://api.mireye.com/v1/fetch \
  -H "Authorization: Bearer $MIREYE_API_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"lat": 10.0, "lng": -74.0, "preset": "terrain"}'
```

```http theme={null}
HTTP/1.1 400 Bad Request
content-type: application/json
x-request-id: req_a1b2c3d4e5f6

{
  "detail": {
    "error": "coord_out_of_bounds",
    "message": "lat=10.0 outside US envelope [18.0, 72.0]"
  }
}
```

### `no_fields_requested`

```bash theme={null}
curl -s -i https://api.mireye.com/v1/fetch \
  -H "Authorization: Bearer $MIREYE_API_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"lat": 40.7128, "lng": -74.0060}'
```

```http theme={null}
HTTP/1.1 400 Bad Request

{
  "detail": {
    "error": "no_fields_requested",
    "message": "Provide `fields` and/or `preset`."
  }
}
```

### `fields_unknown`

```bash theme={null}
curl -s -i https://api.mireye.com/v1/fetch \
  -H "Authorization: Bearer $MIREYE_API_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"lat": 40.7128, "lng": -74.0060, "fields": ["elevation_m", "slope"]}'
```

```http theme={null}
HTTP/1.1 400 Bad Request

{
  "detail": {
    "error": "fields_unknown",
    "message": "Unknown field names: ['elevation_m', 'slope']",
    "fields_unknown": ["elevation_m", "slope"]
  }
}
```

Common gotchas:

* `elevation_m` → `elevation` (no unit suffix).
* `slope` → `slope_degrees` (unit suffix required to disambiguate from
  `aspect_degrees`).
* `floodplain` → `within_floodplain_polygon` (full predicate name).
* `power_plant` → `nearest_power_plant_name` (or `…_distance_m`, `…_primary_fuel`, …).

### `fields_too_many`

```bash theme={null}
# Hypothetical: 60-field request.
curl -s -i https://api.mireye.com/v1/fetch -d @big-request.json
```

```http theme={null}
HTTP/1.1 400 Bad Request

{
  "detail": {
    "error": "fields_too_many",
    "message": "Requested 60 fields; max is 50.",
    "max": 50,
    "requested": 60
  }
}
```

### Pydantic validation errors (FastAPI default)

If the request body fails schema validation (e.g. missing `lat`, wrong
type), FastAPI's default 422 response shape applies:

```http theme={null}
HTTP/1.1 422 Unprocessable Entity

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "lat"],
      "msg": "Field required",
      "input": {"lng": -74.0, "question": "..."}
    }
  ]
}
```

These are caller bugs (malformed request, not a domain-level error) and
should be caught in client-side serialization. The structured `loc` path
tells you which field needs fixing.

## What is NOT an error

* **Partial source failures** in `/v1/fetch` are **200 OK** with a
  populated `partial_failures` array. Failed sources never cause a
  non-200 response. This is the [honesty pattern](/api-reference/fetch#the-honesty-pattern-partial_failures).
* **Low-confidence `/v1/ask` answers** are **200 OK** with `confidence:
  "low"`. We still return the answer the synthesizer wrote; the
  confidence bucket tells the caller how much to trust it.

## Reporting issues

If you hit an unexplained `500`, please include:

* Your `X-Request-ID` request header value if you set one (plain 500s
  don't echo it back, but the server log line has it).
* The request body (sanitized).
* Approximate timestamp (UTC) and endpoint.

We'll find the corresponding log line in Fly logs.
