# 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":"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:

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

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