Get started
Authentication
Session tokens, API keys and administrator sessions: what each one is for, how long it lasts and how to rotate it.
Every request except signup, sign-in, inbound webhooks and health checks needs a bearer token in the Authorization header:
Authorization: Bearer eng_live_a1b2c3d4.<secret>Three kinds of credential
The prefix of a token tells you what it is and what it can reach.
| Prefix | What it is | Get it from | Reaches | Lasts |
|---|---|---|---|---|
eng_live_ / eng_test_ | An API key, for servers and scripts | Signup, POST /api-keys, or an operator | Every tenant endpoint | Until revoked or its expiresAt |
eng_sess_ | A session, for a person signed in to an app | POST /auth/login | Every tenant endpoint | 12 hours by default |
eng_adm_ | An administrator session, for operators | POST /admin/auth/login | Only /admin/* | 12 hours by default |
The three are looked up in different tables. A tenant token sent to /admin/* gets 401, and an administrator token sent to /products gets 401. Neither can be turned into the other.
Use an API key for anything that runs unattended: a backend, a nightly catalogue sync, a CI job. Use a session for a person using a dashboard or app you build.
None of these are JWTs. They are opaque handles the server looks up on every request, so there is nothing to decode. The upside is that revoking one takes effect on the very next request.
Which organization a token acts for
Every tenant credential belongs to exactly one organization, and that is the organization every request acts on. No endpoint accepts an organizationId. To find out what a token is:
curl https://api.getengageai.app/auth/me -H "Authorization: Bearer $TOKEN"{
"organizationId": "3f6c2a1e-…",
"kind": "session",
"scopes": ["*"],
"userId": "8a1d…",
"mustChangePassword": false
}kind is session or api_key.
Signing in
curl -X POST https://api.getengageai.app/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "manager@copperkettle.example", "password": "…" }'{
"accessToken": "eng_sess_76bc0bae.2DrK…",
"expiresAt": "2026-10-16T21:30:00.000Z",
"mustChangePassword": true,
"user": { "id": "…", "email": "manager@copperkettle.example", "name": "Sam Patel" },
"organization": { "id": "…", "name": "Copper Kettle Cafe", "slug": "copper-kettle-cafe", "role": "OWNER" }
}- Read
expiresAtinstead of assuming 12 hours. The lifetime is configurable per deployment. - Every failure looks the same. Unknown email, wrong password and a deleted account all return the same
401with the same message, so the API cannot be used to find out which emails have accounts. organizationIdin the body is only needed when one person belongs to more than one organization.
Changing a password
A password issued at signup or by an operator has mustChangePassword: true. Replace it:
curl -X POST https://api.getengageai.app/auth/password \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "currentPassword": "…", "newPassword": "a-long-passphrase-you-chose" }'Changing a password signs you out everywhere, including the session that made the call. Sign in again with the new password. A change that left other sessions live would not lock out someone who had stolen one.
POST /auth/logout ends the current session. It is idempotent and takes effect immediately.
API keys
An organization manages its own keys without operator help.
# Issue a key. The plaintext is in the response, once.
curl -X POST https://api.getengageai.app/api-keys \
-H "Authorization: Bearer $ENGAGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "CI pipeline", "environment": "live" }'
# List keys. Metadata only: prefix, name, lastUsedAt, expiresAt, revokedAt.
curl https://api.getengageai.app/api-keys -H "Authorization: Bearer $ENGAGE_API_KEY"
# Revoke one. Immediate and irreversible.
curl -X DELETE https://api.getengageai.app/api-keys/$KEY_ID -H "Authorization: Bearer $ENGAGE_API_KEY"| Field | Meaning |
|---|---|
name | How you will recognise it later. Required. |
environment | live gives an eng_live_ key, test gives eng_test_. |
expiresAt | Optional. Omit it for a key that does not expire. |
scopes | Defaults to ["*"]. |
To rotate a key: issue a new one, move your callers to it, then revoke the old one. No downtime and no operator needed. Revoked keys are kept, not deleted, so you can still audit what a compromised key could reach.
Lost credentials
Nothing can retrieve a password or a key. Passwords are stored as scrypt hashes and keys as peppered HMACs. If you lose them:
- You still have a working credential: issue a new API key with
POST /api-keys, or change the password withPOST /auth/password. - You have nothing that works: an operator can issue a new password or key. See Operators.
Keeping credentials safe
- Keep API keys on your server. Never ship them in a browser bundle or a mobile app.
- Use one key per integration, so you can revoke one without breaking the rest.
- Watch
lastUsedAtinGET /api-keysfor keys you thought were unused.