REST API

Complete reference for all API endpoints.

CMS exposes a REST API at /api for all CRUD operations, authentication, and media uploads.

All endpoints require authentication by default. Only /api/auth/login, /api/auth/logout, and /api/health are accessible without a session or API key. To make collection data publicly accessible, create an API key with the public role — see API Keys.

Collection endpoints

Every entity gets these endpoints automatically:


          GET    /api/:collection
GET    /api/:collection/:id
POST   /api/:collection
PUT    /api/:collection/:id
DELETE /api/:collection/:id
        

List records


          GET /api/posts?filter[status]=published&sort=-createdAt&limit=10
        

Response (200):


          {
  "data": [
    { "id": "...", "title": "...", "status": "published" }
  ],
  "meta": {
    "total": 42,
    "limit": 10,
    "offset": 0
  }
}
        

Get single record


          GET /api/posts/uuid-here?resolve[author]=name,bio
        

Response (200):


          {
  "data": {
    "id": "uuid-here",
    "title": "My Post",
    "author": {
      "id": "author-uuid",
      "name": "Pedro",
      "bio": "Design engineer"
    }
  }
}
        

Returns 404 if the record doesn’t exist.

Create record


          POST /api/posts
Content-Type: application/json

{
  "title": "New Post",
  "slug": "/blog/new-post",
  "status": "draft"
}
        

Response (201):


          {
  "data": {
    "id": "new-uuid",
    "title": "New Post",
    "slug": "/blog/new-post",
    "status": "draft",
    "createdAt": "2025-06-01T00:00:00Z",
    "updatedAt": "2025-06-01T00:00:00Z"
  }
}
        

Update record


          PUT /api/posts/uuid-here
Content-Type: application/json

{
  "title": "Updated Title"
}
        

Response (200): Returns the full updated record. Only send the fields you want to change.

Delete record


          DELETE /api/posts/uuid-here
        

Response (200):


          {
  "data": { "id": "uuid-here" }
}
        

Deletes the record and any related junction table rows.

Globals endpoints

Singleton entities get two endpoints instead of the standard collection endpoints:


          GET /api/globals/:name
PUT /api/globals/:name
        

Get global


          GET /api/globals/site-settings
        

Response (200):


          {
  "data": {
    "id": "uuid",
    "siteName": "My Site",
    "siteDescription": "...",
    "logo": "media-uuid",
    "createdAt": "...",
    "updatedAt": "..."
  }
}
        

Update global


          PUT /api/globals/site-settings
Content-Type: application/json

{ "siteName": "New Name" }
        

Response (200): Returns the full updated record.

Authentication endpoints


          POST   /api/auth/login
POST   /api/auth/logout
GET    /api/auth/me
POST   /api/auth/refresh
        

Login


          POST /api/auth/login
Content-Type: application/json

{
  "email": "admin@example.com",
  "password": "secret"
}
        

Response (200):


          {
  "data": {
    "user": {
      "id": "user-uuid",
      "email": "admin@example.com",
      "name": "Admin",
      "role": "admin"
    },
    "expiresAt": "2025-06-08T00:00:00Z"
  }
}
        

Sets an cms_session HTTP-only cookie. Sessions last 7 days.

API token authentication

You can authenticate API requests with a Bearer token instead of a session cookie:


          Authorization: Bearer <token>
        

If a Bearer token is present, it must be valid — the server returns 401 immediately on an invalid token. There is no fallback to the session cookie.

Logout


          POST /api/auth/logout
        

Clears the session cookie.

Current user


          GET /api/auth/me
        

Returns the authenticated user and their role. Returns 401 if not authenticated.

Refresh session


          POST /api/auth/refresh
        

Extends the session expiration. Returns the new expiresAt.

API key authentication

For headless use (SSGs, CI/CD, server-to-server), use a Bearer token instead of a session cookie:


          GET /api/posts?filter[status]=published
Authorization: Bearer cms_aB3xK9mNpQ2rS7vW1yZ...
        

Keys are created from the admin UI at Settings → API Keys or via POST /api/_api-keys (admin session required). See the API Keys reference for details.

Translation endpoint

For translatable entities:


          POST /api/pages/uuid-here/translate
Content-Type: application/json

{ "locale": "pt" }
        

Creates a translation of the record in the target locale.

Media upload


          POST /api/_media/upload
Content-Type: multipart/form-data

file: <binary>
alt: "Optional description"
        

Returns the media record with image dimensions (if applicable). Deduplicates by file hash.

Image transforms


          GET /api/assets/:filename?w=400&format=webp
GET /api/assets/:filename?w=800&fit=cover&format=avif&q=75
        

Serves the original file with no query params, or a transformed image with w, h, fit, format, and q parameters. See Media guide for full parameter reference.

Health check


          GET /api/health
        

Returns 200 when healthy:


          { "status": "ok", "db": "ok" }
        

Returns 503 when the database is unreachable:


          { "status": "degraded", "db": "unreachable" }
        

Admin config


          GET /api/admin/config
        

Returns the full CMS configuration for the admin UI (entity definitions, field types, locales, admin config). Stripped of hooks and internals.

Rate limiting

All endpoints are rate-limited per IP:

ScopeLimit
General API200 requests/minute
Auth endpoints (/api/auth/*)10 requests/minute

When exceeded, the server returns 429 Too Many Requests with a Retry-After header.

Rate limiting is in-memory and per-process — it is not shared across multiple instances. For production deployments with multiple processes, use a reverse-proxy rate limiter (nginx, Cloudflare) in front of the CMS.

Error responses

All errors follow a consistent format:


          {
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      { "field": "title", "message": "Required" },
      { "field": "slug", "message": "Must be unique" }
    ]
  }
}
        

Error codes

CodeHTTP StatusWhen
VALIDATION_ERROR400Invalid input data
UNAUTHORIZED401Missing or invalid session
FORBIDDEN403Role lacks permission
NOT_FOUND404Record or collection not found
CONFLICT409Unique constraint violation
TOO_MANY_REQUESTS429Rate limit exceeded
INTERNAL_ERROR500Unexpected server error

Previous

Deployment

Next

Field Types Reference