Introduction
Install and authenticate the Agility Management SDK in JavaScript or .NET — OAuth, Personal Access Tokens, client setup, and regional endpoints.
Managing content from outside the CMS has never been easier. The Agility Management SDK lets you create and update content, apply workflow actions, and manage pages, models, containers, and assets through the Agility Management API.
The SDK ships for JavaScript/TypeScript and .NET. This section documents both — use the tabs in each code sample to switch languages, and your choice is remembered as you read.
npm install @agility/management-sdk
dotnet add package Agility.Management.SDK --prerelease
Two things will trip you up on the .NET side. The assembly is named
management.api.sdk.dll, but the NuGet package id isAgility.Management.SDK— asking formanagement.api.sdkfails with a package-not-found error. And every published version is still a prerelease, so without--prerelease(or an explicit--version) NuGet reports that no installable version exists. Treat the .NET SDK as beta accordingly.
Both SDKs are open source:
Every request needs an access token. The SDK supports two ways to get one: OAuth 2.0 (for interactive apps) and Personal Access Tokens (for automation — see below).
Send the user to the authorize endpoint. Add the offline_access scope if you want a refresh token so the integration can run unattended:
GET https://mgmt.aglty.io/oauth/authorize
?response_type=code
&redirect_uri=YOUR_REDIRECT_URI
&state=YOUR_STATE
&scope=openid profile email offline_access
Your redirect URI receives an authorization code. Exchange it for an access token:
const response = await fetch("https://mgmt.aglty.io/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ code: "YOUR_AUTHORIZATION_CODE" }),
})
const { access_token, refresh_token, expires_in } = await response.json()
// POST https://mgmt.aglty.io/oauth/token
// Content-Type: application/x-www-form-urlencoded
// code=YOUR_AUTHORIZATION_CODE
using var http = new HttpClient();
var body = new FormUrlEncodedContent(new Dictionary<string, string>
{
["code"] = "YOUR_AUTHORIZATION_CODE"
});
var response = await http.PostAsync("https://mgmt.aglty.io/oauth/token", body);
var json = await response.Content.ReadAsStringAsync();
The response is the same shape either way: access_token, token_type, expires_in, and — when you asked for offline_access — refresh_token.
POST https://mgmt.aglty.io/oauth/refresh?refresh_token=YOUR_REFRESH_TOKEN
Token lifetimes. Access tokens last 24 hours — after that, requests return
401 Unauthorized. Refresh tokens last 30 days, and only exist if you authorized with theoffline_accessscope. Store both securely and never expose them in client-side code.
With a token in hand, initialize the client and make your first request:
import * as mgmtApi from "@agility/management-sdk"
// Initialize the Options class
const options = new mgmtApi.Options()
options.token = "<<your-access-token>>"
// Initialize the ApiClient
const apiClient = new mgmtApi.ApiClient(options)
const guid = "<<your-instance-guid>>"
const locale = "en-us"
// Get the content item with ID 22
const contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale)
console.log(JSON.stringify(contentItem))
using management.api.sdk;
using agility.models;
// Initialize Options with your access token
var options = new Options
{
token = "<<your-access-token>>"
};
// Create the client instance
var client = new ClientInstance(options);
var guid = "<<your-instance-guid>>";
var locale = "en-us";
// Get the content item with ID 22
var contentItem = await client.contentMethods.GetContentItem(22, guid, locale);
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(contentItem));
Note that the .NET using statement is management.api.sdk — the assembly name — even though you installed the Agility.Management.SDK package.
| Field | Purpose |
|---|---|
token | OAuth access token or PAT (required) |
baseUrl | Override the API base URL (optional) |
refresh_token | OAuth refresh token (optional) |
duration | Retry polling interval in ms (default 3000) |
retryCount | Max retry attempts for batch polling (default 500) |
These are SDK-side settings, not API parameters — you won't find them in the Management API spec.
duration and retryCount control the batch polling described in How writes complete below.
The SDK picks the right API host from your instance GUID's suffix — you normally don't set baseUrl yourself:
| GUID suffix | Region | Endpoint |
|---|---|---|
-u | US | https://mgmt.aglty.io |
-us2 | US 2 | https://mgmt-usa2.aglty.io |
-c | Canada | https://mgmt-ca.aglty.io |
-e | Europe | https://mgmt-eu.aglty.io |
-a | Australia | https://mgmt-aus.aglty.io |
-d | Dev | https://mgmt-dev.aglty.io |
Each host serves its own Swagger UI — append /swagger to try endpoints interactively against your own instance.
For automation, CI/CD pipelines, and server-side jobs where an OAuth redirect isn't practical, use a Personal Access Token.
PATs are created through the Management API. Authenticate with OAuth first, then call POST /api/v1/tokens/create — via your region's Swagger UI (append /swagger to the endpoints in the table above), with this body:
{
"name": "my-automation-token",
"expiryDate": "2028-01-01T00:00:00Z"
}
nameis the only required field.expiryDateis optional in the API spec, and the spec doesn't define what happens when you omit it — so always set an explicit future date rather than relying on unspecified behaviour.
A successful call returns 201 with the token in the response's token field. That value is returned only once — save it immediately, and note that the notice field carries an accompanying message about it. Alongside the usual 400, 401, and 403 errors, 429 is a documented response, so automation that creates tokens in bulk should back off and retry.
| Response field | What it tells you |
|---|---|
tokenID | Identifier for the token — used by the token management endpoints |
name | The name you supplied |
token | The secret value, returned only on creation |
notice | Message accompanying the newly created token |
expiryDate | When the token expires |
createdDate | When the token was created |
enabled | Whether the token is currently active |
isExpired | true once the expiry date has passed |
daysUntilExpiration | Days of life left — useful for rotating tokens before they lapse |
lastUsedDate | Last time the token authenticated a request |
Companion endpoints let you audit and rotate tokens: GET /api/v1/tokens/list, GET /api/v1/tokens/{tokenId}, PUT /api/v1/tokens/{tokenId}/update, and DELETE /api/v1/tokens/{tokenId}/delete.
Initialization is identical to OAuth — pass the PAT as token:
import * as mgmtApi from "@agility/management-sdk"
const options = new mgmtApi.Options()
options.token = "<<your-personal-access-token>>"
const apiClient = new mgmtApi.ApiClient(options)
var options = new Options
{
token = "<<your-personal-access-token>>"
};
var client = new ClientInstance(options);
PAT restrictions: PATs cover content operations but cannot touch user management, token management, or admin-level endpoints — those return 403 Forbidden.
Two things about writing through this API surprise people. Both are worth knowing before your first save, because each one looks like a bug when you hit it.
The Management API does not perform a write during your request. It queues a batch and responds with a batch ID — an integer that identifies the queued work, not the content ID and not a result. The actual save happens moments later.
The SDK hides this for you. saveContentItem, publishContent and friends take the batch ID,
poll until the batch completes, and return the real result — which is why Options has duration
and retryCount: they are the polling interval and the attempt ceiling. If a large import times out
waiting, raise retryCount rather than assuming the write failed.
If you want the batch ID instead of waiting, pass returnBatchId: true (JavaScript only). You are
then responsible for polling it yourself:
GET /api/v1/instance/{guid}/batch/{batchID}
That URL is not locale-scoped — /{guid}/batch/{id}, with no /{locale} segment. The batch is
finished when batchState is 3. A freshly created batch ID can return 404 for a moment before it
exists, so treat an early 404 as "not yet", not as failure.
Calling the REST API directly? Then this is your problem, not the SDK's.
POST /itemgives you a batch ID and nothing else; read the item straight back and you will get the old value, which looks exactly like a write that silently did nothing. Poll the batch before you trust the result.
Saving does not publish. A save always writes to Staging, and if the item was already Published it drops back to a Staging state — your live site keeps serving the previous version until you publish explicitly.
So changing one field on a live item is always two operations:
// 1. save — the change now exists, in Staging
const contentID = await apiClient.contentMethods.saveContentItem(item, guid, locale)
// 2. publish — only now is it live
await apiClient.contentMethods.publishContent(contentID, guid, locale)
Skip the second step and the change is real, stored, and invisible to your site — the single most common reason a write "didn't work". The same applies to pages, and to every bulk equivalent: see Content Items and Pages.
The client exposes method groups for each area of the CMS. The JavaScript client has ten; .NET has the first seven.
| Method group | Covers | In .NET? |
|---|---|---|
contentMethods | Content items — CRUD, workflow, publishing | Yes |
containerMethods | Containers (content lists) | Yes |
modelMethods | Content & component models | Yes |
pageMethods | Pages and page templates | Yes |
assetMethods | Media — upload and manage assets | Yes |
instanceUserMethods | Instance users and permissions | Yes |
batchMethods | Batch status, and batch-level workflow actions | Partly — status only |
instanceMethods | Locales and Fetch API sync status | No |
serverUserMethods | The authenticated user | No |
webhookMethods | Webhook CRUD | No |
The .NET SDK trails the JavaScript SDK, and it's worth being precise about what that means: every gap below is an SDK gap, not an API gap. The endpoint exists in all cases, so from .NET you can call the Management API directly and lose nothing but the wrapper.
| JavaScript-only | Call this from .NET instead |
|---|---|
instanceMethods.getLocales() | GET /api/v1/instance/{guid}/locales |
instanceMethods.getFetchApiStatus() | GET /api/v1/instance/{guid}/fetch-api-status?mode=fetch |
serverUserMethods.me() | GET /api/v1/users/me |
webhookMethods (webhook CRUD) | GET/POST /api/v1/instance/{guid}/webhook, GET/DELETE .../webhook/{id} |
contentMethods.batchWorkflowContent() | POST /api/v1/instance/{guid}/{locale}/item/batch-workflow |
contentMethods.getContentList() (POST filtering) | POST /api/v1/instance/{guid}/{locale}/list/{referenceName} |
contentMethods.getContentHistory() | GET /api/v1/instance/{guid}/{locale}/item/{contentID}/history |
contentMethods.getContentComments() | GET /api/v1/instance/{guid}/{locale}/item/{contentID}/comments |
containerMethods.getContainerListPaged() | GET /api/v1/instance/{guid}/container/list/paged |
pageMethods.getPageHistory() | GET /api/v1/instance/{guid}/{locale}/page/{id}/history |
pageMethods.getPageComments() | GET /api/v1/instance/{guid}/{locale}/page/{id}/comments |
pageMethods.batchWorkflowPages() | POST /api/v1/instance/{guid}/{locale}/page/batch-workflow |
batchMethods.publishBatch() and the other batch workflow actions | POST /api/v1/instance/{guid}/batch/{id}/publish (and /unpublish, /approve, /decline, /request-approval) |
assetMethods.deleteFolder(), assetMethods.renameFolder() | The corresponding asset endpoints |
If you'd rather not hand-roll HTTP calls, the JavaScript SDK is the fuller surface today.
Keeping this list honest. These tables are generated from the published packages —
@agility/management-sdkon npm andAgility.Management.SDKon NuGet — rather than written from memory. If you spot a difference, the packages win; please tell us so we can correct the page.