Full detail in Credentials.
Staff sessions
Get one from sign-up, sign-in, or the two-factor exchange:two_factor_required, then exchange the challenge at
/v1/auth/two-factor within 5 minutes. See
Two-factor.
Then send it on every request:
403 not_a_member) and leaves the session good for
the others.
Losing the LAST one does not end it either. Signing in succeeds with
active_organization_id null and organizations empty, and the session reaches
exactly four routes: GET /v1/session, GET /v1/organizations,
POST /v1/organizations and POST /v1/session/logout. Everything else answers
403 not_a_member. Branch on active_organization_code before routing anybody
into an organization.
API keys
Server-side only. Created in the panel, shown once./v1/me once at start-up to confirm which one you are holding.
Publishable keys
Not a header. It goes in the body of the endpoints that accept it:Origin
header is refused.
Contact sessions
Minted by boot, a redeemed magic link or an email code. Reads that contact and nothing else.verified on the session says whether the identity behind it was proven. An
unverified session is usable and marked, but barred from anything another person’s
data could leak through.
Failures
Missing, malformed, expired or revoked credential, and nothing else. It means the
credential is gone, so discarding it and signing in again is the right reaction.
The session is valid; the organization named in
X-Organization-Id is not one you
belong to. Same answer for somebody else’s organization, for one that does not
exist and for a malformed code — distinguishing them would be a directory of our
tenants. The repair is to change organization, never to sign out: the session is
still good for the ones you are in.Authenticated and a member, but your role does not carry the permission. Different
from
401: signing in again will not help.Also the answer for a resource in another organization or another environment.
The 404 never reveals which.
Bringing the wrong family
A well-formed credential from another family is told so, rather than getting a flat “invalid”:401
Not revealing whether an account exists
Several endpoints answer identically whether or not an address is known. This is deliberate and worth preserving if you touch these paths:
Without the timing equalization only real accounts would pay bcrypt’s tens of
milliseconds, and latency alone would answer the question the status code refuses
to.