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, POST invite ({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 plus GET /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? } (locked is owner-only). DELETE409 project_locked if locked.

  • /modules — the plugin manager: GET / lists non-core modules with a per-tenant enabled flag; POST /{slug}/enable / /disable toggle them.

API keys

POST /admin/api-keys { name, scopes? } returns the sk_live_… plaintext once; GET lists (no plaintext); DELETE /{id} revokes.