Skip to content
engage-ai

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.

PrefixWhat it isGet it fromReachesLasts
eng_live_ / eng_test_An API key, for servers and scriptsSignup, POST /api-keys, or an operatorEvery tenant endpointUntil revoked or its expiresAt
eng_sess_A session, for a person signed in to an appPOST /auth/loginEvery tenant endpoint12 hours by default
eng_adm_An administrator session, for operatorsPOST /admin/auth/loginOnly /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 expiresAt instead 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 401 with the same message, so the API cannot be used to find out which emails have accounts.
  • organizationId in 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"
FieldMeaning
nameHow you will recognise it later. Required.
environmentlive gives an eng_live_ key, test gives eng_test_.
expiresAtOptional. Omit it for a key that does not expire.
scopesDefaults 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 with POST /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 lastUsedAt in GET /api-keys for keys you thought were unused.