API Reference
Auto-generated routes, access control levels, rule engine, query parameters, and caching.
Every content type automatically gets a REST API. This page covers the generated routes, access control, filtering, and query parameters.
Auto-Generated Routes
Collection Type (default)
RESTful mode (default):
| Method | Route | Action |
|---|---|---|
| GET | /api/v1/cms/{plural} | List records |
| GET | /api/v1/cms/{plural}/{id} | Get single record |
| POST | /api/v1/cms/{plural} | Create record |
| PUT | /api/v1/cms/{plural}/{id} | Update record |
| DELETE | /api/v1/cms/{plural}/{id} | Delete record |
Simple mode (when api_restful = false):
| Method | Route | Action |
|---|---|---|
| GET | /api/v1/cms/{plural} | List records |
| GET | /api/v1/cms/{plural}/{id} | Get single record |
| POST | /api/v1/cms/{plural}/create | Create record |
| POST | /api/v1/cms/{plural}/{id}/update | Update record |
| POST | /api/v1/cms/{plural}/{id}/delete | Delete record |
Single Type
| Method | Route | Action |
|---|---|---|
| GET | /api/v1/cms/{singular} | Get the single record (auto-creates on first request) |
| PUT | /api/v1/cms/{singular} | Update the single record |
Admin Routes
Every content type also gets admin routes under /api/v1/admin/cms/:
| Method | Route | Action |
|---|---|---|
| GET | /api/v1/admin/cms/{plural} | List all records (bypasses filters) |
| GET | /api/v1/admin/cms/{plural}/{id} | Get any record |
| POST | /api/v1/admin/cms/{plural} | Create record |
| PUT | /api/v1/admin/cms/{plural}/{id} | Update record |
| DELETE | /api/v1/admin/cms/{plural}/{id} | Delete record |
Admin routes bypass filter and filter_auth rules, and always show private fields.
Access Control
Control who can access each endpoint in the TOML definition:
[content_type.api.list]
access = "public"
cache = true
filter = 'status = "published"'
fields = ["title", "cover", "price"]
[content_type.api.create]
access = "admin"
[content_type.api.update]
access = "admin"
[content_type.api.delete]
access = "admin"Access Levels
| Level | Meaning |
|---|---|
none | Fully denied — endpoint returns 403 |
public | No authentication required |
member | Any authenticated user |
admin | Admin role required |
Default Access
| Endpoint | Default |
|---|---|
list | public |
get | public |
create | member |
update | member |
delete | admin |
Endpoint Options
| Option | Type | Description |
|---|---|---|
access | string | Access level: none, public, member, admin |
filter | string | Rule expression applied to all requests |
filter_auth | string | Additional filter ORed for authenticated users |
cache | bool | Enable server-side response caching |
fields | string[] | Whitelist of fields to return |
Rule Engine
Filter data with expressions that compile to SQL WHERE clauses:
[content_type.api.list]
filter = 'status = "published" && price > 0'
filter_auth = 'created_by = @request.auth.id'filter applies to all requests. filter_auth adds an OR condition for authenticated users. Combined logic: filter OR (is_authenticated AND filter_auth).
Operators
| Operator | Meaning | Example |
|---|---|---|
= | Equal | status = "published" |
!= | Not equal | status != "draft" |
> >= < <= | Comparison | price > 0 |
~ | LIKE match | title ~ "%rust%" |
!~ | NOT LIKE | title !~ "%spam%" |
&& | AND | a = 1 && b = 2 |
|| | OR | a = 1 || b = 2 |
Variables
| Variable | Description |
|---|---|
@request.auth.id | Current authenticated user ID |
@request.auth.role | Current user role |
@request.body.* | Request body field value |
@request.query.* | URL query parameter value |
@now | Current timestamp |
Special Syntax
| Syntax | Meaning | Example |
|---|---|---|
field:isset | Field is not null | avatar:isset |
field:length > N | String / array length check | tags:length > 0 |
null | Null literal | deleted_at = null |
true / false | Boolean literals | is_free = false |
Examples
# Only published items visible to public
filter = 'status = "published"'
# Published OR owned by the requesting user
filter = 'status = "published"'
filter_auth = 'created_by = @request.auth.id'
# Complex filter with grouping
filter = '(status = "published" && price > 0) || featured = true'Query Parameters
GET /api/v1/cms/courses?page=2&page_size=10&sort=created_at:desc&status=published&include=instructor,tags| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number |
page_size | int | 20 | Items per page (max configurable) |
sort | string | — | Sort: field:asc or field:desc, comma-separated for multiple |
search | string | — | Full-text search term |
include | string | — | Comma-separated relation fields to populate |
skip_total | bool | false | Skip COUNT query, returns total: -1 for performance |
{field} | string | — | Filter by exact field value |
__meta.{path} | string | — | Filter by JSON metadata path |
Pagination Response
{
"items": [...],
"total": 42,
"page": 2,
"page_size": 10
}Sort Examples
# Single field
?sort=created_at:desc
# Multiple fields
?sort=status:asc,created_at:descFilter Examples
# Exact match
?status=published
# Multiple filters
?status=published&level=beginner
# Metadata filter (requires metaable protocol)
?__meta.featured=trueResponse Caching
Enable caching per endpoint:
[content_type.api.list]
cache = trueCached responses are stored in memory, keyed by query hash. The cache is automatically invalidated on any write operation (create, update, delete) for that content type.
Cache TTL is configurable via cms_cache_ttl_secs (default: 30 seconds).
Private Fields
Fields with private = true are excluded from public API responses:
[[fields]]
name = "internal_notes"
field_type = "text"
private = true- Public API (
/api/v1/cms/): private fields are stripped from responses - Admin API (
/api/v1/admin/cms/): private fields are always included
Error Responses
| Status | Meaning |
|---|---|
400 | Invalid request body or query parameters |
401 | Authentication required |
403 | Access denied (wrong role or access = "none") |
404 | Record not found |
409 | Conflict (optimistic lock mismatch — lockable protocol) |
422 | Validation failed (field-level details in response body) |
