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/v1authentication
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
| method | path | returns |
|---|---|---|
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
| code | meaning |
|---|---|
200 | entry or list returned |
302 | asset redirect to a signed url |
400 | bad query parameter (e.g. an unsupported locale) |
401 | missing, malformed, revoked, or suspended delivery token |
404 | entry 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.