Domma CMS User Manual

Content API

Updated by Darryl Waterhouse on 13 October 2026 4 min read

The Content API is read-only, headless access to what the site publishes - pages, menus, the site settings public pages already show, and search - as JSON at /api/content/v1. Where the External API exposes collection data, this exposes content.

It is off until you turn it on at System > Content API (saved in config/content-api.json); until then every address answers 404.

Endpoints

MethodPathReturns
GET /api/content/v1/pages{ pages, total, page, limit } - summaries, no bodies
GET /api/content/v1/pages/<path>One page. /pages/ is the home page; a directory path serves its index
GET /api/content/v1/menus/:location{ location, slug, name, items }
GET /api/content/v1/siteTitle, brand, footer, social links, SEO defaults, theme
GET /api/content/v1/search?q={ results: [{ urlPath, title, description, tags, snippet, score }] }

Which pages

A page the API serves is exactly a page the site would serve the same caller - the two make one decision.

  • Only published pages. Drafts, scheduled and status-less pages answer 404.
  • Visibility is the site's: a role-gated page is 403 to a caller who could not open it.
  • A page in a switched-off project is 404.
  • Front matter api: false keeps a page off the API - not served, listed or searched. It still shows on the site.

Page list parameters

ParameterDefaultDescription
page1Page number
limit50Page size, capped at the site's page size limit (default 200). 0 means the default
prefix-Only pages whose path starts with this, e.g. /services
tag / category-Only pages with this tag, or in this category
project-Only pages in this project
sort / ordersite ordertitle, updatedAt or createdAt; order=desc reverses

The list can be switched off on its own (single pages stay), so a caller then has to know a page's path.

A page

GET /api/content/v1/pages/about?format=html   (html | markdown | both)

{
  "urlPath": "/about", "title": "About", "description": "…", "tags": ["company"],
  "category": "general", "layout": "default", "createdAt": "…", "updatedAt": "…",
  "seo": { "title": "…", "description": "…", "image": "…" },
  "html": "<section>…</section>",
  "requires": { "domma": true, "components": ["tabs", "effects"] },
  "perViewer": false
}
  • html is the page body (no navbar or footer), shortcodes already rendered. markdown is the source, shortcodes included. A format the site does not offer answers 400.
  • requires names the Domma behaviour the HTML relies on - tabs, accordions, carousels, countdowns, tooltips, reveals, effects, forms and custom components. Without Domma's CSS and JS those parts do nothing.
  • Only the front matter keys shown are returned - never your own custom keys.
  • perViewer: true - the body was rendered for this caller and must not be shared between users.
  • Drafts: in role mode only, ?preview=1 from someone with pages.read returns unpublished pages, never cached.
  • Menus - ?page=/about resolves page-bound menus as on that page. Items carry only what a navbar needs (type, text, url, icon, target, size, badge, items) and are filtered for the caller. The admin sidebar and page overlays are never served.
  • Site - title, brand, footer (hidden links stay hidden), social links, SEO defaults and theme. Never mail settings, the site URL or anything from the admin.
  • Search - the site's own search. A query shorter than the site's minimum is 400; with search off in System > Search it is 404.

Access

Who may call itCredentialPages rendered as
PublicNoneAn anonymous visitor
API token (default)Authorization: Bearer dcms_… - a JWT is refusedAn anonymous visitor
A roleAuthorization: Bearer <access JWT> of that role or more senior. The site's login cookie is never acceptedThat user
  • A token reads only the pages of its own project - single pages, the list and search. A page in another project is 404. Menus and site settings are site-wide, so any valid token may read them.
  • Scopes narrow a token: in the token editor, a line of @pages, @menus, @site or @search (read-only, no verbs). A token with no scopes covers everything in its project; one whose scopes name only collections gets no content.
  • A role the site does not have is refused when you save the settings.

Responses

CaseStatusCache-Control
Public200public, max-age=60 + ETag
API token200private, no-cache
Role, or a draft200no-store
Off, missing, unpublished, disabled project, api: false404no-store
No or invalid credential401no-store
Role too junior, scope or visibility refuses403no-store
Format not offered, query too short400no-store

Calling it

# Public mode
curl 'https://example.com/api/content/v1/pages?prefix=/blog&limit=10'

# Token mode
curl -H 'Authorization: Bearer dcms_0123...cdef' \
  'https://example.com/api/content/v1/pages/about?format=both'

A browser on another domain can call it only when that origin is allowed by cors.origin in config/server.json. With a token or a role the browser sends a preflight first; allowed origins are answered automatically. Calls from a server are unaffected.

Try any page as an anonymous caller would get it - before switching the API on - on the Content API screen's Try it tab.