API ReferenceStart here

GraphQL

One POST endpoint that reads several kinds of record in one request, with only the fields you name. It reads only, and takes a GraphQL-like query rather than full GraphQL.

How it differs from GraphQL

  • The query begins with query. The first five characters are dropped before parsing, so { Page { id } } on its own fails.
  • query is the only key in the body. variables or operationName, which GraphQL client libraries add, make it a 500. Write the values into the query.
  • It reads only. There are no mutations, subscriptions or introspection. Writes go through REST.
  • Fields are separated by commas or line breaks. A space alone glues two fields into one, which comes back as an "Error: …" string.
  • Nothing is limited for you. A part without first returns every record, and nesting has no depth limit.

Each top-level field names a kind of record, and its fields are the model's, as REST's properties uses them: aliases (title: activeRevision.name), relations in braces, and getter arguments by name (published(format: "Y-m-d")). Give every part an alias; the response is keyed by it.

Kinds of record

AreaNames
ContentPage, Blog, BlogPost, Book, BookUnit, BookPage, FaqPage, FaqQuestion, Menu, MenuElement (needs menu), Rewrite
StructureLayout, LayoutCategory, InputType, CustomField, GlobalComponent, Category, Tag
FilesFolder, Filename
FormsForm, FormSubmission (needs form), StepSubmission (needs step)
CommerceProduct, ProductCategory, ProductVariant, Cart
PeopleUser, Person, Group
Custom entitiesRecord for every record, or CustomEntities_CustomEntity_event for one definition's (its identifier at the end)

Anything else, orders and invoices included, is a 500 Entity not supported: read those through REST.

Arguments

ArgumentDoesExample
filterCriteria; dotted paths reach relationsfilter: {parent.id: 1}
first or limitHow many recordsfirst: 10
offsetHow many to skip. There is no pageoffset: 20
orderByField and directionorderBy: {published: "DESC"}
Anything elseAdded to filtermenu: 1

Inside filter, unlike REST's query string, suffixes work: _contains, _not, _gt, _lt, _gte, _lte, _in and _not_in, and OR and AND take a list of conditions. null matches an empty relation; NULL does not. Name fields exactly as in the model, as in REST.

Errors

RequestStatusMessage
A GET405
Another key beside query500Currently, only Query GraphQL requests are supported by this API
A query that does not begin with query500Could not decode … as GraphQL query
A kind of record not in the table500Entity not supported: Order
MenuElement without menu500Required argument missing: menu
A misspelt field200The field holds "Error: Getter for property … not found"

Who can call it

The credentials are REST's: an OAuth access token or an API key, or a signed-in backend user's session with the CSRF token. Site members cannot use it. Each part is read through the same handler REST uses for that kind of record, so a query reaches nothing its credential could not reach through REST. For a headless front end, call it from your server, as a backend user that may read only what the site publishes.

Run a query#

POST /cdna-api/graphql

Reads one or more kinds of record. The example asks for a guide, two how-tos and the first three items of a menu in one request.

Access
Admin users and integrations, with an OAuth access token or an API key.
Returns
The records under data, keyed by each part’s alias. There is no totalCount.

Parameters

querybodyrequired
string
The query, beginning with query. JSON or a form field; no other key.

Example

Run a query
POST /cdna-api/graphql HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{
  "query": "query { guide: Book(filter: {slug: \"apis\"}, first: 1) { id, name, unitsCount }, howtos: BlogPost(filter: {blog.id: 5}, first: 2, orderBy: {id: \"ASC\"}) { id, name, published(format: \"Y-m-d\") }, nav: MenuElement(menu: 1, filter: {parent: null}, orderBy: {position: \"ASC\"}, first: 3) { id, name } }"
}
Response 200 OK
{
  "result": "success",
  "data": {
    "guide": [
      { "id": 1677, "name": "The APIs: REST, GraphQL and custom endpoints", "unitsCount": 6 }
    ],
    "howtos": [
      { "id": 78, "name": "Render a list of child pages under the current page", "published": "2026-09-30" },
      { "id": 79, "name": "Decide between a custom field and a layout area", "published": "2026-09-30" }
    ],
    "nav": [
      { "id": 1, "name": "User Docs" },
      { "id": 2, "name": "Developer Docs" },
      { "id": 3, "name": "Changelog" }
    ]
  }
}
Real response from this documentation site