Skip to content

Latest commit

 

History

History
186 lines (157 loc) · 14.4 KB

api-reference.md

File metadata and controls

186 lines (157 loc) · 14.4 KB

API reference

Generated by reading the routers in backend/app/api/v1/ and the app's route table (app/main.py, app/api/v1/router.py). In development the live schema is at http://localhost:8000/openapi.json and /docs (both are off in production, DOCS_ENABLED). All routes below are under /api/v1 unless marked otherwise. Browsers reach them through the Next.js proxy (http://localhost:3000/api/v1/...).

Conventions

Topic Rule
Auth Cookies qs_access / qs_refresh / qs_csrf set by POST /auth/login; scripts may send Authorization: Bearer <access_token> (then no CSRF). There is no X-User-Id header (it only exists inside the test client helper).
CSRF Cookie-authenticated POST/PATCH/PUT/DELETE must send X-CSRF-Token equal to the qs_csrf cookie, from an allowed Origin (FRONTEND_URL / CORS_ORIGINS). Missing or wrong: 403.
Access column Public no session; User any signed-in active user; Admin role admin; Scoped User, but the resource is loaded through the owner/collaborator check (app/authz.py), so someone else's id answers 404; Owner like Scoped but only the chart owner (collaborators get 403 owner_only).
Optimistic concurrency PATCH of scorecard, version, KPI node, guideline accepts If-Match: <updated_at ISO>; stale: 409 {code: stale_edit, updated_at}.
Idempotency Idempotency-Key header on POST /evaluations/ai/jobs (replay returns the stored result).
Rate limits 429 with Retry-After. Per IP: login, signup, forgot/reset, refresh, register-user. Per user: chat, AI jobs, uploads, export / bulk-delete, admin, share. Share/invite lookups per sender (RATE_LIMIT_SHARE_LOOKUP, lookup_rate_limited). Redis-backed when REDIS_URL is set, in-memory otherwise.
Pagination Evaluations: keyset cursor (GET /evaluations/page); notifications and activity: keyset cursor.

Error codes

Status Meaning / detail.code
400 Invalid or expired reset / verification token
401 Not authenticated, expired access token, rejected refresh (cookies are cleared)
403 CSRF / origin failure; wrong bootstrap token; Administrator access required; owner_only (a collaborator tried an owner action)
404 Not found or not yours (never 403 for foreign data); closed endpoints (register-user after bootstrap); user_not_found when sharing (deliberate)
409 stale_edit; user_job_active (one evaluation job per user); user_chat_active (one chat turn per user); turn_in_progress (session already running a turn); already_collaborator; already_shared; already_saved; not_pending; chart_in_trash; last_admin / self_action (admin rules); weight-sum or other DB conflicts
422 Validation (Pydantic), self_invite, invalid_username, bad Drive URL (per-item detail)
429 Rate limited, or account locked after failed logins
503 Evaluation AWS stack not configured; /ready when Postgres is down

Auth and profile

Method Path Access Purpose
POST /auth/signup Public (only if SIGNUP_ENABLED) Create an account, start a session, send a verification mail
POST /auth/login Public Email, username or email local-part + password; remember_me makes a 30-day refresh cookie
POST /auth/refresh Refresh cookie Rotate the refresh token, issue a new access token
POST /auth/logout Cookie End this browser session (works with an expired access token)
POST /auth/logout-all User Revoke every session of the user
GET /auth/me User Current user
GET /auth/config Public What sign-in pages need: signup_enabled, setup_required, username_login
POST /auth/register-user Admin, or X-Bootstrap-Token while zero users exist Create an account without signup / create the first admin
POST /auth/forgot-password Public Same answer whether or not the address exists (202)
POST /auth/reset-password Public Set a new password from the emailed token
POST /auth/verify-email Public Confirm the emailed token
POST /auth/resend-verification User Send a new verification mail (202)
GET /auth/providers Public OAuth providers and whether each is configured
GET /auth/oauth/{provider_id}/start Public Begin Google / GitHub / Microsoft sign-in (501 until configured)
GET /auth/oauth/{provider_id}/callback Public OAuth redirect target
GET / PATCH /me User Read / update own profile (role is never accepted)
POST /me/password User Change own password (needs the current one)
GET /me/slots User Which of the caller's two slots (evaluation job, chat turn) are busy

Admin users

All Admin, rate limited (RATE_LIMIT_ADMIN), audit logged. Rules: never yourself, never the last active admin (last_admin).

Method Path Purpose
GET /admin/users List / search accounts
POST /admin/users Create a user (admin or user)
PATCH /admin/users/{user_id} Change name, email, username, role
POST /admin/users/{user_id}/password Set a password (revokes the user's sessions)
POST /admin/users/{user_id}/deactivate Block sign-in, revoke sessions, cancel running jobs
POST /admin/users/{user_id}/reactivate Allow sign-in again
DELETE /admin/users/{user_id} Anonymise: tombstone row, private data removed, shared charts transferred

Scorecards, versions, KPIs

Method Path Access Purpose
GET / POST /scorecards Scoped List charts you own or collaborate on / create
POST /scorecards/suggest-similar User Rank existing charts worth reusing for a free-text request (pgvector)
POST /scorecards/validate-formula User Validate a scoring-formula draft
GET / PATCH /scorecards/{scorecard_id} Scoped Read / update (If-Match)
DELETE /scorecards/{scorecard_id} Scoped Owner: move to trash (204). Collaborator: leave the chart (204)
GET / POST /scorecards/{scorecard_id}/versions Scoped List / create a version
GET / PATCH / DELETE /scorecards/{scorecard_id}/versions/{version_id} Scoped Read / update (If-Match) / delete a version
POST /scorecards/{scorecard_id}/versions/{version_id}/validate-formula Scoped Validate a formula against that version's KPIs
GET /scorecard-versions/{version_id} Scoped Version lookup without the scorecard id
GET /scorecard-versions/{version_id}/kpi-nodes Scoped KPI tree (flat, ltree paths)
POST /scorecard-versions/{version_id}/kpi-nodes/bulk Scoped Create a whole sibling group + guidelines in one transaction (weights must sum to 100 at commit)
PATCH /kpi-nodes/weights Scoped Rebalance sibling weights atomically
GET / PATCH / DELETE /kpi-nodes/{node_id} Scoped Read / update (If-Match) / delete a KPI node
GET / POST /kpi-nodes/{node_id}/guidelines Scoped List / add a guideline level
PATCH / DELETE /kpi-nodes/{node_id}/guidelines/{guideline_id} Scoped Update (If-Match) / delete a level

Sharing and collaboration

Method Path Access Purpose
POST /scorecards/{scorecard_id}/invitations Scoped (owner or editor) Invite by username / email (user_not_found, self_invite, already_collaborator; already_pending replays 200)
GET /scorecards/{scorecard_id}/sharing Scoped Collaborators and invitations (an editor sees only their own invitations)
DELETE /scorecards/{scorecard_id}/invitations/{invitation_id} Scoped Revoke a pending invitation (owner: any; editor: own)
DELETE /scorecards/{scorecard_id}/collaborators/{user_id} Owner Remove a collaborator
POST /scorecards/{scorecard_id}/leave Scoped Leave a chart you were invited to
GET /scorecards/{scorecard_id}/activity Scoped Editing log (keyset cursor)
GET /invitations User Invitations addressed to you
GET /invitations/{invitation_id}/preview Pending invitee Read-only look at the chart before accepting
POST /invitations/{invitation_id}/accept Pending invitee Join as editor
POST /invitations/{invitation_id}/decline Pending invitee Decline

Evaluations

Method Path Access Purpose
GET /evaluations/page Scoped Infinite-scroll list: cursor, limit (1-100, default 40), sort (newest oldest score_desc score_asc name_asc name_desc) + filters status band min_score max_score meets_target date_from date_to q runner scorecard_id batch_id shared; returns items, next_cursor, total, selectable, exportable, total_capped
POST /evaluations/refresh Scoped Current state of up to 100 visible rows (live badges)
POST /evaluations/bulk-delete Scoped Delete finished rows: explicit ids (max 5000) or filter + exclude_ids (server-side "select all matching")
POST /evaluations/export Scoped Excel workbook for evaluation_ids (max 200) or a filter (max 500); headers X-Export-Count, X-Export-Skipped
GET / POST /evaluations Scoped Plain list / create a manual evaluation
GET / PATCH / DELETE /evaluations/{evaluation_id} Scoped Read (with results) / update name, score, band, client-settable status / delete (finished rows)
GET / POST /evaluations/{evaluation_id}/results Scoped Per-KPI results / add one (manual scoring)
PATCH / DELETE /evaluations/{evaluation_id}/results/{result_id} Scoped Edit / delete a KPI result
POST /evaluations/{evaluation_id}/run Scoped Legacy synchronous text-only ensemble judge (UI no longer uses it)
POST /evaluations/{evaluation_id}/finalize Scoped Manual scoring: compute final score and RAG band
GET /evaluations/{evaluation_id}/progress Scoped Status, stage, queue position, per-file state, event log
POST /evaluations/{evaluation_id}/cancel Scoped Cancel (202): failed + error_code=cancelled
POST /evaluations/{evaluation_id}/retry Scoped Re-queue a failed evaluation (202; user_job_active if the runner is busy)

AI jobs, uploads, batches

Method Path Access Purpose
POST /evaluations/ai/uploads User (RATE_LIMIT_UPLOADS) Presigned multipart URLs (15 min); daily byte quota per user
POST /evaluations/ai/uploads/complete User Finish multipart uploads; size + magic-byte check
POST /evaluations/ai/uploads/abort User Abort multipart uploads (204)
POST /evaluations/ai/batches/parse User Parse a spreadsheet of submissions (Maverick maps columns)
POST /evaluations/ai/jobs Scoped (RATE_LIMIT_AI_JOBS, Idempotency-Key) Queue one evaluation per submission (202); more than one creates a batch; 409 user_job_active
GET /evaluations/ai/batches/{batch_id} Scoped Batch status and its evaluations

Contract (bodies, S3 layout, error codes): ai-eval-contract.md.

Chat and chat shares

Method Path Access Purpose
GET /chat/sessions User Your sessions
POST /chat/sessions User (RATE_LIMIT_CHAT) Create a session and queue the first turn (202; 409 user_chat_active)
GET / DELETE /chat/sessions/{session_id} Owner of the session State incl. interim draft and turn_in_progress / delete (cancels a running turn)
GET /chat/sessions/{session_id}/messages Owner Persisted history
POST /chat/sessions/{session_id}/messages Owner Follow-up turn (202; 409 turn_in_progress, user_chat_active). ?wait=true runs inline
POST /chat/sessions/{session_id}/cancel Owner Stop the running turn (204; turn_error_code=cancelled)
GET /chat/sessions/{session_id}/turn-events Owner Live agent trace
POST /chat/sessions/{session_id}/shares Owner or recipient Share read-only by handle; with_chart also sends a chart invitation
GET /chat/sessions/{session_id}/shares Owner or recipient List shares (owner sees all, recipient the ones they made)
DELETE /chat/sessions/{session_id}/shares/{share_id} Owner or sharer Revoke a share
GET /chat/shared User Chats shared with you
GET /chat/shared/{session_id} Recipient Read the shared conversation and draft
POST /chat/shared/{session_id}/save Recipient Save your own copy as a chart (once)

Notifications

Method Path Access Purpose
GET /notifications/unread-count User Cheap count, ETag / If-None-Match gives 304 (poll ~15 s)
GET /notifications User Keyset-paged list (cursor)
POST /notifications/{notification_id}/read User Mark one read
POST /notifications/read-all User Mark all read

Event catalogue: invite_received, invite_accepted, invite_declined, invite_revoked, collaborator_removed, collaborator_left, chart_edited, chart_shared, chart_trashed, chart_restored, chat_shared, evaluation_completed, evaluation_failed, evaluation_resumed, evaluation_retry_available, batch_completed, scorecard_saved, chat_question (app/notifications.py).

Trash

All Owner-only and strictly the caller's own trashed charts (an admin has no bypass). Registered before /scorecards/{id}.

Method Path Purpose
GET /scorecards/trash Trashed charts with days_left (retention TRASH_RETENTION_DAYS, default 30)
POST /scorecards/trash/restore {ids} restore; re-queues jobs cancelled by the trash when the runner's slot is free
POST /scorecards/trash/purge {ids} delete permanently
POST /scorecards/trash/empty Delete everything in your trash

Health, readiness, metrics (not under /api/v1)

Method Path Served by Purpose
GET /health API Liveness; does not touch the database
GET /ready API 200 when Postgres answers (reports role, redis: up/down/disabled); 503 otherwise
GET /health Worker (port WORKER_HEALTH_PORT, 8001) Liveness + running evaluations / chat turns; status: draining after SIGTERM
GET /ready Worker 200 when Postgres answers and not draining
GET /metrics Worker JSON autoscaling signal: queued_evaluations, active_evaluations, waiting_chat_turns, workers, backlog_per_worker
GET /docs, /redoc, /openapi.json API, non-production only Interactive docs