Skip to main content
Every website has a built-in headless CMS: collections define a schema of fields, and items hold the content your pages query.

The model

Create a collection with POST /v1/cms/collections, add fields with POST /v1/cms/collections/{collectionId}/fields, then create items with POST /v1/cms/collections/{collectionId}/items. GET /v1/cms/collections returns each collection’s active item count. GET /v1/cms/schema returns the whole schema, including collections and fields, in one call. Slugs and keys are load-bearing: page code queries collections by slug and reads values by field key. Renames go through the normal update endpoints — slug on PATCH /v1/cms/collections/{collectionId}, key on PATCH /v1/cms/fields/{fieldId}, and slug on PATCH /v1/cms/items/{itemId} — and migrate stored data atomically.

Fields

A field has a type (immutable after create), required, and isUnique flags, an optional description, an optional example value (any JSON, a design-phase placeholder that reads back as null when unset), and a position you change by passing position (with anchorFieldId for before/after) to PATCH /v1/cms/fields/{fieldId}. Reference fields also declare an immutable targetCollectionId. To change a field’s type, create a new field, migrate the data, and delete the old one. isUnique is not available on plain_text, markdown, or the multi-valued and asset-backed types. Field keys match ^[a-z][a-z0-9_]{0,63}$. The keys id, slug, publishedAt, publishAt, createdAt, updatedAt, and position are reserved for item metadata.

Field types

All 14 field types, with write-time validation and exact limits: Scalar values live in the item’s data object. Reference and asset values are stored relationally and written through dedicated request properties: references, images, and files on POST .../items and PATCH /v1/cms/items/{itemId}. Image writes accept an asset id, { "assetId": "...", "altText": "..." }, or null to clear. See Assets.

Items: drafts, scheduling, publishing, ordering

An item has one of three publication states:
  • Draft: publishedAt and publishAt are both null.
  • Scheduled: publishedAt is null and publishAt is a future timestamp. Cactal promotes the item shortly after that time, clears publishAt, and records the actual time in publishedAt.
  • Published: publishedAt is set and publishAt is null.
Call POST /v1/cms/items/{itemId}/set-published with { "published": true } to publish immediately, { "publishAt": "2026-08-10T15:00:00Z" } to schedule a draft, or { "published": false } to unpublish or cancel a schedule. Published items must be unpublished before scheduling because scheduled publication promotes the latest saved item rather than a frozen revision. Item data remains patchable in every state. DELETE /v1/cms/items/{itemId} soft-deletes: the item leaves all reads and backreference results, but POST /v1/cms/items/{itemId}/restore brings it back with its references intact. Items order by a position you change on PATCH /v1/cms/items/{itemId} using { "position": "first" | "last" | "before" | "after", "anchorItemId": "..." }.
Published websites render only items with publishedAt set. Draft, scheduled, unpublished-again, and soft-deleted items never appear on the live site. API reads include every state by default; filter with published=true or published=false on GET /v1/cms/collections/{collectionId}/items. Scheduled items match published=false until promotion.

References and hydration

Reference fields link items across collections. Reads return them by id at depth=0 (the default): a single reference is an item id string, a multi-reference is an array of ids. Pass depth up to 3 to hydrate referenced items in place, one level per unit.
A hydrated reference to a missing or soft-deleted item reads as null (single) or is omitted from the array (multi). GET /v1/cms/items/{itemId}/backreferences returns the active items that point at an item. It uses the standard { items, nextCursor } envelope; follow every cursor before deleting content that other items link to. Deleting a collection is refused with 409 while reference fields in other collections target it; the error lists the blocking fields. Reads never break on schema drift: unknown keys are stripped and missing values read as null.
Schema changes are gated by the published site. Collection and field mutations run the published version’s framework validation against the candidate schema; a change that would break the live site — for example deleting a field a published page queries — is rejected with a validation error. Update the source, publish, then change the schema.

Querying items

GET /v1/cms/collections/{collectionId}/items supports cursor/limit pagination plus: Field operators: eq, neq, in, nin, exists on all types; gt/gte/lt/lte on number and date; contains, startsWith, endsWith on text-like types. Reference fields also accept slug for one-level equality matching, such as {"author":{"slug":"jane"}}. Metadata keys accept a narrower set: id and slug take eq, in, and startsWith; publishedAt and publishAt take exists, gt, gte, lt, and lte; createdAt and updatedAt take gt, gte, lt, and lte. Any other operator on a metadata key returns 400. GET /v1/cms/items/find fetches one item by itemId, or by collectionId plus slug.

Limits

Next steps

Manage CMS content

Model a collection and run the full content workflow.

Assets

Upload images and files to use in image, gallery, and file fields.