- Applies to: Sprinklr Task API V3 (
/api/v3/task) - V2 API reference: Task | Sprinklr Developer Portal
A Sprinklr Task is a standard business object used in Modern Marketing to track and manage content using configurable agile Production Dashboards. A task is always created against an entity — a message, a campaign, an asset — identified by assetId and assetType, and it carries assignment, status, due-date, queue, subscriber, and custom-field state.
Task V3 exposes full CRUD on a single resource path — /api/v3/task and differentiates operations by HTTP method, with bulk support on the read, delete, and update paths through a comma-separated ?id= query parameter.
| Operation | Method | Path | Bulk |
|---|---|---|---|
| Create task | POST | /api/v3/task | No — one task per call |
| Fetch task(s) | GET | /api/v3/task?id=taskId1,taskId2 | Yes |
| Update task (full replace) | PUT | /api/v3/task?id=taskId1,taskId2 | Yes |
| Update task (partial upsert) | PATCH | /api/v3/task?id=taskId1,taskId2 | Yes — new in V3 |
| Delete task(s) | DELETE | /api/v3/task?id=taskId1,taskId2 | Yes |
| Layer | Object / field | What it holds |
|---|---|---|
| Identity | id, parentTaskId, nextTaskId | Sprinklr task ID and the task chain it belongs to. |
| Descriptive | title, description, taskType, taskStatus | Task definition and current status. |
| Anchor entity | assetId, assetType | The entity the task was created for (for example a MESSAGE or CAMPAIGN) |
| Assignment | assignment | assigneeId, assigneeType, assignedById, assignmentTime |
| Scheduling | dueDate, completionDate, createdTime, modifiedTime | Epoch-millisecond timestamps |
| Routing | queueIds (request) / queueDetails (response) | Work queues the task sits in |
| Collaboration | subscribers, attachment | Who is watching, and any attached media |
| Extensibility | customProperties | Custom fields, keyed by custom-field ID, always List<String> |
| Lifecycle / integration | inactive, workFlowState, externalCases | Soft-delete marker, workflow execution state, linked third-party cases |
Note: Request and response schemas differ.
POSTandPUToperations accept aTaskRequestobject, while read operations return aTaskobject. These schemas are not interchangeable.
| Field | In TaskRequest (write) | In Task (read) |
|---|---|---|
queueIds — Array[String] | ✅ | — |
queueDetails — Array[Queue] | — | ✅ |
id, createdTime, modifiedTime, inactive, nextTaskId | — | ✅ (server-assigned) |
completionDate, workFlowState, externalCases | — | ✅ |
assignment, taskType, taskStatus, title, description, assetId, assetType, dueDate, customProperties, attachment, subscribers, parentTaskId | ✅ | ✅ |
Note: Write operations use
queueIds (Array[String]), while read operations returnqueueDetails (Array[Queue]). Because these fields use different schemas, do not reuse a read response as a write payload.
In V3, you can address a task only by its Sprinklr task ID, passed in the id query parameter.
?id=6a1437f751d06294d5c8773e # single
?id=6a1437f751d06294d5c8773e,6a15305cad837f75fc5ac662 # bulkTask IDs are 24-character hex ObjectId strings. Store the id returned in the POST response.
All API calls are sent to the production endpoint:
https://api3.sprinklr.com/{env}/api/v3So the task resource in production is:
https://api3.sprinklr.com/{env}/api/v3/taskReplace {env} with your assigned environment identifier. The V3 specification enumerates: prod, prod0, prod2, and so on. See APIs | Sprinklr Developer Portal for the current environment list.
Do not hard-code the host name. Make the base URL a single configuration value so promotion between environments is a config change, not a code change.
All Task API calls are authenticated with OAuth 2.0. See API Overview for portal registration, API key and secret generation, and the Authorize flow.
The V3 specification declares two security schemes, both applied to every operation:
| Scheme | Type | Where |
|---|---|---|
bearerAuth | HTTP bearer, bearerFormat: JWT | Authorization header |
apiKeyAuth | API key | Key header |
| Header | Value | Purpose | Required on |
|---|---|---|---|
Authorization | Bearer {{accessToken}} | Authenticates the user with the server | All requests |
Key | {{apiKey}} | Authenticates the application with the server | All requests |
Content-Type | application/json | Declares the request body media type | POST, PUT, PATCH |
Accept | application/json | Declares the acceptable response type | All requests |
POST /api/v3/task
Use this endpoint to create a task for an existing entity.
| Parameter | Sub Parameter | Required | Type | Description |
|---|---|---|---|---|
assignment | Optional | Object | Assignment details for the task. | |
assigneeId | Optional | String | Identifier of the user or entity assigned to the task. | |
assigneeType | Optional | String | Type of assignee. Example: USER, BOT | |
assignedById | Optional | Long | Identifier of the user who assigns the task. | |
assignmentTime | Optional | Long | Time when the user assigns the task, in epoch milliseconds. | |
taskType | Optional | String | Type of work represented by the task. Example: Design Task, Working task | |
taskStatus | Optional | String | Status of the task. Example: NEW, AVAILABLE | |
title | Optional | String | Title of the task. | |
description | Optional | String | Description of the work required for the task. | |
assetId | Required | String | Identifier of the entity associated with the task. | |
assetType | Required | String | Type of entity associated with the task. Example: MESSAGE, CAMPAIGN | |
dueDate | Optional | Long | Date and time when the task is due, in epoch milliseconds. | |
customProperties | Optional | Map[String, List[String]] | Custom fields for the task, keyed by custom-field ID. Provide each custom-field value as an array of strings, including single-value fields. | |
attachment | Optional | Object | Media or document attached to the task. The applicable fields depend on attachment.type. | |
subscribers | Optional | Array[Object] | Users or entities that subscribe to task updates. | |
type | Required within subscriber | String | Type of subscriber. Example: USER, GLOBAL, CLIENT | |
ids | Optional | Array[String] | Subscriber identifiers. The API interprets each identifier according to subscribers[].type. | |
queueIds | Optional | Array[String] | Identifiers of the work queues to which the task belongs. | |
parentTaskId | Optional | String | Identifier of the parent task when the task belongs to a task hierarchy. |
curl --location 'https://api3.sprinklr.com/{env}/api/v3/task' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"assignment": {
"assigneeId": "600005371",
"assigneeType": "USER"
},
"taskType": "Working task",
"taskStatus": "AVAILABLE",
"title": "Review creative brief",
"description": "Review creative brief before approval",
"assetId": "{{assetId}}",
"assetType": "MESSAGE",
"dueDate": 1779820200000,
"customProperties": {},
"queueIds": ["100623"]
}'A successful request returns 201 Created and the newly created task. The data property contains a single task object.
{
"data": {
"id": "6a15305cad837f75fc5ac662",
"assignment": {},
"taskType": "Working task",
"taskStatus": "AVAILABLE",
"title": "Review creative brief",
"description": "Review the creative brief before production starts.",
"assetId": "{{assetId}}",
"assetType": "MESSAGE",
"dueDate": 1779820200000,
"customProperties": {
"spr_task_status": ["AVAILABLE"],
"spr_task_type": ["Working task"]
},
"inactive": false,
"queueDetails": [],
"createdTime": 1779773532036,
"modifiedTime": 1779773532036,
"externalCases": []
},
"errors": []
}Notes:
POSTresponses return a single object in data.GETresponses return an array in data. Your client must handle both formats.- Sprinklr automatically maps
taskStatusandtaskTypeto the reserved custom propertiesspr_task_statusandspr_task_type. Do not populate these keys directly. SettaskStatusandtaskType, and allow the platform to maintain the mirror fields.
attachment supports multiple attachment schemas. The V3 specification models it as a oneOf across 40+ attachment schemas, discriminated by the type property — IMAGE, VIDEO, LINK, DOCUMENT, AUDIO, CAROUSEL, and others. type is required on every variant.
Fields commonly used on a task attachment (from ImageAttachment / DocumentAttachment):
| Parameter | Required | Type | Description |
|---|---|---|---|
type | Required when attachment is provided | String | Type of attachment. Example: IMAGE, VIDEO, LINK, DOCUMENT, AUDIO, and CAROUSEL. |
url | Optional | String | URL of the attached media or document. |
title | Optional | String | Title of the attachment. |
description | Optional | String | Description of the attachment. |
previewUrl | Optional | String | URL of the attachment preview. |
assetId | Optional | String | Identifier of the attachment in Sprinklr Digital Asset Management. |
mimeType | Optional | String | MIME type of the attachment. |
alternateText | Optional | String | Alternative text that describes the attachment. |
sizeInBytes | Optional | Long | Size of the attachment in bytes. |
"attachment": {
"type": "IMAGE",
"url": "https://cdn.example.com/media/task-brief.jpg",
"title": "Creative brief v2",
"previewUrl": "https://cdn.example.com/media/task-brief_p.jpg"
}PUT /api/v3/task?id={taskId} — TaskApiV3_updateTask
Use this endpoint to replace the entire task. V2 full-update semantics remain unchanged. The API removes any fields that you omit from the request.
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
id | String | Required | Identifier of the task to update, or a comma-separated list of task identifiers for a bulk update. | 6a1437f751d06294d5c8773e |
curl --location --request PUT 'https://api3.sprinklr.com/{env}/api/v3/task?id={{taskId}}' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{
"assignment": {
"assigneeId": "{{assigneeId}}",
"assigneeType": "USER"
},
"taskType": "Working task",
"taskStatus": "AVAILABLE",
"title": "Review updated creative brief",
"description": "Review the updated creative brief before production starts.",
"assetId": "{{assetId}}",
"assetType": "MESSAGE",
"dueDate": 1779820200000,
"customProperties": {},
"queueIds": [
"{{queueId}}"
]
}'A successful request returns 200 OK and the updated task. For information about the response parameters, see Response field reference — Task.
The example above omits customProperties, subscribers, queueIds, and parentTaskId. Under replace semantics those are unset. Before issuing a PUT:
GETthe current task.- Merge your changes into the retrieved document.
- Map
queueDetails[].queueIdback toqueueIds— the read and write shapes differ (§1.1). PUTthe merged result back.
If you only intend to change a few fields, use PATCH (§4.3) instead. PATCH eliminates the need for a read-modify-write cycle when you update only a subset of fields.
PATCH /api/v3/task?id={taskId}
Use the endpoint to update specific task fields without replacing the entire task. Only the fields included in the request body are updated. Supports bulk partial updates through a comma-separated ?id=.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String | Required | Identifier of the task to update, or a comma-separated list of task identifiers for a bulk partial update. |
| Parameter | Required | Type | Description |
|---|---|---|---|
updateFields | Yes | Array[Object] | Field update operations to apply to each identified task. |
updateFields[].fieldName | Yes | String | Name of the task field to update. Examples: title, taskStatus, dueDate. |
updateFields[].value | Yes | Any | New value for the field. Use the data type defined for the selected task field. |
updateFields[].op | Yes | String | Operation to apply to the field. |
Note:
opvalues come from the sharedOperationenum.
curl --location --request PATCH 'https://api3.sprinklr.com/{env}/api/v3/task?id={{taskId}}' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{
"updateFields": [
{
"op": "UPDATE",
"fieldName": "title",
"value": "Reviewed creative brief"
},
{
"op": "UPDATE",
"fieldName": "taskStatus",
"value": "IN_PROGRESS"
}
]
}'POST | PUT | PATCH | |
|---|---|---|---|
| Purpose | Create a task | Replace the full task document | Upsert selected fields |
| Addressing | None — server assigns the ID | ?id= | ?id= |
| Body schema | TaskRequest | TaskRequest | TaskPatchRequestDTO |
| Bulk | No | Yes, via comma-separated ?id= | Yes, via comma-separated ?id= |
| Unspecified fields | N/A | Unset | Preserved |
| Safe for incremental sync | — | No, without read-modify-write | Yes |
| Available in V2 | Yes | Yes (path-based) | No — new in V3 |
Note:
PUTandPATCHtake different body schemas. You cannot reuse one payload for both methods.
DELETE /api/v3/task?id={taskId1},{taskId2}
Use this endpoint to delete one or more tasks by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String | Required | Identifier of the task to delete, or a comma-separated list of task identifiers for a bulk delete. |
# Single delete
curl --location --request DELETE 'https://api3.sprinklr.com/{env}/api/v3/task?id={{taskId}}' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
# Bulk delete
curl --location --request DELETE 'https://api3.sprinklr.com/{env}/api/v3/task?id={{taskId1}},{{taskId2}}' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'Note: Do not confuse deletion with the
inactiveflag on theTaskobject.inactive: truemarks a task as inactive but is not the same as removing it. Verify the supported soft-delete behaviour before implementing either approach.
GET /api/v3/task?id=taskId1,taskId2 — TaskApiV3_getTasks
Use this endpoint to retrieve one or more tasks in a single request trip.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String | Required | Comma-separated list of task IDs. |
# Single fetch
curl --location 'https://api3.sprinklr.com/{env}/api/v3/task?id=6a1437f751d06294d5c8773e' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
# Bulk fetch
curl --location 'https://api3.sprinklr.com/{env}/api/v3/task?id=6a1437f751d06294d5c8773e,6a15305cad837f75fc5ac662' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'A successful request returns 200 OK and the requested task data. The response contains an array of task objects, including when you request a single task.
{
"data": [
{
"id": "6a1437f751d06294d5c8773e",
"assignment": {},
"taskType": "Working task",
"taskStatus": "AVAILABLE",
"title": "Task",
"assetId": "INSTAGRAM_37_18442753900191622_ACCOUNT_600037626_1779440700000_2026_05",
"assetType": "MESSAGE",
"dueDate": 1779820200000,
"customProperties": {
"spr_task_status": ["Available"],
"spr_task_type": ["Working task"],
"_c_64e462458f027e15378d09c5": ["5"],
"_c_64c38d050e8ea978f3f12b82": ["v12"],
"_c_61542d4bfd7c10301568d465": ["Not Authenticated"],
"_c_65169c803884d32abe7e159c": ["Value1"]
},
"inactive": false,
"queueDetails": [],
"createdTime": 1779709943117,
"modifiedTime": 1779709943117,
"externalCases": []
}
],
"errors": []
}| Parameter | Type | Description |
|---|---|---|
id | String | Unique identifier of the task. |
assignment | Object | Assignment details for the task. |
assignment.assigneeId | String | Identifier of the assigned user or entity. |
assignment.assigneeType | String | Type of assignee. |
assignment.assignedById | Long | Identifier of the user who assigned the task. |
assignment.assignmentTime | Long | Time when the user assigned the task, in epoch milliseconds. |
taskType | String | Type of work represented by the task. |
taskStatus | String | Current status of the task. |
title | String | Title of the task. |
description | String | Description of the work required for the task. |
assetId | String | Identifier of the entity for which the task was created. |
assetType | String | Type of entity for which the task was created. |
parentTaskId | String | Identifier of the parent task. |
nextTaskId | String | Identifier of the next task in the task sequence. |
completionDate | Long | Date and time when the task was completed, in epoch milliseconds. |
dueDate | Long | Date and time when the task is due, in epoch milliseconds. |
customProperties | Map[String, List[String]] | Custom fields returned for the task. Each custom-field ID maps to an array of strings. |
inactive | Boolean | Specifies whether the task is inactive. |
attachment | Object | Media or document attached to the task. |
subscribers | Array[Object] | Users or entities that subscribe to task updates. |
subscribers[].type | String | Type of subscriber. |
subscribers[].ids | Array[String] | Subscriber identifiers. |
queueDetails | Array[Object] | Work queues to which the task belongs. |
queueDetails[].queueId | Long | Identifier of the work queue. |
queueDetails[].assignmentTime | Long | Time when the task entered the queue, in epoch milliseconds. |
createdTime | Long | Time when Sprinklr created the task, in epoch milliseconds. |
modifiedTime | Long | Time when Sprinklr last modified the task, in epoch milliseconds. |
workFlowState | Object | Current workflow execution state for the task. |
workFlowState.executionTokenId | String | Identifier of the workflow execution token. |
workFlowState.processVariables | Object | Variables associated with the workflow execution. |
externalCases | Array[Object] | Third-party cases linked to the task. |
Represents third-party cases linked to a Sprinklr universal case.
| Parameter | Type | Description |
|---|---|---|
id | String | Unique identifier of the case in the third-party system. |
caseNumber | String | Case number assigned by the third-party system. |
channelType | String | Third-party platform that manages the case. |
permalink | String | URL of the case in the third-party platform. |
createdTime | Long | Time when the third-party system created the case, in epoch milliseconds. |
modifiedTime | Long | Time when the third-party system last modified the case, in epoch milliseconds. |
clientId | Long | Identifier of the Sprinklr client linked to the case. |
subDomain | String | Subdomain associated with the third-party case. |
additional | Map[String, String] | Additional attributes returned for the third-party case. |
{
"data": [ ... ],
"errors": []
}| Field | Type | Description |
|---|---|---|
data | Object or Array | The task (POST) or tasks (GET) affected. null on an error response. |
errors | Array[Error] | Error objects — empty when there are no errors |
errors[].id | String | 24-character hex ObjectId |
errors[].code | Integer | Error code |
errors[].message | String | Dotted error key, for example account.not.found |
id, code, and message are all required on an Error object.
Partial success on bulk operations. For bulk operations, some task IDs can succeed while others fail.
| HTTP code | Declared on | Scenario |
|---|---|---|
200 OK | GET, POST, PUT, PATCH, DELETE | Success |
204 No Content | DELETE | Delete succeeded, no response body |
400 Bad Request | All | Malformed request — for example a missing or malformed id, or an invalid body |
401 Unauthorized | All | Missing or invalid authentication |
403 Forbidden | All | Insufficient permissions |
404 Not Found | All | Task not found |
400, 401, 403, and 404 all return the ErrorResponse schema — { "data": null, "errors": [ … ], "metadata": { … } }.
| V2 | V3 | Change |
|---|---|---|
POST /api/v2/task | POST /api/v3/task | Version segment only. Body unchanged. |
GET /api/v2/task/{taskId} | GET /api/v3/task?id={taskId} | Path param → query param. Bulk added. Response data becomes an array. |
PUT /api/v2/task/{taskId} | PUT /api/v3/task?id={taskId} | Path param → query param. Bulk added. Replace semantics retained. |
| (none) | PATCH /api/v3/task?id={taskId} | New. Partial upsert. |
DELETE /api/v2/task/{taskId} | DELETE /api/v3/task?id={taskId} | Path param → query param. Bulk added. |
| Aspect | API V2 | API V3 | Impact |
|---|---|---|---|
| Task addressing | Path segment /task/{taskId} | Query parameter ?id= | Update all code that constructs task URLs. |
| Bulk operations | Not supported — one task per call | GET, PUT, PATCH, DELETE accept comma-separated IDs | You can replace multiple calls with a single bulk request. |
| Partial update | Not available | PATCH with updateFields | PATCH removes the need for a read-modify-write workflow for small updates. |
GET response data | Single object | Array | Read data[0], not data |
| Response envelope | { "data": …, "errors": [] } | { "data": …, "errors": [] } | Unchanged |
| Create request body | TaskRequest fields | Identical fields | No change needed |
| Delete response | 204 No Content | 204 per acceptance criteria; 200 per specification | Accept both — see §4.5 |
| Partial success | N/A (single-entity calls) | Possible on bulk calls | Inspect errors on 200 |
- Update the base URL to
https://api3.sprinklr.com/{env}/api/v3. - Move the task ID from the path to
?id=onGET,PUT, andDELETE. This change is straightforward but must be applied throughout your codebase. Search your codebase forapi/v2/task/. - Change
GETresponse parsing fromdatatodata[0]. If existing code expects an object instead of an array, the migration can fail without obvious errors. - Collapse N-call loops into bulk calls. Replace iterative single-task requests with bulk requests where possible.
- Add partial-success handling. After a bulk call, reconcile returned
dataagainst requested IDs and inspecterrors. - Replace read-modify-write updates with
PATCH. Any place youGETa task purely to change one field andPUTit back is a candidate. This also removes thequeueDetails→queueIdsremapping step. - Accept both
200and204fromDELETE, and tolerate an empty body. - Keep your
POSTpayloads as-is. Create request bodies did not change.
Scenario: an inbound Instagram message needs design work before a reply goes out.
POST /api/v3/task with assetType: "MESSAGE", assetId set to the message ID, assignment.assigneeId set to the designer, and a dueDate. Store the returned id — it is the only key you can use to address the task later.
Scenario: a dashboard holds 40 task IDs and needs title, status, assignee, and due date for each.
GET /api/v3/task?id=id1,id2,id3,…You can retrieve all 40 tasks in a single request. Under V2 this was 40 sequential GET /api/v2/task/{taskId} calls; This is a primary efficiency improvement in V3.
const ids = taskIds.join(',');
const res = await get(`/api/v3/task?id=${ids}`);
if (res.errors && res.errors.length) {
handleErrors(res.errors); // partial success is possible on bulk
}
const byId = new Map(res.data.map(t => [t.id, t]));
const missing = taskIds.filter(id => !byId.has(id)); // reconcile, do not assume 1:1Scenario: an agent moves a task from AVAILABLE to IN_PROGRESS.
Use PATCH — not PUT:
{ "updateFields": [ { "op": "UPDATE", "fieldName": "taskStatus", "value": "IN_PROGRESS" } ] }Using PUT here would require reconstructing the whole task document, including remapping queueDetails back to queueIds, to change one string.
Scenario: A campaign is delayed by one week and 15 tasks need a new due date.
PATCH /api/v3/task?id=id1,id2,…,id15{ "updateFields": [ { "op": "UPDATE", "fieldName": "dueDate", "value": 1780425000000 } ] }The same updateFields set is applied to every listed task. If the tasks need different due dates, issue one call per distinct value.
Scenario: an external PM tool owns task state and pushes the complete record.
Use PUT /api/v3/task?id=… with the full TaskRequest. Because the external tool is authoritative, replace semantics are correct. Still GET first if Sprinklr-side agents can edit custom fields, subscribers, or queues — otherwise the sync erases their work (§4.2).
Scenario: a cancelled campaign leaves 8 orphaned tasks.
DELETE /api/v3/task?id=id1,id2,…,id8Handle both 200 OK and 204 No Content as successful responses, and re-GET the IDs if you need positive confirmation of removal.
Method selection
- Default to
PATCHfor edits. Use PUT only when you have the complete authoritative task document. - A
PUTrequest can remove queue membership, subscribers, and custom properties if you omit them. PUTandPATCHtake different body schemas. Payloads are not interchangeable.
Identifiers and shapes
- Task IDs are 24-character hex ObjectId strings.
GETreturnsdataas an array, even for a single ID.POSTreturnsdataas an object.- You write
queueIds(Array[String]); you readqueueDetails(Array[Queue], withqueueIdasint64). Do not use aGETresponse directly in a write operation without remapping fields. - All timestamps —
dueDate,completionDate,createdTime,modifiedTime,assignmentTime— are epoch milliseconds, not seconds and not ISO-8601.
Bulk operations
- Provide multiple IDs as a comma-separated list in the id query parameter. Do not repeat the parameter
- Bulk calls can partially succeed. Reconcile returned
dataagainst requested IDs and always inspecterrors. - The specification does not state a maximum number of IDs per call. Verify the supported bulk-request limit before sending large batches.
Custom properties
customPropertiesis a map of custom-field ID →Array[String]. Even single-valued fields take a list.spr_task_statusandspr_task_typeare platform-maintained mirrors oftaskStatusandtaskType. Set the top-level fields, not the mirrors.
Error handling
- Always inspect the
errorsarray even on a200response. - A
403response typically indicates insufficient permissions. - Code defensively for
429and5xxeven though the specification does not declare them for these operations.