Admin API
Everything the console does is HTTP under /admin/. Every route needs a session JWT or an API key (Authorization: Bearer sk_live_…). Project routes resolve {ref} and enforce a per-verb permission. The success envelope is { "data": … }; deletes return 204.
Nodes
POST /admin/p/{ref}/nodes/
Create a node (node:write). Body: { parent_id?, slug, type, title, kind?("page"|"entry"), locale?, template_id?, blocks?, meta? }. If blocks is empty and template_id is set, blocks are cloned from the template. 409 conflict on slug clash.
PATCH /admin/p/{ref}/nodes/{id}
Update (node:write). Body NodePatch (all optional): { parent_id?, slug?, title?, status?, kind?, locale?, blocks?, meta? }. A status change needs an extra permission for the transition: node:publish (draft→published), node:unpublish, node:archive — else 403 publish_forbidden.
DELETE /admin/p/{ref}/nodes/{id}
Delete (node:delete).
GET /admin/p/{ref}/nodes/{id}/preview
Server-rendered HTML preview of the node’s blocks (node:read).
GET /admin/p/{ref}/nodes/{id}/entries
Paginated entries under a collection node.
Snapshots
Point-in-time versions of a node under /nodes/{id}/snapshots: GET / (timeline, snapshot:read), POST / ({label}, snapshot:write), GET /{sid}, DELETE /{sid}, and POST /{sid}/restore (snapshot:restore).
Templates & starters
Reusable shapes. /templates (single-node) and /starters (multi-node) both offer CRUD; POST /starters/{id}/instantiate materialises a starter’s nodes into the project.
Members, roles & ACLs
/members— GET,POSTinvite ({email, role_id?|role?}— emails a magic link to new users),PATCH /{userId},DELETE /{userId}. Owner role can’t be assigned or removed here./roles— CRUD plusGET /roles/catalog(every known permission string). A role is{ slug, label, permissions:[] }.ACLs— per-role (/roles/{id}/acls) or per-user (/members/{userId}/acls) node-level deny:{ node_id, scope:"this"|"subtree" }.
Projects & plugins
GET/POST /admin/projects— list / create (creator becomes owner).PATCH /{id}patches{ name?, default_locale?, enabled_locales?, public_base_url?, custom_domain?, locked? }(lockedis owner-only).DELETE→409 project_lockedif locked./modules— the plugin manager:GET /lists non-core modules with a per-tenantenabledflag;POST /{slug}/enable//disabletoggle them.
API keys
POST /admin/api-keys { name, scopes? } returns the sk_live_… plaintext once; GET lists (no plaintext); DELETE /{id} revokes.