API Reference
REST API documentation for R3 CMS. Query, filter, and manage content types, documents, and webhooks.
Authentication
All API requests require an API key passed in the x-api-key header:
curl -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content
Generate API keys from your website settings in the CMS admin panel.
Content Types
List All Content Types
GET /api/content-types
curl -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content-types
Response:
{
"docs": [
{
"id": "uuid",
"name": "blog_post",
"label": "Blog Post",
"description": "Blog articles",
"slugField": "title",
"titleField": "title",
"icon": "file-text",
"fields": [...]
}
]
}
Create a Content Type
POST /api/content-types
curl -X POST https://cms.yourdomain.com/api/content-types \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "blog_post",
"label": "Blog Post",
"slugField": "title",
"titleField": "title",
"icon": "file-text",
"fields": [
{ "name": "title", "label": "Title", "type": "text", "required": true },
{ "name": "body", "label": "Body", "type": "richText", "required": true },
{ "name": "tags", "label": "Tags", "type": "tags" }
]
}'
Get a Content Type by Name
GET /api/content-types/:name
curl -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content-types/blog_post
Update a Content Type
PATCH /api/content-types/:name
curl -X PATCH https://cms.yourdomain.com/api/content-types/blog_post \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"label": "Article",
"fields": [
{ "name": "title", "label": "Title", "type": "text", "required": true },
{ "name": "body", "label": "Body", "type": "richText", "required": true },
{ "name": "excerpt", "label": "Excerpt", "type": "textarea", "required": false },
{ "name": "tags", "label": "Tags", "type": "tags" }
]
}'
Delete a Content Type
DELETE /api/content-types/:name
curl -X DELETE -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content-types/blog_post
Content Documents
List Content
GET /api/content
Returns paginated content documents. By default, only published documents are returned.
curl -H "x-api-key: your-api-key" \
"https://cms.yourdomain.com/api/content?type=blog_post&limit=10&page=1"
Response:
{
"docs": [
{
"id": "uuid",
"type": "blog_post",
"slug": "hello-world",
"status": "published",
"authorId": "uuid",
"publishedAt": "2026-03-25T10:00:00.000Z",
"data": {
"title": "Hello World",
"body": { "type": "doc", "content": [] },
"tags": ["nextjs", "cms"]
},
"createdAt": "2026-03-25T09:00:00.000Z",
"updatedAt": "2026-03-25T10:00:00.000Z"
}
],
"totalDocs": 42,
"limit": 10,
"totalPages": 5,
"page": 1,
"hasNextPage": true,
"hasPrevPage": false,
"nextPage": 2,
"prevPage": null
}
Create a Document
POST /api/content
New documents are created with status: "draft" by default.
curl -X POST https://cms.yourdomain.com/api/content \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"type": "blog_post",
"data": {
"title": "Hello World",
"body": { "type": "doc", "content": [] },
"tags": ["nextjs", "cms"]
}
}'
Response:
{
"id": "uuid",
"type": "blog_post",
"slug": "hello-world",
"status": "draft",
"authorId": "uuid",
"publishedAt": null,
"data": {
"title": "Hello World",
"body": { "type": "doc", "content": [] },
"tags": ["nextjs", "cms"]
},
"createdAt": "2026-03-25T09:00:00.000Z",
"updatedAt": "2026-03-25T09:00:00.000Z"
}
Get a Document by ID
GET /api/content/:id
curl -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content/uuid
Get a Document by Slug
GET /api/content/by-slug/:type/:slug
curl -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content/by-slug/blog_post/hello-world
Update a Document
PATCH /api/content/:id
curl -X PATCH https://cms.yourdomain.com/api/content/uuid \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"data": {
"title": "Hello World (Updated)",
"tags": ["nextjs", "cms", "tutorial"]
}
}'
Delete a Document
DELETE /api/content/:id
curl -X DELETE -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content/uuid
Publish a Document
POST /api/content/:id/publish
Sets the document status to published and records the publishedAt timestamp.
curl -X POST -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content/uuid/publish
Unpublish a Document
POST /api/content/:id/unpublish
Reverts the document to draft status and removes it from public API responses.
curl -X POST -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content/uuid/unpublish
List Document Versions
GET /api/content/:id/versions
Returns all version snapshots for a document, ordered by most recent first.
curl -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content/uuid/versions
Restore a Document Version
POST /api/content/:id/restore/:versionId
Restores a document to a previous version. Creates a new version snapshot so the restore is tracked in history. Does not change the publish status.
curl -X POST -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/content/uuid/restore/version-uuid
Filtering
Use the filters query parameter to filter content by field values. Filters support both base columns and JSONB data fields.
Filter Operators
| Operator | Description | Example |
|---|---|---|
$eq | Equals | ?filters[status][$eq]=published |
$ne | Not equals | ?filters[type][$ne]=page |
$gt | Greater than | ?filters[data.price][$gt]=100 |
$gte | Greater than or equal | ?filters[data.price][$gte]=50 |
$lt | Less than | ?filters[publishedAt][$lt]=2026-01-01 |
$lte | Less than or equal | ?filters[publishedAt][$lte]=2026-06-30 |
$in | Value in list | ?filters[status][$in]=draft,published |
$notIn | Value not in list | ?filters[type][$notIn]=internal |
$contains | Substring match (case-insensitive) | ?filters[data.title][$contains]=hello |
$startsWith | Starts with | ?filters[slug][$startsWith]=blog- |
$null | Is null | ?filters[publishedAt][$null]=true |
$notNull | Is not null | ?filters[publishedAt][$notNull]=true |
Base Column Filters
Base columns (status, type, slug, publishedAt, createdAt, updatedAt, authorId) are filtered with direct SQL WHERE clauses using B-tree indexes.
# Published blog posts
curl -H "x-api-key: your-api-key" \
"https://cms.yourdomain.com/api/content?type=blog_post&filters[status][$eq]=published"
JSONB Data Field Filters
Fields stored in the data JSONB column are accessed with the data. prefix. These compile to PostgreSQL data->>'fieldName' expressions.
# Products priced over 100
curl -H "x-api-key: your-api-key" \
"https://cms.yourdomain.com/api/content?type=product&filters[data.price][$gt]=100"
# Blog posts containing "nextjs" in tags
curl -H "x-api-key: your-api-key" \
"https://cms.yourdomain.com/api/content?type=blog_post&filters[data.tags][$contains]=nextjs"
Combining Filters with Logical Operators
Use $and and $or for complex filter logic:
# Published AND featured
curl -H "x-api-key: your-api-key" \
"https://cms.yourdomain.com/api/content?type=blog_post&filters[$and][0][status][$eq]=published&filters[$and][1][data.featured][$eq]=true"
# Draft OR published (exclude archived)
curl -H "x-api-key: your-api-key" \
"https://cms.yourdomain.com/api/content?filters[$or][0][status][$eq]=draft&filters[$or][1][status][$eq]=published"
Sorting
Use the sort query parameter to order results. Prefix a field name with - for descending order.
# Newest first
?sort=-publishedAt
# Oldest first
?sort=publishedAt
# Multi-field sort: newest first, then alphabetical by title
?sort=-publishedAt,data.title
Supported sort fields include base columns (publishedAt, createdAt, updatedAt, slug) and JSONB data fields using data.fieldName notation.
Pagination
All list endpoints return Payload-style paginated responses.
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (1-based) |
limit | number | 20 | Documents per page (max: 100) |
Response envelope:
{
"docs": [],
"totalDocs": 42,
"limit": 20,
"totalPages": 3,
"page": 1,
"hasNextPage": true,
"hasPrevPage": false,
"nextPage": 2,
"prevPage": null
}
Field Selection
Use the fields parameter to include or exclude specific fields from the response.
# Only return title, slug, and publishedAt
?fields=title,slug,publishedAt
# Exclude heavy fields
?fields=-data.body,-data.rawContent
Draft Access
By default, only documents with status=published are returned. To access draft documents, add the draft query parameter:
?draft=true
Draft access requires an API key with admin scope.
Webhooks API
List All Webhooks
GET /api/webhooks
curl -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/webhooks
Create a Webhook
POST /api/webhooks
curl -X POST https://cms.yourdomain.com/api/webhooks \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Deploy on publish",
"url": "https://your-app.com/webhooks/cms",
"secret": "your-webhook-secret",
"events": ["entry.publish", "entry.unpublish"],
"contentTypes": ["blog_post"]
}'
Update a Webhook
PATCH /api/webhooks/:id
curl -X PATCH https://cms.yourdomain.com/api/webhooks/uuid \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"events": ["entry.publish", "entry.unpublish", "entry.delete"]
}'
Delete a Webhook
DELETE /api/webhooks/:id
curl -X DELETE -H "x-api-key: your-api-key" \
https://cms.yourdomain.com/api/webhooks/uuid
Error Responses
All errors follow a standard format:
{
"error": {
"code": "NOT_FOUND",
"message": "Resource not found"
}
}
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Invalid or missing API key |
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 422 | Invalid request body or query parameters |
RATE_LIMITED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Server error |
Rate Limiting
API requests are rate-limited per API key.
| Plan | Requests/minute | Requests/day |
|---|---|---|
| Default | 60 | 10,000 |