Authentication
API keys, capability scopes, and the console session cookie.
Every request to Retidal authenticates one of two ways, depending on which API it targets.
Prerequisites
Section titled “Prerequisites”- A Retidal project and at least one project API key — see Quickstart to issue one.
- For Management API calls, a console session (log in at the console) or a key with the
metrics:read/metrics:writescope.
Ingestion & Decisioning API — API key
Section titled “Ingestion & Decisioning API — API key”Send your project API key in the X-API-Key request header:
curl -X POST https://api.retidal.com/api/v1/t \ -H "X-API-Key: tk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "eventName": "page_view", "visitorId": "vid_abc123" }'For pixel/beacon contexts where a header cannot be set (navigator.sendBeacon()), the key may instead be passed as the _ak (or key) query parameter. Prefer the header everywhere else — query strings are logged by intermediaries.
Capability scopes
Section titled “Capability scopes”Keys carry explicit capability scopes. A legacy key that has never been given scopes behaves as ingest:write only.
| Scope | Grants |
|---|---|
ingest:write / tracking:ingest |
POST /api/v1/t, POST /api/v1/identify |
decide / decision:call |
POST /api/v1/decide |
profile:write |
PUT /api/v1/profile |
coupon:redeem |
POST /api/v1/coupons/redeem |
coupon:read |
GET /api/v1/coupons/{code} |
metrics:read / metrics:write |
read/write metric definitions (Management API) |
mcp:events_read, mcp:events_write, mcp:links_write, mcp:attribution_read |
Retidal MCP server tools |
Scoped keys are issued from the console: POST /api/projects/{projectId}/api-keys (project role ADMIN+) returns the raw key exactly once — it cannot be retrieved again. PATCH /api/projects/{projectId}/api-keys/{id} updates a key’s scopes or name and invalidates its cache entry immediately.
A key is bound to exactly one project. Calling another project’s resource returns the same response shape as “not found” — resource existence is never leaked across tenants. On a representative route like GET /api/auth/me, a revoked or invalid key returns 401; on a scoped route like GET /api/admin/users, a valid key missing the required scope returns 403 (see each endpoint’s own page for its exact codes).
POST /api/v1/t and POST /internal/backfill/events accept any valid project API key with no additional scope gate.
Management API — session cookie
Section titled “Management API — session cookie”Most /api/projects/{projectId}/... routes authenticate with the console session cookie trackly_session, established by the console login flow, and authorize by the caller’s project role (OWNER / ADMIN / MEMBER, or VIEWER+ for read routes).
The Management API accepts a scoped X-API-Key on only two kinds of route, and each operation states explicitly which:
- Routes gated by a capability scope (
requireProjectRoleOrApiScope) — currently the metric-definitions endpoints, requiringmetrics:readormetrics:write. - A small number of read-only project endpoints that accept the project’s own legacy API key with no scope requirement.
Every other Management route — e.g. GET /api/tenant/profile — rejects an API key with 403 and requires the session cookie.
There is also an internal x-worker-token scheme (WorkerToken) used for worker-to-worker calls — it is not for customer use.
What this unlocks
Section titled “What this unlocks”Knowing which credential a call needs unlocks calling every endpoint correctly on the first
try. Sending data needs no scope at all — POST /api/v1/t and
POST /api/v1/identify declare only ApiKeyAuth, so a valid project key is sufficient, and
a scoped key carrying ingest:write or tracking:ingest works the same way.
Decisioning is the opposite: it is scope-gated, and a key
without the right scope is rejected even though it is valid.
Verify it worked
Section titled “Verify it worked”Confirm your key is valid — POST /api/v1/t has no scope gate beyond a valid project key, so this call only proves the key itself is good, not that it carries any particular scope:
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.retidal.com/api/v1/t \ -H "X-API-Key: $RETIDAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"eventName": "page_view", "visitorId": "vid_abc123"}'# expect: 200 (with ?sync=1) or 202 — either confirms the key is valid;# a 401 means it is notTo confirm a specific scope, call the scoped endpoint itself — e.g. POST /api/v1/decide — and expect 403 if the scope is missing — see the scope table above.
If it doesn’t work
Section titled “If it doesn’t work”If the POST /api/v1/t ingestion request above returns 401, work through Events not arriving. If a scoped endpoint like POST /api/v1/decide (decide, profile, coupon routes) returns 403, the key is valid but missing that scope — reissue or PATCH the key with the required scope. If a Management API call or console session fails instead, work through the health checklist.