Breaking changes and migration steps when upgrading from the Content API preview3 to the stable v1 API.
Preview3 CMS REST API reaches end-of-life on August 1, 2026The Preview3 CMS REST API endpoints retire on August 1, 2026. After this date, Optimizely begins disabling access, and applications that still call Preview3 endpoints (URLs containing
/preview3/) may experience failed requests and service interruptions. Migrate to CMS REST API v1 before August 1, 2026. Mixing Preview3 and v1 endpoints is supported until the retirement date.
Migration from preview3 to V1 has a lot of breaking changes between the Optimizely CMS Content API preview3 and v1 versions. Use this guide when migrating an existing integration from preview3 to the stable v1 API.
The v1 API introduces changes to endpoint structure, request and response schemas, validation behavior, and security controls. Review each section carefully and update client configurations, request payloads, and response handling logic to ensure compatibility.
Base URL Change
| Preview3 | V1 |
|---|---|
https://api.cms.optimizely.com/preview3 | https://api.cms.optimizely.com/v1 |
Update your API base URL in all client configurations.
Endpoint Path Changes
Experimental Prefix Removed (Content Endpoints)
All content endpoints have moved from /experimental/ paths to root paths:
| Preview3 | V1 |
|---|---|
/experimental/blueprints | /blueprints |
/experimental/blueprints/{key} | /blueprints/{key} |
/experimental/content | /content |
/experimental/content/{key} | /content/{key} |
/experimental/content/{key}/versions | /content/{key}/versions |
/experimental/content/{key}/versions/{version} | /content/{key}/versions/{version} |
/experimental/content/{key}/items | /content/{key}/items |
/experimental/content/{key}/assets | /content/{key}/assets |
/experimental/content/{key}:copy | /content/{key}:copy |
/experimental/content/{key}:undelete | /content/{key}:undelete |
/experimental/content/{key}/path | /content/{key}/path |
/experimental/content/versions | /content/versions |
NoteRemove the
/experimentalprefix from all content-related endpoint paths.
Packaging API
The Packaging API remains under the /experimental/ prefix in v1. Only the base URL changes:
| Preview3 | V1 |
|---|---|
/preview3/experimental/packages | /v1/experimental/packages |
/preview3/experimental/packages/{key} | /v1/experimental/packages/{key} |
Manifest packages are now handled through a standalone endpoint:
| Preview3 | V1 |
|---|---|
GET /experimental/packages with Accept: application/vnd.optimizely.cms.v1.manifest+json | GET /manifest with Accept: application/json |
POST /experimental/packages with Content-Type: application/vnd.optimizely.cms.v1.manifest+json | POST /manifest with Content-Type: application/json |
The v1 manifest endpoint uses standard application/json content type. The /packages endpoint no longer supports Manifests.
While the Packaging API remain experimental, the /manifest endpoint is now considered stable and has been moved out of experimental status.
Delete Locale Endpoint Changed
| Preview3 | V1 |
|---|---|
DELETE /experimental/content/{key}/versions?locale={locale} | DELETE /content/{key}/locales/{locale} |
The locale parameter has moved from query string to path parameter.
New Endpoints in v1
| New Endpoint | Description |
|---|---|
GET /content/{key}/locales/{locale} | List versions for a specific locale |
DELETE /content/{key}/locales/{locale} | Delete all versions of a specific locale |
GET /content/{key}/versions/{version}/media | Download media content |
POST /content (multipart) | Create media content with binary file upload |
POST /content/{key}/versions/{version}:draft | Transition a version to draft status |
POST /content/{key}/versions/{version}:ready | Transition a version to ready status |
POST /content/{key}/versions/{version}:publish | Publish or schedule a version |
POST /content/{key}/versions/{version}:approve | Approve the active step of an approval |
POST /content/{key}/versions/{version}:reject | Reject the active step of an approval |
GET /manifest | Export a CMS content manifest |
POST /manifest | Import a CMS content manifest |
Schema Renames
The following schemas have been renamed in the OpenAPI specification. These are documentation changes to the model names and do not affect the API endpoints or JSON payloads:
| Preview3 | V1 |
|---|---|
ContentItem | ContentVersion |
ContentItemPage | ContentVersionPage |
ContentMetadata | ContentNode |
ContentMetadataPage | ContentNodePage |
If you generate client code from the OpenAPI specification, update your type references to use the new schema names.
Field Renames
Pagination Field
| Preview3 | V1 |
|---|---|
totalItemCount | totalCount |
Update any code that reads the total item count from paginated responses.
ContentComponent Fields
The ContentComponent model has been renamed to align with other API conventions:
| Preview3 | V1 |
|---|---|
name | displayName |
content | properties |
Update all code that creates or reads ContentComponent objects to use the new field names.
Property Group Names
The default property group names have been renamed to match the UI and .NET API:
| Preview3 | V1 |
|---|---|
Information | Content |
Advanced | Settings |
Update any code that references these system property group names.
Property Value Structure
The structure of content properties has changed. In preview3, property values were stored directly. In v1, all property values are wrapped in an object with a value property. This change enables future scenarios where properties may include additional metadata.
{
"properties": {
"title": "Hello World",
"count": 42,
"tags": ["news", "featured"]
}
}{
"properties": {
"title": {
"value": "Hello World"
},
"count": {
"value": 42
},
"tags": {
"value": ["news", "featured"]
}
}
}Update all code that reads or writes content properties to use the new nested structure.
RichText Property Format
In addition to the value wrapper, richText property values have changed from plain strings to objects with an html property. This change enables future support for additional rich text formats beyond raw HTML.
{
"properties": {
"body": "<p>This is rich text content</p>"
}
}{
"properties": {
"body": {
"value": {
"html": "<p>This is rich text content</p>"
}
}
}
}When reading richText properties, extract the value from properties.{name}.value.html instead of properties.{name}.
When writing richText properties, wrap the HTML string in an object with an html property inside the value wrapper.
Boolean Field Naming
All boolean fields now use is or has prefix for consistency:
| Preview3 | V1 | Location |
|---|---|---|
localized | isLocalized | ContentTypeProperty |
required | isRequired | ContentTypeProperty |
disabled | isDisabled | WebhookSubscription |
enabled | isEnabled | Locale |
deleted | isDeleted | PropertyFormat |
success | isSuccess | PackageImportResult |
Update all boolean field references to use the new naming convention.
Request Body Changes
Create Media Content
In preview3, media content could be created using a JSON request body without binary data. In v1, creating media content requires a multipart form-data request with both metadata and the binary file. You can no longer create media content without providing the binary file.
See the API documentation for details on creating media content.
Create Content
The create content endpoint (POST /content) now uses a different request body structure.
Preview3 used ContentItem directly:
{
"contentType": "MyPageType",
"displayName": "My Page",
"container": "parent-key",
"locale": "en",
"status": "draft"
}V1 uses NewContent with nested initialVersion of type ContentVersion:
{
"key": "optional-content-key",
"contentType": "MyPageType",
"container": "parent-key",
"initialVersion": {
"displayName": "My Page",
"locale": "en"
}
}Key differences:
contentType,container, andownerare now top-level properties inNewContent- Version data (
displayName,locale,properties, etc.) goes insideinitialVersion - The optional
keyfield allows you to specify the content key during creation - The
statusfield is read-only and cannot be set during creation — new content is always created indraftstatus. Use the status transition endpoints (:ready,:publish) to change status after creation.
Required Field Changes
DisplayName Required
The displayName field is now required for several resource types:
| Resource Type | Field | Preview3 | V1 |
|---|---|---|---|
ContentType | displayName | Optional | Required (minLength: 1) |
PropertyGroup | displayName | Optional | Required (minLength: 1) |
Ensure all objects of these types include a non-empty displayName when creating or updating. Existing resources without a displayName value will have the key value assigned.
Field Location Changes
When creating content, several fields have moved:
| Field | Preview3 Location | V1 Location |
|---|---|---|
container | Inside ContentItem | Top-level in NewContent |
owner | Inside ContentItem | Top-level in NewContent |
contentType | Inside ContentItem | Top-level in NewContent (read-only in ContentVersion) |
When reading content, use ContentNode (from GET /content/{key}) to access container and owner information.
Behavior Changes
Command responses
All POST, PATCH, and DELETE command requests now return a 201 Created or 204 No Content response without a body by default. If you want the API to return the resulting or deleted resource as in preview3, you can pass in a Prefer header with the value return=representation as per the RFC 7240 standard.
Non-Primary Locale Versions
In v1, non-primary locale versions only expose culture-specific properties. Properties that are not localizable are no longer merged from the primary locale version into non-primary versions.
In preview3, non-primary locale versions included merged properties from the primary locale. In v1, you must fetch the primary locale version separately if you need non-localizable property values.
Status Is Now Read-Only
In preview3, you could change a version's status by patching the status field:
PATCH /preview3/experimental/content/{key}/versions/{version}
{
"status": "ready"
}In v1, the status field is read-only on both ContentVersion and NewContent.InitialVersion. Attempting to set status to a different value via PATCH returns a validation error. New content is always created in draft status.
The delayPublishUntil field on ContentVersion is also now read-only. To schedule publication, use the :publish endpoint with a delayUntil request body property.
To change a version's status, use the dedicated status transition endpoints described below.
Status Transition Endpoints
V1 introduces three dedicated endpoints for changing version status. These replace the preview3 pattern of patching the status field.
Draft
Transition a version back to draft status.
POST /v1/content/{key}/versions/{version}:draft
200 OK
{
"status": "draft",
...
}Allowed from: ready, scheduled. Calling :draft on a version that is already in draft returns the current version unchanged.
This endpoint does not validate required properties or references.
Ready
Mark a version as ready. The version is available for publishing.
If an approval workflow is configured for the content, this endpoint automatically submits the version for approval. The resulting status will be inReview rather than ready.
POST /v1/content/{key}/versions/{version}:ready
200 OK
{
"status": "ready",
...
}This endpoint now accepts an optional request body with a comment (string, max 255 characters) that is attached to the approval start when the version transitions to inReview.
Allowed from: draft, scheduled. Calling :ready on a version that is already ready returns the current version unchanged.
This endpoint validates required properties and references.
Publish
Publish a version, making it the live version.
POST /v1/content/{key}/versions/{version}:publish
200 OK
{
"status": "published",
...
}Allowed from: draft, ready, scheduled, rejected, inReview (force only).
This endpoint validates required properties and references unless force is set to true.
Optional request body properties:
delayUntil(date-time) — Schedule the version for future publication instead of publishing immediately. The resulting status will bescheduled. If the timestamp is in the past, the version is published immediately.force(boolean) — Bypass content validation and approval workflows. RequiresAdministeraccess on the content; returns403 Forbiddenotherwise.
Scheduled publishing:
POST /v1/content/{key}/versions/{version}:publish
{
"delayUntil": "2026-03-01T09:00:00Z"
}
200 OK
{
"status": "scheduled",
"delayPublishUntil": "2026-03-01T09:00:00Z",
...
}In preview3, scheduled publishing was done by patching the status to scheduled with a delayPublishUntil field. In v1, use the :publish endpoint with delayUntil instead.
Approve
Approve the active step of an approval for a content version in inReview status. See Approve a content version in review for details.
POST /v1/content/{key}/versions/{version}:approveReject
Reject the active step of an approval for a content version in inReview status. The version transitions to rejected. See Reject a content version in review for details.
POST /v1/content/{key}/versions/{version}:rejectStatus Transitions from inReview
inReviewContent in inReview status (awaiting approval) has restricted transitions:
:approve— allowed (advances the active approval step, or completes the approval flow):reject— allowed (rejects the approval and transitions the version torejected):publishwithforce: true— allowed (bypasses approval, requiresAdministeraccess):draft— not allowed:ready— not allowed
Published Versions Cannot Be Updated
In v1, you cannot use PATCH to update a version that is not in draft status. Any attempt to patch a non-draft version returns a 400 Bad Request error.
In preview3, patching a published version would directly modify the published version in place, bypassing the normal versioning workflow. In v1, you must explicitly create a new version using POST /content/{key}/versions to modify content that is already published.
This change aligns the API behavior with the CMS user interface, which requires creating a new draft to edit published content.
Security Changes
Removed act_as Parameter from OAuth Endpoint
act_as Parameter from OAuth EndpointThe act_as parameter has been removed from the OAuth token endpoint in v1. In preview3, this parameter allowed a client to request tokens that impersonate another user, which presented security risks:
- Privilege escalation: A compromised client credential could be used to impersonate any user, including administrators
- Audit trail obfuscation: Actions performed via impersonation could obscure the true actor
- Reduced attack surface: Removing this capability limits the impact of credential theft
Parameter Changes
Skip validation
The cms-skip-validation header value has been changed from accepting a boolean to a string. It currently only supports wildcard (*) values but the intention is to support more granular skip validation instructions in the future.
Replace cms-skip-validation: true with cms-skip-validation: *.
Get Content Node
| Preview3 | V1 |
|---|---|
Query param: allowDeleted | Header: cms-allow-deleted |
Move the allowDeleted flag from query parameter to request header.
Get/Patch Version
| Preview3 | V1 |
|---|---|
Query param: locale | Removed |
The locale query parameter has been removed from version endpoints. Use the new /content/{key}/locales/{locale} endpoint for locale-specific operations.
Copy Content
The copy endpoint, /content/{key}:copy, no longer supports the keepPublishedStatus parameter. Copied content will always be created with draft status. Call the :publish endpoint to publish copied content.
| Preview3 | V1 |
|---|---|
Body: keepPublishedStatus | Removed |
Response Schema Changes
GET /content/{key}
/content/{key}| Preview3 | V1 |
|---|---|
Returns ContentMetadata | Returns ContentNode |
Key differences in the response:
| Field | Preview3 | V1 |
|---|---|---|
locales | Object map of ContentLocaleInfo | Array of locale strings |
primaryLocale | Not present | Added |
created | Not present | Added |
createdBy | Not present | Added |
lastModified | Not present | Added |
lastModifiedBy | Not present | Added |
hasItems | Present | Removed |
{
"locales": {
"en": {
"displayName": "English content",
"status": "published",
"created": "2024-01-01T00:00:00Z",
"createdBy": "admin"
},
"sv": {
"displayName": "Swedish content",
"status": "draft",
"created": "2024-01-02T00:00:00Z",
"createdBy": "admin"
}
}
}{
"primaryLocale": "en",
"locales": ["en", "sv"]
}New Fields in v1
ContentVersion
| Field | Type | Description |
|---|---|---|
binding | ContentBinding | Content binding information with source and contentTypeBinding |
simpleRoute | string | Simple route (shortcut URL) for pages and experiences (max 253 chars) |
ContentType
| Field | Type | Description |
|---|---|---|
createdBy | string | Username of the user who created the content type |
accessRights | array | Access control entries for the content type |
ContentTypeProperty
| Field | Type | Description |
|---|---|---|
displayMode | string | Display mode for the property editor |
editorSettings | object | Additional editor configuration settings (currently only supports preset for richText properties) |
ArrayItem
| Field | Type | Description |
|---|---|---|
editorSettings | object | Additional editor configuration settings for array items |
ContentComponent
| Field | Type | Description |
|---|---|---|
binding | ContentBinding | Content binding information for the component |
Blueprint
| Field | Type | Description |
|---|---|---|
created | date-time | When the blueprint was created |
createdBy | string | Username of the user who created the blueprint |
Array Limits
The following arrays now have explicit maximum item limits of 1000:
mayContainTypesmediaFileExtensionscompositionBehaviors
Stricter Validation
Unknown Fields Cause Errors
V1 uses strict JSON validation. Unknown or unsupported fields in request bodies now result in a 400 Bad Request error. In preview3, unknown fields were silently ignored.
Exception: Read-only properties are ignored. To simplify JSON roundtripping (e.g., fetching content and sending it back in an update), read-only properties such as created, createdBy, lastModified, and lastModifiedBy are silently ignored rather than causing an error. These properties are clearly documented as read-only in the API specification.
Preview3 behavior (ignored unknown field):
{
"displayName": "My Content",
"unknownField": "this was ignored"
}V1 behavior (returns 400 error):
{
"displayName": "My Content",
"unknownField": "this causes an error"
}Review all API requests and remove any fields that are not part of the v1 schema.
Choice Property Validation
V1 validates that values for choice properties match one of the defined choices. In preview3, any value was accepted regardless of the defined choices.
If a choice property has defined values like ["Option1", "Option2", "Option3"], attempting to set an undefined value will now return a validation error.
Locale Must Exist Before Creating Content
V1 requires that the locale exists in the system before creating content in that locale. In preview3, creating content in a non-existent locale would automatically create the locale.
Ensure the required locale is configured in the system before creating content.
Removed Endpoints
The following endpoints are not available in v1:
| Removed Endpoint | Description |
|---|---|
/experimental/changesets | Changeset list and create |
/experimental/changesets/{key} | Changeset get, patch, and delete |
/experimental/changesets/{changeset}/items | Changeset items list |
/experimental/changesets/{changeset}/items/{key}/versions/{version} | Changeset item version operations |
/experimental/content/{key}/versions/{version}/previews | Content version previews |
The Changesets API is not available in v1. This API was built on top of the CMS PaaS feature "Projects" which is not available in CMS SaaS.
Removed Schemas
The following schemas are not available in v1:
| Schema | Notes |
|---|---|
Changeset | Changesets API removed |
ChangesetItem | Changesets API removed |
ChangesetItemPage | Changesets API removed |
ChangesetPage | Changesets API removed |
ContentLocaleInfo | Replaced by simpler locale array in ContentNode |
ContentReference | Removed |
ImageDescriptor | Removed (was used in ContentTypeProperty.imageDescriptor) |
Removed Fields
The following fields have been removed from existing schemas:
| Schema | Removed Field | Notes |
|---|---|---|
ContentTypeProperty | imageDescriptor | Image descriptor configuration removed |
