Skip to content

Projects

The tenant boundary — project details, the legacy key pair, and scoped API keys.

A project is the isolation boundary for everything in Retidal: every event, campaign, short link, and email lives under exactly one projectId, and every management path is scoped to /api/projects/{projectId}/....

Project management is mostly a console session (trackly_session cookie) surface. Two read routes are the exception — they also accept the project’s own legacy API key:

Route Accepts a project API key?
GET /api/projects Yes — returns only that key’s own project.
GET /api/projects/{projectId} Yes — read-only.
Everything else on this page (update, regenerate keys, manage scoped keys) No — session cookie only.

See Authentication for the full model.

GET https://retidal.com/api/projects/{projectId}
PUT https://retidal.com/api/projects/{projectId}

GET returns the safe project shape to VIEWER/EDITOR callers, and additionally includes the project’s apiKey/apiSecret when the caller has role ADMIN or OWNER (or is that project’s own API key).

PUT (requires ADMIN) updates name, description, and domain — all optional, description/domain nullable.

Creating and deleting a project aren't API-key operations at all

POST /api/projects requires a tenant ADMIN role (not a project role) and a name + slug — it’s how a new project gets its apiKey/apiSecret minted in the first place, so naturally it can’t be authenticated with a project key that doesn’t exist yet. DELETE /api/projects/{projectId} requires project OWNER specifically — ADMIN is not enough. Both are console-only actions; there’s no script-friendly path around them.

POST /api/projects/{projectId}/regen-key

Requires ADMIN. Rotates the project’s apiKey/apiSecret — the pair used as X-API-Key on the ingestion API and (where a project key is accepted) on read-only console routes. The old pair is invalidated immediately with no overlap window; update every deployed integration before you rotate, not after.

This is a distinct credential from the scoped API keys below.

GET /api/projects/{projectId}/api-keys
POST /api/projects/{projectId}/api-keys (ADMIN)
PATCH /api/projects/{projectId}/api-keys/{id} (ADMIN)
DELETE /api/projects/{projectId}/api-keys/{id} (ADMIN)

A scoped key is a second, independent kind of credential: it carries an explicit list of capability scopes instead of a project role, and is used as X-API-Key (no secret) on the ingestion/decisioning surface and on the MCP server.

Create a scoped key
{ "name": "Backend event forwarder", "scopes": ["ingest:write", "decision:call"] }
namestringrequired

1–120 characters.

scopesarrayrequired

At least one of: ingest:write, tracking:ingest, decision:call, decision:read, profile:write, coupon:redeem, coupon:read, metrics:read, metrics:write, mcp:events_read, mcp:events_write, mcp:links_write, mcp:attribution_read.

The raw key is shown exactly once

POST returns the raw key string in the response body — that’s the only time it’s ever visible. GET always returns keys masked. Store it in your secret manager immediately; there’s no recovery endpoint.

PATCH updates scopes and/or name (at least one required) and invalidates the credential cache immediately, so a scope change takes effect on the next request rather than waiting out a TTL. DELETE /api/projects/{projectId}/api-keys/{id} revokes a key; it’s idempotent — revoking an already-revoked key still returns 200.

Project roles form a strict hierarchy: VIEWER < EDITOR < ADMIN < OWNER. A project API key (the legacy pair, not a scoped key) authenticates the two read routes above as if it were the project itself — it never satisfies an OWNER-only check (delete project, transfer ownership), which is why project deletion is console-only.