Reten Docs

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 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-id header 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

FieldTypeRequiredDescription
namestringYesHuman-readable name for the key
permissionsstring[]YesPermissions to grant (subset of your current permissions; editable later with PUT /api/api-keys/:id/permissions)
expiresAtISO 8601NoExpiration date
scopestringYesproject (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
projectIdUUIDWith 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

  1. Call POST /api/auth/login with email and password
  2. Receive an access token in the response body; a refresh token is set as an HTTP-only cookie
  3. Include the access token in all subsequent requests via the Authorization header
  4. When the access token expires (15 min), call POST /api/auth/refresh — the server validates the refresh cookie and issues a new access token
  5. On logout, the refresh cookie is cleared

Token Lifetimes

TokenLifetimeTransport
Access token15 minutesAuthorization: Bearer header
Refresh token7 daysHTTP-only cookie

Login Request

FieldTypeRequiredDescription
identifierstringYesUsername or email address
passwordstringYesUser 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:

CredentialActive project
JWTThe x-project-id header; on a project-optional endpoint without it, there is no active project
Tenant keyThe x-project-id header; on a project-optional endpoint without it, there is no active project
Project keyThe 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

EndpointsScope
Activities — /api/activities/*, including GET /api/activities/tasks, GET /api/activities/tasks/:id and POST /api/activities/tasks/:id/dispatchProject-scoped
Activity results — POST /api/activity-results/tasks, PATCH /api/activity-results/tasks/:idProject-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/tasksProject-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 endpointsTenant-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:

StatusWhenmessage
403A project key sends an x-project-id that names another projectLa API key pertenece a un proyecto distinto del que declara el header x-project-id.
400The header is missing on a project-scoped endpoint (JWT or tenant key)Falta el header obligatorio x-project-id.
400A 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.
400The header is not a valid UUID (JWT or tenant key)El header x-project-id debe ser un UUID válido.
404The project does not exist or belongs to another tenantEl proyecto no existe.