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.

TopicRule
Base path/v1/public
AuthenticationAuthorization: Bearer atl_read_..., or the X-Atlas-Token header. Without a token the brand comes from the Host header.
EnvelopeSingle resources and plain lists come back as { "data": ... }. The entries list is a cursor page with data, links and meta.
ContentOnly published content. A preview token also reaches drafts, and its responses are private, no-store.
LocalePass locale to pick a language. The brand default is used when it is left out.
terminalbash
curl -s "https://api.atlascms.id/v1/public/settings" \
  -H "Authorization: Bearer $ATLAS_TOKEN" | jq '.data.brand.slug'

Errors

StatusMeaning
401The token is missing where one is required, invalid, expired or revoked. More than 30 invalid tokens a minute from one address answers 429.
403The brand is suspended, or a custom content type needs a higher role than a public caller has.
404There is no published entry at that path, the locale is not one of the brand's, or a record belongs to another brand.
422A required parameter is missing or invalid, for example a page request without path. The body is { "message", "errors": { "field": ["..."] } }.
429Too 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.

terminalbash
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
HeaderValue
Cache-Controlmax-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.
VaryAuthorization, 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

CallLimit
Reads with a token6000 a minute per token
Reads without a token600 a minute per host and address
POST forms/{key}10 a minute per token and visitor address
POST beacon300 a minute per token and visitor address
POST events600 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

GET/v1/public/page

The published page at a path: blocks, SEO, translation links, the route that matched and the theme that renders it.

ParameterDescription
pathRequired. The entry path, up to 255 characters. / returns the front page the brand chose in Reading settings.
localeOptional. A language of the brand.
typeOptional. 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.

terminalbash
curl -s "https://api.atlascms.id/v1/public/page?path=/pricing&locale=en" \
  -H "Authorization: Bearer $ATLAS_TOKEN"

Route

GET/v1/public/route

The 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.

ParameterDescription
pathRequired. The path to resolve.
localeOptional.
qOptional. The search text, for a search route.
response.jsonjson
{
  "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

GET/v1/public/entries

A cursor-paged list of published entries of one type: posts, pages or a custom type.

ParameterDescription
typeThe entry type, such as post, page or a custom key.
localeA language of the brand.
term, taxonomyFilter by a term slug. taxonomy is category or tag and says which one the slug means.
limit1 to 100. The default is 25, or the brand's posts per page for type=post.
cursorThe meta.next_cursor of the previous page.
qText search, up to 100 characters, over the path, title, excerpt and SEO description.
fieldsOmitted: no blocks, with SEO. light: no blocks and only the share image in SEO, plus author_name for posts. full: adds blocks.
selectA comma separated list of at most 12 fields, such as title,path,fields.version. It wins over fields. An unknown field is a 422.
count1 adds meta.total, the number of matches ignoring the cursor.
filter[field]Equality on a declared field of a custom type, up to 5.
terminalbash
curl -s "https://api.atlascms.id/v1/public/entries?type=post&limit=10&count=1&fields=light" \
  -H "Authorization: Bearer $ATLAS_TOKEN"
response.jsonjson
{
  "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

GET/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.

response.jsonjson
{
  "data": {
    "id": "01m518vv88kdjb68enh6w74ded",
    "key": "header",
    "locale": "en",
    "items": [
      { "key": "features", "label": "Features", "url": "/features" },
      { "key": "pricing", "label": "Pricing", "url": "/pricing" }
    ]
  }
}

Settings

GET/v1/public/settings

The 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

GET/v1/public/theme

The 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.

ParameterDescription
localeOptional. The language of the option texts.

Strings

GET/v1/public/strings

Interface texts the brand manages, grouped by namespace.

ParameterDescription
localeOptional.
namespaceOptional. Only this group, such as site.
response.jsonjson
{ "data": { "locale": "en", "strings": { "site": { "copyright": "AtlasCMS.", "language": "English" } } } }

Terms

GET/v1/public/terms

Categories and tags, each as { taxonomy, slug, name, path } where path is the archive path from the brand's permalink bases.

ParameterDescription
taxonomycategory or tag.
slugOne term.

Content types

GET/v1/public/content-types

The 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

GET/v1/public/sitemap-urls

Every public path with its language, last change and the alternates for hreflang: [{ path, locale, type, lastmod, alternates: [{ locale, path }] }].

Redirects

GET/v1/public/redirects

The redirect map as [{ from, to, code }]. Moving a published entry adds a 301 here for you.

Machine readable files

GET/v1/public/llms

The data for llms.txt, as JSON, or as text with format=text. Takes locale.

GET/v1/public/indexnow-key

The IndexNow key file. A 404 when IndexNow is not enabled for the brand.

GET/v1/public/manifest.webmanifest

The web app manifest of the brand: names, icons (192, 512 and maskable), theme colour, display: standalone and start_url.

Submit a form

POST/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.

FieldDescription
fieldsRequired object with one entry per form field, checked against the form definition (email, phone, select and length rules).
consentOptional boolean.
utm, fbclid, gclidOptional campaign attribution.
source_page, referrer, localeOptional context.
websiteA honeypot. Leave it empty; a filled value gets a 202 and nothing is stored.
terminalbash
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

POST/v1/public/not-found

Lets a site record a 404 so editors see what visitors miss in the dashboard and can add a redirect.

Analytics beacon

POST/v1/public/beacon

Counts 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

POST/v1/public/events

Sends 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.

FieldDescription
event_name, event_idName and a unique id, so a browser and a server copy of one event count once.
sourcebrowser (default) or server.
email, phone, external_idMatching data for the platform.
fbp, fbc, ttclidClick and browser ids.
client_ip, client_user_agent, source_url, custom_dataContext and extra data.

Status

GET/status.json

Public 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?

Suggest a change