# Management SDK - Migrating to 2.0

> Source: https://agilitycms.com/docs/dotNet/management-sdk-dotnet-migrating-to-2

Version 2.0 of the .NET Management SDK is a rewrite. The Management API it calls is the same, but the SDK's shape changed: one consistent argument order, async methods with cancellation, models named after the API's schemas, typed errors, and saves that wait for their batch. This guide maps each 1.x call to its 2.0 replacement.

> **Using 1.x?** Version 1.x targets .NET 6, so it runs on .NET 6 or later, and it gets fixes only. .NET 6 and 7 are already out of support, and .NET 8 and 9 reach end of support on November 10, 2026, so move to .NET 10 and SDK 2.0 for new and upgraded projects. The 1.x documentation and source are in the [1.0.12-beta release on GitHub](https://github.com/agility/agility-cms-management-sdk-dotnet/releases/tag/v1.0.12-beta), and the package is [Agility.Management.SDK 1.0.12-beta on NuGet](https://www.nuget.org/packages/Agility.Management.SDK/1.0.12-beta). Use 1.0.12-beta or later: earlier 1.x versions remove every zone's default components when they save a page model.

## Before you start

- 2.0 targets **.NET 10** (1.x targeted .NET 6). Move your project to `net10.0` first.
- The NuGet package ID is the same, `Agility.Management.SDK`. Update it to 2.0:

```bash
dotnet add package Agility.Management.SDK --version 2.0.0
```


For an introduction to the 2.0 client, see the [Management SDK intro](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-intro).

## Setup

```csharp
// 1.x
var clientInstance = new ClientInstance(new Options { token = token });
var item = await clientInstance.contentMethods.GetContentItem(42, guid, "en-us");

// 2.0
using var client = new AgilityManagementClient(new AgilityManagementOptions { AccessToken = token });
var item = await client.Content.GetContentItemAsync(guid, "en-us", 42);
```

- **Namespaces:** `management.api.sdk` is now `Agility.Management.Sdk`. The area clients are in `Agility.Management.Sdk.Clients`. `agility.models` and `agility.enums` are both now `Agility.Management.Sdk.Models`.
- **Argument order:** every instance-level method takes its arguments in one order: the instance GUID, then the locale (where the route has one), then IDs, then optional settings. 1.x mixed `(id, guid, locale)` and `(guid, locale, id)`.
- **Area properties:** the `*Methods` properties become area properties: `contentMethods` becomes `client.Content`, `pageMethods` becomes `client.Pages`, and so on.
- **Options objects:** the three methods with many optional settings take an options object: `GetContentListAsync` (`ContentListOptions`), `SavePageAsync` (`SavePageOptions`) and `GetContainerListPagedAsync` (`ContainerListOptions`).
- **Async:** every method ends in `Async` and takes a `CancellationToken`.
- **Dependency injection:** use `services.AddAgilityManagement(...)`.

### Options

| 1.x `Options` | 2.0 `AgilityManagementOptions` |
|---|---|
| `token` | `AccessToken` |
| `refresh_token` (unused in 1.x) | `RefreshToken`: the client now uses it to get and renew access tokens |
| `baseUrl` (ignored in 1.x) | `BaseUrl`: now honoured |
| `duration` (ms between batch checks) | `BatchPolling.Interval` (a `TimeSpan`) |
| `retryCount` (batch checks) | `BatchPolling.Timeout` (a `TimeSpan`, default 15 minutes) |
| `Local` / `BaseLocalURL` environment variables | `BaseUrl` |

## Behaviour changes

| Area | 1.x | 2.0 |
|---|---|---|
| Page model saves | Sent every zone's default components as `[]`, clearing them | Leaves `DefaultModules` out unless you set it. A read-then-save keeps them. |
| Batch operations | Returned the first item's ID, or threw `ApplicationException` on timeout | Return a `BatchResult` (batch ID, every item ID, the batch). Failed items throw `AgilityBatchException`. |
| Batch wait | Stopped at half the configured budget (the counter was decremented twice per check) | Waits the full `BatchPolling.Timeout` |
| Errors | `ApplicationException` with a message | `AgilityManagementException` with status code, API message, body and request ID |
| Retries | None | Reads are retried on transient failures; writes never are |
| Content lists | Called a hidden `GET` route that ignored `filter` | Calls the documented `POST` route with a `ContentListFilterModel` |
| USA 2 region (`-us2`) | Sent to the USA host | Sent to `mgmt-usa2.aglty.io` |
| Unknown GUID suffix | Silently used the USA host | Throws `ArgumentException` |
| Query values | Not URL-encoded | Encoded (fixes emails with `+`, folders with spaces) |
| Async | Blocked on `.Result` inside `async` methods | Fully asynchronous |

For how batches, errors and retries work in 2.0, see the [intro](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-intro#batches).

## Models

Models are generated from the API's OpenAPI schemas, so some names changed, and properties are PascalCase.

| 1.x | 2.0 |
|---|---|
| `Container` | `ContentContainer` |
| `Model`, `ModelField` | `ContentModel`, `ContentModelField` |
| `Media` | `AssetMedia` |
| `PagedResult<Container>` | `ContentContainerPagedResult` |
| `ContentItem.contentID`, `.properties`, `.fields` | `ContentItem.ContentID`, `.Properties`, `.Fields` |
| `ContentItem.fields` (`Dictionary<string, object>`) | `ContentItem.Fields` (`JsonObject`) |
| `ContentItem.GetField(name, type)` | `item.Fields?[name]?.GetValue<T>()` |
| `ContentList.items` (`ArrayList`) | `ContentList.Items` (`List<JsonNode>`) |

Collections on models are `null` until you set them (1.x initialised some to empty lists). Before adding to one on a new object, create it:

```csharp
zone.DefaultModules ??= [];
```

Leaving a collection `null` means "don't change it". See [Null means omit](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-intro#null-means-omit).

## Method map

Every 2.0 method below also takes the instance GUID as its first argument (shown as `guid`). Methods marked † return a `BatchResult` instead of an ID: use `result.ItemId` for the old return value.

### Assets

`assetMethods` becomes `client.Assets`. See [Assets](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-assets).

| 1.x | 2.0 |
|---|---|
| `Upload(files, guid, folderPath, groupingID)` | `UploadAsync(guid, folderPath, [new AssetUpload(name, stream)], galleryId)` |
| `CreateFolder(originKey, guid)` | `CreateFolderAsync(guid, originKey)` |
| `DeleteFile(mediaID, guid)` | `DeleteAssetAsync(guid, mediaId)` |
| `MoveFile(mediaID, newFolder, guid)` | `MoveAssetAsync(guid, mediaId, newFolder)` |
| `GetMediaList(pageSize, recordOffset, guid)` | `GetMediaListAsync(guid, pageSize, recordOffset)` |
| `GetGalleries(guid, search, pageSize, rowIndex)` | `GetGalleriesAsync(guid, search, pageSize, rowIndex)` |
| `GetGalleryById(guid, id)` | `GetGalleryAsync(guid, galleryId)` |
| `GetGalleryByName(guid, name)` | `GetGalleryByNameAsync(guid, galleryName)` |
| `GetDefaultContainer(guid)` | `GetDefaultContainerAsync(guid)` |
| `SaveGallery(guid, gallery)` | `SaveGalleryAsync(guid, gallery)` |
| `DeleteGallery(guid, id)` | `DeleteGalleryAsync(guid, galleryId)` |
| `GetAssetByID(mediaID, guid)` | `GetAssetAsync(guid, mediaId)` |
| `GetAssetByURL(url, guid)` | `GetAssetByUrlAsync(guid, url)` |

### Batches

`batchMethods` becomes `client.Batches`.

| 1.x | 2.0 |
|---|---|
| `GetBatch(id, guid)` | `GetBatchAsync(guid, batchId)` |
| `Retry(func)` | `WaitForBatchAsync(guid, batchId)`; batch operations wait on their own |
| `contentMethods.GetBatchObject` / `pageMethods.GetBatchObject` | `GetBatchAsync(guid, batchId)` |

### Containers

`containerMethods` becomes `client.Containers`. See [Containers](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-containers).

| 1.x | 2.0 |
|---|---|
| `GetContainerById(id, guid)` | `GetContainerAsync(guid, containerId)` |
| `GetContainerByReferenceName(name, guid)` | `GetContainerByReferenceNameAsync(guid, referenceName)` |
| `GetContainersByModel(modelId, guid)` | `GetContainersByModelAsync(guid, modelId)` |
| `GetContainerSecurity(id, guid)` | `GetContainerSecurityAsync(guid, containerId)` |
| `GetContainerList(guid)` | `GetContainerListAsync(guid)` |
| `GetContainerListPaged(guid, ...)` | `GetContainerListPagedAsync(guid, new ContainerListOptions { ... })` |
| `GetNotificationList(id, guid)` | `GetNotificationsAsync(guid, containerId)` |
| `SaveContainer(container, guid)` | `SaveContainerAsync(guid, container)` |
| `DeleteContainer(id, guid)` | `DeleteContainerAsync(guid, containerId)` |

### Content

`contentMethods` becomes `client.Content`. See [Content](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-content).

| 1.x | 2.0 |
|---|---|
| `GetContentItem(contentID, guid, locale)` | `GetContentItemAsync(guid, locale, contentId)` |
| `GetContentItems(referenceName, guid, locale, filter, fields, sortDirection, sortField, take, skip)` | `GetContentListAsync(guid, locale, referenceName, new ContentListOptions { Filter, Take, Skip, Fields, SortField, SortDirection })` |
| `SaveContentItem(item, guid, locale)` † | `SaveContentItemAsync(guid, locale, item)` |
| `SaveContentItems(items, guid, locale)` | `SaveContentItemsAsync(guid, locale, items)`: use `result.ItemIds`. Failures throw instead of appearing as strings in the list. |
| `DeleteContent(contentID, guid, locale, comments)` † | `DeleteContentItemAsync(guid, locale, contentId, comments)` |
| `PublishContent(...)` † | `PublishContentItemAsync(guid, locale, contentId, comments)` |
| `UnPublishContent(...)` † | `UnpublishContentItemAsync(guid, locale, contentId, comments)` |
| `ApproveContent(...)` † | `ApproveContentItemAsync(guid, locale, contentId, comments)` |
| `DeclineContent(...)` † | `DeclineContentItemAsync(guid, locale, contentId, comments)` |
| `ContentRequestApproval(...)` † | `RequestApprovalContentItemAsync(guid, locale, contentId, comments)` |

> **Note:** In 2.0, `ContentListOptions.Filter` is a `ContentListFilterModel`, not a string. The `filter` string from 1.x is gone, because the route 1.x called ignored it.

### Instance users

`instanceUserMethods` becomes `client.InstanceUsers`. See [Instance Users](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-instance-users).

| 1.x | 2.0 |
|---|---|
| `GetUsers(guid)` | `GetUsersAsync(guid)` |
| `SaveUser(email, roles, guid, first, last)` | `SaveUserAsync(guid, email, roles, firstName, lastName)` |
| `DeleteUser(userID, guid)` | `DeleteUserAsync(guid, userId)` |

### Models

`modelMethods` becomes `client.Models`. See [Models](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-models).

| 1.x | 2.0 |
|---|---|
| `GetContentModel(id, guid)` | `GetModelAsync(guid, modelId)` |
| `GetModelByReferenceName(name, guid)` | `GetModelByReferenceNameAsync(guid, referenceName)` |
| `GetContentModules(includeDefaults, guid, includeModules)` | `GetContentModelsAsync(guid, includeDefaults, includeModules)` |
| `GetPageModules(guid, includeDefault)` | `GetComponentModelsAsync(guid, includeDefault)` |
| `SaveModel(model, guid)` | `SaveModelAsync(guid, model)` |
| `DeleteModel(id, guid)` | `DeleteModelAsync(guid, modelId)` |

### Pages

`pageMethods` becomes `client.Pages`. See [Pages](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-pages).

| 1.x | 2.0 |
|---|---|
| `GetSiteMap(guid, locale)` | `GetSitemapAsync(guid, locale)` |
| `GetPage(pageID, guid, locale)` | `GetPageAsync(guid, locale, pageId)` |
| `SavePage(page, guid, locale, parentPageID, placeBeforePageItemID, pageIDInOtherLocale, otherLocale)` † | `SavePageAsync(guid, locale, page, new SavePageOptions { ParentPageId, PlaceBeforePageId, OtherLocale, PageIdInOtherLocale })`: settings left `null` use the API's defaults |
| `DeletePage(...)` † | `DeletePageAsync(guid, locale, pageId, comments)` |
| `PublishPage` / `UnPublishPage` / `ApprovePage` / `DeclinePage` / `PageRequestApproval` † | `PublishPageAsync` / `UnpublishPageAsync` / `ApprovePageAsync` / `DeclinePageAsync` / `RequestApprovalPageAsync`, each `(guid, locale, pageId, comments)` |
| `GetPageTemplates(guid, locale, includeModuleZones, searchFilter)` | `GetPageTemplatesAsync(guid, locale, includeModuleZones, searchFilter)` |
| `GetPageTemplate(guid, locale, id)` | `GetPageTemplateAsync(guid, locale, pageTemplateId)` |
| `GetPageTemplateByName(guid, locale, name)` | `GetPageTemplateByNameAsync(guid, locale, templateName)` |
| `GetPageItemTemplates(guid, locale, id)` | `GetPageTemplateZonesAsync(guid, locale, pageTemplateId)` |
| `SavePageTemplate(guid, locale, template)` | `SavePageTemplateAsync(guid, locale, template)` |
| `DeletePageTemplate(guid, locale, id)` | `DeletePageTemplateAsync(guid, locale, pageTemplateId)` |

Delete methods that returned the API's message string now return `Task`. A failure throws.

## Removed in 2.0

- `ClientInstance`, `Options` and the `*Methods` classes. Use `AgilityManagementClient`, `AgilityManagementOptions` and the area properties, as above.
- `ContentItem.GetField`. Read `Fields` directly.
- The `filter` string on content lists.

## Other changes

- Requests use `System.Net.Http` and source-generated `System.Text.Json`. RestSharp is no longer a dependency.
- `null` properties are left out of requests. The API keeps a stored list it isn't sent, and rejects an explicit `null` for many properties.
- An aborted batch is reported as a failure.
- Every request sends an identifying `User-Agent` (`agility-management-sdk-dotnet/<version>`), with an optional application name from `ApplicationName`, and an `X-Agility-SDK` header with the same product token.
- The whole public API has XML documentation.
- The package has Source Link and symbol packages, and is trimming- and AOT-compatible.

## New in 2.0

2.0 covers every operation in the Management API's OpenAPI spec. New areas and operations include:

- [Webhooks](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-webhooks)
- Locales, and [localization](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-localization) (initialize and translate)
- [URL redirections](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-url-redirections)
- Personal Access Tokens, and the current user
- Batch actions and custom batches
- Batch workflow, cascade publish, and content and page history and comments
- Asset folder rename and delete
- Fetch API sync status and keys
- OAuth sign-in and refresh helpers

The [API coverage table](https://github.com/agility/agility-cms-management-sdk-dotnet/blob/main/docs/api-coverage.md) on GitHub lists every API operation and the SDK method that calls it. The full list of changes is in the [changelog](https://github.com/agility/agility-cms-management-sdk-dotnet/blob/main/CHANGELOG.md).
