CMP REST API Integration
Why use the CMP API
Section titled “Why use the CMP API”The CMP user interface covers day-to-day content operations, but integrations require programmatic access. You might need to pull content into a custom dashboard, sync CMP data with a project management tool, trigger publishing from a CI/CD pipeline, or feed content metrics back into CMP from an analytics platform.
The CMP REST API provides these capabilities through standard HTTP endpoints with JSON payloads.
What you will do
Section titled “What you will do”- Authenticate with the CMP API
- Query content items
- Manage assets programmatically
- Trigger content publishing
- Handle pagination and errors
Authenticate with the CMP API
Section titled “Authenticate with the CMP API”CMP uses OAuth 2.0 client credentials flow. You exchange a client ID and secret for an access token, then include that token in subsequent requests.
async function getAccessToken(clientId, clientSecret) {
const response = await fetch('https://api.optimizely.com/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
scope: 'cmp:read cmp:write',
}),
});
if (!response.ok) {
throw new Error(`Auth failed: ${response.status}`);
}
const data = await response.json();
return data.access_token;
} Access tokens expire after one hour. Cache the token and refresh it before expiration. The response includes an expires_in field (in seconds) that tells you when to request a new one.
Query content items
Section titled “Query content items”Retrieve content items with filtering, sorting, and pagination.
async function listContent(token, filters = {}) {
const params = new URLSearchParams({
page: filters.page || 1,
per_page: filters.perPage || 25,
...(filters.status && { status: filters.status }),
...(filters.contentType && { content_type: filters.contentType }),
...(filters.campaignId && { campaign_id: filters.campaignId }),
});
const response = await fetch(
`https://api.optimizely.com/v1/cmp/content?${params}`,
{
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}
// Usage: fetch all approved blog posts
const results = await listContent(token, {
status: 'approved',
contentType: 'blog-post',
perPage: 50,
}); Retrieve a single content item
Section titled “Retrieve a single content item”async function getContentItem(token, contentId) {
const response = await fetch(
`https://api.optimizely.com/v1/cmp/content/${contentId}`,
{
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
} The response includes all content fields, metadata, workflow state, and references to associated assets and campaigns.
Manage assets
Section titled “Manage assets”Upload and retrieve assets from the CMP repository.
async function uploadAsset(token, file, metadata) {
const formData = new FormData();
formData.append('file', file);
formData.append('title', metadata.title);
formData.append('description', metadata.description || '');
formData.append('tags', JSON.stringify(metadata.tags || []));
formData.append('folder_id', metadata.folderId || '');
const response = await fetch(
'https://api.optimizely.com/v1/cmp/assets',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
body: formData,
}
);
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
return response.json();
} Trigger content publishing
Section titled “Trigger content publishing”Push approved content to a configured publishing channel programmatically.
async function publishContent(token, contentId, channelId, options = {}) {
const response = await fetch(
`https://api.optimizely.com/v1/cmp/content/${contentId}/publish`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel_id: channelId,
schedule_at: options.scheduleAt || null,
target_location: options.targetLocation || null,
}),
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(`Publish failed: ${error.message}`);
}
return response.json();
}
// Publish immediately
await publishContent(token, 'content-123', 'channel-cms-prod');
// Schedule for later
await publishContent(token, 'content-456', 'channel-cms-prod', {
scheduleAt: '2026-04-01T09:00:00Z',
}); The publish endpoint only works for content in an approved workflow state. Attempting to publish content that has not completed its workflow returns a 422 Unprocessable Entity error.
Handle pagination and errors
Section titled “Handle pagination and errors”Pagination
Section titled “Pagination”List endpoints return paginated results. Use the page and per_page parameters to navigate.
async function getAllContent(token, contentType) {
const allItems = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await listContent(token, {
contentType,
page,
perPage: 100,
});
allItems.push(...response.items);
hasMore = response.pagination.has_next;
page++;
}
return allItems;
} Error handling
Section titled “Error handling”The API uses standard HTTP status codes:
| Status | Meaning | Action |
|---|---|---|
400 | Invalid request parameters | Check your request body and query parameters |
401 | Authentication failed or token expired | Refresh your access token |
403 | Insufficient permissions | Verify API key scopes |
404 | Resource not found | Check the content or asset ID |
422 | Validation error (e.g., publishing unapproved content) | Read the error message for specifics |
429 | Rate limit exceeded | Wait and retry with exponential backoff |
Rate limits
Section titled “Rate limits”The CMP API allows 100 requests per minute per API key. If you exceed this, the API returns 429 Too Many Requests with a Retry-After header indicating how long to wait.