Guide 00 · Developer docs

Core dna for site builders

Read this guide first. Core dna is hosted, so there is nothing to install; your repository holds only templates and front-end assets; and a new kind of page starts in the admin, with a layout. In half a day, from what the platform is to a page you built, deployed and rendered in Smarty or Twig.

L0
Unit 01 · What you are building on

The URL surfaces of a site

Every Core dna site answers on the same paths: the public site at /, the admin at /cdna-admin, the REST API at /cdna-api, GraphQL at /cdna-api/graphql, custom endpoints at /cdna-api/webhook and the MCP server at /cdna-mcp. A real request to each, and what each answers when something is wrong.

Who this is for. Everyone, and above all whoever will call a site from code. After this page you will recognise each part of a site by its URL, and know what each one answers when a credential, a method or a path is wrong.

These are not separate systems. Each is a door into the same site, on the site's own hostname, answering from the same database. The requests below were sent to this documentation site; in the plain HTTP examples its hostname is shown as www.example.com.

The public site: /

Every path that does not start with one of the prefixes below belongs to the public site, the frontend: pages, blog posts, products, forms, the member area. Most of its URLs come from records rather than from routes anyone wrote: a page's URL is its place in the page tree. Files in the site's own folder, such as /dist/app.css, are served as they are, and uploaded media is served from /files/….

HTTP
GET /

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com 'nonce-9PqAxZeVwUAO6B3lrd0oIh6k+hk='

The Content-Security-Policy header carries a new nonce on every request. An inline script without that nonce does not run, which is why templates print it into every <script> tag.

The admin: /cdna-admin

The backend, where content, layouts, users and settings are managed. Signed out, it sends you to its sign-in page.

HTTP
HEAD /cdna-admin

HTTP/1.1 302 Found
Location: https://www.example.com/cdna-admin/login

Signing in needs a backend user on that site. A member's account is refused at the admin's sign-in, even with the right password.

The REST API: /cdna-api

URLs have the shape /cdna-api/<module>/<entity>: a site's pages are /cdna-api/pages/pages, and one page is /cdna-api/pages/page/{id}. /cdna-api/pages on its own is a 404.

List two pages, with and without a credential
GET /cdna-api/pages/pages?properties={id,name,fullPath}&limit=2 HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Response
{
  "result": "success",
  "totalCount": 158,
  "data": [
    {"id": 1, "name": "Home", "fullPath": "/"},
    {"id": 1885, "name": "API sandbox", "fullPath": "/api-sandbox"}
  ]
}
x-reason: not logged in

Real responses from this documentation site

Most requests need a credential: an OAuth access token, which is the preferred kind, or an API key, the older kind, sent as Authorization: Bearer …; or the session of someone signed in. With none, the answer is 401. Authentication and authorisation says which to use, and the API reference lists every endpoint.

GraphQL: /cdna-api/graphql

Queries only, sent as a POST with the query in a JSON body, with the same credentials as REST. GraphQL on Core dna reads data; writes go through the REST API. A GET answers 405.

A GraphQL query, and the same URL fetched with GET
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 { Page(first: 2) { id, name, fullPath } }"}
Response
{
  "result": "success",
  "data": {
    "Page": [
      {"id": 1, "name": "Home", "fullPath": "/"},
      {"id": 1885, "name": "API sandbox", "fullPath": "/api-sandbox"}
    ]
  }
}
{
  "result": "error",
  "message": "Access to the specified record has been denied. Please contact your administrator if you think this is a mistake."
}
Real responses from this documentation site; the debug element a development environment adds to the 405 is left out

Read the status, not the message: the 405 above carries the platform's generic access-denied text rather than a method-specific one.

Custom endpoints: /cdna-api/webhook/<slug>

An endpoint you define in the admin without platform code: a job whose trigger is a web event, with a slug, the methods it accepts, and whether it requires authentication. A synchronous job answers the request itself, with whatever its actions produce; any other job is queued and runs afterwards. Anything in the path after the slug is passed to the job.

A web event with Authentication required on admits backend users only: an OAuth access token, an API key or a signed-in admin session. Anyone else, a signed-in member included, gets a 403. A slug that does not exist, or a method the web event does not accept, answers 404.

A synchronous job's endpoint on this site, which requires authentication
POST /cdna-api/webhook/api-sandbox-add-record HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{"externalId": "docs-g00"}
Response
{
  "error": "name_required",
  "message": "Send a JSON body with a non-empty name."
}
{
  "result": "error",
  "message": "Access to the specified record has been denied. Please contact your administrator if you think this is a mistake."
}
Real responses from this documentation site. The 422 and its body come from the job; the 403 comes from the platform

The 422 shows the point of a custom endpoint: the status and the body are the job's own. Custom endpoints with synchronous jobs shows how to build one.

MCP: /cdna-mcp

The site's MCP server, for AI assistants such as the one in your editor. It is protected by OAuth: a client without a token is told where to start, signs you in on the site, and from then on works as you, with your permissions. Only a backend user can authorise it.

HTTP
GET /cdna-mcp

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
WWW-Authenticate: Bearer resource_metadata="https://www.example.com/.well-known/oauth-protected-resource/cdna-mcp"

{"error":"unauthorized","error_description":"Missing or invalid Authorization header. Initiate OAuth using the resource_metadata URL in the WWW-Authenticate header."}

Connecting your editor to the MCP server sets it up.

OAuth: /oauth

Every site is also an OAuth authorisation server, which is where the tokens for the API and the MCP server come from: /oauth/authorize signs a person in and asks their consent, /oauth/token issues the tokens, and /.well-known/oauth-authorization-server describes the rest. OAuth clients walks through a complete flow.

What is never served

Some paths under the site's folder are never sent as files: the templates/ folder, any path with a part that starts with a dot (.git, .settings.json), files such as package.json and composer.json, and any file type outside the allowed list of styles, scripts, images, fonts, icons, source maps, text, CSV and XML. A request for one of them goes on to the public site, which usually answers 404.

A site can add file types to the allowed list with a .static-assets.json file at the site root, an allowedTypes map of extension to MIME type. Script and server-configuration types, such as php, can never be added.

Go deeper

Was this page helpful?

Your answer goes to the docs team.