Asynchronous operations, batches, and idempotency
Idempotency
Every POST request that creates something or has a side effect requires an Idempotency-Key header. Generate one UUID or ULID per intent. The key must be 1 to 128 characters long and use only letters, digits, ., _, :, and -:
POST /api/v3/users
Idempotency-Key: 5f7d9f0a-3f8f-4a14-8b5c-6f9d2e0c1a77
{"name": "Ana Souza", "email": "[email protected]"}- Keys are scoped to the credential. Repeating a key with the same body on the same route returns the stored response with
Idempotent-Replayed: true, without running the request again. - Reusing a key with a different body or on a different route returns
422 idempotency_key_reused. - Reusing a key while the original request is still running returns
409 idempotency_in_progress. Wait and retry. - A key is remembered for 48 hours. After that, the same key is treated as a new request.
- A request rejected with a validation error does not consume the key.
- A missing key returns
422 idempotency_key_required, and a malformed key returns422 idempotency_key_invalid.
This makes timeouts safe to handle: if you did not receive a response, repeat the request with the same key. Either the change had not happened yet and now happens exactly once, or it had already happened and you receive the original result. See the idempotency-replay.http example in Examples.
PATCH, PUT, and DELETE accept the key but do not require it.
Asynchronous operations
When the work is large or spans several resources, the API responds with 202 and an operation resource, plus a Location header pointing to it:
{
"data": {
"object": "operation",
"id": "op_01J8Z3ZQ4H8K2M0T1S9P7YQF5C",
"type": "product.publish_all",
"status": "queued",
"progress": { "total": 1, "done": 0, "failed": 0 },
"items_url": "/operations/op_01J8Z3ZQ4H8K2M0T1S9P7YQF5C/items",
"result_url": null
}
}Track progress with GET /operations/{operation_id}. The possible statuses are queued, running, succeeded, partially_succeeded, failed, and canceled. The last four are terminal.
Suggested polling interval: every 2 seconds for the first 30 seconds, then every 10 seconds. Depending on the batch size, allow up to a few hours before giving up. Operations are retained for 30 days after they are created; expires_at tells you when an operation will no longer be available.
See Operations for the full schema of operations, items, and attempts.
Items, partial failure, and resuming
GET /operations/{operation_id}/items returns one item per unit of work, with the key you sent, its status, and its error if it failed. An operation that finishes with some items failed ends as partially_succeeded: the successful items are saved, and only the failed ones need attention. A partially_succeeded operation is final and cannot be resumed. To retry its failed items, send a new batch that contains only those items, with a new Idempotency-Key.
When an operation ends as failed, you can resume it. Resuming retries the recoverable items without running the successful ones again:
POST /api/v3/operations/{operation_id}/attempts
Idempotency-Key: 8b1e0e2f-9f6d-4a3b-9c11-1c2b3d4e5f60The request has no body. Only operations in the failed status that still have recoverable items can be resumed: items in the pending status, or failed items with retryable set to true. Any other operation returns 422 operation_not_resumable, including one that ended as partially_succeeded.
GET /operations/{operation_id}/attempts lists the attempts of an operation. The origin of each attempt is automatic, manual, or replay. See the operations-partial-failure.http and operations-resume.http examples in Examples.
Cancellation
PATCH /api/v3/operations/{operation_id}
{"cancellation_requested": true}Cancellation is cooperative: a queued operation is canceled immediately, and a running operation stops between items. Items that were already processed are not reverted. A cancellation request cannot be withdrawn. Operations that have already reached a terminal status return 422 operation_not_cancelable.
Batches
POST /api/v3/operations/batches
Idempotency-Key: 0d5e6f70-1a2b-4c3d-8e9f-0a1b2c3d4e5f
{
"type": "product.publish_all",
"items": [
{ "key": "course-1", "payload": { "product_id": "prd_12" } }
]
}typemust be one of the supported operation types, and a batch has a single type. An unsupported type returns422 operation_type_unknown. Besidesoperations.manage, the credential needs the permission required by the selected type.keyis yours to choose and must be unique within the batch (422 duplicate_item_key). Use it to match each item to its result.- A batch accepts at most 1,000 items, and many types accept fewer.
product.publish_all, for example, accepts one item per operation. Exceeding the limit for the type returns422 too_many_items. The request body is limited to 1 MiB (413 payload_too_large). - An account can have at most 100 unfinished operations (
queuedorrunning) in each environment. Beyond that, new batches return422 operation_backlog_exceeded. - Items are checked as they are processed. An item that references a resource that does not exist or is not accessible with the current credentials fails on its own, so the batch can end as
partially_succeeded.
Operations are also created by dedicated endpoints that name what happens: publishing a product (POST /products/{product_id}/publications), duplicating a lesson, replaying webhooks, analyzing a spreadsheet, generating an export, or resetting the sandbox.