{/* Metadata lives in ./layout.tsx */}

# Errors

Machine-readable codes. Request errors abort the whole call; item errors leave the rest of the batch free to accept. {{ className: 'lead' }}

---

## Request errors

| Code | HTTP | Meaning |
| --- | --- | --- |
| `unauthorized` | 401 | Missing, invalid, or revoked key |
| `forbidden_scope` | 403 | Valid key, wrong kind or missing scope (`pk_plp_` on a write route) |
| `rate_limited` | 429 | Over limit; see `Retry-After` + `X-RateLimit-*` |
| `malformed_json` | 400 | Body is not valid JSON |
| `batch_too_large` | 400 | More than 250 orders (or spend batch cap) |
| `payload_too_large` | 413 | Body over 4 MB |
| `unknown_source` | 409 | Orders before `PUT /v1/source` |
| `source_locked` | 409 | Currency/timezone change after first accepted order |

Error body shape: `{ "error": string, "code"?: string }`.

---

## Item errors

Returned inside the batch result; the HTTP status is still `200` when the request itself is well-formed.

| Code | Meaning |
| --- | --- |
| `money_must_be_string` | JSON number in a money field |
| `money_invalid` / `money_scale` | Regex or scale violation |
| `sum_mismatch` | Identity failure; detail includes computed vs supplied |
| `rounding_adjustment_too_large` | Beyond currency cash-rounding bound |
| `refunds_exceed_order` | Sum of refund totals > order total |
| `refund_split_exceeds_total` | tax + shipping portions > refund total |
| `refund_line_exceeds_order` | Refund line qty > order line qty |
| `timestamp_missing_offset` | Bare local timestamp |
| `timestamp_in_future` | More than 48h ahead of server clock |
| `duplicate_in_batch` | Same natural key twice with differing payloads |
| `duplicate_line_id` | `line_id` not unique within the order |
| `pii_rejected` | `customer.id` looks like PII |
| `unknown_field` | Request field not in the v1 schema |
| `expense_invalid` | Expense validation failure |

`superseded` is an **outcome**, not an error: older `updated_at` acknowledged as a no-op.
