Skip to content

CMP REST API Integration

⏱ 25 minutes intermediate

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.

  1. Authenticate with the CMP API
  2. Query content items
  3. Manage assets programmatically
  4. Trigger content publishing
  5. Handle pagination and errors

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.

Obtain an access token
javascript
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.

Retrieve content items with filtering, sorting, and pagination.

List content items with filters
javascript
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,
});
Get content item by ID
javascript
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.

Upload and retrieve assets from the CMP repository.

Upload an asset
javascript
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();
}

Push approved content to a configured publishing channel programmatically.

Publish content to a channel
javascript
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.

List endpoints return paginated results. Use the page and per_page parameters to navigate.

Paginate through all results
javascript
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;
}

The API uses standard HTTP status codes:

StatusMeaningAction
400Invalid request parametersCheck your request body and query parameters
401Authentication failed or token expiredRefresh your access token
403Insufficient permissionsVerify API key scopes
404Resource not foundCheck the content or asset ID
422Validation error (e.g., publishing unapproved content)Read the error message for specifics
429Rate limit exceededWait and retry with exponential backoff

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.