Docs/cms/API Reference

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

OperatorDescriptionExample
$eqEquals?filters[status][$eq]=published
$neNot equals?filters[type][$ne]=page
$gtGreater than?filters[data.price][$gt]=100
$gteGreater than or equal?filters[data.price][$gte]=50
$ltLess than?filters[publishedAt][$lt]=2026-01-01
$lteLess than or equal?filters[publishedAt][$lte]=2026-06-30
$inValue in list?filters[status][$in]=draft,published
$notInValue not in list?filters[type][$notIn]=internal
$containsSubstring match (case-insensitive)?filters[data.title][$contains]=hello
$startsWithStarts with?filters[slug][$startsWith]=blog-
$nullIs null?filters[publishedAt][$null]=true
$notNullIs 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.

ParameterTypeDefaultDescription
pagenumber1Page number (1-based)
limitnumber20Documents 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"
  }
}
CodeHTTP StatusDescription
UNAUTHORIZED401Invalid or missing API key
NOT_FOUND404Resource not found
VALIDATION_ERROR422Invalid request body or query parameters
RATE_LIMITED429Too many requests
INTERNAL_ERROR500Server error

Rate Limiting

API requests are rate-limited per API key.

PlanRequests/minuteRequests/day
Default6010,000