Errors & Status Codes
Error envelope and the status codes Retidal returns.
Error responses carry a JSON body with an error message. Some endpoints add fields such as retryAfter (seconds), affectedIndices, message, or a per-item errors[] array.
{ "error": "API Key 无效" }Prerequisites
Section titled “Prerequisites”- Familiarity with which API you called — Ingestion & Decisioning or Management — since the same code can mean different things on each.
Status codes
Section titled “Status codes”| Code | Meaning |
|---|---|
200 |
Success (synchronous). POST /api/v1/t returns 200 only with ?sync=1 (or header X-Trackly-Sync: 1). |
202 |
Accepted — validated and dispatched; persistence completes asynchronously. The default for POST /api/v1/t and POST /api/v1/identify. Do not treat 202 as proof of persistence — only a sync=1 200 on POST /api/v1/t with processed: true guarantees it. |
302 |
Short link resolved (GET /s/{code}) — always a 302 redirect, never 301, so the redirect is never cached and attribution is never lost. |
400 |
Malformed JSON, missing required fields, batch-size overflow (POST /api/v1/t), or (identify/profile) a traits payload over 8 KiB / nested past 2 levels (POST /api/v1/identify, PUT /api/v1/profile). |
401 |
Missing or invalid API key (POST /api/v1/t) or session (GET /api/auth/me). |
403 |
Authenticated but missing the required capability scope (POST /api/v1/decide) or project role (GET /api/admin/users). |
404 |
Resource not found (also returned for cross-tenant access, by design — resource existence is never leaked across projects) — e.g. GET /api/v1/coupons/{code}, GET /api/projects/{projectId}. |
409 |
Conflict — a coupon that is not in a redeemable state (already_redeemed, expired, revoked, pool_not_ready) on POST /api/v1/coupons/redeem. |
410 |
Short link is paused or expired (GET /s/{code}). |
422 |
Validation rejected the request. For ingestion (POST /api/v1/t) in enforce mode with every event in the batch rejected, the body includes errors[] and enforceRejected. |
429 |
Rate limited. Respect retryAfter (ingestion) or retryAfterSeconds (Management API). POST /api/v1/decide and POST /api/v1/coupons/redeem are limited to 30 req/min per (project, IP); PUT /api/v1/profile to 60 req/min per (project, IP). |
503 |
A dependency (queue / selector lookup) was transiently unavailable (POST /api/v1/t). Retry after retryAfter. |
Ingestion response contract
Section titled “Ingestion response contract”POST /api/v1/t is the one endpoint where the status code alone does not tell you what happened — read the body.
| Mode | Trigger | Status | Body |
|---|---|---|---|
Async (default, POST /api/v1/t) |
no sync param |
202 |
{ accepted, failed, queued, processed: false, invalid? } — events were validated and handed to background processing; persistence is not proven. |
Sync (POST /api/v1/t) |
?sync=1 or header X-Trackly-Sync: 1 |
200 |
{ accepted, failed, queued, processed: true, errors?, enforceRejected? } — processed: true is the only proof non-metering events reached D1 and Analytics Engine. Use this mode for integration checks and health checks only; keep production traffic on the async default. |
Enforce, all rejected (POST /api/v1/t) |
validation mode enforce, every event fails catalog validation, sync path |
422 |
{ accepted: 0, failed, queued: 0, processed: true, enforceRejected, errors: [{ index, eventName, errors: [{ field, rule, expected, actual, message }] }] } |
Queue/selector unavailable (POST /api/v1/t) |
metering event present and the queue binding is missing or the send failed, or the metering-selector lookup fails | 503 |
{ error: "QUEUE_UNAVAILABLE", retryAfter: 60 } or { error: "SELECTOR_LOOKUP_FAILED", retryAfter: 30 } |
POST /internal/backfill/events is always synchronous (processed: true) regardless of query params, and requires every metering (conversion) event to carry a client-supplied eventId; a missing one fails the whole request with 400 { error: "MISSING_EVENT_ID", affectedIndices: [...] }.
Ingestion validation modes
Section titled “Ingestion validation modes”POST /api/v1/t honors a per-key or per-project validation mode (resolution order: key-level validationMode > project settings.ingestValidation.mode > warn):
off— accept everything, no catalog validation.warn(default) — accept, but surface warnings via?sync=1errors[]and Analytics Engine.enforce— reject events that fail catalog validation onPOST /api/v1/t. Only property-schema violations (not envelope warnings like a missingvisitorId) cause rejection. If every event in the batch is rejected, the response is422.
What this unlocks
Section titled “What this unlocks”Knowing each code’s exact meaning unlocks correctly handling retries and validation failures in your integration instead of treating every non-2xx the same — see Send data for the event contract these codes are validated against.
Verify it worked
Section titled “Verify it worked”Trigger a known error and confirm the body matches this page’s contract:
curl -s -X POST https://api.retidal.com/api/v1/t \ -H "X-API-Key: invalid" \ -H "Content-Type: application/json" \ -d '{}'# expect: 401 { "error": "..." } — an invalid key is rejected before validation runsIf it doesn’t work
Section titled “If it doesn’t work”The same status code means different things depending on which call produced it — on the
Management API, POST /api/auth/login sign-in returns 429 when rate limited, and
POST /api/auth/register registration returns 503 when a dependency is unavailable,
neither of which is an ingestion or decisioning fault. So pick the row by what you were
doing, not by the number:
- Sending events to
POST /api/v1/tand getting a rejection (400,401,422,503) → Events not arriving. - Calling
POST /api/v1/decideorPOST /api/v1/coupons/redeemand getting no decision, a409on a coupon, or a429from the 30 req/min limit → Decisioning not firing. - Following a short link (
GET /s/{code}) that returns410or lands somewhere unexpected → Attribution looks wrong. - Any Management API call or console action failing — e.g.
GET /api/tenant/profilereturning401/403on a session or scoped key,404on a resource in another project, and the auth and registration limits above → the health checklist.