Skip to content

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.

json
{ "error": "API Key 无效" }
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.

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: [...] }.

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=1 errors[] and Analytics Engine.
  • enforce — reject events that fail catalog validation on POST /api/v1/t. Only property-schema violations (not envelope warnings like a missing visitorId) cause rejection. If every event in the batch is rejected, the response is 422.

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.

Trigger a known error and confirm the body matches this page’s contract:

bash
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 runs

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/t and getting a rejection (400, 401, 422, 503) → Events not arriving.
  • Calling POST /api/v1/decide or POST /api/v1/coupons/redeem and getting no decision, a 409 on a coupon, or a 429 from the 30 req/min limit → Decisioning not firing.
  • Following a short link (GET /s/{code}) that returns 410 or lands somewhere unexpected → Attribution looks wrong.
  • Any Management API call or console action failing — e.g. GET /api/tenant/profile returning 401/403 on a session or scoped key, 404 on a resource in another project, and the auth and registration limits above → the health checklist.