Skip to content
authreads Docs

Examples

Every auth request sends your tenant identity in the headers. The body carries only data (email, password, code) — never ids or secrets.

Replace atr_…, alcs_… and the host with your own values. The two headers are the same on every call:
X-Tenant-Id: atr_xxxxxxxxxxxx
Authorization: Bearer alcs_xxxxxxxx...

Start a login (preflight)

POST /api/v1/auth/login/preflight
curl -X POST https://auth.example.com/api/v1/auth/login/preflight \
  -H "X-Tenant-Id: atr_xxxxxxxxxxxx" \
  -H "Authorization: Bearer alcs_xxxxxxxx..." \
  -H "Content-Type: application/json" \
  -d '{"email":"user@acme.com","password":"••••••••"}'

Verify the one-time code

POST /api/v1/auth/login/verify-otp
curl -X POST https://auth.example.com/api/v1/auth/login/verify-otp \
  -H "X-Tenant-Id: atr_xxxxxxxxxxxx" \
  -H "Authorization: Bearer alcs_xxxxxxxx..." \
  -H "Content-Type: application/json" \
  -d '{"email":"user@acme.com","code":"123456"}'

Request a password reset

POST /api/v1/auth/password/forgot
curl -X POST https://auth.example.com/api/v1/auth/password/forgot \
  -H "X-Tenant-Id: atr_xxxxxxxxxxxx" \
  -H "Authorization: Bearer alcs_xxxxxxxx..." \
  -H "Content-Type: application/json" \
  -d '{"email":"user@acme.com"}'

Exchange a grant for a session

After preflight (or verify-otp) hands you a one-time grant, trade it for a durable session. Returns { user, session_id, account_session_id, session_policy, session_activity_token, kind } — store all of it server-side, then mint an access token from session_id. Full sequence, and the idle-timeout contract carried by session_policy / session_activity_token: Sessions & tokens.

POST /api/v1/auth/session/exchange
curl -X POST https://auth.example.com/api/v1/auth/session/exchange \
  -H "X-Tenant-Id: atr_xxxxxxxxxxxx" \
  -H "Authorization: Bearer alcs_xxxxxxxx..." \
  -H "Content-Type: application/json" \
  -d '{"grant":"<one-time-grant>"}'
# → {"user": {"id": "...", "email": "user@acme.com"}, "session_id": "5b1e...",
#     "account_session_id": "9c2a...", "session_policy": {"idle_timeout_minutes": 30, ...},
#     "session_activity_token": "...", "kind": null}

Create a user (Management API)

Get a machine token, then create the user in a tenant org with a role:

1 · POST /oauth/token
curl -X POST https://auth.example.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=...&client_secret=..."
# → { "access_token": "...", "token_type": "Bearer", "expires_in": 900, "scope": "users:write ..." }
2 · POST /api/v1/management/users
curl -X POST https://auth.example.com/api/v1/management/users \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@acme.com","organization_id":"<org uuid>","role_id":"<role uuid>"}'
# → { "id": "<user id>", "email": "...", "email_verified": false,
#     "set_password": { "link": "..." } | null }
The returned idis the user's authreads id — you don't copy it anywhere; your product resolves the user automatically on their first login. Omit password to get a set-password link instead. Full field reference: see the schema for CreateUserRequest in the API Reference.

Email verification

New users are unverified unless you explicitly assert evidence. Omit email_verified during creation to assert nothing. Authreads automatically records first-hand proof when the user redeems a login or step-up code, invite/onboarding link, or reset link delivered by Authreads email. A raw set-password link returned through this API does not verify the address.

Assert verification later
curl -X PATCH \
  https://auth.example.com/api/v1/management/users/<user-id>/email-verification \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"email_verified":true}'
# → { "id":"…", "email_verified":true,
#     "email_verified_method":"tenant_asserted", "email_verified_at":"…" }
Set this only when your tenant has actually verified control of the address. A false request revokes only your own tenant_asserted evidence; it cannot downgrade authreads_otp or authreads_token proof. Every transition is tenant-scoped and durably audited.
A 401 almost always means the X-Tenant-Id is wrong, or the Bearersecret doesn't match that tenant. Confirm both from the Tenant Credentials card (rotate to get a fresh secret if unsure).