Public API reference
Everything under /v1/public: what you send, what comes back and how it is cached.
5 min read
Overview
The public API is read-only apart from three small write endpoints (lead forms, 404 logging and analytics). Every path below is relative to https://api.atlascms.id.
| Topic | Rule |
|---|---|
| Base path | /v1/public |
| Authentication | Authorization: Bearer atl_read_..., or the X-Atlas-Token header. Without a token the brand comes from the Host header. |
| Envelope | Single resources and plain lists come back as { "data": ... }. The entries list is a cursor page with data, links and meta. |
| Content | Only published content. A preview token also reaches drafts, and its responses are private, no-store. |
| Locale | Pass locale to pick a language. The brand default is used when it is left out. |
curl -s "https://api.atlascms.id/v1/public/settings" \
-H "Authorization: Bearer $ATLAS_TOKEN" | jq '.data.brand.slug'Errors
| Status | Meaning |
|---|---|
| 401 | The token is missing where one is required, invalid, expired or revoked. More than 30 invalid tokens a minute from one address answers 429. |
| 403 | The brand is suspended, or a custom content type needs a higher role than a public caller has. |
| 404 | There is no published entry at that path, the locale is not one of the brand's, or a record belongs to another brand. |
| 422 | A required parameter is missing or invalid, for example a page request without path. The body is { "message", "errors": { "field": ["..."] } }. |
| 429 | Too many requests. See rate limits below. |
Every response carries an X-Request-Id header (req_ and ten hex characters). Quote it when you write to support.
Caching and ETags
Every response carries an ETag and a Cache-Control header, and a matching If-None-Match is answered 304 without a body. For page, route, entries and sitemap-urls the ETag is derived from the brand content version, so a 304 is answered before any rendering. A publish, or any change that alters live output, raises the content version and so invalidates every older ETag at once.
URL="https://api.atlascms.id/v1/public/page?path=/&locale=en"
ETAG=$(curl -s -D - -o /dev/null "$URL" -H "Authorization: Bearer $ATLAS_TOKEN" \
| grep -i '^etag:' | cut -d' ' -f2- | tr -d '\r')
curl -s -o /dev/null -w '%{http_code}\n' "$URL" \
-H "Authorization: Bearer $ATLAS_TOKEN" -H "If-None-Match: $ETAG"
# 304| Header | Value |
|---|---|
Cache-Control | max-age=60, public, stale-while-revalidate=300 on delivery responses. Preview responses are private, no-store. |
ETag | "cv{version}-{window}-{hash}" for page, route, entries and sitemap-urls. A hash of the body for the other endpoints. |
Vary | Authorization, X-Atlas-Token |
A short server-side body cache (120 seconds by default) sits behind the ETag; a content version bump makes every older body unreachable. The SDK and the Nuxt layer revalidate with ETags for you.
Rate limits
| Call | Limit |
|---|---|
| Reads with a token | 6000 a minute per token |
| Reads without a token | 600 a minute per host and address |
POST forms/{key} | 10 a minute per token and visitor address |
POST beacon | 300 a minute per token and visitor address |
POST events | 600 a minute per token and visitor address |
A site that calls the API from its server should pass the visitor's address in X-Atlas-Client-Ip and X-Atlas-Client-User-Agent, so the visitor is judged and not the server. The API believes these headers only on a call that carries a read token.
Page
/v1/public/pageThe published page at a path: blocks, SEO, translation links, the route that matched and the theme that renders it.
| Parameter | Description |
|---|---|
path | Required. The entry path, up to 255 characters. / returns the front page the brand chose in Reading settings. |
locale | Optional. A language of the brand. |
type | Optional. Restrict the lookup to one entry type. |
Response fields: id, type, locale, path, is_front_page, template, title, seo, fields (the typed fields of a custom type), terms, published_at, modified_at, blocks, translations ([{ locale, path }]), fragments, route, theme and fallback. A 404 means there is no published page. When the brand lists the latest posts at the front, path=/ is a 404 and the route endpoint answers instead.
curl -s "https://api.atlascms.id/v1/public/page?path=/pricing&locale=en" \
-H "Authorization: Bearer $ATLAS_TOKEN"Route
/v1/public/routeThe route and theme blocks alone, for a path the page endpoint answers 404 for: an archive, the posts page, search, a not found page, or the locale root when it lists the latest posts. An unknown locale is a 404.
| Parameter | Description |
|---|---|
path | Required. The path to resolve. |
locale | Optional. |
q | Optional. The search text, for a search route. |
{
"data": {
"path": "/search",
"locale": "en",
"route": { "kind": "search", "template": "index", "candidates": ["search", "index"] },
"theme": {
"slug": "atlascms",
"version": "0.1.0",
"template": { "slug": "index", "source": "package", "blocks": null },
"parts": { "header": { "slug": "header", "area": "header", "source": "package", "blocks": null } }
}
}
}Entries
/v1/public/entriesA cursor-paged list of published entries of one type: posts, pages or a custom type.
| Parameter | Description |
|---|---|
type | The entry type, such as post, page or a custom key. |
locale | A language of the brand. |
term, taxonomy | Filter by a term slug. taxonomy is category or tag and says which one the slug means. |
limit | 1 to 100. The default is 25, or the brand's posts per page for type=post. |
cursor | The meta.next_cursor of the previous page. |
q | Text search, up to 100 characters, over the path, title, excerpt and SEO description. |
fields | Omitted: no blocks, with SEO. light: no blocks and only the share image in SEO, plus author_name for posts. full: adds blocks. |
select | A comma separated list of at most 12 fields, such as title,path,fields.version. It wins over fields. An unknown field is a 422. |
count | 1 adds meta.total, the number of matches ignoring the cursor. |
filter[field] | Equality on a declared field of a custom type, up to 5. |
curl -s "https://api.atlascms.id/v1/public/entries?type=post&limit=10&count=1&fields=light" \
-H "Authorization: Bearer $ATLAS_TOKEN"{
"data": [
{ "id": "01m5ffh6...", "type": "post", "locale": "en", "path": "/blog/hello", "title": "Hello", "seo": { "og_image": null }, "author_name": "Sari", "fields": {}, "terms": [], "published_at": "2026-10-21T14:58:33.000000Z" }
],
"links": { "first": null, "last": null, "prev": null, "next": "https://.../v1/public/entries?type=post&cursor=eyJ..." },
"meta": { "per_page": 10, "next_cursor": "eyJ...", "prev_cursor": null, "total": 6 }
}Walk the list by sending meta.next_cursor as cursor until it is null.
Menus
/v1/public/menus/{key}A navigation menu such as header or footer. Items form a tree. Each item has a label and may have url, key, eyebrow, description, attributes and an image ({ url, alt, width, height }). locale picks the language; an unknown key is a 404.
{
"data": {
"id": "01m518vv88kdjb68enh6w74ded",
"key": "header",
"locale": "en",
"items": [
{ "key": "features", "label": "Features", "url": "/features" },
{ "key": "pricing", "label": "Pricing", "url": "/pricing" }
]
}
}Settings
/v1/public/settingsThe brand and the settings the registry marks public. The answer carries brand (name, slug, default_locale, locales), settings, branding (logo, favicon, share image), and the resolved reading and permalinks objects. Private keys never appear. A preview token also gets brand.slug, which the Nuxt layer compares before it honours a preview cookie.
Theme
/v1/public/themeThe brand token set and the resolved theme options for one locale. tokens holds the colours with their scales, fonts and radius, and options holds what the theme exposes for editing. A preview token receives the draft options.
| Parameter | Description |
|---|---|
locale | Optional. The language of the option texts. |
Strings
/v1/public/stringsInterface texts the brand manages, grouped by namespace.
| Parameter | Description |
|---|---|
locale | Optional. |
namespace | Optional. Only this group, such as site. |
{ "data": { "locale": "en", "strings": { "site": { "copyright": "AtlasCMS.", "language": "English" } } } }Terms
/v1/public/termsCategories and tags, each as { taxonomy, slug, name, path } where path is the archive path from the brand's permalink bases.
| Parameter | Description |
|---|---|
taxonomy | category or tag. |
slug | One term. |
Content types
/v1/public/content-typesThe custom content types the brand serves, each with its field list so a site does not have to parse the schema. Pass locale to get labels in one language.
Sitemap URLs
/v1/public/sitemap-urlsEvery public path with its language, last change and the alternates for hreflang: [{ path, locale, type, lastmod, alternates: [{ locale, path }] }].
Redirects
/v1/public/redirectsThe redirect map as [{ from, to, code }]. Moving a published entry adds a 301 here for you.
Machine readable files
/v1/public/llmsThe data for llms.txt, as JSON, or as text with format=text. Takes locale.
/v1/public/indexnow-keyThe IndexNow key file. A 404 when IndexNow is not enabled for the brand.
/v1/public/manifest.webmanifestThe web app manifest of the brand: names, icons (192, 512 and maskable), theme colour, display: standalone and start_url.
Submit a form
/v1/public/forms/{key}Sends a lead from a form the brand defined. Only the fields the form declares are accepted and stored. The form has to be active, and the Forms module has to be on.
| Field | Description |
|---|---|
fields | Required object with one entry per form field, checked against the form definition (email, phone, select and length rules). |
consent | Optional boolean. |
utm, fbclid, gclid | Optional campaign attribution. |
source_page, referrer, locale | Optional context. |
website | A honeypot. Leave it empty; a filled value gets a 202 and nothing is stored. |
curl -s -X POST "https://api.atlascms.id/v1/public/forms/contact" \
-H "Authorization: Bearer $ATLAS_TOKEN" -H "Content-Type: application/json" \
-d '{"fields":{"name":"Sari","email":"[email protected]"},"consent":true,"source_page":"/contact"}'Success is 201 with { "data": { "id", "message" } }. An unknown or inactive form is a 404, and invalid fields are a 422.
Log a not found page
/v1/public/not-foundLets a site record a 404 so editors see what visitors miss in the dashboard and can add a redirect.
Analytics beacon
/v1/public/beaconCounts a page view. Send path (starts with /, up to 512 characters), and optionally referrer and a visitor id of 8 to 64 characters. The answer is 204 with no body.
Tracking events
/v1/public/eventsSends a tracking event to the ad platforms the brand connected. It needs a read token. A browser event may only be PageView, ViewContent or Lead, and custom_data.value and custom_data.currency are dropped. Purchase style events and money need source: "server", which only a call with a read token can send.
| Field | Description |
|---|---|
event_name, event_id | Name and a unique id, so a browser and a server copy of one event count once. |
source | browser (default) or server. |
email, phone, external_id | Matching data for the platform. |
fbp, fbc, ttclid | Click and browser ids. |
client_ip, client_user_agent, source_url, custom_data | Context and extra data. |
Status
/status.jsonPublic health of the platform, with no token: status (operational, degraded, outage or unknown), generated_at, the API version and a list of services (database, redis, queue, scheduler) with 90 days of history. Cached for 10 seconds. The same data feeds status.atlascms.id.
The complete reference
The API serves a generated reference of every endpoint, including the dashboard API, at /docs/api on its own address. Use this page as the guide and that one as the full list.
Was this page helpful?