---
title: Migrate from Content API preview3 to v1
slug: cms-saas/api/migrate-from-content-api-preview3-to-v1
docTags: 
createdAt: 2026-07-30T20:20:18.789Z
---

:::hint{type="warning"}
**Preview3 CMS REST API reaches end-of-life on August 1, 2026**
The 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`                 |

:::hint{type="info"}
**Note**
Remove the `/experimental` prefix 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.

```json
{
  "properties": {
    "title": "Hello World",
    "count": 42,
    "tags": ["news", "featured"]
  }
}
```

```json
{
  "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.

```json
{
  "properties": {
    "body": "<p>This is rich text content</p>"
  }
}
```

```json
{
  "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:

```json
{
  "contentType": "MyPageType",
  "displayName": "My Page",
  "container": "parent-key",
  "locale": "en",
  "status": "draft"
}
```

**V1** uses `NewContent` with nested `initialVersion` of type `ContentVersion`:

```json
{
  "key": "optional-content-key",
  "contentType": "MyPageType",
  "container": "parent-key",
  "initialVersion": {
    "displayName": "My Page",
    "locale": "en"
  }
}
```

Key differences:

- `contentType`, `container`, and `owner` are now top-level properties in `NewContent`
- Version data (`displayName`, `locale`, `properties`, etc.) goes inside `initialVersion`
- The optional `key` field allows you to specify the content key during creation
- The `status` field is read-only and cannot be set during creation — new content is always created in `draft` status. 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](https://www.rfc-editor.org/rfc/rfc7240) 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:

:::CodeblockTabs
Preview3

```http
  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.

:::CodeblockTabs
HTTP

```http
  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`.

:::CodeblockTabs
HTTP

```http
  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.

:::CodeblockTabs
HTTP

```http
  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 be `scheduled`. If the timestamp is in the past, the version is published immediately.
- `force` (boolean) — Bypass content validation and approval workflows. Requires `Administer` access on the content; returns `403 Forbidden` otherwise.

Scheduled publishing:

:::CodeblockTabs
HTTP

```http
  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](docId\:CR95f0kp3YilGj3G4OFJT) for details.

:::CodeblockTabs
HTTP

```http
  POST /v1/content/{key}/versions/{version}:approve
  
```
:::

### Reject

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](docId\:CR95f0kp3YilGj3G4OFJT) for details.

:::CodeblockTabs
HTTP

```http
  POST /v1/content/{key}/versions/{version}:reject
  
```
:::

### Status Transitions from `inReview`

Content 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 to `rejected`)
- `:publish` with `force: true` — allowed (bypasses approval, requires `Administer` access)
- `: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

The `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}`

| 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                 |

```json
{
  "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"
    }
  }
}
```

```json
{
  "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:

- `mayContainTypes`
- `mediaFileExtensions`
- `compositionBehaviors`

## 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):**

```json
{
  "displayName": "My Content",
  "unknownField": "this was ignored"
}
```

**V1 behavior (returns 400 error):**

```json
{
  "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 |
