# Errors

Every API error uses one JSON envelope with a stable code and a readable message. The codes, their HTTP statuses, and what to do about each.

Errors from the public API are JSON with the same shape:

```json
{ "ok": false, "error": { "code": "invalid_input", "message": "u is required and must be at most 2048 bytes" } }
```

Successful payment calls answer `{ "ok": true, "data": { ... } }`; successful event calls answer `204` with no body. Branch on `error.code`, which is stable, and log `error.message`, which explains the cause in plain words and may be reworded.

## Codes

| Code | Status | Meaning | What to do |
|---|---|---|---|
| `invalid_input` | 400 | A field failed validation, or the JSON is malformed or has unknown fields | Fix the request; retrying it unchanged fails again |
| `invalid_input` | 413 | An events body is larger than 8 KB | Send fewer events per request |
| `unauthorized` | 401 | The key is missing, malformed, unknown or revoked | Check the `Authorization: Bearer mk_live_...` header |
| `forbidden` | 403 | The key lacks the endpoint's scope, or the event names another site | Use a key with the right scope for the right site |
| `not_found` | 404 | The site does not exist | Check the site id |
| `quota_exceeded` | 429 | The account has used its monthly events | Upgrade, or wait for `Retry-After` seconds (the start of the next UTC month) |
| `rate_limited` | 503 | The ingest queue is momentarily full | Retry after `Retry-After` seconds (2) |
| `internal` | 500 | Something failed on ProductLense's side | Retry with backoff; payments are idempotent by `payment_id` |

The API also defines `plan_required`, `provider_error` and `rate_limited` with status 429 for other surfaces; the two public endpoints do not return them today.

## Validation messages

Payment validation lists every bad field at once, sorted by field name:

```text
invalid payment input: amount: must be greater than 0; kind: must be one of charge, refund, chargeback
```

Events stop at the first bad event and name the rule it broke, for example `e must be pageview, $identify or match ^[a-zA-Z0-9_\- ]{1,64}$` or `hostname not allowed for this site`. The full lists are on [Events API](/docs/api/events) and [Payments API](/docs/api/payments).

## Retries

- Retry `500` and `503` with exponential backoff and jitter, honouring `Retry-After` when present.
- Do not retry `400`, `401`, `403` or `404` without changing the request.
- Payments can be retried safely: the same `payment_id` updates instead of duplicating. Events have no idempotency key, so a retried event after a timeout may be counted twice.
