Skip to content

API reference

All routes below are injected by @caretcms/core when you add caret() to your Astro config. The base path defaults to /api/cms and is configurable via apiBasePath.

Status Meaning
200 Success
400 Validation error (bad payload, prototype-pollution attempt, malformed field)
401 No session cookie or session expired
403 Missing CSRF header or insufficient permissions
404 Entry / collection not found
409 Optimistic-locking conflict (expectedRevision mismatch)
429 Too many failed logins (rate limit) — POST /api/cms/auth/login only, with Retry-After
500 Server error
Route Method Purpose
/api/cms/auth/login POST Log in with CARET_EDIT_PASSWORD when password mode is active
/api/cms/auth/session GET Return authentication state and editor identity
/api/cms/auth/logout POST Clear the Caret session and resolve provider logout when configured
POST /api/cms/auth/login
Content-Type: application/json
{ "password": "your-password" }

Sets caret_session cookie on success (HttpOnly; SameSite=Lax; Secure on HTTPS). When an external identity provider is authoritative, password login returns 409 instead of falling back to the shared password.

Route Method Purpose
/api/cms/entries GET List entries by collection
/api/cms/schema GET Collection schema (registered, dynamic, or inferred)
/api/cms/collections-metadata GET List metadata for all dynamic collections
/api/cms/mutate POST Execute a mutation command
/api/cms/publish POST Flush the current editor’s draft overlay into base storage
/api/cms/draft DELETE Discard draft overlay without publishing
/api/cms/history GET / POST Read or restore from revision history
/api/cms/upload POST Upload a file (multipart/form-data)
GET /api/cms/entries?collection=pages
→ {
"collection": "pages",
"entries": [
{ "id": "home", "data": { "headline": "..." }, "revision": 7 },
{ "id": "about", "data": { "title": "..." }, "revision": 2 }
]
}
GET /api/cms/schema?collection=pages
→ {
"collection": "pages",
"source": "explicit", // or "dynamic" or "inferred"
"schema": { /* JSON Schema */ },
"template": { /* default value */ },
"metadata": {
"label": "Pages",
"description": "Site pages",
"icon": "📄",
"creatable": true,
"orderable": false,
"deletable": false,
"singletonId": null,
"order": 10
}
}

See Schemas for the resolution order and supported features.

GET /api/cms/collections-metadata
→ {
"collections": [
{
"id": "products",
"label": "Products",
"icon": "📦",
"creatable": true,
"orderable": true,
"deletable": true,
"order": 20,
"schema": { /* JSON Schema */ },
"created_at": 1714225200000,
"updated_at": 1714225200000
}
]
}

All writes go through here.

POST /api/cms/mutate
Content-Type: application/json
x-caret-request: 1
Cookie: caret_session=...
{ "type": "save_field", "collection": "pages", "id": "home", "field": "headline", "value": "New" }
→ { "ok": true, "revision": 8 }
type Purpose
save_field Update one field on one entry
put_entry Create or replace an entry’s full data
delete_entry Delete an entry
reorder_entries Reorder entries within a collection
update_page_layout Update section composer state
md_block Save one supported rendered Markdown block into the private draft overlay
create_collection Create a dynamic collection (schema + metadata)
delete_collection Delete a dynamic collection (and all its entries + history)
Field Required by Notes
type all One of the values above
collection most Lowercase, alphanumeric, - / _
id most Entry ID
field save_field Dot-path (hero.title); rejects __proto__ etc.
value save_field New value for the field
data put_entry Full entry data object (creates or replaces)
expectedRevision optional on writes Pass for optimistic locking; returns 409 on mismatch

create_collection metadata can include label, description, icon, creatable, orderable, deletable, singletonId, and order. The same capabilities configured for registered collections are enforced server-side.

{
"type": "md_block",
"collection": "blog",
"id": "hello-world",
"blockPath": "3",
"src": "120:168:4f83c21a",
"html": "New <strong>safe</strong> paragraph"
}

The server verifies the source hint, sanitizes the small inline HTML allowlist, derives Markdown itself, and stores the result as a private body draft. Clients never send source Markdown. A stale source range returns 409 with the current revision and requires a fresh render for new stamps.

{ "error": "Revision conflict", "currentRevision": 9 }

with HTTP 409. Re-read the entry, re-apply the user’s intent, and retry with the new currentRevision.

Schema validation failures return HTTP 400 with structured field issues:

{
"error": "Entry does not match collection schema",
"issues": [
{ "path": "gallery.0.width", "code": "too_small", "message": "Must be at least 1" }
]
}
GET /api/cms/history?collection=pages&id=home
→ {
"history": [
{
"ts": 1714225200000,
"data": {...},
"action": "save",
"editor": { "id": "editor_01", "name": "Alex Rivera" }
}
]
}

Capped at the last 50 revisions per entry. action includes save, put, delete, reorder, publish, or restore (the operation that produced the snapshot, not necessarily the mutation type). Markdown publish/restore snapshots can also carry an internal bodySource so the prior prose and frontmatter can be restored together.

Restore an entry to a previous revision:

POST /api/cms/history
x-caret-request: 1
Cookie: caret_session=...
{ "collection": "pages", "id": "home", "ts": 1714225200000 }

The corresponding history entry is re-applied as a put_entry mutation.

Multipart form with a single file field:

curl
curl -X POST http://localhost:4321/api/cms/upload \
-H 'x-caret-request: 1' \
-H 'Cookie: caret_session=...' \
-F file=@./hero.jpg
→ { "url": "/uploads/abc123.jpg" }

Flush the authenticated editor’s draft overlay into base storage. Optional body scopes the flush:

POST /api/cms/publish
Content-Type: application/json
x-caret-request: 1
Cookie: caret_session=...
{} → publish all draft entries
{ "collection": "pages" } → one collection
{ "collection": "pages", "id": "home" } → one entry
→ {
"ok": true,
"published": [{ "collection": "pages", "id": "home", "revision": 8, "deleted": false }],
"conflicts": [],
"commit": "abc1234", // when CARET_GIT_ON_PUBLISH=true and git repo present
"rebuild": { "triggered": true, "ok": true }
}

When delivery.publish.webhookUrl is configured, a successful publish POSTs a rebuild payload to CI. See Static delivery.

For Markdown drafts, conflicts can include stale_body when the source range changed or invalid_body when stored draft data is malformed. A conflicted entry is not partially published and its draft remains available.

Drop draft state without writing to base storage:

DELETE /api/cms/draft
x-caret-request: 1
Cookie: caret_session=...
DELETE /api/cms/draft?collection=pages&id=home → scoped discard
→ { "ok": true, "cleared": ["pages/home"] }
Route Purpose
/admin Login / redirect
/admin/cms Content Studio dashboard
/admin/cms/[collection] Collection list view
/admin/cms/[collection]/[id] Entry editor

These mount at ${mountPath} (default /admin). Disable with enableAdmin: false.

Route Purpose
/__caret/editor.js Inline editor runtime
/__caret/editor.css Editor styles

Loaded automatically when authentication succeeds and the page has a data-caret or data-caret-md binding. Disable with enableInlineEditor: false.

The hosted control plane is not generally available. Its API is intentionally not documented as a production contract yet. Use embedded mode for production and follow release notes for future cloud documentation.