HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunityAcademySubmit a ticketLog In
Dev Guide

Upgrade Content Delivery API from version 3 to version 13

Describes how to upgrade Content Delivery API from version 3 to version 13, including the System.Text.Json migration, the new base endpoint, and the removal of Content Definitions API and Content Management API.

Content Delivery API 13 runs on Optimizely CMS 13 and .NET 10, and serializes content with System.Text.Json instead of Newtonsoft.Json. Upgrade to keep delivering content through a supported API after you move a solution off CMS 12. This article covers the package updates, the serializer changes, and the endpoint changes that your client applications require.

Version 13 is the supported path for solutions that run Content Delivery API 3.x. Upgrade to version 13 rather than rebuilding the integration on another API.

Version 13 also removes Content Definitions API and Content Management API. The version number moves from 3 to 13 so that it matches the CMS release it supports.

🚧

Important

The base endpoint moves from /api/episerver/v3.0 to /api/episerver/v4.0. Every client application breaks until you update its API URLs. Plan a coordinated release of the solution and its client applications.

For details about the CMS platform changes behind this upgrade, see Upgrade to CMS 13 from CMS 12.

Prerequisites

Before you start the upgrade, verify the following requirements:

  • Your solution runs Optimizely CMS 13.0.2 or a later 13.x release.
  • Your project targets .NET 10.
  • Your solution runs Commerce Connect 15.0.0 or a later 15.x release, when the solution uses Commerce.
  • Your solution runs Optimizely Forms 6.0.1 or a later 6.x release, when the solution uses Forms.
  • You hold access to update both the server-side project files and the client applications that call the API.
  • You hold a staging environment for testing the upgrade before production.
  • You hold an inventory of custom IPropertyModel, ContentApiModelFilter, and IJsonConverter implementations in the solution.
  • You hold an inventory of the calls your solution makes to Content Management API and Content Definitions API. Version 13 adds no replacement for either, so those calls need a separate integration. See Content Definitions API and Content Management API.

Upgrade sequence

Complete the upgrade in the following order, because each step depends on the one before it:

  1. Upgrade CMS to version 13 and retarget the project to .NET 10.
  2. Update the Content Delivery API package references.
  3. Replace the Newtonsoft.Json serializer configuration with System.Text.Json.
  4. Remove the references to Content Definitions API and Content Management API.
  5. Update the client applications to the new base endpoint and response model.
  6. Build the solution and resolve the remaining errors.
  7. Test the API responses against the client applications in a staging environment.

Server-side changes

The changes in this section apply to the C# solution that hosts the API.

Update NuGet packages

Update the package references in your project file to version 13.0.0:

<PackageReference Include="EPiServer.ContentDeliveryApi.Core" Version="13.0.0" />
<PackageReference Include="EPiServer.ContentDeliveryApi.Cms" Version="13.0.0" />

Solutions that use Commerce Connect also update the Commerce package:

<PackageReference Include="EPiServer.ContentDeliveryApi.Commerce" Version="13.0.0" />

Remove Newtonsoft.Json dependencies

Content Delivery API 13.0 uses System.Text.Json. Remove the Newtonsoft.Json packages that served the Content API alone:

<!-- Remove when only the Content API required this package -->
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="..." />
📘

Note

Keep Newtonsoft.Json installed when other parts of your solution depend on it. The Content Delivery API packages no longer reference it.

For the framework-wide Newtonsoft.Json removals in CMS 13, see Framework and platform breaking changes.

Update JSON attribute usage

Update the model classes that carry Newtonsoft.Json attributes and feed the Content API. The following table maps each attribute to its System.Text.Json equivalent:

Newtonsoft.JsonSystem.Text.Json
using Newtonsoft.Json;using System.Text.Json.Serialization;
[JsonProperty("name")][JsonPropertyName("name")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)][JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonIgnore][JsonIgnore] (namespace change only)
[JsonConverter(typeof(X))][JsonConverter(typeof(X))] (namespace change only)
[JsonExtensionData][JsonExtensionData] (namespace change only)

Update custom JSON converters

Custom JSON converters registered through IJsonConverter now extend System.Text.Json.Serialization.JsonConverter<T> rather than Newtonsoft.Json.JsonConverter. Version 3.x used the following pattern:

public class MyConverter : Newtonsoft.Json.JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { ... }
    public override object ReadJson(JsonReader reader, Type type, object existing, JsonSerializer serializer) { ... }
    public override bool CanConvert(Type objectType) => objectType == typeof(MyModel);
}

Version 13.0 uses the following pattern:

public class MyConverter : System.Text.Json.Serialization.JsonConverter<MyModel>
{
    public override void Write(Utf8JsonWriter writer, MyModel value, JsonSerializerOptions options) { ... }
    public override MyModel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { ... }
}

Update serializer configuration

The serializer configuration callback receives JsonSerializerOptions from System.Text.Json rather than JsonSerializerSettings from Newtonsoft.Json. Version 3.x used the following configuration:

services.ConfigureContentDeliveryApiSerializer(settings =>
{
    settings.NullValueHandling = NullValueHandling.Ignore;
    settings.ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new CamelCaseNamingStrategy()
    };
});

Version 13.0 uses the following configuration:

services.ConfigureContentDeliveryApiSerializer(options =>
{
    options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
});

The following table maps each Newtonsoft.Json setting to its System.Text.Json equivalent:

Newtonsoft.JsonSystem.Text.Json
NullValueHandling.IgnoreDefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
CamelCaseNamingStrategyPropertyNamingPolicy = JsonNamingPolicy.CamelCase
StringEnumConverterJsonStringEnumConverter(JsonNamingPolicy.CamelCase)
MaxDepth = 32MaxDepth = 32
MissingMemberHandling.IgnoreDefault behavior, so no action follows
TypeNameHandling.NoneDefault behavior, so no action follows

Solutions that use Commerce Connect apply the same changes to ConfigureCommerceApiSerializer.

Set camelCase keys on custom property models

System.Text.Json does not apply DictionaryKeyPolicy to [JsonExtensionData] dictionary keys. This affects custom IPropertyModel implementations and custom ContentApiModelFilter implementations that add keys to ContentApiModel.Properties.

In version 3.x, the Newtonsoft.Json CamelCasePropertyNamesContractResolver converted every dictionary key to camelCase. [JsonExtensionData] keys received the same treatment. Version 3.x accepted a PascalCase key:

contentApiModel.Properties.Add("PreviewUrl", value); // Serialized as "previewUrl"

In version 13.0, set the key in camelCase yourself:

contentApiModel.Properties.Add("previewUrl", value); // Serialized as written

A custom property key sometimes collides with a built-in ContentApiModel property name after camelCase conversion. A content property named Url becomes url, which matches the built-in url property. Content Delivery API skips the colliding property to avoid duplicate JSON keys.

Replace ConfigureForExternalTemplates

Version 13.0 deprecates ConfigureForExternalTemplates(). Register headless applications in CMS 13 as RemoteWebsite instead. A RemoteWebsite is an application type for an external front end. It holds the front-end host and edit URLs, and serves no templates from CMS.

Version 3.x used the following registration:

services.AddContentApiCore();
services.ConfigureForExternalTemplates();

In version 13.0, configure the application as a RemoteWebsite in the CMS 13 admin interface, then remove the ConfigureForExternalTemplates() call. To disable template validation for content URLs, set the option directly:

services.AddContentApiCore(options =>
{
    options.ValidateTemplateForContentUrl = false;
});

Update the renamed site types

CMS 13 replaces the SiteDefinition model with the Application model, and Content Delivery API follows that change. Version 13.0 handles the renamed types in two ways. It removes some outright, and marks others obsolete. The following table lists each type and the result at compile time:

Version 3.xVersion 13.0Compile result
SiteDefinitionModelApplicationModelError, because version 13.0 removes the type
ContentApiTrackingContext.ReferencedSitesContentApiTrackingContext.ReferencedApplicationsError, because version 13.0 removes the property
SiteDefinitionLanguageModelApplicationLanguageModelWarning, because the type stays as obsolete
DependencyTypes.SiteDependencyTypes.ApplicationWarning, because the constant stays as obsolete
ReferencedSiteMetadataReferencedApplicationMetadataWarning, because the type stays as obsolete

Update every entry in the table, including the obsolete ones. A later release removes the obsolete types.

The ReferencedApplicationMetadata constructor also takes a different first argument. ReferencedSiteMetadata took a globally unique identifier (GUID), and ReferencedApplicationMetadata takes the application name as a string.

For the wider CMS 13 changes behind the Application model, see Sites-to-applications and routing breaking changes.

Remove Search & Navigation package references

Version 13.0 drops the Content Search API packages. Remove the following package references:

<!-- Remove -->
<PackageReference Include="EPiServer.ContentDeliveryApi.Search" Version="..." />
<PackageReference Include="EPiServer.ContentDeliveryApi.Search.Commerce" Version="..." />

Client application changes

The changes in this section apply to the applications that consume the API. Review each one against the client code before you deploy.

Base endpoint change

The base endpoint moves from /api/episerver/v3.0 to /api/episerver/v4.0. Update every API URL in your client applications.

Site endpoint changes

The endpoint path /site/ and the response header x-epi-siteid keep their version 3.x names. The underlying CMS model moves from SiteDefinition to Application, which changes the endpoint in three ways.

First, the single-item endpoint accepts a name instead of an identifier. Version 13.0 accepts a site name string at GET /api/episerver/v4.0/site/{name}. Version 3.x accepted a globally unique identifier (GUID) at GET /api/episerver/v3.0/site/{id}. A request that carries a name with no match returns 404 Not Found rather than 400 Bad Request.

Second, version 13.0 removes the id property from the response. Use name to identify applications. The response takes the following shape:

{
  "name": "My Site",
  "editLocation": "...",
  "contentRoots": { ... },
  "languages": [ ... ],
  "hosts": [ ... ]
}

Third, the x-epi-siteid header carries the application name as a string rather than a GUID.

Two further behaviors change in the response:

  • Content roots – CMS 13 adds a blueprints content root. A blueprint is a reusable content structure in CMS 13, and the blueprints root holds those structures. The response always includes this root in contentRoots, whatever the value of the IncludeInternalContentRoots setting. Handle the extra entry in code that iterates the dictionary.
  • No wildcard hosts – The response returns only the hosts that you configure explicitly. CMS 13 rejects a wildcard host. The validation error reads Host cannot be a wildcard '*'. Make the application default instead. Mark the application as default, then update client code that tests for a wildcard host.

JSON serialization differences

System.Text.Json output differs from Newtonsoft.Json output in the following cases. Review each one against the parsing logic in your client applications.

  • Null handling – By default, the response includes properties that hold null. Set DefaultIgnoreCondition to JsonIgnoreCondition.WhenWritingNull to omit them.
  • Number handling – System.Text.Json applies stricter rules to number types. A value such as 1.0 serializes as 1 for an integer type.
  • String-to-number coercion – System.Text.Json rejects a JSON string where a numeric type belongs. Newtonsoft.Json converted the JSON string "99" to the integer 99. System.Text.Json throws a JsonException for the same value.

Custom date formats also need review, because System.Text.Json and Newtonsoft.Json apply them differently. Both libraries write ISO 8601 values by default, such as 2024-01-15T10:30:00Z.

Removed APIs

Version 13.0 removes two of the three APIs that shipped in version 3.x. Content Delivery API is the only one that remains.

Content Definitions API and Content Management API

Content Delivery API 13.0 ships neither EPiServer.ContentDefinitionsApi, EPiServer.ContentDefinitionsApi.Commerce, nor EPiServer.ContentManagementApi. Remove the package references:

<!-- Remove -->
<PackageReference Include="EPiServer.ContentDefinitionsApi" Version="..." />
<PackageReference Include="EPiServer.ContentDefinitionsApi.Commerce" Version="..." />
<PackageReference Include="EPiServer.ContentManagementApi" Version="..." />

Remove the matching service registrations from Startup.cs:

// Remove from ConfigureServices
services.AddContentDefinitionsApi(...);
services.AddContentManagementApi(...);

Version 13 adds no replacement for either API. Optimizely has no plans to upgrade them, so no version 13 equivalent of EPiServer.ContentManagementApi or EPiServer.ContentDefinitionsApi follows.

CMS 13 exposes content management through the CMS REST API at /_cms/v1, which AddCms() registers automatically. Treat that API as a separate integration, not a drop-in replacement. Account for two differences before you plan the work:

  • Authentication – The CMS REST API supports the client credentials grant only. Content Management API also accepted the authorization code and resource owner password grants. Solutions that used either one need a new authentication design.
  • API surface – The endpoints, routes, and payloads differ from Content Management API and Content Definitions API. Budget for rewriting the calls rather than repointing them.

For the endpoint reference and the authentication setup, see Introduction to the CMS REST API.

OpenID Connect scopes for the removed APIs

Remove the scopes for the removed APIs from your OpenID Connect configuration:

// Remove these scopes from OpenIDConnectApplication configurations
ContentDefinitionsApiOptionsDefaults.Scope  // "epi_content_definitions"
ContentManagementApiOptionsDefaults.Scope   // "epi_content_management"

OpenID Connect changes

Update OpenID Connect packages

Update the OpenID Connect package references to version 13.0.0:

<PackageReference Include="EPiServer.OpenIDConnect" Version="13.0.0" />
<PackageReference Include="EPiServer.OpenIDConnect.UI" Version="13.0.0" />

Protected module path change

CMS 13 moves the protected module root, the path that serves the Optimizely user interfaces, from /EPiServer/ to /Optimizely/. CMS 13 serves the OpenID Connect interface from /Optimizely/OpenIDConnect/ instead of /EPiServer/OpenIDConnect/. Update any application that references the path directly.

The same change applies to the CMS edit interface, which moves from /EPiServer/CMS/Content/ to /Optimizely/CMS/Content/.

Package version compatibility

Version 13.0 declares a supported range for each Optimizely dependency. The following table maps the versions across the two releases:

PackageVersion 3.xVersion 13.0
EPiServer.ContentDeliveryApi.Core3.x13.0.0
EPiServer.ContentDeliveryApi.Cms3.x13.0.0
EPiServer.ContentDeliveryApi.Commerce3.x13.0.0
EPiServer.Cms12.x13.0.2 up to, but excluding, 14.0
EPiServer.Commerce.Core14.x15.0.0 up to, but excluding, 16.0
EPiServer.Forms5.x6.0.1 up to, but excluding, 7.0
.NET6.010.0
JSON serializerNewtonsoft.JsonSystem.Text.Json

The Commerce package carries its own version number, 15.0.0. The other Content Delivery API packages use 13.0.0.

Troubleshooting

Build error: JsonSerializerSettings not found

Your code references the version 3.x serializer configuration type. Replace JsonSerializerSettings with JsonSerializerOptions, and change the using directive from Newtonsoft.Json to System.Text.Json. See Update serializer configuration.

Build error: JObject or JToken not found

Your code references the Newtonsoft.Json dynamic JSON types. Replace them with the System.Text.Json equivalents in the following table:

Newtonsoft.JsonSystem.Text.Json
JObjectJsonDocument, JsonElement, or JsonNode
JTokenJsonElement or JsonNode
JArrayJsonElement.EnumerateArray() or JsonArray
JObject.Parse(json)JsonDocument.Parse(json).RootElement
JToken.FromObject(obj)JsonSerializer.SerializeToElement(obj)

Build error: SiteDefinitionModel not found

Version 13.0 removes SiteDefinitionModel. Replace it with ApplicationModel. The related type SiteDefinitionLanguageModel stays as obsolete, so it raises a warning rather than this error. See Update the renamed site types.

Runtime error: JsonSerializerOptions must specify a TypeInfoResolver

Your code creates JsonSerializerOptions manually and passes them to an ASP.NET Core formatter. Set the resolver on the options:

options.TypeInfoResolver = new DefaultJsonTypeInfoResolver();

Runtime error: No service for type 'ServiceAccessor<Application>'

CMS 13 does not register ServiceAccessor<Application> in the dependency injection container. Inside an HTTP request context, resolve the application through IApplicationResolver:

var resolver = serviceProvider.GetRequiredService<IApplicationResolver>();
var currentApp = resolver.GetByContext();

Outside an HTTP request context, such as a background task, resolve it through IApplicationRepository:

var repository = serviceProvider.GetRequiredService<IApplicationRepository>();
var app = repository.List().First();

Both interfaces also expose asynchronous members, GetByContextAsync and ListAsync. Prefer the asynchronous overloads in request handlers, which is the pattern that the Alloy sample templates follow.

Content properties missing from the JSON response

The JSON response omits custom content properties, or returns them with PascalCase keys. System.Text.Json does not apply DictionaryKeyPolicy to [JsonExtensionData] dictionary keys. A property stored under a PascalCase key such as MainContentArea serializes as MainContentArea rather than mainContentArea. See Set camelCase keys on custom property models.


Did this page help you?