Skip to main content

Content Management

Day-to-day authoring surfaces. Entries live in a tree. Every new entry takes a place under a parent node — there are no orphan rows.

Lifecycle

  1. CreatePOST /api/v1/projects/{slug}/entries with content type, locale, tree parent, fields. Always lands as draft.
  2. UpdatePUT /api/v1/projects/{slug}/entries/{id} with the current version for optimistic concurrency. Mismatched versions return 409.
  3. PublishPOST /api/v1/projects/{slug}/entries/{id}/publish with locale. Required references must already be published, or the publish returns 422 required_references_unpublished with the blockers in details.unpublished[].
  4. UnpublishPOST .../unpublish. The entry stays as a draft; route index drops the locale's path.
  5. DeleteDELETE .../entries/{id}. Soft-delete: the row stays for audit, references resolve to null at delivery.

Sub-pages

  • Content tree — sites, folders, materialized paths
  • Entries — create, update, version, restore
  • Publishing — workflow, scheduling, publish gates
  • Media — assets, transforms, locale overlays
  • Localization — per-locale field values, fallback chains
  • Search — Postgres FTS by default, Meilisearch optional

Quick example

POST /api/v1/projects/demo/entries
Content-Type: application/json
Authorization: Bearer krios_mk_…

{
"contentTypeApiName": "blogPost",
"locale": "en-US",
"treeParentId": "node_blogIndex",
"treeNodeName": "Hello World",
"slug": "hello-world",
// siteId is derived from the parent when treeParentId is set;
// only pass siteId for root placement (treeParentId: null).
// A mismatching siteId here returns 422 tree_site_mismatch.
"fields": {
"title": "Hello World",
"body": {
"type": "doc",
"content": [
{ "type": "paragraph", "content": [{ "type": "text", "text": "First post." }] }
]
}
}
}

Common pitfalls

  • Forgetting version on update. The PUT body must include the current version. version is required — omitting it fails Zod validation with 422 validation (the server never defaults to 1).
  • Publishing an entry whose required reference is still draft. Returns 422 required_references_unpublished with the blockers in details.unpublished[]. Publish each blocker first or remove the reference.
  • Two update_entry calls in parallel. Both pass version=N; the second 409s. Catch and re-fetch.