Skip to content

REST API

0.9.3+

Kyro CMS auto-generates REST endpoints for every collection at /api/{slug}. Globals (singletons) are exposed under /api/globals/{slug}.

Endpoint Reference

MethodPathDescription
GET/api/{slug}List documents (paginated)
GET/api/{slug}/:idGet document by ID
POST/api/{slug}Create document
PATCH/api/{slug}/:idUpdate document
DELETE/api/{slug}/:idDelete document
GET/api/globals/{slug}Get global singleton
PATCH/api/globals/{slug}Update global singleton
POST / GET/api/auth/*User authentication, session management, password resets & email verification

Query Parameters (List GET)

ParamTypeDefaultDescription
pagenumber1Page number
limitnumber10Documents per page
sortstringField sort, e.g. createdAt_desc
depthnumberRelationship population depth
selectstringField projection (comma-separated)
wherestringJSON filter object
draftbooleanVersion-merge behaviour (see below)

Filtering with ?where=

The where parameter accepts a JSON object with per-field operators:

bash
curl "http://localhost:4321/api/posts?where={\"status\":{\"equals\":\"published\"}}"

Available operators:

  • equals, not_equals
  • contains, not_contains
  • in, not_in
  • gt, gte, lt, lte
  • exists
  • within

Combinators AND and OR are supported at the top level:

bash
curl "http://localhost:4321/api/posts?where={\"AND\":[{\"status\":{\"equals\":\"published\"}},{\"title\":{\"contains\":\"Kyro\"}}],\"OR\":[{\"author\":\"alice\"},{\"views\":{\"gt\":100}}]}"

WARNING

Invalid JSON in ?where= returns a 400 BAD_USER_INPUT error.

Pagination Response

json
{
  "docs": [
    { "id": "abc", "title": "Hello" }
  ],
  "totalDocs": 42,
  "page": 1,
  "totalPages": 5,
  "hasNextPage": true,
  "hasPrevPage": false
}

Draft Operations

Drafts are controlled via the X-Draft header on write requests and the draft query parameter on reads:

ActionRequestBehaviour
Save draftPATCH /api/posts/:id with header X-Draft: trueCreates or updates a draft version without publishing
Create draftPOST /api/posts with header X-Draft: trueCreates a document as a draft
Read mergedGET /api/posts?draft=trueReturns documents with draft content merged over published
Read publishedGET /api/posts?draft=falseReturns published documents only

INFO

Without ?draft, the default depends on auth state: authenticated requests see version-merged documents, anonymous requests see published-only.

Globals

Globals follow the same semantics but operate on a single document per slug:

bash
# Get the site settings singleton
curl http://localhost:4321/api/globals/site-settings

# Update (upsert semantics when id: "global")
curl -X PATCH http://localhost:4321/api/globals/site-settings \
  -H "Content-Type: application/json" \
  -d '{"title": "My Site"}'

Error Handling

The app.onError() middleware returns structured error responses:

json
{
  "error": "Validation failed for field 'title'",
  "code": "BAD_USER_INPUT"
}
CodeHTTP StatusWhen
BAD_USER_INPUT400Zod validation error
FORBIDDEN403Access denied by access control
CONFLICT409Revision conflict during update
INTERNAL_ERROR500Unexpected server error

CRUD Examples

List Documents

bash
curl "http://localhost:4321/api/posts?page=1&limit=5&sort=createdAt_desc"
json
{
  "docs": [
    { "id": "1", "title": "Post One", "createdAt": "2025-06-01T00:00:00Z" }
  ],
  "totalDocs": 1,
  "page": 1,
  "totalPages": 1,
  "hasNextPage": false,
  "hasPrevPage": false
}

Get by ID

bash
curl http://localhost:4321/api/posts/abc123
json
{
  "id": "abc123",
  "title": "Post One",
  "content": "Hello world"
}

Create Document

bash
curl -X POST http://localhost:4321/api/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "New Post", "slug": "new-post"}'
json
{
  "id": "def456",
  "title": "New Post",
  "slug": "new-post",
  "createdAt": "2025-06-10T12:00:00Z"
}

Update Document

bash
curl -X PATCH http://localhost:4321/api/posts/def456 \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated Title"}'
json
{
  "id": "def456",
  "title": "Updated Title",
  "slug": "new-post"
}

Delete Document

bash
curl -X DELETE http://localhost:4321/api/posts/def456
json
{
  "message": "Document deleted successfully"
}

Projection with ?select=

bash
curl "http://localhost:4321/api/posts?select=title,slug"
json
{
  "docs": [
    { "id": "abc", "title": "Post One", "slug": "post-one" }
  ],
  "totalDocs": 1,
  "page": 1,
  "totalPages": 1,
  "hasNextPage": false,
  "hasPrevPage": false
}

Draft Create

bash
curl -X POST http://localhost:4321/api/posts \
  -H "Content-Type: application/json" \
  -H "X-Draft: true" \
  -d '{"title": "Draft Post"}'

Draft Read

bash
curl "http://localhost:4321/api/posts?draft=true"

Nested Relationship with ?depth=

bash
curl "http://localhost:4321/api/posts?depth=2"

Populates related documents up to two levels deep.

Released under the MIT License.