Skip to content
authreads Docs

Management API

Everything your backend needs to provision a tenant without the admin dashboard: create users and orgs, define roles and packages, wire access, and stream audit events. All server-to-server, authenticated with a machine token.

Get a machine token

Open API and credentials → Generate credential in the dashboard and give it a name, a target environment, a purpose, and scopes:

  • Purpose decides which scopes even exist in the picker — this is the first decision, and it can't be changed later. Choose Product runtimeif this credential's backend also calls the auth endpoints your product uses at request time (minting tokens, reading /me, step-up, account-security). Choose Management automation onlyif it exclusively provisions tenant data (users, roles, packages) and never touches a live user session. Pick Product runtime if you host your own login pages — you almost certainly need both families on one credential, and “Management automation only” hides the runtime scopes from the picker entirely rather than merely leaving them unselected.
  • Scopes are frozen at creation, along with purpose. Getting the purpose and scope list right the first time avoids creating a replacement credential and cutting every caller over — see the full scope catalogue below.

You receive a client_id (ac_…) and client_secret (as_…) with exactly the scopes you granted. Exchange them for a short-lived, EdDSA-signed machine token at the token endpoint:

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" \
  -d "client_id=ac_xxxxxxxx" \
  -d "client_secret=as_xxxxxxxx" \
  -d "scope=users:write roles:read"
# → { "access_token":"…", "token_type":"Bearer", "expires_in":900,
#     "scope":"users:write roles:read" }

Send it as Authorization: Bearer <access_token> on every management call. The token is audience-bound to authreads-management-api and tenant-scoped — it can only touch your own tenant.

Cache the token; don't mint one per call. Reuse it until roughly 60s before expires_inelapses, then refresh proactively. Requesting a fresh token on every API call is the fastest way to hit the token endpoint's rate limit.
The client secret is shown once and stored only as a hash. Keep it in a secret manager, grant the credential only the scopes it needs, and rotate on a schedule — see API & credentials.

Scopes

Scopes are granted per credential — request only what the caller needs. There are nineteen scopes in total: ten Management scopes (available to any purpose) and nine runtime scopes (available only on a Product runtime credential — see purpose, above). This is the complete catalogue; nothing is granted implicitly.

Management scopes — any purpose

ResourceReadWrite
Usersusers:readusers:write
Rolesroles:readroles:write (dangerous)
Packagespackages:readpackages:write (dangerous)
Entitlement policyentitlements:readentitlements:write (dangerous)
Organizations / workspacesworkspaces:readworkspaces:write (dangerous)

Runtime scopes — Product runtime purpose only

These authenticate the auth endpoints your backend calls at request time, not tenant provisioning. Most are marked dangerousin the dashboard because they can read or end a live user's session.

ScopeGrantsDangerous
tokens:issueMint a user access token from a session (POST /auth/token).Yes
identity:readRead session identity and full memberships (GET /auth/me).Yes
sessions:reverifyStep-up re-verification (POST /auth/session/reverify).Yes
sessions:validateCheck whether a direct session id is still valid (POST /auth/session/validate).No
sessions:invalidateForce-end a direct session by id (POST /auth/session/invalidate).Yes
users:password_changeChange a signed-in user's password (POST /auth/password/change).Yes
account-security:readRead a user's sessions, trusted devices, and renderable policy.No
account-security:manageRevoke a user's sessions and trusted devices.Yes
audit:writeIngest a product-owned event (POST /api/v1/audit). Gated here, not in the Management table above.Yes
tokens:issue, identity:read, sessions:reverify, and the account-security scopes are covered in request/response detail in Sessions & tokens. sessions:validate and sessions:invalidate operate on a raw session_iddirectly — for a backend that manages its own direct sessions rather than going through account-security — and aren't covered elsewhere on this site.

What you can manage

AreaEndpoints
UsersGET/POST /management/users · GET/PATCH/DELETE /management/users/{id} · PATCH …/{id}/email-verification · POST …/suspend · POST …/reactivate
OrganizationsPOST /management/organizations · POST …/{id}/disable · POST …/{id}/enable
RolesGET/POST /management/roles · PATCH/DELETE /management/roles/{id} · GET/POST …/{id}/apps
PackagesGET/POST /management/packages · PATCH/DELETE /management/packages/{id} · POST …/{id}/apps
Apps & entitlementsGET /management/apps · PATCH …/apps/{id}/package-policy · GET/PATCH /management/entitlement-policy
WorkspacesPATCH …/workspaces/{org}/package · POST …/{org}/environment · PATCH …/{org}/organization-type
AuditPOST /api/v1/audit — ingest a product-owned audit event
All paths are under /api/v1. Full request/response schemas — every field, every status — live in the API reference.

Create a user

The only required field is email. Add organization and role together when the identity should immediately join a workspace:

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>",
    "display_name": "Ada Lovelace"
  }'
# → { "id":"<user id>", "email":"…", "organization_id":"…", "role_id":"…",
#     "status":"active", "email_verified":false,
#     "set_password": { "token":"…", "link":"…" } | null }
Omit password to receive a set_password link the user follows to choose their own — pass password to set one directly. The returned id is the user's authreads id; your product resolves it automatically on first login.

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.

Conventions & limits

  • Errors are RFC 7807 application/problem+json, and rate limits return Retry-After — see Errors & rate limits.
  • Not idempotent by header. There is no Idempotency-Key; guard retries in your own code (creating a user that already exists preserves the existing record rather than duplicating it).
  • List endpoints are unpaginated today— they return the full set. Don't assume a cursor/limit contract; treat large lists defensively.
  • No general webhooks. Management events are not delivered outbound; poll the API, or push your own events in with POST /api/v1/audit. OIDC back-channel logout is the narrow standards-based exception for ending RP sessions.