HTTP API Reference
Base URL is your mounted app (e.g. http://localhost:3000). JSON bodies use Content-Type: application/json unless noted.
Errors
Failed requests return JSON:
{ "code": "VALIDATION_ERROR", "message": "Validation failed", "details": {}, "requestId": "…"}code values align with @timbl/core ErrorCodes (e.g. NOT_FOUND, UNAUTHORIZED). Typical status codes: 400 validation, 401 unauthorized, 404 not found, 409 conflict, 501 not implemented, 500 internal.
Collections
:collection is the collection key from defineCollection.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/:collection | public | List entries (limit, offset, sort, filters, … query params) |
| GET | /api/:collection/:slug | public | Get one by slug |
| POST | /api/:collection | session | Create |
| PUT | /api/:collection/:id | session | Replace / update by id |
| DELETE | /api/:collection/:id | session | Delete |
List response (GET /api/posts?status=published):
{ "collection": "posts", "total": 42, "limit": 20, "offset": 0, "items": [ { "id": "abc123", "title": "Hello", "slug": "hello", "body": "# Hello", "bodyHtml": "<h1>Hello</h1>", "status": "published", "createdAt": "2026-06-19T12:00:00.000Z", "updatedAt": "2026-06-19T12:00:00.000Z" } ]}Single entry (GET /api/posts/hello):
{ "id": "abc123", "title": "Hello", "slug": "hello", "body": "# Hello", "bodyHtml": "<h1>Hello</h1>", "status": "published", "createdAt": "2026-06-19T12:00:00.000Z", "updatedAt": "2026-06-19T12:00:00.000Z"}Globals
| Method | Path | Auth |
|---|---|---|
| GET | /api/globals/:key | public |
| PUT | /api/globals/:key | session |
Response (GET /api/globals/siteSettings):
{ "key": "siteSettings", "siteName": "My Site", "siteUrl": "https://example.com"}Assets
| Method | Path | Auth |
|---|---|---|
| GET | /api/assets | public (list) |
| GET | /api/assets/:id | public |
| POST | /api/assets | session (multipart/form-data: file, optional directory, filename, alt) |
| DELETE | /api/assets/:id | session |
Asset response (GET /api/assets/asset_123):
{ "id": "asset_123", "filename": "cover.jpg", "originalName": "photo.jpg", "mimeType": "image/jpeg", "size": 524288, "path": "posts/cover.jpg", "url": "/uploads/posts/cover.jpg"}GET /uploads/* is reserved; the default headless stack does not serve files from that path—use your reverse proxy or storage URLs.
OpenAPI & docs
| Method | Path | Notes |
|---|---|---|
| GET | /openapi.json | OpenAPI 3.1 document with auth security schemes, route schemas, and plugin OpenAPI extensions |
| GET | /docs | API docs UI (default Scalar; configurable via apiDocs) |
The default Scalar docs consume /openapi.json. Auth-capable operations include
OpenAPI security metadata for sessionCookie and bearerAuth, so protected
requests can be authorized from the docs UI. Auth provider passthrough routes
are shown when the runtime adapter advertises providerRoutes; the default
Better Auth adapter exposes /api/auth/*. Timbl always
shows canonical auth actions as concrete routes such as
/api/cms/auth/actions/login, /api/cms/auth/actions/register, and
/api/cms/auth/actions/logout; adapter actions can refine those schemas or add
more routes through AuthCapabilities.actions.
Health
| Method | Path | Notes |
|---|---|---|
| GET | /api/health | Process health check |
| GET | /api/ready | Dependency readiness check (503 when the database check fails) |
Export
| Method | Path | Auth |
|---|---|---|
| GET | /api/export | session |
The export route is registered only when ENABLE_CMS_EXPORT=true. The default export handler requires an authenticated service principal.
Session & auth actions
| Method | Path | Auth |
|---|---|---|
| GET | /api/cms/auth/session | public |
| POST | /api/cms/auth/actions/:action | public |
| POST | /api/cms/auth/actions/<advertised-action> | public |
POST forwards the JSON body as the second argument to AuthAdapter.invoke. Better Auth provider traffic is also handled under /api/auth/* by default.
See Auth API Reference for OpenAPI security scheme behavior, action response conventions, and provider passthrough details.
Plugin routes
Static routes on plugins and routes registered in setup use the exact paths you provide; they are merged into the router with core routes.