Appearance
Errors, pagination and limits
Error bodies
Two shapes exist. Branch on status first.
Application errors (auth, permissions, not found, conflicts, ...):
json
{ "error": "concurrent_limit" }error is a machine code; details is optional and is omitted when undefined. It can be structured data or a string. Some operations can return both application and validation errors with HTTP 400; inspect the body as well as the status.
Request validation errors from zod validators on body, query, params and headers use a different shape, with status 400:
json
{
"success": false,
"error": {
"name": "ZodError",
"issues": [
{ "code": "too_small", "minimum": 8, "type": "string", "inclusive": true, "path": ["idempotency-key"], "message": "String must contain at least 8 character(s)" }
]
}
}Other cases:
| Status | Body |
|---|---|
| 401 | {"error":"missing_token" | "invalid_token" | "invalid_audience"} |
| 403 | {"error":"forbidden","details":{"perm":"..."}} |
| 404 (unknown route) | {"error":"not_found"} |
| 429 | {"error":"rate_limited"} |
| 500 | {"error":"internal"} (details are only in server logs; quote x-request-id) |
Device-code endpoints under /v1/public/device return OAuth-style {"error":"..."} bodies instead.
Pagination
Several list endpoints (for example admin users and store lists) take:
| Query | Default | Notes |
|---|---|---|
q | none | Free-text filter where supported. |
page | 1 | 1-based. |
per_page | 25 | Maximum 100. |
sort | endpoint-specific | field:asc or field:desc. |
json
{ "data": [], "meta": { "page": 1, "per_page": 25, "total": 0 } }List shapes differ by operation:
| Style | Example | Important difference |
|---|---|---|
| Page and total | Staff users/store lists | meta includes total. |
| Page without total | User credit ledger | meta only has page/per_page. |
| Limit and offset | Storage browsing | Metadata includes prefix/limit/offset. |
| Cursor and limit | Runtime session lists | Follow the operation's cursor envelope. |
| Bare array | Public products | No data/meta wrapper. |
Do not use a universal pagination parser. Check each operation in the reference.
Rate limits
Fixed-window limits, counted per user (or client IP when unauthenticated). Exceeding one returns 429 rate_limited.
| Endpoint | Limit | Window | Keyed by |
|---|---|---|---|
POST /v1/public/device/code | 3 | 60 s | IP |
POST /v1/public/device/token | 30 | 60 s | IP (returns 400 slow_down) |
POST /v1/me/device/approve | 5 | 60 s | user |
POST /v1/me/support group | 5 | 1 h | user |
Waitlist signup (/v1/public) | 5 | 60 s | IP |
Email unsubscribe (/v1/public) | 30 | 60 s | IP |
Device polling also enforces its own interval (slow_down includes the required interval).
Maintenance mode
Operators can put the platform in degraded or lockdown mode. Clients then see:
http
HTTP/1.1 503 Service Unavailable
Retry-After: 30
{ "error": "panic_mode", "level": "lockdown", "reason": "..." }degraded: new checkouts andPOST /v1/me/sessionsare refused; everything else works.lockdown: all writes from public and/v1/meclients are refused.- Reads,
POST /v1/me/sessions/stop,/healthand staff routes stay available.
Retry after the Retry-After delay. This is distinct from 503 unavailable on session start, which means the runtime failed to create the session.