The error envelope
{
"error": "Human-readable message; safe to surface.",
"code": "machine_readable_slug",
"http_status": 403,
"...": "optional extras (e.g. current_tier, required_tier, ticker)"
}
How to use it
- Branch on
code. The slug list below is stable; the prose can change. - Surface
error. It is written for end-users — never raw stack traces or implementation detail. - Trust
http_status. It always matches the HTTP response status code; useful when your transport hides it. - Look for extras. 403
upgrade_requiredcarriescurrent_tierandrequired_tier; 404ticker_not_foundcarries the offendingticker.
Request ID for support
Every response — success or error — includes an X-Request-ID
header. When opening a support issue for a 500 internal_error,
include that ID and we can locate the trace within seconds. We retain
request logs for 30 days.
Complete code list
| HTTP | Slug | When |
|---|---|---|
| 400 | bad_request | Missing or malformed query parameter. The error field names the offending parameter. |
| 400 | invalid_email | Email field failed RFC 5322 validation. |
| 401 | missing_api_key | No X-API-Key header and no api_key query parameter. |
| 401 | invalid_api_key | Key not recognized. Could be revoked, never existed, or has a typo. |
| 401 | not_authenticated | Browser session cookie is missing or expired. Used on dashboard-facing endpoints (/auth/web/*, /billing/usage, /billing/invoices). |
| 403 | upgrade_required | Caller's plan is below the endpoint's minimum tier. Payload includes current_tier and required_tier. |
| 404 | ticker_not_found | Ticker is not in our coverage. Could be delisted, foreign ADR, or simply not yet ingested. |
| 404 | not_found | Generic 404 — usually an unknown plan slug in /billing/checkout/{plan}. |
| 409 | email_already_registered | An account with this email already exists. Sign in instead. |
| 429 | rate_limit_exceeded | Per-minute or daily quota hit. Retry after the window resets — the error field hints the next retry time. |
| 500 | internal_error | Server bug. Include the X-Request-ID header value when reporting. |
| 503 | internal_error | A dependency (DB, LemonSqueezy, etc.) is unavailable. Same shape as 500 but transient — retry with backoff. |
Retry semantics
- 4xx are caller errors — fix the request, don't retry blindly.
- 429 is throttling — back off (1s, then 5s, then 30s) and resume.
- 5xx are server-side and usually transient — exponential backoff with jitter, cap at 3 retries.
Idempotency. All GET endpoints are idempotent. POST
/auth/register is not — a retry will fail with 409 if the first call succeeded. Check whether you received the API key in the failed response before retrying.