Who this is for. Three readers: the developer building a headless front end that reads content from Core dna, the JavaScript developer adding live behaviour to a server-rendered site, and the integrations engineer moving data between Core dna and another system. Each section below says which of the three it serves. It assumes you can already build and deploy a template (Core dna for site builders) and know what a layout, a layout area and a component are (Input types, layouts, components and images).
Every Core dna site answers three kinds of API request, on its own hostname, under /cdna-api/. Teams tend to reach for REST for everything and then find a home page needs five round trips. Pick the surface first: most of what follows depends on which one you chose.
The units, in order
- The REST API: predicting an endpoint, filtering, paging and shaping the response, and the errors you will meet.
- Writing content: how a write changes versioned content: revisions, areas, component collections, custom fields and URLs.
- Reading with GraphQL: one query across several entity types, with fields separated by commas, not spaces.
- Custom endpoints: a job with a web event standing in for code you would otherwise write, and how to secure one.
- Credentials and access: authentication, authorisation and the CSRF token.
- Client code and imports: the client layer, external ids, bulk reads and failure handling.
- AI assistants and the MCP server: what the MCP server does for an editor or an agent.
Three surfaces, one platform
| Surface | URL | What it is for |
| REST | /cdna-api/<module>/<entity> | The complete surface. Everything a site holds can be listed, read, created, edited and deleted here, one entity type per request. |
| GraphQL | POST /cdna-api/graphql | Reads only. Fetches several entity types in one request, with the fields you name and nothing else. |
| Custom endpoint | /cdna-api/webhook/<slug> | An endpoint you define in the admin as a job with a web event, with no framework code. A synchronous job answers the request itself, with whatever body and status its actions produce. |
Which one to use
Decide before reading further. The consequences in the last three rows are what should decide it, not habit.
| REST | GraphQL | Custom endpoint |
| Choose it for | Writes, uploads, and any read of one entity type | A read that spans several entity types | A question neither of the others expresses: a calculation, a lookup across records, a response in someone else's format |
| Methods | GET, POST, PUT, DELETE | POST only | The ones set on the web event, from GET, POST, PUT and DELETE |
| Can write | Yes | No. Mutations are rejected | Whatever the job's actions do |
| Round trips for pages, posts and a menu | Three | One | One |
| Caching | The platform caches no API response. A GET is keyed by its URL, so your own layer can cache it | A POST has no URL to key on: cache it in your application, by query | As REST, for a GET web event; the job runs on every request that reaches it |
| Where its reference lives | The API reference, and the models in the generated Reference | This section; nothing is generated for it | You write it, because you defined it |
REST in one request
For all three readers. A list and a detail call, each with the fields chosen by properties. These are real responses from an example site.
Shell
curl -G 'https://www.example.com/cdna-api/blogs/blogs' \
--data-urlencode 'properties={ id, name, slug, posts: publishedPostsCount }' \
--data-urlencode 'limit=2' \
--data-urlencode 'orderBy=id:ASC' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
JSON
{"result":"success","totalCount":5,"data":[
{"id":1,"name":"Blogs Listing","slug":"main-blog","posts":12},
{"id":2,"name":"Events Listing","slug":"events-listing","posts":8}
]}
Shell
curl -G 'https://www.example.com/cdna-api/blogs/blog/1' \
--data-urlencode 'properties={ blog { id, name, slug, created } }' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
JSON
{"result":"success","data":{"blog":{"id":1,"name":"Blogs Listing","slug":"main-blog","created":"2019-08-27T09:27:38-05:00"}}}
This is the whole of the platform, not a subset: anything an editor can do in the admin to a record has an endpoint here. The REST API unit explains how to predict any endpoint without looking it up, and Writing content how a write changes versioned content: revisions, areas, component collections, custom fields and URLs.
GraphQL in one request
For the headless developer. Two entity types, three fields each, one round trip. The response is keyed by the name you gave each part of the query.
Shell
curl 'https://www.example.com/cdna-api/graphql' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{"query": "query { latest: BlogPost(filter: {blog.id: 1}, first: 3, orderBy: {published: \"DESC\"}) { id, name, fullPath }, sections: Page(filter: {parent.id: 1}, first: 2, orderBy: {position: \"ASC\"}) { id, name, fullPath } }"}'
JSON
{"result":"success","data":{
"latest":[{"id":35,"name":"New","fullPath":"/blog/new"},{"id":12,"name":"99 problems but a batch ain’t one","fullPath":"/blog/99-problems-but-a-batch-ain-t-one"},{"id":11,"name":"What is Roast Development?","fullPath":"/blog/what-is-roast-development"}],
"sections":[{"id":79,"name":"Form Example","fullPath":"/form-example"},{"id":78,"name":"Form Integration Example","fullPath":"/form-integration-example"}]
}}
It reads and never writes: every write goes through REST. The GraphQL API has the query shape, the arguments and the rule that keeps a query from failing: fields on one line must be separated by commas, not spaces.
A custom endpoint in one request
For the JavaScript developer and the integrations engineer. This is the surface few people know exists. In the admin, under Orchestration, a job gets a web event trigger with a slug and the HTTP methods it answers. Mark the job synchronous and give it an HTTP response action, and the platform answers the request with the status code, headers and body that action produces. The body is a template, so it can read the request and any records the job's earlier actions fetched. (Jobs were called hooks, and the route still says webhook.)
Shell
curl 'https://www.example.com/cdna-api/webhook/store-hours?date=2026-10-01'
JSON
{"date":"2026-10-01","open":"09:00","close":"17:30","holiday":false}
That response is whatever the job's author wrote; nothing about its shape comes from the platform. Two defaults to know before you build one: a web event is open to anyone unless its authentication flag is set, and that flag admits backend users only, not site members. Anything after the slug in the path is passed to the job as well, so one web event can serve /cdna-api/webhook/store-hours/sydney and /…/melbourne. The whole pattern, with its authentication and its failure modes, is in Custom endpoints.
True of all three
- One site, one API. There is no central endpoint. Each site answers on its own hostname and sees its own records.
- No cross-origin headers. The platform sends none, so a browser on another origin cannot read a response. A headless front end calls the API from its server, not from the visitor's browser; scripts on the site's own pages are same-origin and unaffected.
- Site members are refused. A signed-in member calling a REST or GraphQL endpoint is refused on everything except the routes built for visitors, with a 404 by default, or a 403 where the site’s access-denied setting asks for it (Responses and errors). The routes built for visitors: the cart, checkout and payment methods, form submission, the sign-in and registration calls, and reading the member's own profile at
/cdna-api/users/account. Members reach content through templates, not through the API.
- How a request authenticates (an OAuth access token, the preferred way; an API key, the older one; the session cookie; or Basic credentials) is set out on the API reference's Authentication page.
Covered elsewhere
- Every endpoint, with parameters and example requests in raw HTTP, cURL, PHP, Python and JavaScript: the API reference.
- Task-sized: the how-tos under APIs and JavaScript in the how-to library.
- The records a template can read without any API call: the Models reference.
Contents
7 units, in reading order.
How the REST API is laid out, so that you can predict an endpoint instead of looking it up, then its parameters, its answers and the checks that confirm a request did what you asked. For anyone who reads or writes records over REST. 8 pages
Predicting an endpoint
Every module answers under /cdna-api on a predictable prefix, and every entity gets the same requests from the same generic controllers. The rule, the handful of prefixes that differ from it, and the standard set, so you can write a request without looking it up. Read more →
Filters, ordering and paging
Everything in a list's query string that is not a shared parameter is a filter on the entity's own fields. How to write one, how to page and order a list, and the two habits that keep a filter honest: check it against a count, and always send a limit. Read more →
Shaping the response with properties
One parameter decides what a response contains: properties, a selector that names fields, renames them, reaches into relations and calls getters with arguments. How to write one, what shape the response then takes, and how a mistake shows up, which is not as an error. Read more →
Query string or request body
Where each part of a request goes: filters and paging in the query string, the record in the body, and properties in either, depending on the method. The long selector that does not fit in a URL, and the one client that cannot send it. Read more →
Responses and errors
Every JSON answer has the same envelope, and the status codes mean the same thing everywhere. What each code tells a client to do, the 401 that has no body, the 403 that means “refresh your token”, and how to put a validation error next to the field that caused it. Read more →
A relation as an id or an object
A payload can set a relation with a bare id or with an object carrying one, and both do the same thing. What the neighbouring forms do, which is not the same thing at all: an object with more fields edits the related record, and one without an id creates a new one. Read more →
Confirming a request did what you asked
What to send for each kind of request, and the checks that confirm a read or a write did what you asked. Each one below was reproduced on this site. Read more →
The generated documentation
What a record looks like in a response is declared in the platform's code, and the documentation of it is generated from that declaration. Which pages are generated, which are written by hand, and which to trust first when they differ. Read more →
The reference lists the write endpoints; this unit explains how a write behaves, so you choose the write shape that keeps the content you expect. Revisions and the live revision, the three ways to write one, going live and rolling back, writing areas and component collections, custom fields, custom URLs, and writes that may have applied when the client gave up waiting. 8 pages
Versioned content and the live revision
Most content does not change when you edit it: the edit becomes a new revision beside the old ones, and only the one marked live is what visitors see. Which records work this way, the three separate things that decide what is on the site, and how to read them. Read more →
patchRevision, revisions, or a revision by id
Three request shapes write the content of a versioned record, and they differ in what they keep. patchRevision copies a revision and changes only what you send; revisions without an id replaces everything; revisions with an id rewrites a revision in place. Which to use, with the two keys that make a patch safe. Read more →
Making a revision live, rolling back and publishing
Choosing which revision is live, going back to an older one, and deciding when visitors see the record at all. The request for each, the routes the types with their own rules use, and how to pin a patch so it does not undo a rollback. Read more →
Writing areas
A layout area takes a value in the shape its input type expects, and that shape is often not the one a read gives back. The write shape for each input type, the ones that fail loudly and the ones that do not, and why a record read with areasContent cannot be sent back as it is. Read more →
Editing component collections
A component collection is a list of components in one layout area: the sections of a page, the blocks of a guide page. Which key changes it, how positions count, why a position can point at the wrong element after a removal, and how to place a global component. Read more →
Custom fields
Custom fields are the extra fields an administrator adds to menus, products, users and a dozen other record types. How to find a record's fields, read them by name, write one without touching the others, and why the answer to a write can look as if fields were lost. Read more →
Custom URLs and slugs
A record's address comes from its slug, or from a custom URL that replaces it. How slugs are made and checked, how to set and clear a custom URL, and three things to get right: clearing a custom URL, setting one only after the record is created, and starting it with a slash. Read more →
Retries and writes that may have applied
A write that timed out may have happened. Which requests are safe to send again, what a repeat of the others does, measured on this site, and how to make a create, a collection edit and a cart change safe to retry. Read more →
The read-only endpoint that fetches several entity types in one round trip, with only the fields you name. Mainly for the headless developer whose front end needs pages, posts and a menu on one screen. 1 page
A web event on a synchronous job (called a hook before the module was renamed Orchestration) is an API endpoint you define in the admin, with no framework code. For the JavaScript developer who needs one answer REST does not give, and the integrations engineer whose partner calls the site. 2 pages
Custom endpoints with synchronous jobs
A job with a web event, marked synchronous and ending in an HTTP response action, is an API endpoint defined in the admin, with no repository change. What the flag costs and rules out, who the endpoint runs as, shaping JSON replies and errors, a worked lookup and a worked write, and how to secure it. Read more →
Securing a custom endpoint
What to add for each kind of caller, a signature check for a partner system, three things to add yourself, and the signs that an endpoint has outgrown a job. Read more →
Which credential each kind of client should send, how the platform reads it, and why the same request answers a site member, a backend user and a script differently. For every API reader, and above all the integrations engineer. 2 pages
Authentication and authorisation
Most integration bugs are the wrong credential, not a wrong implementation. The five strategies in the order the platform tries them, which one each kind of client should use, API keys, a complete OAuth flow, the CSRF 403, visitor routes, and why an admin key proves nothing about production. Read more →
The CSRF token, and the 403 on a POST
A write that works from the admin or with an API key answers 403 “Missing or invalid CSRF token” when a script on the site sends it. When the token is checked, which routes check it, where a page gets it, and the fix for each kind of client. Read more →
How to structure the code that talks to the API: the scripts on a server-rendered or hybrid site, and the server-side imports and syncs that move records in and out. For the JavaScript developer and the integrations engineer. 3 pages
Writing the client layer
An architecture for the scripts on a server-rendered or hybrid site, not snippets: one module owns every request, each kind of failure gets its own answer, an optimistic cart defers to the server, drafts are saved without secrets, and data the page already has is never fetched twice. Read more →
The external-id pattern
Store the other system's identifier on every record you import, and look it up before every write, so that running an import twice leaves the site exactly as running it once did. Where externalId exists, the upsert, what to handle yourself, the test, and duplicates in the source. Read more →
Bulk reads and failure handling
Settings that let a sync or an export read a whole catalogue without slowing a production site, an incremental read that pages by id, and what to do when a run fails halfway: which requests to retry, which to read before resending, and what to log. Read more →
Every Core dna site runs an MCP server, which lets an AI assistant in your editor read and change the site's records and configuration as you. For any developer or editor who works with an assistant. 1 page