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.
curl --get 'https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/content/content_item' \
-H 'Authorization: Bearer YOUR_PUBLIC_KEY' \
--data-urlencode 'limit=5' \
--data-urlencode 'locale=en'
const url = new URL("https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/content/content_item");
url.search = new URLSearchParams({
"limit": "5",
"locale": "en"
}).toString();
const response = await fetch(url, {
credentials: 'omit',
headers: {
Authorization: 'Bearer YOUR_PUBLIC_KEY',
'Content-Type': 'application/json'
}
});
const body = await response.json();
if (!response.ok) throw new Error(body.error?.message ?? body.error ?? `HTTP ${response.status}`);
console.log(body.data);
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.
curl --get 'https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/content/content_item/ENTRY_UUID' \
-H 'Authorization: Bearer YOUR_PUBLIC_KEY' \
--data-urlencode 'locale=en'
const url = new URL("https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/content/content_item/ENTRY_UUID");
url.search = new URLSearchParams({
"locale": "en"
}).toString();
const response = await fetch(url, {
credentials: 'omit',
headers: {
Authorization: 'Bearer YOUR_PUBLIC_KEY',
'Content-Type': 'application/json'
}
});
const body = await response.json();
if (!response.ok) throw new Error(body.error?.message ?? body.error ?? `HTTP ${response.status}`);
console.log(body.data);
Understand the response
Collections return data: [] and meta.next_cursor/limit. Items return data: {...}. Entry values are inside fields, alongside id and published_at. An empty array is success with no matching published content; an unavailable item is HTTP 404.
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.
{
"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
}
}
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.
curl --get 'https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/content/content_item' \
-H 'Authorization: Bearer YOUR_PUBLIC_KEY' \
--data-urlencode 'filters=[{"field":"position","op":"gte","value":1}]' \
--data-urlencode 'limit=5' \
--data-urlencode 'locale=es' \
--data-urlencode 'select=title,position,summary' \
--data-urlencode 'sort={"direction":"asc","field":"position"}'
const url = new URL("https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/content/content_item");
url.search = new URLSearchParams({
"filters": "[{\"field\":\"position\",\"op\":\"gte\",\"value\":1}]",
"limit": "5",
"locale": "es",
"select": "title,position,summary",
"sort": "{\"direction\":\"asc\",\"field\":\"position\"}"
}).toString();
const response = await fetch(url, {
credentials: 'omit',
headers: {
Authorization: 'Bearer YOUR_PUBLIC_KEY',
'Content-Type': 'application/json'
}
});
const body = await response.json();
if (!response.ok) throw new Error(body.error?.message ?? body.error ?? `HTTP ${response.status}`);
console.log(body.data);
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.
// Forward: keep the same URL and query options.
url.searchParams.delete('before');
url.searchParams.set('after', body.meta.next_cursor);
// To go backward instead, remove after and set before to the non-null
// body.meta.previous_cursor. Never send both boundary parameters.
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.
url.searchParams.set('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.
curl --get 'https://YOUR_CMS_HOST/api/v1/projects/YOUR_WORKSPACE_UUID/preview/content_item' \
-H 'Authorization: Bearer YOUR_SECRET_KEY' \
--data-urlencode 'limit=5' \
--data-urlencode 'locale=en'
References, images and publication
A parent cannot publish until its referenced entries and assets are published. Published dependencies cannot be unpublished while used, and deletion is blocked while draft or published references remain.
REST and RPC expand reference IDs into delivery objects with id/fields; the SDK preserves those nested wire shapes. Expansion is bounded, so keep the target ID when deeper fields are absent. GraphQL traverses references only when selected: related { id fields { title } }.
Image/file delivery includes metadata and a short-lived URL. Keep that URL intact; fetch fresh content to obtain a new URL after expiry. Asset requests recheck availability and key validity. PDFs download rather than display inline. Draft metadata stays private until publication.
Treat Markdown as source, and render it with a renderer that excludes raw HTML. Do not inject arbitrary source into innerHTML.
REST reference
GET /content/:model_key and /content/:model_key/:entry_id read published content. Replace content with preview for server-secret draft reads. select is a comma-separated string; locale, page/pageSize or limit/after/before, filters and sort are query parameters.
GET /api/v1/projects/:project_id/schema exports model metadata with a valid project bearer key. It contains schema definitions, not entry content. SDK codegen uses this endpoint.
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.