Every API error response uses the same envelope. The HTTP status line indicates the class; the error.code string gives you a stable identifier you can branch on in client code.
{
"error": {
"code": "insufficient_scope",
"message": "This endpoint requires the 'pixel:write' scope",
"details": { "required": "pixel:write", "available": ["pixel:read"] }
},
"meta": { "request_id": "req_abc123" }
}
Include the request_id in any support email — it lets us pull your exact request out of the server logs.
| HTTP | Code | Meaning & Fix |
|---|---|---|
| 401 | missing_authorization | No Authorization header. Add Authorization: Bearer YOUR_KEY. |
| 401 | invalid_authorization_format | Header was present but not in Bearer <key> form. |
| 401 | invalid_api_key | Key does not match any stored hash. Check for trailing whitespace, env vars from the wrong environment, or a pasted hash vs raw key. |
| 401 | api_key_revoked | Key’s revoked_at is set. Create a new one. |
| 401 | api_key_expired | Key’s expires_at has passed. Create a new one. |
| 403 | insufficient_scope | Key exists but lacks the scope required for this route. details.required names the scope; add it and rotate. |
| 400 | invalid_pipe | The pipe path segment was not in the allowlist. See Endpoints. |
| 400 | invalid_visitor | The /visitors/{id} id was empty after trimming. |
| 400 | invalid_body | Zod validation failed on POST body. details contains field-level errors. |
| 429 | rate_limit_exceeded | Caller exceeded 60 req/minute. Respect the Retry-After header. |
| 503 | ingest_unavailable | analytics ingest env var is missing on this server. Contact support. |
| 503 | identify_unavailable | Identify ingest env var is missing. Contact support. |
| 503 | analytics_error | Downstream analytics warehouse error. Retry after a short backoff. |
| 500 | internal_error | Unhandled server error. The request_id is your best lead for support. |
Retry-After seconds. Add jitter.POST /pixel/events.Every error response uses the same envelope: { "error": { "code", "message", "details" }, "meta": { "request_id" } }. The HTTP status indicates the class; error.code is the stable string you branch on; meta.request_id identifies the request in our server logs.
Retry 429 rate_limit_exceeded after the Retry-After header with added jitter. Retry 500 internal_error and 503 codes with exponential backoff (1s, 2s, 4s, 8s, 16s) and give up after 5 attempts. Never retry any other 4xx — the request is wrong and a retry will not fix it.
Three common causes: trailing whitespace on the pasted key, environment-mismatched keys (dev key deployed to prod), or pasting the stored hash instead of the raw key value. API keys are environment-scoped because the hash table is different per database.
The API key exists and authenticates but lacks the scope required for this endpoint. The details.required field names the scope (e.g., pixel:write). Open Settings › API Keys, create a new key with the additional scope explicitly checked, and rotate.
Make them idempotent on your side: client-assigned event_id values, dedup tables on the receiving end, or upsert semantics. Admaxxer’s ingest pipeline tolerates duplicate event_id values within a 24-hour window, so a safe retry will not double-count.
Include the meta.request_id from the failed response, the HTTP status, the error.code string, the timestamp (UTC), and a redacted copy of the request body. With request_id we can pull the exact request out of the server logs in seconds.
Overview · Auth · Endpoints · Rate Limits