Authentication
Authenticate with the Reten API using API keys or JWT tokens.
All endpoints require authentication unless marked as Public. Reten supports two authentication methods.
API Keys (Recommended for Integrations)
API keys provide stateless authentication ideal for integrations, CI/CD pipelines, and service-to-service communication.
Key Characteristics
- Restricted permissions — each key carries an explicit list of permissions chosen by its owner (a subset of the owner's current permissions, editable by the owner at any time); on every request, the effective permissions are the intersection of that list with the owner's current permissions
- Tenant-bound — each key is associated with a specific tenant (no
x-tenant-idheader needed); its scope decides whether it is also bound to one project (see Project Context) - Secure storage — only a SHA-256 hash is stored; the plaintext key is shown once at creation
- Optional expiration — keys can have an expiry date after which they are automatically rejected
Creating an API Key
Create an API key via POST /api/api-keys with the desired permissions. The plaintext key is returned once and must be stored securely.
Request
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable name for the key |
permissions | string[] | Yes | Permissions to grant (subset of your current permissions; editable later with PUT /api/api-keys/:id/permissions) |
expiresAt | ISO 8601 | No | Expiration date |
scope | string | Yes | project (the key is bound to one project) or tenant (the key serves every project of the tenant and sends x-project-id where the endpoint requires it or to narrow a request to one project) — see Create API Key |
projectId | UUID | With scope: "project" | Project the key belongs to |
curl -X POST https://api.reten.ai/api/api-keys \
-H "Authorization: Bearer <token>" \
-H "x-tenant-id: <tenant-id>" \
-H "Content-Type: application/json" \
-d '{
"name": "Integration Key",
"permissions": ["activity:view", "activity:create"],
"scope": "project",
"projectId": "<project-id>"
}'import axios from 'axios';
const response = await axios.post(
'https://api.reten.ai/api/api-keys',
{
name: 'Integration Key',
permissions: ['activity:view', 'activity:create'],
scope: 'project',
projectId: '<project-id>',
},
{
headers: {
Authorization: 'Bearer <token>',
'x-tenant-id': '<tenant-id>',
},
}
);
// Save this key securely — it is only shown once
const apiKey = response.data.plaintextKey; // rtn_sk_...Using an API Key
Include the key in the x-api-key header for all requests:
x-api-key: rtn_sk_<key>When using an API key, the tenant is resolved from the key — no x-tenant-id header is needed. Whether the key also sends x-project-id depends on its scope — see Project Context.
Email + Password (JWT)
JWT tokens are used for interactive user sessions. The flow involves a login request that returns an access token and sets a refresh token cookie.
Auth Flow
- Call
POST /api/auth/loginwith email and password - Receive an access token in the response body; a refresh token is set as an HTTP-only cookie
- Include the access token in all subsequent requests via the
Authorizationheader - When the access token expires (15 min), call
POST /api/auth/refresh— the server validates the refresh cookie and issues a new access token - On logout, the refresh cookie is cleared
Token Lifetimes
| Token | Lifetime | Transport |
|---|---|---|
| Access token | 15 minutes | Authorization: Bearer header |
| Refresh token | 7 days | HTTP-only cookie |
Login Request
| Field | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Username or email address |
password | string | Yes | User password (min 8 characters) |
curl -X POST https://api.reten.ai/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"identifier": "admin@example.com",
"password": "SecurePass123!"
}'import axios from 'axios';
const response = await axios.post(
'https://api.reten.ai/api/auth/login',
{
identifier: 'admin@example.com',
password: 'SecurePass123!',
}
);
const { accessToken, user } = response.data;Response 201 Created
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "jdoe",
"email": "jdoe@example.com",
"firstName": "Jane",
"lastName": "Doe",
"roles": ["viewer"],
"permissions": [
"project:view",
"user:view",
"activity:view",
"commerce-lifecycle-snapshot:view"
],
"tenantIds": ["660e8400-e29b-41d4-a716-446655440000"]
}
}Using JWT with Tenant Context
When authenticating with JWT, most endpoints require a tenant context via the x-tenant-id header:
Authorization: Bearer <access_token>
x-tenant-id: <tenant_uuid>You can retrieve the list of tenants your user belongs to via GET /api/tenant/mine.
Project-scoped endpoints also need the x-project-id header, and project-optional endpoints accept it — see Project Context.
Project Context
The data of a tenant is split into projects. Project-scoped endpoints work in exactly one project per request — the active project. Tenant-level endpoints do not look at the project.
Project-optional endpoints accept x-project-id but do not require it. When it is sent, it narrows the request to that project and is validated as on a project-scoped endpoint. Without it, a read covers the whole tenant and a result takes the project of its activity; a result without activity_id has no activity to take a project from, so it needs the header — without it the API responds 400. A project key always works in its own project: it does not need the header, and a result without activity_id goes to the key's project.
The active project comes from:
| Credential | Active project |
|---|---|
| JWT | The x-project-id header; on a project-optional endpoint without it, there is no active project |
| Tenant key | The x-project-id header; on a project-optional endpoint without it, there is no active project |
| Project key | The key's own project — the x-project-id header is not needed |
Send the header next to x-tenant-id. Project IDs come from GET /api/projects:
Authorization: Bearer <access_token>
x-tenant-id: <tenant_uuid>
x-project-id: <project_uuid>Tenant-level endpoints ignore the header: sending it or not does not change the response. With an API key the tenant still comes from the key; only the project depends on the key's scope — see Integration → Authentication.
Project-scoped, project-optional and tenant-level endpoints
| Endpoints | Scope |
|---|---|
Activities — /api/activities/*, including GET /api/activities/tasks, GET /api/activities/tasks/:id and POST /api/activities/tasks/:id/dispatch | Project-scoped |
Activity results — POST /api/activity-results/tasks, PATCH /api/activity-results/tasks/:id | Project-scoped |
Activity Generation Configs — /api/activity-generation-configs/* | Project-scoped |
Audiences — /api/audience-criterias/*, including the catalog (tables, tables/:tableName/columns) | Project-scoped |
Event filters — /api/event-criterias/* | Project-scoped |
Lifecycle pillars — /api/activation-configs/*, /api/engagement-configs/*, /api/resurrection-configs/* | Project-scoped |
Integration — GET /api/integration/activities/tasks, POST /api/integration/activity-results/tasks | Project-optional |
Every other endpoint: projects, API keys, auth, users, tenants, commerces, commerce lifecycle snapshots, operators, routes, supervisors, dispatch configs and providers, activity config catalogs, lifecycle defaults; on the integration API, GET /api/integration/task-result-type-configs and the batch-upsert endpoints | Tenant-level |
GET /api/activity-results/tasks/sync is tenant-level: it ignores x-project-id and returns the results of every project of the tenant.
Project errors
On a project-scoped endpoint, and on a project-optional endpoint that receives the header, the API checks the project before permissions and the request body. The exception is the 400 of a result without activity_id on a project-optional endpoint, which comes after the request body is validated:
| Status | When | message |
|---|---|---|
403 | A project key sends an x-project-id that names another project | La API key pertenece a un proyecto distinto del que declara el header x-project-id. |
400 | The header is missing on a project-scoped endpoint (JWT or tenant key) | Falta el header obligatorio x-project-id. |
400 | A result without activity_id is submitted on a project-optional endpoint without a project (JWT or tenant key without the header) | Falta el header obligatorio x-project-id: un resultado sin activity_id necesita proyecto. |
400 | The header is not a valid UUID (JWT or tenant key) | El header x-project-id debe ser un UUID válido. |
404 | The project does not exist or belongs to another tenant | El proyecto no existe. |