CMS
The CMS module manages sites, collections, entries, revisions, marketing campaigns, and marketing posts for content and go-to-market workflows.
What it does
CMS is an app-plane module for structured content operations. It supports headless CMS records and marketing planning records inside the same workspace boundary as CRM, Tasks, Assets, Activity Log, and Data & Insights.
Public capabilities:
- Create and list CMS sites.
- Create and manage content collections.
- Create, search, and update richer entries.
- Move entries through draft, review, published, and archived states.
- Create and list marketing campaigns.
- Create and list marketing posts for campaign channels.
- Keep all CMS operations tenant-scoped, audited, and available through REST and MCP.
Resource model
| Resource | Purpose |
|---|---|
sites | Workspace site records for docs, marketing, help center, or customer education surfaces. |
collections | Content schemas such as case studies, posts, docs, landing pages, or changelog items. |
entries | Structured content records with title, status, fields, and queryable custom_fields. |
revisions | Version history for entry changes. |
marketing_campaigns | Campaign containers for launches, webinars, lifecycle programs, or customer outreach. |
marketing_posts | Channel-specific posts linked to campaigns, sites, entries, assets, or CRM context. |
CMS entries use fields for content schema data and custom_fields for workspace-specific query metadata, such as persona, product area, lifecycle stage, or experiment key.
Production website delivery
Slab5 CMS REST reads are metered API reads. They are intended for builders, server-side applications, previews, content tooling, agents, and controlled publish flows.
Production websites should not fetch published CMS entries from Slab5 on every pageview. Use build-time reads, server-side caching, route-level revalidation, or a publish pipeline that writes a cached website artifact. This keeps websites fast, protects workspace usage, and avoids turning every visitor request into a CMS API request.
Recommended launch patterns:
| Pattern | Use it when | Billing behavior |
|---|---|---|
| Build-time CMS read | Content changes are deployed with the website or through a rebuild. | Slab5 meters the CMS reads during build, not each visitor pageview. |
| Server-side cached read | The site needs fresh content without rebuilding for every change. | Slab5 meters cache misses and revalidation reads. Visitor hits served from cache do not call Slab5. |
| On-demand revalidation | A publish action or webhook can revalidate specific pages. | Slab5 meters the publish/revalidation reads instead of every pageview. |
| Direct per-request CMS read | Internal tools, previews, or low-traffic admin surfaces only. | Every request can become a metered CMS API read. Avoid this for public websites. |
For public marketing sites, use build-time or server-side cached reads before launch. Publish-to-CDN delivery can be added later as a separate publishing layer.
Next.js caching examples
Use a server-only CMS client and set an explicit cache policy for published content reads.
// lib/slab5-cms.ts
import 'server-only'
const SLAB5_API_URL = process.env.SLAB5_API_URL ?? 'https://api.slab5.com/v1'
const SLAB5_WORKSPACE_ID = process.env.SLAB5_WORKSPACE_ID
const SLAB5_API_KEY = process.env.SLAB5_API_KEY
export async function readPublishedEntries(query: string) {
if (!SLAB5_WORKSPACE_ID || !SLAB5_API_KEY) {
throw new Error('Slab5 CMS is not configured')
}
const url = new URL(`${SLAB5_API_URL}/cms/entries`)
url.searchParams.set('status', 'published')
url.searchParams.set('query', query)
url.searchParams.set('limit', '10')
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${SLAB5_API_KEY}`,
'x-slab5-workspace-id': SLAB5_WORKSPACE_ID,
},
next: {
revalidate: 300,
tags: [`slab5-cms:${query}`],
},
})
if (!response.ok) {
throw new Error(`Unable to load published CMS content: ${response.status}`)
}
return response.json()
}
For pages that can tolerate a fixed refresh interval, call the helper from a server component:
// app/products/page.tsx
import { readPublishedEntries } from '@/lib/slab5-cms'
export const revalidate = 300
export default async function ProductsPage() {
const entries = await readPublishedEntries('products')
const products = entries.data?.[0]?.fields?.products
return <ProductsView products={products} />
}
For content that should refresh immediately after publish, add a protected revalidation route in the website and call it from the publish workflow:
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const secret = request.headers.get('x-revalidate-secret')
if (secret !== process.env.REVALIDATE_SECRET) {
return NextResponse.json({ error: 'unauthorized' }, { status: 401 })
}
const { tag } = await request.json()
revalidateTag(tag)
return NextResponse.json({ revalidated: true })
}
Set downstream response caching for server-rendered routes where appropriate:
export async function GET() {
const body = await renderCachedContent()
return new Response(body, {
headers: {
'content-type': 'text/html; charset=utf-8',
'cache-control': 'public, s-maxage=300, stale-while-revalidate=3600',
},
})
}
Keep the Slab5 API key on the server. Do not expose workspace API keys in browser JavaScript.
Marketing workflow
A marketing/content agent can run a launch workflow:
create_marketing_campaignfor the launch or customer cohort.create_cms_siteif the site does not exist yet.create_collectionfor the content type.create_entryfor the draft page, post, or case study.create_asset_upload_intentfor images, PDFs, or video attachments.create_marketing_postfor LinkedIn, email, blog, or changelog distribution.log_activityto preserve planning and publish activity.create_bi_dashboardorrun_bi_queryfor campaign tracking when the Data & Insights module is enabled.
Permissions
Use cms:read to list sites, campaigns, posts, and search entries. Use cms:write to create or update CMS resources.
Recommended scoped agent credentials:
cms:read
cms:write
assets:read
assets:write
activity:write
bi:read
bi:write
MCP tools
create_collection
Create a CMS collection that defines a content type for entries.
- Required scopes
- cms:write
- API equivalent
- POST /v1/cms/collections
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Human-readable collection name. |
| key | string | Yes | Stable collection key used by API and MCP clients. |
| description | string | No | Optional description of the collection purpose. |
| schema | object | No | JSON schema-like field definition for entries in this collection. |
| idempotency_key | string | No | Stable client-generated key that makes retries safe for this write. |
Additional properties are rejected.
Example input
{
"name": "Blog Posts",
"key": "blog_posts",
"schema": {
"fields": [
{
"key": "title",
"type": "string",
"required": true
},
{
"key": "slug",
"type": "string",
"required": true
},
{
"key": "body",
"type": "markdown",
"required": true
}
]
}
}Example response
{
"collection": {
"id": "col_123",
"key": "blog_posts",
"name": "Blog Posts"
},
"request_id": "req_135"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
The idempotency key was reused with a different request body.
create_cms_site
Create a website or marketing site managed by the CMS.
- Required scopes
- cms:write
- API equivalent
- POST /v1/cms/sites
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| key | string | Yes | No description yet. |
| name | string | Yes | No description yet. |
| primary_domain | string | No | No description yet. |
| description | string | No | No description yet. |
| settings | object | No | No description yet. |
| custom_fields | object | No | No description yet. |
| idempotency_key | string | No | No description yet. |
Additional properties are rejected.
Example input
{
"key": "slab5_com",
"name": "slab5.com",
"primary_domain": "slab5.com"
}Example response
{
"site": {
"id": "site_123",
"key": "slab5_com"
},
"request_id": "req_212"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
The idempotency key was reused with a different request body.
list_cms_sites
List CMS-managed websites.
- Required scopes
- cms:read
- API equivalent
- GET /v1/cms/sites
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| query | string | No | No description yet. |
| limit | integer · min 1 · max 100 | No | No description yet. |
| cursor | string | No | No description yet. |
Additional properties are rejected.
Example input
{
"limit": 10
}Example response
{
"sites": [
{
"id": "site_123",
"name": "slab5.com"
}
],
"request_id": "req_213"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
create_entry
Create a CMS entry in a collection.
- Required scopes
- cms:write
- API equivalent
- POST /v1/cms/entries
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| collection_id | string | Yes | Collection ID where the entry should be created. |
| site_id | string | No | Optional CMS site this content belongs to. |
| campaign_id | string | No | Optional marketing campaign this content supports. |
| content_type | string · website_page | landing_page | blog_post | article | social_post | email | case_study | asset_brief | other | No | No description yet. |
| slug | string | No | No description yet. |
| title | string | No | Optional display title for the entry. |
| excerpt | string | No | No description yet. |
| fields | object | Yes | Entry field values keyed by collection schema field key. |
| status | string · draft | review | published | archived | No | Initial publishing status. |
| seo | object | No | No description yet. |
| custom_fields | object | No | User-defined queryable JSON fields. |
| idempotency_key | string | No | Stable client-generated key that makes retries safe for this write. |
Additional properties are rejected.
Example input
{
"collection_id": "col_123",
"title": "AI follow-up workflows",
"status": "draft",
"fields": {
"slug": "ai-follow-up-workflows",
"body": "Draft content..."
}
}Example response
{
"entry": {
"id": "ent_123",
"collection_id": "col_123",
"status": "draft"
},
"request_id": "req_136"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
The requested resource does not exist in this workspace.
search_entries
Search CMS entries by collection, status, title, or free-text query.
- Required scopes
- cms:read
- API equivalent
- GET /v1/cms/entries
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| collection_id | string | No | Restrict results to one collection. |
| site_id | string | No | No description yet. |
| campaign_id | string | No | No description yet. |
| content_type | string · website_page | landing_page | blog_post | article | social_post | email | case_study | asset_brief | other | No | No description yet. |
| status | string · draft | review | published | archived | No | Restrict results by publishing status. |
| query | string | No | Free-text search across title and fields. |
| custom_field_key | string | No | No description yet. |
| custom_field_value | string | No | No description yet. |
| limit | integer · min 1 · max 100 | No | Maximum number of entries to return. |
| cursor | string | No | Opaque cursor from a previous response's next_cursor. |
Additional properties are rejected.
Example input
{
"query": "follow-up",
"status": "draft",
"limit": 10
}Example response
{
"entries": [
{
"id": "ent_123",
"title": "AI follow-up workflows",
"status": "draft"
}
],
"request_id": "req_137"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
update_entry_status
Move a CMS entry between draft, review, published, and archived states. Deprecated in favor of update_entry.
- Required scopes
- cms:write
- API equivalent
- PATCH /v1/cms/entries/{entry_id}
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| entry_id | string | Yes | Entry ID to update. |
| status | string · draft | review | published | archived | Yes | Target publishing status. |
| dry_run | boolean · default false | No | Preview validation and audit effects without changing the entry. |
| idempotency_key | string | No | Stable client-generated key that makes retries safe for this write. |
Additional properties are rejected.
Example input
{
"entry_id": "ent_123",
"status": "review"
}Example response
{
"entry": {
"id": "ent_123",
"status": "review"
},
"request_id": "req_138"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
The requested resource does not exist in this workspace.
update_entry
Update a CMS entry title, fields, or publishing status and create a revision.
- Required scopes
- cms:write
- API equivalent
- PATCH /v1/cms/entries/{entry_id}
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| entry_id | string | Yes | Entry ID to update. |
| slug | string | No | No description yet. |
| excerpt | string | No | No description yet. |
| content_type | string · website_page | landing_page | blog_post | article | social_post | email | case_study | asset_brief | other | No | No description yet. |
| title | string | No | Optional display title for the entry. |
| fields | object | No | Optional replacement field values keyed by collection schema field key. |
| status | string · draft | review | published | archived | No | Optional target publishing status. |
| seo | object | No | No description yet. |
| custom_fields | object | No | No description yet. |
| idempotency_key | string | No | Stable client-generated key that makes retries safe for this write. |
Additional properties are rejected.
Example input
{
"entry_id": "ent_123",
"title": "AI follow-up workflows",
"fields": {
"slug": "ai-follow-up-workflows",
"body": "Updated draft content..."
},
"status": "review"
}Example response
{
"entry": {
"id": "ent_123",
"title": "AI follow-up workflows",
"status": "review"
},
"request_id": "req_138"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
The requested resource does not exist in this workspace.
create_marketing_campaign
Create a marketing campaign for website, blog, email, social, or paid channels.
- Required scopes
- cms:write
- API equivalent
- POST /v1/marketing/campaigns
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| key | string | Yes | No description yet. |
| name | string | Yes | No description yet. |
| status | string · planned | active | paused | completed | archived | No | No description yet. |
| channel | string · website | blog | email | linkedin | x | youtube | paid_ads | partner | other | No | No description yet. |
| objective | string | No | No description yet. |
| budget_cents | integer · min 0 | No | No description yet. |
| custom_fields | object | No | No description yet. |
| idempotency_key | string | No | No description yet. |
Additional properties are rejected.
Example input
{
"key": "slab5_beta_launch",
"name": "Slab5 beta launch",
"channel": "website"
}Example response
{
"campaign": {
"id": "camp_123",
"key": "slab5_beta_launch"
},
"request_id": "req_214"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
The idempotency key was reused with a different request body.
list_marketing_campaigns
List marketing campaigns by status, channel, or custom field.
- Required scopes
- cms:read
- API equivalent
- GET /v1/marketing/campaigns
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| status | string | No | No description yet. |
| channel | string | No | No description yet. |
| query | string | No | No description yet. |
| limit | integer · min 1 · max 100 | No | No description yet. |
| cursor | string | No | No description yet. |
Additional properties are rejected.
Example input
{
"status": "active"
}Example response
{
"campaigns": [
{
"id": "camp_123",
"name": "Slab5 beta launch"
}
],
"request_id": "req_215"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
create_marketing_post
Create a social, blog, email, or website marketing post linked to an optional campaign or CMS entry.
- Required scopes
- cms:write
- API equivalent
- POST /v1/marketing/posts
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| campaign_id | string | No | No description yet. |
| entry_id | string | No | No description yet. |
| channel | string · website | blog | email | linkedin | x | youtube | paid_ads | partner | other | Yes | No description yet. |
| status | string · draft | review | published | archived | No | No description yet. |
| title | string | No | No description yet. |
| body | string | Yes | No description yet. |
| scheduled_at | string · date-time | No | No description yet. |
| external_url | string | No | No description yet. |
| custom_fields | object | No | No description yet. |
| idempotency_key | string | No | No description yet. |
Additional properties are rejected.
Example input
{
"channel": "linkedin",
"title": "Beta launch",
"body": "We are opening Slab5 private beta."
}Example response
{
"post": {
"id": "post_123",
"channel": "linkedin"
},
"request_id": "req_216"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
The requested resource does not exist in this workspace.
list_marketing_posts
List marketing posts by channel, status, campaign, or custom field.
- Required scopes
- cms:read
- API equivalent
- GET /v1/marketing/posts
Example prompt
Input schema
| Property | Type | Required | Description |
|---|---|---|---|
| campaign_id | string | No | No description yet. |
| channel | string | No | No description yet. |
| status | string | No | No description yet. |
| query | string | No | No description yet. |
| limit | integer · min 1 · max 100 | No | No description yet. |
| cursor | string | No | No description yet. |
Additional properties are rejected.
Example input
{
"channel": "linkedin",
"status": "draft"
}Example response
{
"posts": [
{
"id": "post_123",
"channel": "linkedin"
}
],
"request_id": "req_217"
}Common errors
The token does not include the scope required for this operation.
The request payload failed schema validation.
