Skip to content

Documentation JSON-RPC

Choose a guide from the library.

JSON-RPC

Authenticate, fetch content, and understand every part of the response.

Examples use an illustrative schema. Replace placeholders with your own configuration.

Authenticate and choose a content type

Replace YOUR_CMS_HOST, YOUR_WORKSPACE_UUID and YOUR_PUBLIC_KEY. These public examples use the illustrative content_item schema from Getting started. Substitute your actual type and field API identifiers; they are case-sensitive.

Send Authorization: Bearer with every external call, including introspection. Studio cookies do not grant delivery access. A public key reads published snapshots; secret-key previews belong on your server.

Fetch a collection

Run the curl example in your terminal or use the fetch example inside a JavaScript module. Neither this documentation page nor its Copy buttons send content requests.

Start with published content and inspect the response before adding filters. Use the content type API identifier, rather than its display name or UUID.

Terminal · published collection
curl 'https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/rpc' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_PUBLIC_KEY' \
  --data '{
  "id": 1,
  "params": {
    "limit": 5,
    "mode": "published",
    "model": "content_item"
  },
  "method": "content.list",
  "jsonrpc": "2.0"
}'
Application module · collection and errors
const url = "https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/rpc";
const response = await fetch(url, {
  method: 'POST',
  credentials: 'omit',
  headers: {
    Authorization: 'Bearer YOUR_PUBLIC_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
  "id": 1,
  "params": {
    "limit": 5,
    "mode": "published",
    "model": "content_item"
  },
  "method": "content.list",
  "jsonrpc": "2.0"
})
});
const body = await response.json();
if (!response.ok) throw new Error(body.error?.message ?? body.error ?? `HTTP ${response.status}`);
if (body.error) throw new Error(body.error.message);
console.log(body.result);

Fetch one entry

Replace ENTRY_UUID with an ID returned by the collection. The GraphQL request sends it in variables; JSON-RPC sends params.id; REST puts it in the path. Missing or unpublished items return REST 404, GraphQL null, or RPC error -32004.

Terminal · single entry
curl 'https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/rpc' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_PUBLIC_KEY' \
  --data '{
  "id": 1,
  "params": {
    "id": "ENTRY_UUID",
    "mode": "published",
    "model": "content_item"
  },
  "method": "content.get",
  "jsonrpc": "2.0"
}'
Application module · single entry
const url = "https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/rpc";
const response = await fetch(url, {
  method: 'POST',
  credentials: 'omit',
  headers: {
    Authorization: 'Bearer YOUR_PUBLIC_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
  "id": 1,
  "params": {
    "id": "ENTRY_UUID",
    "mode": "published",
    "model": "content_item"
  },
  "method": "content.get",
  "jsonrpc": "2.0"
})
});
const body = await response.json();
if (!response.ok) throw new Error(body.error?.message ?? body.error ?? `HTTP ${response.status}`);
if (body.error) throw new Error(body.error.message);
console.log(body.result);

Understand the response

Responses echo jsonrpc and your request id, then contain either result or error. content.list result has data and meta.next_cursor/limit; content.get result wraps one entry in data. Errors can arrive with HTTP 200: inspect error as well as HTTP status.

This response is synthetic. Fields can be absent or null because optional fields, historical entries and incomplete drafts do not guarantee a value. A null next cursor means the last page.

Synthetic collection response · not workspace data
{
  "id": 1,
  "result": {
    "data": [
      {
        "id": "11111111-1111-4111-8111-111111111111",
        "fields": {
          "position": 1,
          "title": "Getting started",
          "image": null,
          "related": null,
          "summary": "Example summary",
          "publication_date": "2026-10-15"
        },
        "published_at": "2026-10-15T12:00:00Z"
      }
    ],
    "meta": {
      "limit": 5,
      "next_cursor": null,
      "previous_cursor": null,
      "has_next_page": false,
      "has_previous_page": false
    }
  },
  "jsonrpc": "2.0"
}

Filter and sort

Collections support up to ten AND filter clauses and 4 KB of filter JSON. eq checks equality (null matches missing values); in accepts 1–100 values. gt/gte/lt/lte are supported for numbers, dates and UTC date/times. Fields and values must match your schema.

Sort by one supported scalar field using asc or desc. Text, choice, color, boolean, number, date and date/time support sorting. Reference/media and Markdown/long-text fields do not. Dates use ISO strings; timestamps must include a UTC offset.

The example selects title/position/summary, filters position >= 1 and sorts by position. REST accepts existing filters/sort JSON query parameters and readable aliases: filter[position][gte]=1&sort=-position&select=title,position. Do not combine filter with filters. RPC uses JSON values; GraphQL exposes typed inputs. Locale examples require Spanish to be enabled and its content published.

Readable filters use filter[FIELD][OP]=VALUE for eq/gt/gte/lt/lte. For in, repeat filter[FIELD][in][]=VALUE. Numbers, true/false and null are JSON literals; ordinary text is unquoted. Quote literal-looking text, such as filter[title][eq]="123", to retain its string type. sort=position is ascending; sort=-position is descending. URLSearchParams handles escaping. The same schema validation, ten-clause and 4 KB limits apply.

Terminal · typed query options
curl 'https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/rpc' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_PUBLIC_KEY' \
  --data '{
  "id": 1,
  "params": {
    "filters": [
      {
        "field": "position",
        "op": "gte",
        "value": 1
      }
    ],
    "limit": 5,
    "locale": "es",
    "mode": "published",
    "model": "content_item",
    "sort": {
      "direction": "asc",
      "field": "position"
    }
  },
  "method": "content.list",
  "jsonrpc": "2.0"
}'
Application module · encoded query
const url = "https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/rpc";
const response = await fetch(url, {
  method: 'POST',
  credentials: 'omit',
  headers: {
    Authorization: 'Bearer YOUR_PUBLIC_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
  "id": 1,
  "params": {
    "filters": [
      {
        "field": "position",
        "op": "gte",
        "value": 1
      }
    ],
    "limit": 5,
    "locale": "es",
    "mode": "published",
    "model": "content_item",
    "sort": {
      "direction": "asc",
      "field": "position"
    }
  },
  "method": "content.list",
  "jsonrpc": "2.0"
})
});
const body = await response.json();
if (!response.ok) throw new Error(body.error?.message ?? body.error ?? `HTTP ${response.status}`);
if (body.error) throw new Error(body.error.message);
console.log(body.result);

Move forward and backward

For numbered reads use page=1&pageSize=6 in REST, {page: 1, pageSize: 6} in RPC params, or collection(page: 1, pageSize: 6) in GraphQL. REST/RPC add meta.page/page_size; GraphQL exposes page/pageSize. Numbered browsing skips at most 10,000 entries, with sizes 1–100 and default 25. Do not combine page/pageSize with limit/after/before. No exact totals are calculated.

Cursor page sizes are 1–100, default 25. Pass next_cursor as after to move forward, or previous_cursor as before to move backward; never send both. REST/RPC return those cursors plus has_next_page and has_previous_page inside meta. GraphQL exposes nextCursor, previousCursor, hasNextPage and hasPreviousPage on each collection. Treat cursors as opaque and let URLSearchParams encode them for REST.

Keep workspace, model, filters, sort, locale and published/preview mode unchanged. New cursors also bind schema revision and expire after one hour; restart without after/before when they expire or the schema changes. Page size and field selection may change. Existing UUID boundaries and issued advanced forward cursors remain accepted for compatibility.

Default reads use UUID ordering, which is not creation time. Sorting uses entry ID ascending to break ties and puts missing values last. Backward traversal retains this display order. Reads fetch one extra row to detect the page boundary; exact totals are not calculated. Pages remain live across concurrent writes, so restart if content moves or a page becomes empty.

Page boundaries · adapt the first request
// Keep params.model/mode/locale/filters/sort unchanged.
// Forward: add after with result.meta.next_cursor.
"after": "RETURNED_NEXT_CURSOR"
// Or backward: remove after and add before instead.
"before": "RETURNED_PREVIOUS_CURSOR"

Choose a language

Set locale to an enabled workspace locale, such as es or pt-BR. Omit it for the workspace default. REST uses a query parameter, GraphQL a root field argument/variable, and RPC params.locale.

Localized text has independently published translations. Other fields stay shared. Missing translated text uses the default-language value only when the workspace enables fallback; otherwise it can be absent/null. Preview fallback uses drafts, not public snapshots. Unpublishing the default entry hides its translations.

Spanish · change the locale parameter
// Inside params:
"locale": "es"

Preview drafts on your server

Replace YOUR_SECRET_KEY only in server-owned code. Authorize your own application’s user before returning draft data. Public keys cannot read previews.

Saving changes does not update a published snapshot until you publish again. Preview requests read current drafts and draft reference targets; published reads use current published snapshots.

Server terminal · draft collection
curl 'https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/rpc' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_SECRET_KEY' \
  --data '{
  "id": 1,
  "params": {
    "limit": 5,
    "mode": "preview",
    "model": "content_item"
  },
  "method": "content.list",
  "jsonrpc": "2.0"
}'

References, images and publication

Request IDs, notifications and protocol errors

Handle failures

Always check HTTP status and parse the response body. REST returns error/code. GraphQL returns an errors array; JSON-RPC returns an error object with code/message. The fetch examples above handle both HTTP and protocol errors.

401: check the workspace and bearer key. 403: previews require a secret key. Invalid fields/locales/queries/cursors require changing the request, not retrying it unchanged. Avoid logging credentials or private response bodies.

Need a hand? Check troubleshooting. Documentation describes the locally verified pilot; production qualification remains in progress.