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.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. |
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/<guid> |
{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.
/api/messagesThe 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.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/<id>/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.
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}.