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

# Errors

> Error envelope, status codes, and the full code reference

## Error envelope

Every error response uses the same JSON shape:

```json theme={null}
{
  "detail": {
    "error": {
      "type": "not_found_error",
      "message": "course not found",
      "code": "course_not_found"
    }
  }
}
```

| Field     | Required | Description                                                                              |
| --------- | -------- | ---------------------------------------------------------------------------------------- |
| `type`    | Yes      | High-level category. Stable across versions.                                             |
| `message` | Yes      | Human-readable description. Subject to change. Do NOT pattern-match it programmatically. |
| `code`    | No       | Machine-readable sub-code. Use this for branching logic.                                 |
| `param`   | No       | Field name for parameter-specific errors                                                 |

## Error types

| `type`                  | Meaning                                               |
| ----------------------- | ----------------------------------------------------- |
| `authentication_error`  | Missing, invalid, or expired API key                  |
| `invalid_request_error` | Malformed body, missing required field, wrong shape   |
| `not_found_error`       | Resource does not exist or is not owned by the caller |
| `rate_limit_error`      | Per-minute rate limit or regen cap                    |
| `quota_exceeded`        | Monthly generation quota exhausted                    |
| `server_error`          | Internal error                                        |

## Full code reference

| HTTP | type                    | code                        | Meaning                                                                 |
| ---- | ----------------------- | --------------------------- | ----------------------------------------------------------------------- |
| 400  | `invalid_request_error` | `idempotency_key_required`  | `Idempotency-Key` header missing on launch                              |
| 400  | `invalid_request_error` | (varies)                    | Malformed request body or invalid field                                 |
| 401  | `authentication_error`  | (none)                      | Missing or invalid API key                                              |
| 404  | `not_found_error`       | `course_not_found`          | Course ID not found or not owned by caller                              |
| 404  | `not_found_error`       | `preset_not_found`          | Preset ID not found or not owned by caller                              |
| 409  | `invalid_request_error` | `already_launched` (varies) | Conflict, e.g. course already launched or in wrong state for the action |
| 429  | `rate_limit_error`      | `retry_after_<n>`           | Per-minute rate limit exceeded. `<n>` is seconds to wait.               |
| 429  | `rate_limit_error`      | `regen_cap_reached`         | Account artifact regen cap exhausted; note still recorded               |
| 429  | `quota_exceeded`        | `quota_exceeded`            | Monthly generation quota exhausted                                      |
| 500  | `server_error`          | (none)                      | Internal server error; safe to retry                                    |

## Handling

### Always branch on `type` first, then `code`

`type` is stable, `code` is more specific. Pattern:

```python theme={null}
err = response.json()["detail"]["error"]

if err["type"] == "rate_limit_error":
    if err.get("code") == "regen_cap_reached":
        # Regen cap, do not retry, surface to user
        pass
    else:
        # Plain rate limit, honor Retry-After
        retry_after = response.headers.get("Retry-After", 60)
        time.sleep(int(retry_after))
        retry()
```

### Never pattern-match `message`

The text is subject to change. Use `type` and `code`.

### Retry policy

* `429 rate_limit_error` with `Retry-After`: wait the indicated seconds, then retry.
* `500 server_error`: exponential backoff with jitter, max 3 retries.
* `400`, `401`, `404`, `409`, `429 quota_exceeded`: do NOT retry. Surface to your caller.

### Privacy: 404 instead of 403

If you request a course ID that exists but belongs to a different account, the API returns `404 not_found_error / course_not_found`, not `403`. This is intentional, to avoid leaking the existence of resources you do not own.
