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.
Conventions
Section titled “Conventions”HTTP status codes
Section titled “HTTP status codes”| 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/loginContent-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.
GET /api/cms/auth/session
→ { "authenticated": true, "identity": { "id": "editor_01", "name": "Alex Rivera", "roles": ["editor"] }}Used by the inline editor bootstrap to decide whether to load editor JS.
POST /api/cms/auth/logoutClears the cookie. Returns { ok: true }. (No CSRF header required — logout only clears state.)
Content
Section titled “Content”| 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
Section titled “GET /api/cms/entries”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
Section titled “GET /api/cms/schema”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
Section titled “GET /api/cms/collections-metadata”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 } ]}POST /api/cms/mutate
Section titled “POST /api/cms/mutate”All writes go through here.
POST /api/cms/mutateContent-Type: application/jsonx-caret-request: 1Cookie: caret_session=...
{ "type": "save_field", "collection": "pages", "id": "home", "field": "headline", "value": "New" }
→ { "ok": true, "revision": 8 }Mutation command types
Section titled “Mutation command types”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) |
Common fields
Section titled “Common fields”| 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.
Markdown block mutation
Section titled “Markdown block mutation”{ "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.
Conflict response
Section titled “Conflict response”{ "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
Section titled “GET /api/cms/history”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.
POST /api/cms/history
Section titled “POST /api/cms/history”Restore an entry to a previous revision:
POST /api/cms/historyx-caret-request: 1Cookie: caret_session=...
{ "collection": "pages", "id": "home", "ts": 1714225200000 }The corresponding history entry is re-applied as a put_entry mutation.
POST /api/cms/upload
Section titled “POST /api/cms/upload”Multipart form with a single file field:
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" }POST /api/cms/publish
Section titled “POST /api/cms/publish”Flush the authenticated editor’s draft overlay into base storage. Optional body scopes the flush:
POST /api/cms/publishContent-Type: application/jsonx-caret-request: 1Cookie: 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.
DELETE /api/cms/draft
Section titled “DELETE /api/cms/draft”Drop draft state without writing to base storage:
DELETE /api/cms/draftx-caret-request: 1Cookie: caret_session=...
DELETE /api/cms/draft?collection=pages&id=home → scoped discard→ { "ok": true, "cleared": ["pages/home"] }Admin pages
Section titled “Admin pages”| 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.
Editor assets
Section titled “Editor assets”| 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.
Cloud mode Alpha
Section titled “Cloud mode ”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.