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.

StatusMeaning
200Success
400Validation error (bad payload, prototype-pollution attempt, malformed field)
401No session cookie or session expired
403Missing CSRF header or insufficient permissions
404Entry / collection not found
409Optimistic-locking conflict (expectedRevision mismatch)
429Too many failed logins (rate limit) — POST /api/cms/auth/login only, with Retry-After
500Server error
RouteMethodPurpose
/api/cms/auth/loginPOSTLog in with CARET_EDIT_PASSWORD
/api/cms/auth/sessionGETCheck whether the current editor session is authenticated
/api/cms/auth/logoutPOSTClear the editor session
POST /api/cms/auth/login
Content-Type: application/json
{ "password": "your-password" }

Sets caret_session cookie on success (HttpOnly; SameSite=Lax; Secure on HTTPS).

RouteMethodPurpose
/api/cms/entriesGETList entries by collection
/api/cms/schemaGETCollection schema (registered, dynamic, or inferred)
/api/cms/collections-metadataGETList metadata for all dynamic collections
/api/cms/mutatePOSTExecute a mutation command
/api/cms/publishPOSTFlush the current editor’s draft overlay into base storage
/api/cms/draftDELETEDiscard draft overlay without publishing
/api/cms/historyGET / POSTRead or restore from revision history
/api/cms/uploadPOSTUpload 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": "registered", // or "dynamic" or "inferred"
"schema": { /* JSON Schema */ },
"template": { /* default value */ },
"metadata": { // present when source is "dynamic"
"label": "Pages",
"icon": "📄",
"creatable": true,
"orderable": false
}
}

See Schemas for the resolution order and supported features.

GET /api/cms/collections-metadata
→ {
"collections": [
{
"id": "products",
"label": "Products",
"icon": "📦",
"creatable": true,
"orderable": true,
"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 }
typePurpose
save_fieldUpdate one field on one entry
put_entryCreate or replace an entry’s full data
delete_entryDelete an entry
reorder_entriesReorder entries within a collection
update_page_layoutUpdate section composer state
create_collectionCreate a dynamic collection (schema + metadata)
delete_collectionDelete a dynamic collection (and all its entries + history)
FieldRequired byNotes
typeallOne of the values above
collectionmostLowercase, alphanumeric, - / _
idmostEntry ID
fieldsave_fieldDot-path (hero.title); rejects __proto__ etc.
valuesave_fieldNew value for the field
dataput_entryFull entry data object (creates or replaces)
expectedRevisionoptional on writesPass for optimistic locking; returns 409 on mismatch
{ "error": "Revision conflict", "currentRevision": 9 }

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

GET /api/cms/history?collection=pages&id=home
→ {
"history": [
{ "ts": 1714225200000, "data": {...}, "action": "save" }
]
}

Capped at the last 50 revisions per entry. action is one of save, put, delete, reorder, or restore (the operation that produced the snapshot, not the mutation type).

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 }],
"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.

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"] }
RoutePurpose
/adminLogin / redirect
/admin/cmsContent Studio dashboard
/admin/cms/[collection]Collection list view
/admin/cms/[collection]/[id]Entry editor

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

RoutePurpose
/__caret/editor.jsInline editor runtime
/__caret/editor.cssEditor styles

Loaded automatically when an editor session cookie is present. Disable with enableInlineEditor: false.

Hosted cloud API Coming soon

Section titled “Hosted cloud API ”

The hosted version of CaretCMS exposes a separate API surface for cloud mode clients. These routes are served by the hosted control plane, not by your Astro site.

RouteMethodPurpose
/contentGETFetch entries for a project
/content/mutatePOSTWrite mutations
/content/schemaGETProject schema
/content/historyGETEntry history
/content/uploadPOSTFile upload
/content/auth/loginPOSTEditor login (Better Auth)
/content/auth/logoutPOSTEditor logout

Cloud routes use bearer token authentication (Authorization: Bearer ...) instead of cookies. Project membership is verified on every request. These docs will be expanded once the hosted service launches.