# Batches: How Management API Work Is Queued and Completed

> Source: https://agilitycms.com/docs/javascript/management-sdk/batches

Most writes in the Management API don't happen during your request. Saves, deletes and workflow operations (publish, unpublish, approve, decline, request approval) are queued as a **batch**, and the API answers with a **batch ID**. The change happens when Agility processes the batch, usually within seconds. This page explains that lifecycle: how a batch starts, how to poll it, how to read item-level errors, and how to act on many items at once.

If you use the JavaScript or .NET Management SDK, the SDK waits for batches for you by default. Read this page when you turn that waiting off, call the REST API directly, or need to handle partial failures.

## The batch endpoints

The Management API's `Batch` tag has seven operations. None of the paths has a `{locale}` segment: a batch belongs to the instance, and each item in it carries its own locale.

| Operation | Endpoint | Returns |
| --- | --- | --- |
| Create a batch from a list of items | `POST /api/v1/instance/{guid}/batch` | `BatchCreateResult` |
| Get a batch's status and items | `GET /api/v1/instance/{guid}/batch/{id}` | `Batch` |
| Publish every item in a batch | `POST /api/v1/instance/{guid}/batch/{id}/publish` | A batch ID |
| Unpublish every item in a batch | `POST /api/v1/instance/{guid}/batch/{id}/unpublish` | A batch ID |
| Approve every item in a batch | `POST /api/v1/instance/{guid}/batch/{id}/approve` | A batch ID |
| Decline every item in a batch | `POST /api/v1/instance/{guid}/batch/{id}/decline` | A batch ID |
| Request approval for every item in a batch | `POST /api/v1/instance/{guid}/batch/{id}/request-approval` | A batch ID |

Two more operations, on the `ContentItem` and `Page` tags, run one workflow operation on many items in a single batch:

| Operation | Endpoint |
| --- | --- |
| Workflow operation on many content items | `POST /api/v1/instance/{guid}/{locale}/item/batch-workflow?contentIDs=101,102&operation=Publish` |
| Workflow operation on many pages | `POST /api/v1/instance/{guid}/{locale}/page/batch-workflow?pageIDs=7,8&operation=Publish` |

`operation` is one of `Publish`, `Unpublish`, `Approve`, `Decline` or `RequestApproval`. Each call accepts at most 250 IDs.

Full request and response schemas are in the [Management API reference](https://agilitycms.com/docs/api-reference).

## The lifecycle

1. **Start.** A write or workflow call returns a batch ID, or `POST batch` returns a `BatchCreateResult` with one or more batch IDs.
2. **Poll.** Call `GET batch/{id}` until `batchState` is `3` (Processed).
3. **Check the items.** A processed batch can still contain failed items. Read `items[].errorMessage`.
4. **Then read your data.** Only after the batch is processed does reading the item back show the new value.

### Batch states

| `batchState` | Name | Meaning |
| --- | --- | --- |
| `0` | None | |
| `1` | Pending | Queued, not started |
| `2` | InProcess | Being processed |
| `3` | Processed | Finished. Check the items for errors. |
| `4` | Deleted | The batch was deleted |

The state names come from the `BatchState` enum in both SDKs. A newly created batch ID can return `404` for a moment before it exists, so treat an early `404` as "not created yet" rather than as a failure.

### Polling a batch

`GET batch/{id}` takes an `expandItems` query parameter (default `true`). Pass `expandItems=false` while you poll to get only the batch summary, then fetch once with items when it is processed.

```bash
curl -H "Authorization: Bearer $AGILITY_TOKEN" \
  "https://mgmt.aglty.io/api/v1/instance/$GUID/batch/$BATCH_ID?expandItems=false"
```

Useful fields on the `Batch` response:

| Field | What it tells you |
| --- | --- |
| `batchState` | Where the batch is in its lifecycle (see above) |
| `percentComplete`, `numItemsProcessed`, `batchItemCount` | Progress through the batch |
| `statusMessage` | A status message for the batch |
| `errorData` | Error information for the batch as a whole |
| `abortYN` | Whether the batch was aborted |
| `items` | The items, when `expandItems` is `true` |

Each entry in `items` has `itemID`, `itemType`, `languageCode`, `itemTitle` and `errorMessage`. An item with an `errorMessage` failed.

Use the host for your instance's region (for example `mgmt.aglty.io` for a `-u` instance). The region table and authentication options are in [Getting Started](https://agilitycms.com/docs/javascript/management-sdk/getting-started).

### Partial failures

Every batch endpoint only queues work. Agility checks your permission on each item when it processes the batch, so an item you can't act on fails inside the batch while the other items go ahead. The request that created the batch still succeeds. Always check `items[].errorMessage` before you report success.

## Creating a batch from your own list of items

`POST batch` creates a batch for an explicit list of pages and content items and queues it.

```bash
curl -X POST -H "Authorization: Bearer $AGILITY_TOKEN" -H "Content-Type: application/json" \
  "https://mgmt.aglty.io/api/v1/instance/$GUID/batch" \
  -d '{
    "batchName": "Spring campaign",
    "operation": "Publish",
    "comments": "Campaign launch",
    "items": [
      { "itemID": 1201, "itemType": 1, "languageCode": "en-us" },
      { "itemID": 3456, "itemType": 2, "languageCode": "en-us" }
    ]
  }'
```

| Property | Notes |
| --- | --- |
| `operation` | `Publish`, `Unpublish`, `Approve`, `Decline` or `RequestApproval` |
| `items` | At least one item. `itemID` is a page ID or a content ID of an existing item. |
| `itemType` | `1` Page, `2` ContentItem (values from the `BatchItemType` enum) |
| `languageCode` | The item's locale, for example `en-us` |
| `batchName` | Optional. Defaults to "Custom Batch". |
| `isPrivate` | Optional. Defaults to `true` (visible only to the creator). |
| `comments` | Optional notes, stored with the batch |

The `processNow` query parameter (default `true`) queues the batch for processing immediately.

A list longer than one batch holds is split across several batches instead of being rejected. The response tells you what happened:

```json
{
  "batchIDs": [90121, 90122],
  "batchCount": 2,
  "itemCount": 400,
  "failedItemCount": 0
}
```

Poll every ID in `batchIDs`, not just the first. `failedItemCount` counts items that were not queued because the batch carrying them failed to create. It is normally `0`, and `itemCount + failedItemCount` always equals the number of items you sent.

## Publishing an item with its nested content

In the Content Manager, publishing an item or page now publishes its nested linked content by default (see [Previewing, Publishing, and Content States](https://agilitycms.com/docs/editors/preview-and-publishing)). The Management API keeps the two behaviors on separate routes:

| Endpoint | What it publishes |
| --- | --- |
| `GET /{locale}/item/{contentID}/publish` | The content item only |
| `GET /{locale}/page/{id}/publish` | The page only |
| `POST /{locale}/item/{contentID}/publish-cascade` | The content item and its one-level nested content (non-shared containers its fields point at) |
| `POST /{locale}/page/{id}/publish-cascade` | The page, its components, and their one-level nested content |
| `GET /{locale}/item/{contentID}/cascade-items` | Read-only preview of what a cascading publish of the item would include, as a tree |
| `GET /{locale}/page/{id}/cascade-items` | Read-only preview of what a cascading publish of the page would include, as a tree |

The `publish-cascade` routes can create more than one batch, so they return a `BatchCreateResult`. Poll every batch ID.

To publish only part of the tree, read `cascade-items`, pick the nodes you want, and submit them with `POST batch`. The preview is not filtered by workflow state and is not capped, so it can show items that a cascading publish would skip.

## Using the SDKs

### JavaScript (`@agility/management-sdk`)

Batch methods are on `apiClient.batchMethods`: `getBatch`, `publishBatch`, `unpublishBatch`, `approveBatch`, `declineBatch` and `requestApprovalBatch`. Write and workflow methods on `contentMethods` and `pageMethods` (including `batchWorkflowContent` and `batchWorkflowPages`) wait for their batch by default. Pass `returnBatchId: true` to get the batch ID back immediately and poll it yourself.

<div class="code-tabs" data-tabs="JavaScript,.NET">

```ts
import * as mgmtApi from "@agility/management-sdk"

const options = new mgmtApi.Options()
options.token = process.env.AGILITY_TOKEN
const apiClient = new mgmtApi.ApiClient(options)
const guid = "<<your-instance-guid>>"

// Queue a bulk publish and get the batch ID back immediately (as a one-element array)
const [batchId] = await apiClient.contentMethods.batchWorkflowContent(
	[101, 102, 103],
	guid,
	"en-us",
	mgmtApi.WorkflowOperationType.Publish,
	true, // returnBatchId
)

// Later: read the batch with its items and look for failures
const batch = await apiClient.batchMethods.getBatch(batchId, guid, true)
if (batch.batchState === mgmtApi.BatchState.Processed) {
	const failed = batch.items.filter((i) => i.errorMessage)
	console.log(`${failed.length} item(s) failed`, failed)
}
```

```csharp
using Agility.Management.Sdk;
using Agility.Management.Sdk.Models;

using var client = new AgilityManagementClient(new AgilityManagementOptions
{
    AccessToken = Environment.GetEnvironmentVariable("AGILITY_TOKEN"),
});
var guid = "<<your-instance-guid>>";

// Queue a bulk publish without waiting
var queued = await client.Content.BatchWorkflowContentItemsAsync(
    guid, "en-us", [101, 102, 103], WorkflowOperationType.Publish, waitForBatch: false);

// Later: wait for it. Throws AgilityBatchException if any item failed.
try
{
    Batch batch = await client.Batches.WaitForBatchAsync(guid, queued.BatchId);
}
catch (AgilityBatchException ex)
{
    foreach (var failed in ex.Batch?.Items?.Where(i => i.ErrorMessage is not null) ?? [])
        Console.WriteLine($"{failed.ItemID}: {failed.ErrorMessage}");
}
```

</div>

When the JavaScript SDK waits for a batch, it stops waiting once the batch is processed. Some methods, such as `batchWorkflowContent`, throw if the processed batch reports `errorData`, but the SDK does not check each item's `errorMessage` for you. Read the batch with `getBatch` and check the items yourself when partial failure matters. The polling interval and attempt limit are the `duration` and `retryCount` options described in [Getting Started](https://agilitycms.com/docs/javascript/management-sdk/getting-started).

The JavaScript SDK does not yet have methods for `POST batch`, `cascade-items` or `publish-cascade`. Call those endpoints directly.

### .NET (`Agility.Management.SDK` 2.0)

In 2.0, batches are on `client.Batches`:

| Method | Endpoint |
| --- | --- |
| `GetBatchAsync(guid, batchId, expandItems)` | `GET batch/{id}` |
| `CreateBatchAsync(guid, request, processNow)` | `POST batch`, returns `BatchCreateResult` |
| `PublishBatchAsync`, `UnpublishBatchAsync`, `ApproveBatchAsync`, `DeclineBatchAsync`, `RequestApprovalBatchAsync` | `POST batch/{id}/...` |
| `WaitForBatchAsync(guid, batchId)` | Polls until the batch is processed |

The cascade operations are on the content and pages clients: `client.Content.GetCascadeItemsAsync`, `client.Content.PublishContentItemCascadeAsync`, `client.Pages.GetCascadeItemsAsync` and `client.Pages.PublishPageCascadeAsync`. The bulk workflow operations are `client.Content.BatchWorkflowContentItemsAsync` and `client.Pages.BatchWorkflowPagesAsync`.

Every batch-producing method has a `waitForBatch` parameter (default `true`) and returns a `BatchResult`. While waiting, the SDK checks the batch every 3 seconds, treats a `404` in the first 30 seconds as "not created yet", and gives up after 15 minutes with `AgilityBatchTimeoutException`. A processed batch with failed items, or an aborted or deleted batch, throws `AgilityBatchException`. All three settings are on `BatchPolling` in the client options. See [Management SDK - Intro](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-intro#batches) for the details.

```csharp
// Publish a hand-picked set of items as one or more batches
var created = await client.Batches.CreateBatchAsync(guid, new CreateBatchWithItemsRequest
{
    BatchName = "Spring campaign",
    Operation = WorkflowOperationType.Publish,
    Items =
    [
        new AddBatchItemRequest { ItemID = 1201, ItemType = BatchItemType.Page, LanguageCode = "en-us" },
        new AddBatchItemRequest { ItemID = 3456, ItemType = BatchItemType.ContentItem, LanguageCode = "en-us" },
    ],
}, processNow: true);

foreach (var id in created.BatchIDs ?? [])
    await client.Batches.WaitForBatchAsync(guid, id);
```

## Good practice

- Prefer the bulk endpoints over looping single-item calls. One `batch-workflow` call queues one batch for up to 250 items.
- Poll every batch ID you get back. A `BatchCreateResult` can name several.
- Don't read an item back to confirm a write until its batch is processed. Before then you get the old value.
- Treat "processed" and "succeeded" as different things. Check item errors.
- A processed publish batch means the Management API has published the item. Your website reads the Fetch API, which syncs shortly afterwards.
