rest api reference

The REST delivery API — endpoints, the delivery-token header, query params, and response shapes.

rest api reference

REST is the public delivery channel: read-only access to published content, one schema, scoped to a single tenant by a delivery token. Every content type in your chili.config.ts gets two routes automatically — a list and a get-by-id — named after the type's URL plural.

This page is the reference. For the typed client over the same endpoints, read the sdk reference.

base url

All delivery routes live under /api/v1. In the default container the API binds port 4000; a hosted instance fronts it at your own domain.

https://api.your-instance.dev/api/v1

authentication

Every delivery read is gated by a delivery token — an opaque, read-only, tenant-scoped secret. Send it in the X-ChiliCMS-Delivery-Token header:

curl https://api.your-instance.dev/api/v1/posts \
  -H "X-ChiliCMS-Delivery-Token: $CHILICMS_DELIVERY_TOKEN"

Authorization: Bearer <token> works too — the header is checked when X-ChiliCMS-Delivery-Token is absent. The dedicated header is preferred; it keeps the delivery credential distinct from session bearers.

A delivery token is not a session. It can only read published content for the one tenant it was minted for, and it can never reach the GraphQL surface, the write routes, or another tenant's rows — row-level security is forced in the database, so the boundary holds even if the application is wrong.

There is no anonymous fallback. A request with no token, a malformed token, or a token that has been revoked (or whose tenant is suspended) gets 401. Those cases are deliberately indistinguishable — the response never tells you whether a token exists.

{ "error": "delivery token required" }
{ "error": "invalid delivery token" }

A valid token is 43 base64url characters. Mint, list, and revoke tokens from the Studio's tenant settings; the plaintext is shown once at mint time and never again.

endpoints

methodpathreturns
GET/api/v1/<plural>list of published entries
GET/api/v1/<plural>/{id}one published entry, or 404
GET/api/v1/assets/{id}302 redirect to a signed asset url

<plural> is the content type's URL plural — posts for Post, pages for Page, and the plural you declared for each custom type.

Writes (POST, PATCH, DELETE) exist on the same paths but require an authenticated Editor session, not a delivery token. They are a management surface, out of scope for delivery; this page covers reads.

list entries

curl https://api.your-instance.dev/api/v1/posts \
  -H "X-ChiliCMS-Delivery-Token: $CHILICMS_DELIVERY_TOKEN"

The list is wrapped in a data array:

{
  "data": [
    {
      "id": "0f9c2e7a-1b3d-4c8e-9a21-7f5c1e2b4d60",
      "title": "Hello world",
      "slug": "hello-world",
      "body": { "type": "doc", "content": [] },
      "publishedAt": "2026-06-01T09:00:00.000Z",
      "locale": "en",
      "createdAt": "2026-05-30T14:12:00.000Z",
      "updatedAt": "2026-06-01T09:00:00.000Z"
    }
  ]
}

Only published entries appear. For a type with a publishedAt field, an entry is published when publishedAt is set and at or before now — drafts and future-scheduled entries are invisible on this surface. A custom type without a publishedAt field has no draft concept, so every entry for the tenant is returned.

Each entry is projected through the type's declared fields. Posts and pages always return id, title, slug, body, publishedAt, locale, createdAt, and updatedAt; a custom type returns whatever fields it declares. Timestamps serialise as ISO 8601 strings.

get one entry

curl https://api.your-instance.dev/api/v1/posts/0f9c2e7a-1b3d-4c8e-9a21-7f5c1e2b4d60 \
  -H "X-ChiliCMS-Delivery-Token: $CHILICMS_DELIVERY_TOKEN"

A single entry is returned unwrapped — the entry object itself, not a data envelope:

{
  "id": "0f9c2e7a-1b3d-4c8e-9a21-7f5c1e2b4d60",
  "title": "Hello world",
  "slug": "hello-world",
  "body": { "type": "doc", "content": [] },
  "publishedAt": "2026-06-01T09:00:00.000Z",
  "locale": "en",
  "createdAt": "2026-05-30T14:12:00.000Z",
  "updatedAt": "2026-06-01T09:00:00.000Z"
}

404 covers every miss the same way — the id doesn't exist, it belongs to another tenant, it's a draft, or it's scheduled for the future. A scheduled entry's url is therefore not enumerable.

{ "error": "Post not found" }

query parameters

locale

Filter a list by locale with ?locale=. The allowlist is en, de, fr, es, nl, and it.

curl "https://api.your-instance.dev/api/v1/posts?locale=de" \
  -H "X-ChiliCMS-Delivery-Token: $CHILICMS_DELIVERY_TOKEN"

A locale outside the allowlist is rejected with 400:

{ "error": "locale not in allowlist" }

assets

curl -L https://api.your-instance.dev/api/v1/assets/{id} \
  -H "X-ChiliCMS-Delivery-Token: $CHILICMS_DELIVERY_TOKEN"

An asset read resolves the id within the tenant and returns a 302 redirect to a short-lived signed url for the underlying object — follow the redirect (-L in curl) to fetch the bytes. A missing or cross-tenant id is 404.

status codes

codemeaning
200entry or list returned
302asset redirect to a signed url
400bad query parameter (e.g. an unsupported locale)
401missing, malformed, revoked, or suspended delivery token
404entry not found, draft, scheduled, or belonging to another tenant

Next: read the concepts behind the delivery model, or use the sdk reference for a typed client over these endpoints.