Beta . Ortha CMS is in public beta. Every plan is free until it ends.
Features Ortha CMS on GitHub

HTTP endpoints

Every route, grouped by surface, with the permission each one needs.

Documents 0.13.1 Updated Edit this page Report a problem

On this page
SurfacePrefixAuthenticates withWorkspace
Public API/api/v1/...Authorization: Bearer orthacms_… (API token)X-Workspace-Id, optional for a one-workspace token
Admin API/api/...orthacms_session cookie from POST /api/auth/login, plus RBACX-Workspace-Id on workspace-scoped routes

Build integrations against /api/v1. The admin API is not versioned.

A token’s scope decides which permissions it carries: read → content:read, media:read, segments:read; full adds content:create, content:update, content:publish, content:delete, media:create, segments:manage. See permissions.

Example calls

Example language
curl 'https://cms.example.com/api/v1/content/article?locale=en&pageSize=10&sort=-publishedAt' \
-H 'Authorization: Bearer orthacms_…' \
-H 'X-Workspace-Id: 7d0c…' \
-H 'X-Reader-Tags: premium, eu'
curl -X POST https://cms.example.com/api/v1/content/article \
-H 'Authorization: Bearer orthacms_…' \
-H 'Content-Type: application/json' \
-d '{ "locale": "en", "values": { "title": "Hello world", "slug": "hello-world" } }'
# 201 → the draft entry; then POST /api/v1/content/article/:id/publish
# Sign in — stores the orthacms_session cookie
curl -c cookies.txt -X POST https://cms.example.com/api/auth/login \
-H 'Content-Type: application/json' \
-d '{ "email": "admin@example.com", "password": "…" }'
# { "ok": true }

# Who am I, and what may I do
curl -b cookies.txt https://cms.example.com/api/auth/me
# { "id": "…", "email": "admin@example.com", …, "permissions": ["workspaces:create", …] }

# A workspace-scoped admin read
curl -b cookies.txt 'https://cms.example.com/api/content/article?source=all' \
-H 'X-Workspace-Id: 7d0c…'

X-Reader-Tags names the reader for segments; send it from your backend, never a browser. Without it the reader is anonymous.

Common errors:

StatusWhen
400Unknown query parameter, malformed body, unknown locale
401No or invalid session / token
403Missing permission (Insufficient permissions), token scope, or workspace outside the token
404Unknown type, type the workspace cannot reach, entry not visible
409Conflict — restrict delete, duplicate translation, blocked publish (protection), a stale or busy schema-builder apply
422Validation — { "message": "Entry validation failed", "issues": [...] }

Public API

Content

MethodPathPermission
GET/api/v1/content-typescontent:read
GET/api/v1/content-types/:namecontent:read
GET/api/v1/content/:typecontent:read
GET/api/v1/content/:type/:idcontent:read
GET/api/v1/content/:type/:id/relations/:fieldcontent:read
GET/api/v1/content/:type/:id/mediacontent:read
GET/api/v1/content/:type/:id/translationscontent:read
GET/api/v1/content/:type/group/:groupId (+ /relations/:field, /media, /translations)content:read
POST/api/v1/content/:typecontent:create
PATCH/api/v1/content/:type/:id, /group/:groupIdcontent:update
POST/api/v1/content/:type/:id/publish, /unpublish (also by group/:groupId)content:publish
DELETE/api/v1/content/:type/:id, /group/:groupIdcontent:delete
POST/api/v1/content/:type/bulkcontent:create + content:update
POST/api/v1/content/:type/bulk/publish, /bulk/unpublishcontent:publish
POST/api/v1/content/:type/bulk/deletecontent:delete
  • Reads serve published entries — for a Modified entry, its published version. ?status=draft|any needs a full token (403 otherwise).
  • A type’s list includes published records from shared workspaces the workspace takes the type from.
  • Public PATCH merges over stored values.

Media, access, GraphQL and MCP

MethodPathPermission
POST/api/v1/media/assets — multipart, field filemedia:create
GET/api/v1/media/assets/:id/raw — ?variant=thumb|previewmedia:read
GET/api/v1/content/:type/:id/accesssegments:read
PUT/api/v1/content/:type/:id/accesssegments:manage
POST/api/v1/graphqlcontent:read + per-operation checks
GET/api/v1/graphql — the schema as SDLcontent:read
GET/api/v1/graphql/playground — only when API_DOCS is onpublic page
POST/api/v1/mcp — stateless JSON-RPC; GET/DELETE → 405token; each tool checks its own permission

MCP is off unless MCP_ENABLED=true. A multi-workspace token names the workspace with X-Workspace-Id or ?workspaceId= on the URL.

curl -X POST https://cms.example.com/api/v1/mcp \
  -H 'Authorization: Bearer orthacms_…' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
        "params": { "name": "content_list", "arguments": { "typeName": "article", "pageSize": 5 } } }'

Admin API

Authentication and account

MethodPathPermission
POST/api/auth/login, /api/auth/logoutpublic
GET/api/auth/mesession
GET/api/auth/invite/:tokenpublic
POST/api/auth/invite/acceptpublic
GET/api/auth/reset/:tokenpublic
POST/api/auth/resetpublic
GET/api/auth/password-recovery — { available }public
POST/api/auth/password-recovery — 202 for any address, 404 without mailpublic
GET, PUT/api/preferences — the theme and the admin’s panel layout; PUT is a partial patchsession
GET/api/users/:id/sessionsusers:update
DELETE/api/users/:id/sessions/:sessionIdusers:update
DELETE/api/users/:id/sessions — sign out everywhereusers:update

Single sign-on

All public and rate-limited. See single sign-on.

MethodPathWhat
GET/api/auth/ssoRegistered providers, for the sign-in buttons. [] when none
GET/api/auth/sso/:provider/startRedirects to the provider
GET/api/auth/sso/:provider/callbackVerifies, opens a session, redirects into the admin
POST/api/auth/sso/:provider/callbackThe same, for SAML’s form post
POST/api/auth/sso/:provider/backchannel-logoutEnds sessions on the provider’s word

Content, revisions and schema

MethodPathPermission
GET/api/content-schema, /api/content-schema/:name, /:name/filter-fieldscontent:read
GET/api/content/:type — ?source=own|shared|all, ?sourceWorkspaceId=content:read
GET/api/content/:type/:id (+ /media, /relations, /relations/:field)content:read
GET/api/content/:type/:id/usages — links from other workspacescontent:read
GET/api/content/:type/:id/references — links from this workspace, and what a permanent delete does to themcontent:read
POST/api/content/:typecontent:create
PATCH/api/content/:type/:id — replaces the values bagcontent:update
POST/api/content/:type/:id/publish, /unpublishcontent:publish
DELETE/api/content/:type/:id, /api/content/:type/:id/permanentcontent:delete
POST/api/content/:type/:id/restorecontent:delete
POST/api/content/:type/bulk/publish, /bulk/publish/preview, /bulk/unpublishcontent:publish
POST/api/content/:type/bulk/delete, /bulk/restore, /bulk/purgecontent:delete
GET/api/content/:type/:id/revisions, /revisions/:numbercontent:read
POST/api/content/:type/:id/revisions/:number/restorecontent:update
POST/api/content/:type/:id/revisions/:number/publishcontent:publish

The create, the update and the two version routes take askBeforeSiblingSync and allowInvalidSiblings (the create also skipReferrers): the way the admin asks before a write that changes other languages or records. See errors.

Saved views

See saved views. Sharing a view with the workspace additionally needs views:share, checked per write.

MethodPathPermission
GET, POST/api/viewscontent:read
PATCH, DELETE/api/views/:idcontent:read
PUT, DELETE/api/views/:id/defaultcontent:read

Export and import

See export and import.

MethodPathPermission
POST/api/content/:type/export/preview, /exportcontent:export
POST/api/content/:type/import/preview, /importcontent:import (+ create/update per write)
GET/api/content/:type/import/templatecontent:read

Media

MethodPathPermission
GET/api/media/assets, /api/media/foldersmedia:read
GET/api/media/assets/:id/rawmedia:read
POST/api/media/assets, /api/media/folders, /api/media/assets/:id/duplicatemedia:create
PATCH/api/media/assets/:id, /api/media/folders/:idmedia:update
DELETE/api/media/assets, /api/media/folders/:idmedia:delete

Users, tokens and activity

MethodPathPermission
GET/api/users, /api/users/:id, /api/users/rolesusers:read
GET/api/users/:id/sign-inusers:update
POST/api/users/invites, /api/users/:id/invites/resendusers:create
POST/api/users/invites/batch — all or nothingusers:create and users:read
PATCH/api/users/:idusers:update
POST/api/users/:id/disable, /enable, /password-resetusers:update
DELETE/api/users/:id/invitesusers:delete
POST/api/users/:id/reveal-link — 409 MAIL_NOT_CONFIGURED without mailusers:manage
GET/api/api-tokenstokens:read
POST/api/api-tokenstokens:create
DELETE/api/api-tokens/:idtokens:delete
GET/api/activity, /api/activity/export (CSV)activity:read
GET/api/activity/entries/:entryIdcontent:read
GET/api/activity/dead-lettersactivity:read
POST/api/activity/dead-letters/:id/retry — 404 unknown/delivered, 409 not parkedactivity:manage

Workspaces

MethodPathPermission
GET/api/workspacesworkspaces:read
GET/api/workspaces/slug-availableworkspaces:create
POST/api/workspacesworkspaces:create
PATCH/api/workspaces/:id — incl. { "isShared": true }workspaces:update
POST/api/workspaces/:id/archive, /unarchiveworkspaces:update
GET/api/workspaces/:id/archive-impactworkspaces:update
DELETE/api/workspaces/:idworkspaces:delete
GET/api/workspaces/:id/entry-countworkspaces:delete
POST/api/workspaces/:id/membersworkspaces:update
DELETE/api/workspaces/:id/members/:userIdworkspaces:update
POST/api/workspaces/:id/content — { slug, sourceWorkspaceId? }workspaces:update
DELETE/api/workspaces/:id/content/:slug — ?source= for a shared grantworkspaces:update
GET/api/workspaces/:id/content/:slug/entry-countworkspaces:update
GET/api/workspaces/:id/shared-sourcesworkspaces:update
GET/api/content-typesworkspaces:create or workspaces:update

shared-sources is new in 0.7.0: the shared workspaces this one can take a shared grant from, each with the types it offers.

Alarms

See alarms.

MethodPathPermission
GET/api/alarms/rulesalarms:read
POST/api/alarms/rules, /api/alarms/rules/previewalarms:manage
PATCH, DELETE/api/alarms/rules/:idalarms:manage
POST/api/alarms/rules/:id/rescanalarms:manage
GET/api/alarms/findings, /findings/by-entry, /findings/summaryalarms:read

Segments

See segments.

MethodPathPermission
GET/api/segments, /api/segments/lookup, /api/segments/:idsegments:read
POST/api/segmentssegments:manage
PATCH, DELETE/api/segments/:idsegments:manage
GET/api/segments/entries/:entryIdsegments:read
GET/api/segments/:id/usage — per workspacesegments:read and content:read
PUT/api/segments/entries/:entryIdsegments:manage

The admin itself sends audiences in the entry save’s extensions, so access commits with the save and lands in its revision.

Publication protection

See publication protection.

MethodPathPermission
GET/api/protection/rulesprotection:manage
PUT, DELETE/api/protection/rules/:kind/:slugprotection:manage
GET/api/protection/entries/:type/:idcontent:read
GET/api/protection/entries/:type/statuscontent:read
GET/api/protection/entries/:type/:id/reviewerscontent:update
POST, DELETE/api/protection/entries/:type/:id/requestcontent:update
POST, DELETE/api/protection/entries/:type/:id/approvecontent:approve
GET/api/protection/types/:typecontent:read
GET/api/protection/queue — ?mine=1content:read
GET/api/protection/queue/approved — what you approved, last 30 dayscontent:read

A blocked publish is 409 (protection.insufficient_approvals, protection.token_refused), never 403. { "bypass": true } on the publish body without protection:manage is 403 protection.bypass_refused.

Webhooks

Global, not workspace-scoped. See webhooks.

MethodPathPermission
GET/api/webhook-eventswebhooks:read
GET/api/webhooks, /api/webhooks/:idwebhooks:read
POST/api/webhooks — returns the secret oncewebhooks:manage
PATCH, DELETE/api/webhooks/:idwebhooks:manage
POST/api/webhooks/:id/secret (rotate), /api/webhooks/:id/test (ping)webhooks:manage
GET/api/webhooks/:id/deliveries — ?status=, ?eventKind=webhooks:read
GET/api/webhooks/:id/deliveries/:deliveryIdwebhooks:read
POST/api/webhooks/:id/deliveries/:deliveryId/redeliverwebhooks:manage

A refused URL is 422, not 400. A delivery id from another endpoint is 404.

Schema builder

Global, not workspace-scoped. See the schema builder.

MethodPathPermission
GET/api/schema-builder/documentcontent:read
POST/api/schema-builder/planschema:manage + an editable server
POST/api/schema-builder/apply — 202 { operationId, bootId }schema:manage + an editable server
GET/api/schema-builder/operations/:idschema:manage

Plan and apply answer 403 schema-builder.disabled in production, with SCHEMA_BUILDER off, or without the source tree — whatever the caller’s role. A stale draft or a second apply is 409 (schema-builder.stale, schema-builder.busy).

Localization, insights and copilot

MethodPathPermission
GET/api/i18n/localessession
GET/api/i18n/content/:type/:id/localescontent:read
POST/api/i18n/content/:type/locale-summarycontent:read
GET/api/insights/content/{totals,pipeline,velocity,stale,unshipped,punchcard}content:read
GET/api/insights/content/stale/records, /unshipped/recordscontent:read
GET/api/insights/media/{storage,uploads,alt}, /alt/missingmedia:read
GET/api/insights/i18n/coveragecontent:read
GET/api/insights/protection/reviewscontent:read
POST/api/copilot/runscopilot:use
POST/api/copilot/runs/:runId/permission, /permission/extendcopilot:use
GET/api/copilot/conversations, /conversations/:idcopilot:use
PATCH/api/copilot/conversations/:idcopilot:use
GET/api/copilot/models, /proposals, /proposals/:idcopilot:use
GET/api/copilot/skills, /api/copilot/suggestionscopilot:use
GET/api/copilot/usage — today’s spend and quotacopilot:use
GET/api/copilot/skills/manage, /skills/:idcopilot:skills:manage
POST, PATCH, DELETE/api/copilot/skills, /skills/:idcopilot:skills:manage

API reference document

PathWhat
/referenceInteractive API reference
/reference/jsonRaw OpenAPI
  • Outside the /api prefix; both paths are configurable in the host’s docs options.
  • On by default outside production; in production only with API_DOCS=true.
  • MCP is not in the document — JSON-RPC over one route is not REST.