Werkmail Docs
Contents

API

API authentication

All /api/* routes except auth and /api/public/* require a bearer credential.

http
Authorization: Bearer <token>

X-Admin-Token: <ADMIN_API_TOKEN> is also accepted for operator automation.

Session (dashboard)

bash
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":"<code from the email>"}'

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. After 5 failed password attempts for one email (or 20 from one IP) within 15 minutes, further attempts return 429 rate_limited with a Retry-After header until the window frees up. Sign-in errors use the standard JSON error envelope (invalid_credentials, invalid_code, rate_limited).

Team and project tokens

Create under Settings → API tokens or:

bash
# 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.

Expiry and rotation

Add "expires_in_days": 90 (or an RFC 3339 "expires_at") when creating a token to give it a lifetime. Tokens do not expire unless you ask, or the operator configured a maximum lifetime. An expired token gets 401 with code token_expired. The token list shows last_used_at and expires_at.

Rotate a token without downtime:

bash
curl -sS -X POST -H "Authorization: Bearer $SESSION" \
  -H "Content-Type: application/json" \
  -d '{"grace_period_hours": 24}' \
  "$BASE/api/practices/$PRACTICE_ID/api-tokens/$TOKEN_ID/rotate"

The response is the replacement (same name, scope and project) with its new token, shown once. The old token is revoked right away, or after grace_period_hours (0 to 168) during which both work.

Roles

System roles: Owner, Admin, Member, plus Editor (legacy). Custom roles exist on higher plans.

bash
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.

View this page as Markdown · llms.txt · full corpus