API overview
Tarnished exposes a FastAPI backend that serves both the web app and machine clients.
Core API areas
The current backend includes routes for:
- authentication
- applications
- application history
- profile
- rounds
- settings and API keys
- analytics and dashboard data
- admin functions
- imports and exports
- job leads
- files and signed file URLs
- user preferences and streaks
- AI settings and insights
- user management
Authentication modes
The API supports two primary auth modes:
- JWT-backed browser sessions for the web app
- API keys for machine clients such as the CLI and browser extension
The /api/auth/whoami endpoint can identify whether the current caller is authenticated with:
jwtapi_key
OpenAPI and built-in docs
The backend is a FastAPI application, so it exposes:
- an OpenAPI schema at
/openapi.json - Swagger UI at
/docs - ReDoc at
/redoc
These built-in docs are the raw API reference source of truth.
Preferences and day-boundary behavior
The user-preferences surface includes:
- dashboard feature visibility
- time zone mode (
deviceormanual) - manual time zone override
Day-based surfaces such as streaks, dashboard KPI windows, follow-up buckets, and analytics period windows use the caller's effective local day rather than a hard-coded server-local date.
For callers using device mode, date-sensitive routes accept request-time timezone context so the backend can interpret the caller's local day correctly. Manual mode uses the stored timezone override instead.
Application dates and round times
applied_at is a calendar date (YYYY-MM-DD), not a timestamp. Explicit dates
are preserved. Omit it on application creation/extraction to use the effective
local date; the web app, CLI and extension send a Time-Zone IANA zone header.
A manual user preference takes precedence over that header.
Round scheduled_at and completed_at writes are normalized to UTC. An explicit
offset identifies the instant; a timestamp without an offset uses the effective
user zone. Without a known zone, supply a Time-Zone header or an explicit offset.
Nonexistent daylight-saving wall times return 422. A newly edited ambiguous wall
time selects the first occurrence; use an explicit offset for the second one.
Round creation/update clients editing wall times may send
Expected-Round-Time-Zone with the effective IANA zone displayed during editing.
If a naive, non-null date is submitted and that zone no longer matches the server's
effective zone, the whole mutation returns 409 before saving any fields. Refetch
preferences and confirm/reload dates before retrying. This precondition does not
change the device Time-Zone header or manual-preference precedence; explicit
offsets, omitted dates and null clears do not depend on it. The web round form
uses this precondition to detect changes made in another tab or by an API client.
Omit unchanged round dates to preserve their exact instant, including seconds;
send null to clear them. SQLite round responses mark stored naive values as UTC.
Legacy round values without timezone provenance cannot be reconstructed reliably;
there is no bulk historical reinterpretation or change to other timestamp fields.
Common route groups
Auth
/api/auth/login/api/auth/refresh/api/auth/me/api/auth/whoami/api/auth/setup-status(read-only; reports whether first-run setup is needed)POST /api/auth/setup(creates the first admin once; 409 after setup is complete)POST /api/auth/change-password(current_password,new_password; JWT only)POST /api/auth/signout-all(JWT only)
There is no /api/auth/register route. Use browser first-run setup, then
POST /api/admin/users for administrator-managed enrollment. Admin routes require
both current admin role and matching scopes for API keys. No
/api/admin/applications browsing route exists. Password changes/resets invalidate
old access/refresh tokens and signed links; API keys remain unless revoked.
See account lifecycle and recovery.
Applications and job leads
/api/applications/api/applications/extract/api/job-leads/api/job-leads/{id}/convert
Files
/api/files/...
This group includes:
- signed URLs
- application document access
- round transcript access
- media access
Import and export
/api/export/json/api/export/csv/api/export/zip-jobs/api/import/...
The heavier ZIP import/export paths use durable transfer jobs.
Machine-client expectations
CLI
The CLI is API-key-first and expects an API key with the scopes required by the selected commands.
Browser extension
The extension also uses API-key auth and calls endpoints for:
- job leads
- applications
- statuses
- profile
Related pages
Recorded evidence and calculations
POST /api/statuses,PATCH /api/statuses/{id}accept stablemeaning. Applications retainstatus_meaning/ provenance independently of the current definition. History retains from/to meanings, individual provenance, correction metadata and content-freeis_gapboundaries.- Application create/update accept
response_evidence: {occurred_on?, reference?}. An explicit empty object records an undated substantive fact; omission preserves it, null clears it. Neither arbitrary status transitions nor round creation imply a response. Update accepts optimisticexpected_revision. PATCH /api/applications/{id}/meaningtakesmeaning, expected_revisionfor a current-only correction.PATCH /api/applications/{id}/history/{event_id}takesexpected_revisionand suppliedfrom_meaning,to_meaningor offsetchanged_at; optionalcorrection_noteis not an attestation requirement. Untouched legacy fields remain unknown. Deletion retains an evidence boundary.GET /api/analytics/{pipeline,kpis,sankey,activity,weekly,interview-rounds}acceptsperiod=7d|30d|3m|alland optional offsetas_of. Pipeline/KPIs expose cohort/as-of/denominator, response unknowns, current-versus-ever counts, real visits, totals and coverage. Current classification has its own observed-at basis and is not historical reconstruction. Round activity uses its own dates; scheduled-to-completed duration is not time in application stage or response speed. Sankey node identity is event ID, never label text.POST /api/analytics/insightsremains explicit generation, not a cache read. It uses corrected deterministic inputs and requiresanalytics:generateas well as analytics read/input authorization. No read or web query invalidation generates advice.- Persisted grounded feedback is available at three scopes, each an explicit
request, never a read-triggered provider call:
GET|POST /api/rounds/{round_id}/interview-feedbackfor interview feedback;GET|POST /api/applications/{application_id}/feedbackfor one application;GET|POST /api/analytics/feedbackfor the whole pipeline (acceptsperiod=7d|30d|3m|alland optional offsetas_of). Requests requireanalytics:generateplus the read scopes; reads require the read scopes only. A stored report carries its scope, run time, provider/model, configuration revision, source fingerprint, cited passages and coverage. Relevant source changes mark the latest report per scope stale; deletion of a source clears dependent report content and asks for an explicit rerun. There is no report history and no automatic rerun. Pipeline reports quote the deterministic metrics computed by the application and must not recalculate them. A configured text model is not a claim of verified model quality.
See CLI evidence examples for executable body-file contracts. Personal archive evidence fields are supported by the versioned archive format. Use matching client and server versions when moving records between installations.