# HTTP API reference All `/api/*` routes require an authenticated session. State-changing methods (non-GET) require an `X-CSRF-Token` header whose value comes from `GET /api/config` → `csrf_token`. Unauthorised API calls return `401`; non-admin calls to admin routes return `403`. ## Auth (`auth.py`) | Method | Path | Purpose | |--------|------|---------| | GET/POST | `/login` | OIDC: redirect to Okta. Static: render/submit the dev form. | | GET | `/authorize` | OIDC callback; validates token, checks allowed-users, sets session. | | GET | `/logout` | Clear session. | ## Quarantine (`app.py`) | Method | Path | Returns | |--------|------|---------| | GET | `/` | The single-page UI. | | GET | `/api/config` | `{folders, deleted_folder, report_release, prefs, csrf_token, config_version, is_admin}` | | GET | `/api/messages?folder=&limit=&before=` | `{folder, messages[], page:{raw_count, oldest, has_more}}` | | GET | `/api/message/` | `{headers, text, html, attachments[], raw_headers}` | | POST | `/api/actions` | `202 {job_id, queued}` — body `{action, folder, messages[], targetfolder?}` | | GET | `/api/jobs` | `{jobs[], config_version}` — scoped to the caller (admins see all) | | GET/PUT/DELETE | `/api/prefs` | Per-user preferences (get / set patch / reset) | `action` ∈ `release | report_release | delete | move`. `messages[]` items carry `{localguid, guid, subject, sender, recipient}`. For `move`, `targetfolder` must be a configured folder. ### Paging `/api/messages` The PPS API caps a search at 1000 rows and has no offset paging. The client pages the whole folder backwards by date: fetch a page, then pass the oldest `date` seen as `before` for the next page, until `page.has_more` is false. `page.oldest` is the oldest date in the raw batch (before hidden/acted messages are filtered), so paging never terminates early. ## Admin (`admin.py`, all `@admin_required`) | Method | Path | Returns | |--------|------|---------| | GET | `/admin` | The admin panel page. | | GET | `/api/admin/config` | `{config: redacted, schema, version}` — secrets shown as `*_set` booleans | | PUT | `/api/admin/config` | `{version, applied[], restart_required[], config}` / `400 {errors:[{field,message}]}` | | POST | `/api/admin/pps-test` | `{ok, detail}` — probes PPS with pending form values, never persists | | GET | `/api/admin/jobs?limit=` | `{jobs[], active}` — all users | | POST | `/api/admin/jobs//retry` | `{job_id, status}` — failed → pending | | GET | `/api/admin/logs?source=ops\|app&lines=&q=` | `{source, lines:[{text, level}], truncated}` | Users and the denied message are ordinary config keys under `[auth]`, saved through `PUT /api/admin/config` (one atomic write). `source` is an enum, never a file path. ## Error shape PPS failures return `502 {error, detail, status, reqid}` (`reqid` = the PPS `x-pps-reqid` header, for cross-referencing PPS logs). Validation failures return `400 {errors:[…]}` or `400 {error}`.