The two-token model: partner backends hold an application token, browsers hold a short-lived access token.
Two credentials, two questions: who is the partner, and which person is logged in.
Consumer authentication is an on-behalf-of model. Your backend holds a long-lived application_token — a server-to-server credential that identifies your tenant and must never reach the browser. The consumer's browser holds a short-lived access_token identifying the person. Consumer-facing calls carry both, and KeenAgents cross-checks that the access token belongs to the same tenant as the application token before anything proceeds. Forgot-password is the one single-token flow — the user is logged out, and the emailed link proves ownership instead.
the two headers
Authorization: Bearer <application_token> # who the partner is (server-to-server, never in the browser) X-Access-Token: <consumer access_token> # which person is logged in (short-lived, browser-held)
Both tokens are signed and are verified on every request — nothing is trusted from an earlier call. Everything sensitive inside a consumer token travels encrypted: treat tokens as opaque strings, pass them in the headers above, and never try to parse or store their contents.
client_id and secret credentials at POST /v1/auth/application_token. It identifies your tenant on every consumer route and is re-verified against your organization's current API-key bindings on each call — so deleting or rotating the API key it was issued for invalidates it immediately, with nothing to wait out.10 minutes) and a longer-lived refresh token (default 30 days). The access token accompanies every consumer call; the refresh token is used only to obtain a fresh pair, and each refresh token works exactly once.Exact lifetimes, formats, and refusal semantics.
10 minutes by default. Refresh token lifetime: 30 days by default. Plan the browser session around refreshing, not around a long-lived access token.401 that looks like a logout.401. There is no fallback for a missing origin.401 Invalid token, whatever actually failed. Why: a caller probing with bad credentials should learn nothing about which check stopped it.403 Token does not belong to this tenant. A missing X-Access-Token answers 401; an API key with no consumer contract bound answers 403 Action forbidden.2 minutes (plus a small grace window), and spent by a single confirm attempt. The pin is bound to the tenant it was issued under, so a leaked pin cannot be confirmed anywhere else.From the password POST to a rotated token pair.
/v1/auth/access_token with the consumer's email + password from an allow-listed origin. Requests from unknown origins are refused without a response.406 Login is not allowed for this account., the consumer's spaces are clamped to those your contract currently allows, and whether OTP is required is read from your contract's policy.Verify challenge while a 6-digit pin is emailed to the consumer; POSTing /v1/auth/otp/confirm with the pin and the challenge releases the token pair. Note: the organization-wide OTP setting must also be on for confirms to succeed./v1/auth/refresh_token): if the current access token is still valid, the reply is Token is still valid! and nothing rotates — keep the tokens you have. Otherwise a new pair is issued carrying the consumer's live spaces and tenant binding, and the old refresh token is spent.Nothing is trusted from the connection — every message proves itself.
On the live chat connection, the access token is verified on every message, not once at connect. The requested space must be both in the consumer's own grant and currently allowed by your contract — revoking a space stops it working immediately, not at token expiry. KeenAgents also determines the tenant from the verified credentials on every message, overwriting anything the client sends — a client can never choose its own tenant partition. Why: a connection can outlive logins, policy changes and rotations, so no message may ride on yesterday's proof.
An expired token mid-conversation returns error E3002 but leaves the connection open — refresh the token and retry on the same socket. The connection outlives the token by design. A rotated contract key surfaces as E3101 on every outstanding token for your tenant; re-login is the recovery.
E3101). If a consumer "can log in but nothing works", compare your contract's allowed spaces against the account's spaces.Keen Agents 2026
Documentation
Release 15