# Errors

> **TL;DR for agents:** All errors: `{ statusCode: <int>, message: <string | string[]> }`. Validation errors use `string[]`. No leaked internals, no stack traces.

## Response shape

Every error response has the same minimal shape — both validation failures (400) and authorisation failures (401, 429). Internal errors (5xx) are never exposed with stack traces.

```js
// Single-message variant
{ "statusCode": 401, "message": "Invalid credentials" }

// Validation variant (class-validator)
{
  "statusCode": 400,
  "message": [
    "email must be an email",
    "property foo should not exist"
  ]
}
```

## Status codes you can encounter

- `400 Bad Request` — body validation failed. Inspect the returned `message[]`.
- `401 Unauthorized` — see [Authentication](/authentication). Always generic.
- `402 Payment Required` — `{"error":"quota_exceeded", ...}` from `/v1/messages` when the monthly quota is exhausted.
- `404 Not Found` — unknown endpoint, or `/v1/messages/:id` for a non-existent message: `{"statusCode":404,"message":"Message not found"}`.
- `410 Gone` — full-account confirmation/agreement link expired, or registration was rejected.
- `413 Payload Too Large` — request body exceeds endpoint cap (64 KB on `/v1/signup`, 1 MB elsewhere). Returned by nginx as **plain HTML**, not the JSON shape above.
- `429 Too Many Requests` — rate limit hit; see [Limits & Restrictions](/limits).

## What we deliberately do NOT return

- Distinct error messages for missing vs invalid vs revoked bearer (anti-enumeration).
- `X-RateLimit-*` headers (anti-calibration).
- Stack traces or internal field names from validation libraries beyond the message string.
- `Server: nginx/<version>` — `server_tokens off` on all public endpoints.
