API authentication
All /api/* routes except auth and /api/public/* require a bearer credential.
Authorization: Bearer <token>
X-Admin-Token: <ADMIN_API_TOKEN> is also accepted for operator automation.
Session (dashboard)
curl -sS -X POST "$BASE/api/auth/request-otp" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
curl -sS -X POST "$BASE/api/auth/verify-otp" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","code":"123456"}'
verify-otp returns a bearer used like any other token. GET /api/auth/me (authenticated) returns the current member.
POST /api/auth/logout invalidates the session. Password login (POST /api/auth/login-password) is available when the operator enabled it.
Team and project tokens
Create under Settings → API tokens or:
# Team token — manage projects, routes, stats
curl -sS -X POST -H "Authorization: Bearer $SESSION" \
-H "Content-Type: application/json" \
-d '{"name":"CI deploy","scope":"team"}' \
"$BASE/api/practices/$PRACTICE_ID/api-tokens"
# Project token — send for one project
curl -sS -X POST -H "Authorization: Bearer $SESSION" \
-H "Content-Type: application/json" \
-d '{"name":"Production send","scope":"project","project_id":"'"$PROJECT_ID"'"}' \
"$BASE/api/practices/$PRACTICE_ID/api-tokens"
The plaintext token is returned once (wm_team_… or wm_proj_…). Store it in a secret manager. Use it as Authorization: Bearer on /api/practices/{id}/….
Project tokens cannot create workspaces or rotate other tokens.
Roles
System roles: Owner, Admin, Member, plus Editor (legacy). Custom roles exist on higher plans.
curl -sS -H "Authorization: Bearer $TOKEN" \
"$BASE/api/practices/$PRACTICE_ID/roles"
curl -sS -X PATCH -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"role_id":"'"$ROLE_ID"'","all_projects":false,"project_ids":["'"$PROJECT_ID"'"]}' \
"$BASE/api/practices/$PRACTICE_ID/members/$MEMBER_ID"
Permission messages.view_content controls whether message APIs return bodies.
Viewers are read-only. Creating a workspace is an owner/admin (or platform-admin) action.
Practice guard
Even with a valid bearer, practice-scoped routes require membership in that workspace (or a token minted for it). Cross-tenant IDs return 404, not 403, to avoid leaking whether a UUID exists.