Beta . Ortha CMS is in public beta. Every plan is free until it ends.
Get started
Menu

Delivery · 17 / 23

Delivery API

REST and GraphQL over one implementation, with the same token, the same permissions and the same visibility rules.

200 OK · 38 ms

Request

GET /api/v1/content/article/group/9b2d7c1e?locale=deAuthorization: Bearer orthacms_0TYI4Q…X-Workspace-Id: 3c1e5a…

Response

{  "id": "e41f0a2b-…",  "status": "published",  "locale": "de",  "localeGroupId": "9b2d7c1e-…",  "publishedAt": "2026-09-20T14:03:11.000Z",  "values": {    "title": "Die stille Rückkehr der Nachtzüge",    "slug": "nachtzuege"  }}

Request

POST /api/v1/graphql query {  article(localeGroupId: "9b2d7c1e-…", locale: "de") {    title    status    author { name }  }}

Response

{  "data": {    "article": {      "title": "Die stille Rückkehr der Nachtzüge",      "status": "PUBLISHED",      "author": { "name": "Mara Lind" }    }  }}

Request

POST /api/v1/mcp {  "jsonrpc": "2.0",  "method": "tools/call",  "params": {    "name": "content_get",    "arguments": {      "typeName": "article",      "localeGroupId": "9b2d7c1e-…",      "locale": "de"    }  }}

Response

{  "jsonrpc": "2.0",  "result": {    "structuredContent": {      "id": "e41f0a2b-…",      "status": "published",      "values": {        "title": "Die stille Rückkehr der Nachtzüge"      }    }  }}

How it works

  1. 01

    Create a read-only token for your front end.

  2. 02

    Read entries over REST or GraphQL, by id or by language group.

  3. 03

    Bring relations, media and translations back in the same request.

REST
GET /api/v1/content/:type
GraphQL
POST /api/v1/graphql
By default
Published content only

In depth · For the engineers building the front end

Two protocols that cannot disagree

REST and GraphQL are served by the same code — the same query, the same guards, the same visibility rule.

One implementation, two front doors

GraphQL at /api/v1/graphql calls the same services as the REST routes, so token checks, workspace grants and the published-only rule apply identically. Only a bearer token gets in; an admin session cookie does not.

The same filtered, localised request written as a REST URL and as a GraphQL query, above the single JSON response both return.

Same filter, same visibility rule, same answer.

The read surface

Show the table
Route What it returns
GET /v1/content-types Every type granted to this token’s workspace.
GET /v1/content-types/:name One type’s full field schema.
GET /v1/content/:type Published entries, with filter, sort, fields and locale.
GET /v1/content/:type/:id One published entry.
…/relations/:field A page of one relation field’s links.
…/media Media fields resolved to metadata and URLs.
…/translations The entry’s other language rows.

Details that decide an integration

Scoped tokens
Read or full, granted to specific workspaces; read tokens see published entries only.
Batch writes
A full token creates, patches, publishes and deletes in batches, with a verdict per item.
Batch publish is all-or-nothing
Candidates are re-validated inside one transaction before they go live.
The API describes itself
An OpenAPI document is generated from the same types the routes use.
The admin’s filter grammar
The filter you build in the content library is the JSON your front end sends.
Sparse fieldsets are enforced
Ask for three fields and you get three; an unknown field is an error.

Questions

How do I know when content changes?

Subscribe a webhook to entry.published and entry.unpublished, or keep polling if you prefer.

Can I read drafts for a preview environment?

Yes, with a full-access token and status=draft or status=any. Give the public site a read-only token and the preview build a full one.

What if an entry lacks the requested language?

It is not found by default. Ask for the fallback and you get the default language’s row.

Full reference in the docs