> ## Documentation Index
> Fetch the complete documentation index at: https://metacognition-fdc534de-master.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors and retries

> Exception types, which status codes retry automatically, and what to log before you open a ticket.

When something fails, start with the exception class. If you need a symptom-based guide, use [Troubleshooting](/troubleshooting).

All Tex exceptions inherit from `tex.TexError`. Most apps catch one of these:

| Class                                                   | Status | Inherits                          | When                                     |
| ------------------------------------------------------- | ------ | --------------------------------- | ---------------------------------------- |
| [`BadRequestError`](#badrequesterror)                   | 400    | `APIStatusError`                  | Malformed payload                        |
| [`AuthenticationError`](#authenticationerror)           | 401    | `APIStatusError`                  | Bad / expired key                        |
| [`PermissionDeniedError`](#permissiondeniederror)       | 403    | `APIStatusError`                  | Key revoked or lacks scope               |
| [`NotFoundError`](#notfounderror)                       | 404    | `APIStatusError`                  | Unknown resource                         |
| [`ConflictError`](#conflicterror)                       | 409    | `APIStatusError`                  | Resource exists (e.g. duplicate org\_id) |
| [`UnprocessableEntityError`](#unprocessableentityerror) | 422    | `APIStatusError`                  | Pydantic validation failure              |
| [`RateLimitError`](#ratelimiterror)                     | 429    | `APIStatusError`                  | Daily quota exceeded                     |
| [`InternalServerError`](#internalservererror)           | 5xx    | `APIStatusError`                  | Our problem; SDK retried                 |
| [`APITimeoutError`](#apitimeouterror)                   | —      | `APIConnectionError` → `APIError` | Network or server too slow               |
| [`APIConnectionError`](#apiconnectionerror)             | —      | `APIError`                        | DNS, TLS, connection reset               |
| \[`APIResponseValidationError`]                         | -      | `APIError`                        | Server returned an unexpected response   |

`TexHTTPError` (alias of `APIStatusError`) and `TexAuthError` (alias of `AuthenticationError`) are kept for backward compatibility.

## Common fields

```python theme={null}
# All TexError subclasses
e.message         # human-readable

# APIStatusError subclasses (everything with an HTTP status)
e.status_code     # int
e.request_id      # X-Correlation-ID; include this in support tickets
e.details         # dict; server JSON, may include field errors
e.response_text   # raw response body, capped at 2KB
```

<Warning>
  `APITimeoutError` and `APIConnectionError` are **network errors**, not HTTP errors. They do not have `status_code`, `request_id`, `details`, or `response_text` because the request never produced a response. Catch them separately.
</Warning>

```python theme={null}
from tex import Tex, RateLimitError, AuthenticationError, APITimeoutError, BadRequestError

try:
    tex.recall(q=q, session_id=sid)
except RateLimitError:
    return cached_or_fallback()
except AuthenticationError:
    page_oncall("tex auth broken")
    raise
except APITimeoutError:
    return degraded_no_memory_response()
except BadRequestError as e:
    log.warning("bad payload: %s", e.details)
    raise
```

## Per-class details

### `BadRequestError`

Raised when a payload is malformed. Common causes:

* Missing required field on a turn (e.g. no `text`)
* Invalid `mode` value on `recall`
* Invalid `session_id` value. It must be a string.

`e.details` includes a Pydantic-style `loc` list that points to the bad field.

### `AuthenticationError`

Status 401. The SDK already tried one JWT refresh before raising.

```python theme={null}
try:
    tex.recall(q=q, session_id=sid)
except AuthenticationError as e:
    if "Invalid API key" in e.message:
        # Bad API key
        rotate_key_alarm()
    else:
        # JWT refresh failed, likely revoked
        notify_user("please log in again")
```

### `PermissionDeniedError`

Status 403. The credential is valid but lacks scope. This mostly matters for scoped keys. Default keys should not hit this.

### `NotFoundError`

Status 404. You referenced something that does not exist. This is often a stale `key_id` on `DELETE /me/api-keys/{id}`.

### `UnprocessableEntityError`

Status 422. FastAPI validation rejected the payload. The SDK builds payloads for you, so this usually means an argument has the wrong type.

### `RateLimitError`

Status 429. The SDK retries on `429` like other transient codes (with exponential backoff and `Retry-After` honored), so by the time you see this exception the SDK has already exhausted retries.

For **daily-quota 429s**, retries will not help until midnight UTC. Set `max_retries=0` on paths where you would rather fail fast:

```python theme={null}
try:
    tex.recall(q=q, session_id=sid)
except RateLimitError as e:
    return generate_without_memory(q)   # graceful degradation
```

The `e.details` payload tells you which cap was exceeded (`tokens_in_daily` or `tokens_out_daily`) and when it resets:

```python theme={null}
e.details
# {"error":"quota_exceeded","kind":"tokens_in_daily","used":1000123,"limit":1000000,"period":"day_utc","period_start":"2026-05-08T00:00:00+00:00","message":"…"}
```

### `InternalServerError`

Status 5xx. The SDK already tried the request again with exponential backoff. If you still see this, file a ticket with `e.request_id`.

### `APITimeoutError`

The request did not return within `timeout`. The SDK retries timeouts. If every attempt fails, it raises `APITimeoutError`.

```python theme={null}
try:
    hits = tex.recall(q=q, session_id=sid)
except APITimeoutError:
    hits = None   # fall back to no-memory generation
```

### `APIConnectionError`

DNS, TLS, or socket-level failure. The retry behavior is the same as `APITimeoutError`. If you see this in production, check your egress proxy or firewall.

## Built-in retries

The SDK retries automatically on:

* Status codes: `408`, `429`, `500`, `502`, `503`, `504`
* `httpx.TimeoutException`
* `httpx.HTTPError` (network)

Default: **2 retries** with exponential backoff (0.5s, 1s). Override:

```python theme={null}
tex = Tex(api_key=..., max_retries=5)
```

The SDK honors `Retry-After`. If the server says wait 3 seconds, the SDK waits at least 3 seconds.

<Note>
  Quota `429`s retry like other `429`s. The retry will still fail if you are over the cap. Set `max_retries=0` on quota-sensitive paths if you want to fail faster.
</Note>

## Idempotency

`remember` is idempotent because Tex deduplicates turns by hash. `recall` and `usage.*` are read-only. It is safe to retry any of them.

<Card title="Next: REST API" icon="code" href="/api-reference/overview">
  Direct HTTP integration without the SDK.
</Card>
