Two surfaces, and the difference matters:
| Surface | Prefix | Authenticates with |
|---|---|---|
| Public API | /api/v1/... | An API token, as a bearer |
| Admin API | /api/... | A session cookie, plus RBAC |
Build integrations against /api/v1. The admin API is the application’s own and
is not versioned.
A running server serves the exhaustive generated document at /reference, with
raw OpenAPI at /reference/json.
Public API
Content
| Method | Path |
|---|---|
GET | /api/v1/content-types |
GET | /api/v1/content-types/:name |
GET | /api/v1/content/:type |
GET | /api/v1/content/:type/:id |
GET | /api/v1/content/:type/:id/relations/:field |
GET | /api/v1/content/:type/:id/media |
GET | /api/v1/content/:type/:id/translations |
GET | /api/v1/content/:type/group/:groupId |
GET | /api/v1/content/:type/group/:groupId/relations/:field |
GET | /api/v1/content/:type/group/:groupId/media |
GET | /api/v1/content/:type/group/:groupId/translations |
POST | /api/v1/content/:type |
PATCH | /api/v1/content/:type/:id |
POST | /api/v1/content/:type/:id/publish |
POST | /api/v1/content/:type/:id/unpublish |
DELETE | /api/v1/content/:type/:id |
PATCH | /api/v1/content/:type/group/:groupId |
POST | /api/v1/content/:type/group/:groupId/publish |
POST | /api/v1/content/:type/group/:groupId/unpublish |
DELETE | /api/v1/content/:type/group/:groupId |
POST | /api/v1/content/:type/bulk |
POST | /api/v1/content/:type/bulk/publish |
POST | /api/v1/content/:type/bulk/unpublish |
POST | /api/v1/content/:type/bulk/delete |
Media, GraphQL and MCP
| Method | Path | Notes |
|---|---|---|
POST | /api/v1/media/assets | Multipart upload |
GET | /api/v1/media/assets/:id/raw | Streams bytes; ?variant= |
POST | /api/v1/graphql | Queries and mutations |
GET | /api/v1/graphql | The schema as SDL |
GET | /api/v1/graphql/playground | GraphiQL |
ALL | /api/v1/mcp | Off unless MCP_ENABLED=true |
GET | /api/v1/content/:type/:id/access | An entry’s audiences. segments:read |
PUT | /api/v1/content/:type/:id/access | Sets them. segments:manage |
Every read above is filtered by reader entitlements when the segments plugin holds any audience — including on each relation hop, inside both the page and its total.
Admin API
Authentication and account
| Method | Path |
|---|---|
POST | /api/auth/login, /api/auth/logout |
GET | /api/auth/me |
GET | /api/auth/invite/:token |
POST | /api/auth/invite/accept |
GET | /api/auth/reset/:token |
POST | /api/auth/reset |
GET, PUT | /api/preferences |
GET | /api/users/:id/sessions |
DELETE | /api/users/:id/sessions/:sessionId |
Single sign-on
Added in 0.4.0. All three of the first routes are public and rate-limited; the
callback carries no origin guard, because it is a top-level GET from a third
party with no Origin header. See single sign-on.
| Method | Path | What |
|---|---|---|
GET | /api/auth/sso | The registered providers, for the sign-in buttons. [] when none |
GET | /api/auth/sso/:provider/start | Opens an attempt and redirects to the provider |
GET | /api/auth/sso/:provider/callback | Verifies, opens a session, redirects into the admin |
POST | /api/auth/sso/:provider/callback | The same, for SAML’s form post |
POST | /api/auth/sso/:provider/backchannel-logout | Ends sessions because the provider said so |
Content, revisions and schema
| Method | Path |
|---|---|
GET | /api/content-schema, /api/content-schema/:name |
GET | /api/content-schema/:name/filter-fields |
GET | /api/content/:type, /api/content/:type/:id |
GET | /api/content/:type/:id/media, /api/content/:type/:id/relations |
POST | /api/content/:type |
PATCH | /api/content/:type/:id |
POST | /api/content/:type/:id/publish, /unpublish, /restore |
DELETE | /api/content/:type/:id, /api/content/:type/:id/permanent |
POST | /api/content/:type/bulk/publish, /publish/preview, /unpublish, /delete, /restore, /purge |
GET | /api/content/:type/:id/revisions, /revisions/:number |
POST | /api/content/:type/:id/revisions/:number/restore, /publish |
Saved views
Named slices of a content list. The whole surface needs content:read; sharing
one with the workspace additionally needs views:share, checked per write. See
saved views.
| Method | Path | What |
|---|---|---|
GET | /api/views | Every view the caller may see, for a scope |
POST | /api/views | Saves the current slice under a name |
PATCH | /api/views/:id | Renames, re-slices or changes visibility |
DELETE | /api/views/:id | Removes it |
PUT | /api/views/:id/default | Makes it the caller’s default for that list |
DELETE | /api/views/:id/default | Clears that default |
Export and import
Added in 0.4.0, and gated on content:export / content:import — both of
which a contributor holds. See export and
import.
| Method | Path | What |
|---|---|---|
POST | /api/content/:type/export/preview | The counts, from the same graph walk the export runs |
POST | /api/content/:type/export | The document, or an archive when files are included |
POST | /api/content/:type/import/preview | The verdicts. Writes nothing |
POST | /api/content/:type/import | Applies them |
GET | /api/content/:type/import/template | An empty file of the right shape. content:read |
Media, users, workspaces, tokens, activity
| Method | Path |
|---|---|
GET | /api/media/assets, /api/media/folders |
POST | /api/media/assets, /api/media/folders |
PATCH | /api/media/assets/:id, /api/media/folders/:id |
POST | /api/media/assets/:id/duplicate |
GET | /api/media/assets/:id/raw |
DELETE | /api/media/assets, /api/media/folders/:id |
GET | /api/users, /api/users/:id |
POST | /api/users/invites, /api/users/:id/invites/resend |
POST | /api/users/:id/disable, /enable, /password-reset |
PATCH | /api/users/:id |
DELETE | /api/users/:id/invites |
GET | /api/workspaces, /api/workspaces/slug-available |
POST | /api/workspaces, /api/workspaces/:id/archive, /unarchive |
PATCH | /api/workspaces/:id |
DELETE | /api/workspaces/:id |
POST | /api/workspaces/:id/members, /api/workspaces/:id/content |
DELETE | /api/workspaces/:id/members/:userId, /api/workspaces/:id/content/:slug |
GET | /api/workspaces/:id/entry-count, /api/workspaces/:id/content/:slug/entry-count |
GET | /api/content-types |
GET, POST | /api/api-tokens |
DELETE | /api/api-tokens/:id |
GET | /api/activity, /api/activity/entries/:entryId |
GET | /api/activity/dead-letters |
/api/activity/dead-letters lists the outbox events that exhausted their
attempts — most often an audit row that was never written. Same key as the log,
activity:read; see the activity plugin.
Alarms
Content rules and their findings, all workspace-scoped. Reads need
alarms:read; every write needs alarms:manage. See
alarms.
| Method | Path | What |
|---|---|---|
GET | /api/alarms/rules | The workspace’s rules |
POST | /api/alarms/rules | Creates one, and scans immediately |
POST | /api/alarms/rules/preview | How many entries a filter would match |
PATCH | /api/alarms/rules/:id | Edits one; a filter change forces a rescan |
DELETE | /api/alarms/rules/:id | Removes it and its findings |
POST | /api/alarms/rules/:id/rescan | Re-evaluates the whole collection |
GET | /api/alarms/findings | The open findings, paged |
GET | /api/alarms/findings/by-entry | Findings for up to 100 entry ids |
GET | /api/alarms/findings/summary | The severity tally for the whole set |
Segments
Reader audiences and per-entry access. See segments.
| Method | Path | What |
|---|---|---|
GET | /api/segments | The audience directory. segments:read |
POST | /api/segments | Creates one. segments:manage |
GET | /api/segments/lookup | Resolves ids to names. segments:read |
GET | /api/segments/:id | One audience. segments:read |
PATCH | /api/segments/:id | Edits it. segments:manage |
DELETE | /api/segments/:id | Removes it. segments:manage |
GET | /api/segments/entries/:entryId | One entry’s two lists. segments:read |
PUT | /api/segments/entries/:entryId | Sets them. segments:manage |
The admin does not use that PUT. It sends the audiences in the entry save’s
extensions bag, so the access row commits on the save’s own transaction and is
captured by the revision that save appends.
Publication protection
Rules, reviews and the reviewer’s queue, all workspace-scoped. Reading where an
entry stands needs content:read; asking for a review needs content:update;
voting needs content:approve; the rules themselves need protection:manage.
See publication protection.
| Method | Path | What |
|---|---|---|
GET | /api/protection/rules | Every rule in the workspace. protection:manage |
PUT | /api/protection/rules/:kind/:slug | Writes one. 404 on a type the workspace was not granted |
DELETE | /api/protection/rules/:kind/:slug | Removes it. Same effect as disabling, with no row left |
GET | /api/protection/entries/:type/:id | The requirement, the approvals with their staleness, the open request, and the verdict a save by this caller would meet |
GET | /api/protection/entries/:type/:id/reviewers | Who may be asked: the other members holding content:approve |
POST | /api/protection/entries/:type/:id/request | Opens the request, or replaces who the open one names |
DELETE | /api/protection/entries/:type/:id/request | Withdraws it. Requester or administrator |
POST | /api/protection/entries/:type/:id/approve | Records this caller’s approval of the current version |
DELETE | /api/protection/entries/:type/:id/approve | Withdraws their own |
GET | /api/protection/entries/:type/status | The review state of many entries at once, for the records column |
GET | /api/protection/types/:type | What publishing a new entry of the type would meet, for the create form |
GET | /api/protection/queue | Open requests across every type; ?mine=1 for the ones naming the caller |
A blocked publish answers 409 — protection.insufficient_approvals, with
the numbers, or protection.token_refused for a bearer token on a type that
does not allow one — never 403. The two have to be told apart at the other end:
403 means ask an administrator for a permission, 409 means ask a colleague to
read your work. Publishing past a rule is the ordinary publish call plus
{ bypass: true }, and it is a 403 without admin_bypass or without being an
administrator.
The routes do not hang off /api/content/... on purpose: a plugin mounting into
another plugin’s path makes the prefix ambiguous to read and to guard.
Webhooks
Outgoing endpoints and their delivery log. Global, not workspace-scoped — there is no workspace guard on any of them — and both keys are admin-only. Every write carries the origin guard. See webhooks.
| Method | Path | What |
|---|---|---|
GET | /api/webhook-events | The subscribable event catalogue. webhooks:read |
GET | /api/webhooks | Every endpoint, with its last delivery. webhooks:read |
POST | /api/webhooks | Creates one and returns the secret, once. webhooks:manage |
GET | /api/webhooks/:id | One endpoint, with secretHint only. webhooks:read |
PATCH | /api/webhooks/:id | Replaces its configuration. webhooks:manage |
DELETE | /api/webhooks/:id | Removes it and its log. webhooks:manage |
POST | /api/webhooks/:id/secret | Rotates the secret and returns the new one, once. webhooks:manage |
POST | /api/webhooks/:id/test | Sends a synchronous ping and reports the response. webhooks:manage |
GET | /api/webhooks/:id/deliveries | One page of the log; ?status=, ?eventKind=. webhooks:read |
GET | /api/webhooks/:id/deliveries/:deliveryId | One delivery, with the frozen body and the response snippet. webhooks:read |
POST | /api/webhooks/:id/deliveries/:deliveryId/redeliver | Queues the same event again, same event id. webhooks:manage |
A rejected URL answers 422, not 400: the request was well-formed and the value was refused by policy, and the message is written to be shown in the form that produced it. A delivery id from another endpoint answers 404 — the log is not a way to enumerate deliveries across endpoints.
Localization, insights and copilot
| Method | Path |
|---|---|
GET | /api/i18n/locales |
GET | /api/i18n/content/:type/:id/locales |
POST | /api/i18n/content/:type/locale-summary |
GET | /api/insights/content/{totals,pipeline,velocity,stale,unshipped,punchcard} |
GET | /api/insights/media/{storage,uploads,alt} |
GET | /api/insights/i18n/coverage |
GET | /api/insights/protection/reviews |
POST | /api/copilot/runs |
GET | /api/copilot/conversations, /api/copilot/conversations/:id |
PATCH | /api/copilot/conversations/:id |
POST | /api/copilot/runs/:runId/permission, /permission/extend |
GET | /api/copilot/models, /api/copilot/proposals, /api/copilot/proposals/:id |
GET, POST | /api/copilot/skills |
GET | /api/copilot/skills/manage, /api/copilot/skills/:id |
PATCH, DELETE | /api/copilot/skills/:id |
Not under a prefix
| Path | What |
|---|---|
/reference | The interactive API reference |
/reference/json | The raw OpenAPI document |
These sit outside the global /api prefix.