API docs
All endpoints below are namespaced under /api/v1. This page is public and requires no login.
Authentication
Two ways to authenticate against the API:
- Session cookie — log in via the browser at /login; the admin SPA and its JSON calls use the resulting session cookie automatically.
- Bearer token — send
Authorization: Bearer chq_…. Tokens are minted under Admin → Settings → API tokens. Minting a token itself requires an active cookie session (tokens cannot mint other tokens).
Envelopes
Errors (any non-2xx response) are shaped as:
{ "error": { "code": "invalid", "message": "...", "fields": { "name": "required" } } }List endpoints are shaped as:
{ "items": [ ... ], "total": 42, "page": 1, "perPage": 20 }CSRF
Cookie-authenticated JSON mutations (POST/PATCH/PUT/DELETE) must include the header x-chq-csrf: 1. This is a same-origin fetch signal, not a secret token. Requests authenticated with a Bearer chq_… token are exempt from this check.
Events, tracks & rooms
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events | organizer |
| POST | /api/v1/events | organizer |
| GET | /api/v1/events/:eventId | organizer |
| PATCH | /api/v1/events/:eventId | organizer |
| GET | /api/v1/events/:eventId/tracks | organizer |
| POST | /api/v1/events/:eventId/tracks | organizer |
| PATCH | /api/v1/tracks/:trackId | organizer |
| DELETE | /api/v1/tracks/:trackId | organizer |
| GET | /api/v1/events/:eventId/rooms | organizer |
| POST | /api/v1/events/:eventId/rooms | organizer |
| PATCH | /api/v1/rooms/:roomId | organizer |
| DELETE | /api/v1/rooms/:roomId | organizer |
| GET | /api/v1/events/:eventId/overview | organizer |
| GET | /api/v1/events/:eventId/public-surfaces | organizer (per-surface published counts, DEC-767) |
Portal settings & resources
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/portal-settings | organizer |
| PUT | /api/v1/events/:eventId/portal-settings | organizer |
| GET | /api/v1/events/:eventId/resources | organizer |
| POST | /api/v1/events/:eventId/resources | organizer (JSON -> wiki, multipart -> file) |
| PATCH | /api/v1/resources/:resourceId | organizer |
| DELETE | /api/v1/resources/:resourceId | organizer |
Forms & fields
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/forms | organizer |
| PATCH | /api/v1/forms/:formId | organizer |
| POST | /api/v1/forms/:formId/fields | organizer |
| PATCH | /api/v1/fields/:fieldId | organizer |
| DELETE | /api/v1/fields/:fieldId | organizer (409 if dependent rules/answers exist; ?cascade=1 to confirm) |
| POST | /api/v1/forms/:formId/fields/reorder | organizer |
Submissions
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/submissions | organizer |
| GET | /api/v1/submissions/:id | organizer |
| PATCH | /api/v1/submissions/:id | organizer (edit title/description/trackIds) |
| POST | /api/v1/events/:eventId/submissions | organizer |
| POST | /api/v1/submissions/:id/clone | organizer |
| POST | /api/v1/events/:eventId/submissions/status | organizer (bulk status change) |
| GET | /api/v1/events/:eventId/submissions/delete-plan | organizer (blast-radius preview for the delete confirmation page; DEC-921) |
| POST | /api/v1/events/:eventId/submissions/delete | organizer (guarded cascade delete; DEC-886, refuses any id with a submitted evaluation) |
| POST | /api/v1/submissions/:id/participants | organizer (invite co-presenter, invite_status='invited') |
| PATCH | /api/v1/submissions/:id/participants/:participantId | organizer (toggle visible, set inviteStatus) |
| DELETE | /api/v1/submissions/:id/participants/:participantId | organizer (remove a co-presenter; the lead participant cannot be removed) |
| GET | /api/v1/submissions/:id/revisions | organizer |
| POST | /api/v1/submissions/:id/revisions/:revisionId/restore | organizer |
| GET | /api/v1/submissions/:id/history | organizer |
Speaker pipeline
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/pipeline | organizer |
| POST | /api/v1/pipeline | organizer |
| GET | /api/v1/pipeline/:id | organizer |
| PATCH | /api/v1/pipeline/:id | organizer |
| POST | /api/v1/pipeline/:id/notes | organizer |
Review: plans, queue, evaluations, results
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/plans | organizer |
| POST | /api/v1/events/:eventId/plans | organizer |
| GET | /api/v1/plans/:id | organizer |
| PATCH | /api/v1/plans/:id | organizer |
| DELETE | /api/v1/plans/:id | organizer |
| GET | /api/v1/plans/:id/delete-preview | organizer (DEC-929: names what the DELETE below destroys) |
| POST | /api/v1/plans/:id/advance-round | organizer |
| POST | /api/v1/plans/:id/waves | organizer |
| POST | /api/v1/plans/:id/reviewers | organizer |
| GET | /api/v1/plans/:id/reviewers | organizer |
| DELETE | /api/v1/plans/:id/reviewers/:reviewerId | organizer |
| GET | /api/v1/plans/:id/scope-preview | organizer |
| GET | /api/v1/plans/:id/progress | organizer |
| GET | /api/v1/plans/:id/results | organizer |
| POST | /api/v1/plans/:id/remind | organizer |
| GET | /api/v1/review/plans | reviewer |
| GET | /api/v1/review/plans/:id | reviewer (DEC-819: the plan-scoped queue's own name, same assignment scoping as its /queue) |
| GET | /api/v1/review/plans/:id/queue | reviewer |
| GET | /api/v1/review/submissions/:id | reviewer |
| GET | /api/v1/submissions/:id/evaluations | organizer (DEC-596: every evaluation of one submission, across plans; DEC-736: reviewerName always populated; DEC-723: each item carries its own round's criteria + weighted score) |
| PUT | /api/v1/review/plans/:planId/evaluations/:submissionId | reviewer |
| POST | /api/v1/review/plans/:planId/recusals/:submissionId | reviewer |
| DELETE | /api/v1/review/plans/:planId/recusals/:submissionId | reviewer |
| GET | /api/v1/plans/:id/assignments/distribute/preview | organizer (DEC-786: pure round-robin preview, writes nothing) |
| POST | /api/v1/plans/:id/assignments/distribute | organizer (DEC-786: applies exactly the previewed pairs) |
Users
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/users | organizer |
| POST | /api/v1/users | organizer |
| POST | /api/v1/users/:id/reset-password | organizer |
| PATCH | /api/v1/users/:id | organizer (DEC-778: role change; refuses self and the org's last organizer) |
Tasks & assignments
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/onboarding | organizer |
| GET | /api/v1/events/:eventId/speakers/:contactId | organizer (DEC-930: per-speaker detail read) |
| POST | /api/v1/events/:eventId/tasks | organizer |
| PATCH | /api/v1/tasks/:id | organizer |
| DELETE | /api/v1/tasks/:id | organizer |
| GET | /api/v1/tasks/:id/delete-preview | organizer (DEC-933 amendment: names what the DELETE above destroys) |
| POST | /api/v1/tasks/:id/assign | organizer |
| GET | /api/v1/tasks/:id/roster | organizer (design pack v12: one task across every speaker who holds it) |
| POST | /api/v1/tasks/:id/unassign | organizer (design pack v12 'not needed': removes the task for those speakers only) |
| PATCH | /api/v1/task-assignments/:id | organizer or assigned speaker |
| GET | /api/v1/task-assignments/:id/response | organizer (kind='form' only) |
| POST | /api/v1/events/:eventId/onboarding/remind | organizer |
| POST | /api/v1/events/:eventId/onboarding/remind/preview | organizer |
Templates, compose & email log
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/templates | organizer |
| POST | /api/v1/events/:eventId/templates | organizer |
| PATCH | /api/v1/templates/:templateId | organizer |
| DELETE | /api/v1/templates/:templateId | organizer |
| POST | /api/v1/events/:eventId/compose/preview | organizer |
| POST | /api/v1/events/:eventId/compose/send | organizer |
| POST | /api/v1/events/:eventId/portal-invites | organizer |
| GET | /api/v1/events/:eventId/email-log | organizer |
| GET | /api/v1/events/:eventId/email-log/:emailId | organizer |
| GET | /api/v1/mail-status | organizer (DEC-996: provider/configured/fromEmail, never binding internals) |
Agenda & slots
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/agenda | organizer |
| PUT | /api/v1/submissions/:id/slot | organizer |
| DELETE | /api/v1/submissions/:id/slot | organizer |
| POST | /api/v1/events/:eventId/agenda/publish | organizer |
| POST | /api/v1/events/:eventId/agenda/auto-schedule | organizer |
| GET | /api/v1/events/:eventId/breaks | organizer |
| POST | /api/v1/events/:eventId/breaks | organizer |
| PATCH | /api/v1/breaks/:id | organizer |
| DELETE | /api/v1/breaks/:id | organizer |
Files & comments
| Method | Path | Role |
|---|---|---|
| POST | /api/v1/submissions/:id/files | organizer or submission's speaker |
| GET | /api/v1/submissions/:id/files | organizer or submission's speaker |
| POST | /api/v1/submissions/:id/content-status | organizer |
| POST | /api/v1/submissions/:id/content-note | organizer |
| POST | /api/v1/events/:eventId/submissions/content-status | organizer |
| GET | /api/v1/events/:eventId/files | organizer |
| POST | /api/v1/events/:eventId/files/archive | organizer |
| GET | /api/v1/files/:fileId/comments | organizer or submission's speaker |
| POST | /api/v1/files/:fileId/comments | organizer or submission's speaker |
| DELETE | /api/v1/files/:fileId | organizer (any version), or the uploading speaker (own latest version, pending only) |
Contacts, segments & import
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/contacts | organizer |
| POST | /api/v1/contacts | organizer |
| GET | /api/v1/contacts/duplicates | organizer |
| POST | /api/v1/contacts/duplicates/dismiss | organizer (DEC-770 'Not a duplicate' / 'Keep both') |
| GET | /api/v1/contacts/duplicates/check | organizer (DEC-788 create-time duplicate hint) |
| GET | /api/v1/contacts/stats | organizer |
| GET | /api/v1/contacts/:id | organizer |
| PATCH | /api/v1/contacts/:id | organizer |
| DELETE | /api/v1/contacts/:id | organizer (409 naming dependent counts; merge instead, DEC-758) |
| POST | /api/v1/contacts/:id/headshot | organizer |
| POST | /api/v1/contacts/:id/add-to-event | organizer |
| POST | /api/v1/contacts/import | organizer |
| POST | /api/v1/events/:eventId/import/sessionboard | organizer |
| GET | /api/v1/contacts/merge/preview | organizer |
| POST | /api/v1/contacts/merge | organizer |
| POST | /api/v1/contacts/bulk-email | organizer |
| POST | /api/v1/contacts/bulk-email/preview | organizer |
| GET | /api/v1/segments | organizer |
| POST | /api/v1/segments | organizer |
| PATCH | /api/v1/segments/:id | organizer |
| DELETE | /api/v1/segments/:id | organizer |
Saved views
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/views | organizer |
| POST | /api/v1/events/:eventId/views | organizer |
| DELETE | /api/v1/views/:id | organizer |
Saved embeds
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/embeds | organizer |
| POST | /api/v1/events/:eventId/embeds | organizer |
| PATCH | /api/v1/embeds/:id | organizer |
| DELETE | /api/v1/embeds/:id | organizer |
Exports
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/events/:eventId/export/:kind?format=csv|json | organizer (kind: submissions, speakers, evaluations, agenda, email-log, contacts (DEC-597, org-scoped)) |
| GET | /api/v1/events/:eventId/exports/showflow.csv | organizer (fixed-column show-flow export, DEC-055) |
API tokens
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/tokens | organizer |
| POST | /api/v1/tokens | organizer (cookie session only, DEC-027) |
| DELETE | /api/v1/tokens/:id | organizer |
Current user
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/me | any authenticated user |
Public read surfaces (no login)
Everything below needs no token and no login — no Authorization header, no session cookie.
Instance home & docs
| Method | Path | Role |
|---|---|---|
| GET | / | public event hub — the org's events grouped by open CFP / published programme / past; signed-in users are redirected to /admin or /portal |
| GET | /docs/api | this page |
| GET | /docs | user-facing documentation site — index of how-to articles |
| GET | /docs/:slug | user-facing documentation site — a single article |
Public event surfaces (HTML)
| Method | Path | Role |
|---|---|---|
| GET | /e/:eventSlug | public (redirects to /e/:eventSlug/sessions) |
| GET | /e/:eventSlug/sessions | public |
| GET | /e/:eventSlug/speakers | public |
| GET | /e/:eventSlug/gallery | public |
| GET | /e/:eventSlug/agenda | public |
| GET | /e/:eventSlug/schedule | public |
| GET | /e/:eventSlug/sessions/:sessionId | public |
| GET | /e/:eventSlug/speakers/:contactId | public |
| GET | /e/:eventSlug/programme | public (print-first one-page programme) |
Public calendar feeds
| Method | Path | Role |
|---|---|---|
| GET | /e/:eventSlug/agenda.ics | public (whole published agenda) |
| GET | /e/:eventSlug/schedule.ics | public (itinerary; ?ids= selects a subset) |
Embeds (chromeless, iframe-safe)
| Method | Path | Role |
|---|---|---|
| GET | /embed/:eventSlug/:surface | public, chromeless, iframe-safe |
| GET | /embed/:eventSlug/:surface.json | public, chromeless, iframe-safe (JSON feed twin) |
| GET | /embed/:eventSlug/:surface.xml | public, chromeless, iframe-safe (XML feed twin) |
| GET | /embed/:eventSlug/sessions/:sessionId | public, chromeless, iframe-safe |
| GET | /embed/:eventSlug/speakers/:contactId | public, chromeless, iframe-safe |
| GET | /embed/e/:embedId | public, saved embed; disabled returns an empty 200 |
Public submission (CFP)
| Method | Path | Role |
|---|---|---|
| GET | /submit/:eventSlug | public |