Skip to content

Task API V3 — Developer Guide


1. Overview

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.

OperationMethodPathBulk
Create taskPOST/api/v3/taskNo — one task per call
Fetch task(s)GET/api/v3/task?id=taskId1,taskId2Yes
Update task (full replace)PUT/api/v3/task?id=taskId1,taskId2Yes
Update task (partial upsert)PATCH/api/v3/task?id=taskId1,taskId2Yes — new in V3
Delete task(s)DELETE/api/v3/task?id=taskId1,taskId2Yes

1.1 The task data model

LayerObject / fieldWhat it holds
Identityid, parentTaskId, nextTaskIdSprinklr task ID and the task chain it belongs to.
Descriptivetitle, description, taskType, taskStatusTask definition and current status.
Anchor entityassetId, assetTypeThe entity the task was created for (for example a MESSAGE or CAMPAIGN)
AssignmentassignmentassigneeId, assigneeType, assignedById, assignmentTime
SchedulingdueDate, completionDate, createdTime, modifiedTimeEpoch-millisecond timestamps
RoutingqueueIds (request) / queueDetails (response)Work queues the task sits in
Collaborationsubscribers, attachmentWho is watching, and any attached media
ExtensibilitycustomPropertiesCustom fields, keyed by custom-field ID, always List<String>
Lifecycle / integrationinactive, workFlowState, externalCasesSoft-delete marker, workflow execution state, linked third-party cases

Note: Request and response schemas differ. POST and PUT operations accept a TaskRequest object, while read operations return a Task object. These schemas are not interchangeable.

FieldIn 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 return queueDetails (Array[Queue]). Because these fields use different schemas, do not reuse a read response as a write payload.

1.2 Addressing a task

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         # bulk

Task IDs are 24-character hex ObjectId strings. Store the id returned in the POST response.


2. Base URLs and environments

All API calls are sent to the production endpoint:

https://api3.sprinklr.com/{env}/api/v3

So the task resource in production is:

https://api3.sprinklr.com/{env}/api/v3/task

Replace {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.


3. Authentication and common headers

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:

SchemeTypeWhere
bearerAuthHTTP bearer, bearerFormat: JWTAuthorization header
apiKeyAuthAPI keyKey header
HeaderValuePurposeRequired on
AuthorizationBearer {{accessToken}}Authenticates the user with the serverAll requests
Key{{apiKey}}Authenticates the application with the serverAll requests
Content-Typeapplication/jsonDeclares the request body media typePOST, PUT, PATCH
Acceptapplication/jsonDeclares the acceptable response typeAll requests

4. Write operations

4.1 Create a task

POST /api/v3/task

Use this endpoint to create a task for an existing entity.

Request body

ParameterSub ParameterRequiredTypeDescription
assignmentOptionalObjectAssignment details for the task.
assigneeIdOptionalStringIdentifier of the user or entity assigned to the task.
assigneeTypeOptionalStringType of assignee.

Example: USER, BOT
assignedByIdOptionalLongIdentifier of the user who assigns the task.
assignmentTimeOptionalLongTime when the user assigns the task, in epoch milliseconds.
taskTypeOptionalStringType of work represented by the task.

Example: Design Task, Working task
taskStatusOptionalStringStatus of the task.

Example: NEW, AVAILABLE
titleOptionalStringTitle of the task.
descriptionOptionalStringDescription of the work required for the task.
assetIdRequiredStringIdentifier of the entity associated with the task.
assetTypeRequiredStringType of entity associated with the task.

Example: MESSAGE, CAMPAIGN
dueDateOptionalLongDate and time when the task is due, in epoch milliseconds.
customPropertiesOptionalMap[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.
attachmentOptionalObjectMedia or document attached to the task. The applicable fields depend on attachment.type.
subscribersOptionalArray[Object]Users or entities that subscribe to task updates.
typeRequired within subscriberStringType of subscriber.

Example: USER, GLOBAL, CLIENT
idsOptionalArray[String]Subscriber identifiers. The API interprets each identifier according to subscribers[].type.
queueIdsOptionalArray[String]Identifiers of the work queues to which the task belongs.
parentTaskIdOptionalStringIdentifier of the parent task when the task belongs to a task hierarchy.

Request

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"]
}'

Response

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:

  • POST responses return a single object in data. GET responses return an array in data. Your client must handle both formats.
  • Sprinklr automatically maps taskStatus and taskType to the reserved custom properties spr_task_status and spr_task_type. Do not populate these keys directly. Set taskStatus and taskType, and allow the platform to maintain the mirror fields.

4.1.1 attachment

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):

ParameterRequiredTypeDescription
typeRequired when attachment is providedStringType of attachment.

Example: IMAGE, VIDEO, LINK, DOCUMENT, AUDIO, and CAROUSEL.
urlOptionalStringURL of the attached media or document.
titleOptionalStringTitle of the attachment.
descriptionOptionalStringDescription of the attachment.
previewUrlOptionalStringURL of the attachment preview.
assetIdOptionalStringIdentifier of the attachment in Sprinklr Digital Asset Management.
mimeTypeOptionalStringMIME type of the attachment.
alternateTextOptionalStringAlternative text that describes the attachment.
sizeInBytesOptionalLongSize 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"
}

4.2 Update a task — full update

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.

Query parameters

ParameterTypeRequiredDescriptionExample
idStringRequiredIdentifier of the task to update, or a comma-separated list of task identifiers for a bulk update.6a1437f751d06294d5c8773e

Request

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}}"
    ]
}'

Response

A successful request returns 200 OK and the updated task. For information about the response parameters, see Response field reference — Task.

⚠️ PUT replaces — it does not merge

The example above omits customProperties, subscribers, queueIds, and parentTaskId. Under replace semantics those are unset. Before issuing a PUT:

  1. GET the current task.
  2. Merge your changes into the retrieved document.
  3. Map queueDetails[].queueId back to queueIds — the read and write shapes differ (§1.1).
  4. PUT the 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.


4.3 Update a task — partial update

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=.

Query parameters

ParameterTypeRequiredDescription
idStringRequiredIdentifier of the task to update, or a comma-separated list of task identifiers for a bulk partial update.

Request body — TaskPatchRequestDTO

ParameterRequiredTypeDescription
updateFieldsYesArray[Object]Field update operations to apply to each identified task.
updateFields[].fieldNameYesStringName of the task field to update.
Examples: title, taskStatus, dueDate.
updateFields[].valueYesAnyNew value for the field. Use the data type defined for the selected task field.
updateFields[].opYesStringOperation to apply to the field.

Note: op values come from the shared Operation enum.

Request

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"
        }
    ]
}'

4.4 Method comparison — when to use which

POSTPUTPATCH
PurposeCreate a taskReplace the full task documentUpsert selected fields
AddressingNone — server assigns the ID?id=?id=
Body schemaTaskRequestTaskRequestTaskPatchRequestDTO
BulkNoYes, via comma-separated ?id=Yes, via comma-separated ?id=
Unspecified fieldsN/AUnsetPreserved
Safe for incremental sync—No, without read-modify-writeYes
Available in V2YesYes (path-based)No — new in V3

Note: PUT and PATCH take different body schemas. You cannot reuse one payload for both methods.


4.5 Delete tasks

DELETE /api/v3/task?id={taskId1},{taskId2}

Use this endpoint to delete one or more tasks by ID.

Query parameters

ParameterTypeRequiredDescription
idStringRequiredIdentifier of the task to delete, or a comma-separated list of task identifiers for a bulk delete.

Request

# 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 inactive flag on the Task object. inactive: true marks a task as inactive but is not the same as removing it. Verify the supported soft-delete behaviour before implementing either approach.


5. Read operations

5.1 GET /api/v3/task — Fetch tasks by ID

GET /api/v3/task?id=taskId1,taskId2 — TaskApiV3_getTasks

Use this endpoint to retrieve one or more tasks in a single request trip.

ParameterTypeRequiredDescription
idStringRequiredComma-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'

Response

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": []
}

Response field reference — Task

ParameterTypeDescription
idStringUnique identifier of the task.
assignmentObjectAssignment details for the task.
assignment.assigneeIdStringIdentifier of the assigned user or entity.
assignment.assigneeTypeStringType of assignee.
assignment.assignedByIdLongIdentifier of the user who assigned the task.
assignment.assignmentTimeLongTime when the user assigned the task, in epoch milliseconds.
taskTypeStringType of work represented by the task.
taskStatusStringCurrent status of the task.
titleStringTitle of the task.
descriptionStringDescription of the work required for the task.
assetIdStringIdentifier of the entity for which the task was created.
assetTypeStringType of entity for which the task was created.
parentTaskIdStringIdentifier of the parent task.
nextTaskIdStringIdentifier of the next task in the task sequence.
completionDateLongDate and time when the task was completed, in epoch milliseconds.
dueDateLongDate and time when the task is due, in epoch milliseconds.
customPropertiesMap[String, List[String]]Custom fields returned for the task. Each custom-field ID maps to an array of strings.
inactiveBooleanSpecifies whether the task is inactive.
attachmentObjectMedia or document attached to the task.
subscribersArray[Object]Users or entities that subscribe to task updates.
subscribers[].typeStringType of subscriber.
subscribers[].idsArray[String]Subscriber identifiers.
queueDetailsArray[Object]Work queues to which the task belongs.
queueDetails[].queueIdLongIdentifier of the work queue.
queueDetails[].assignmentTimeLongTime when the task entered the queue, in epoch milliseconds.
createdTimeLongTime when Sprinklr created the task, in epoch milliseconds.
modifiedTimeLongTime when Sprinklr last modified the task, in epoch milliseconds.
workFlowStateObjectCurrent workflow execution state for the task.
workFlowState.executionTokenIdStringIdentifier of the workflow execution token.
workFlowState.processVariablesObjectVariables associated with the workflow execution.
externalCasesArray[Object]Third-party cases linked to the task.

5.2 externalCases

Represents third-party cases linked to a Sprinklr universal case.

ParameterTypeDescription
idStringUnique identifier of the case in the third-party system.
caseNumberStringCase number assigned by the third-party system.
channelTypeStringThird-party platform that manages the case.
permalinkStringURL of the case in the third-party platform.
createdTimeLongTime when the third-party system created the case, in epoch milliseconds.
modifiedTimeLongTime when the third-party system last modified the case, in epoch milliseconds.
clientIdLongIdentifier of the Sprinklr client linked to the case.
subDomainStringSubdomain associated with the third-party case.
additionalMap[String, String]Additional attributes returned for the third-party case.

6. Response format and status codes

6.1 The V3 envelope

{
  "data": [ ... ],
  "errors": []
}
FieldTypeDescription
dataObject or ArrayThe task (POST) or tasks (GET) affected. null on an error response.
errorsArray[Error]Error objects — empty when there are no errors
errors[].idString24-character hex ObjectId
errors[].codeIntegerError code
errors[].messageStringDotted 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.

6.2 Response codes

HTTP codeDeclared onScenario
200 OKGET, POST, PUT, PATCH, DELETESuccess
204 No ContentDELETEDelete succeeded, no response body
400 Bad RequestAllMalformed request — for example a missing or malformed id, or an invalid body
401 UnauthorizedAllMissing or invalid authentication
403 ForbiddenAllInsufficient permissions
404 Not FoundAllTask not found

400, 401, 403, and 404 all return the ErrorResponse schema — { "data": null, "errors": [ … ], "metadata": { … } }.


7. V2 → V3 migration

7.1 Endpoint mapping

V2V3Change
POST /api/v2/taskPOST /api/v3/taskVersion 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.

7.2 What changed, and what did not

AspectAPI V2API V3Impact
Task addressingPath segment /task/{taskId}Query parameter ?id=Update all code that constructs task URLs.
Bulk operationsNot supported — one task per callGET, PUT, PATCH, DELETE accept comma-separated IDsYou can replace multiple calls with a single bulk request.
Partial updateNot availablePATCH with updateFieldsPATCH removes the need for a read-modify-write workflow for small updates.
GET response dataSingle objectArrayRead data[0], not data
Response envelope{ "data": …, "errors": [] }{ "data": …, "errors": [] }Unchanged
Create request bodyTaskRequest fieldsIdentical fieldsNo change needed
Delete response204 No Content204 per acceptance criteria; 200 per specificationAccept both — see §4.5
Partial successN/A (single-entity calls)Possible on bulk callsInspect errors on 200

7.3 Migration steps

  1. Update the base URL to https://api3.sprinklr.com/{env}/api/v3.
  2. Move the task ID from the path to ?id= on GET, PUT, and DELETE. This change is straightforward but must be applied throughout your codebase. Search your codebase for api/v2/task/.
  3. Change GET response parsing from data to data[0]. If existing code expects an object instead of an array, the migration can fail without obvious errors.
  4. Collapse N-call loops into bulk calls. Replace iterative single-task requests with bulk requests where possible.
  5. Add partial-success handling. After a bulk call, reconcile returned data against requested IDs and inspect errors.
  6. Replace read-modify-write updates with PATCH. Any place you GET a task purely to change one field and PUT it back is a candidate. This also removes the queueDetails → queueIds remapping step.
  7. Accept both 200 and 204 from DELETE, and tolerate an empty body.
  8. Keep your POST payloads as-is. Create request bodies did not change.

8. Use cases

8.1 Create a task on an inbound message

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.

8.2 Hydrate a production dashboard

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:1

8.3 Advance a task's status

Scenario: 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.

8.4 Bulk-reschedule a set of tasks

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.

8.5 Full sync from an external project-management tool

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).

8.6 Clean up a batch of cancelled tasks

Scenario: a cancelled campaign leaves 8 orphaned tasks.

DELETE /api/v3/task?id=id1,id2,…,id8

Handle both 200 OK and 204 No Content as successful responses, and re-GET the IDs if you need positive confirmation of removal.


9. Caveats and best practices

Method selection

  • Default to PATCH for edits. Use PUT only when you have the complete authoritative task document.
  • A PUT request can remove queue membership, subscribers, and custom properties if you omit them.
  • PUT and PATCH take different body schemas. Payloads are not interchangeable.

Identifiers and shapes

  • Task IDs are 24-character hex ObjectId strings.
  • GET returns data as an array, even for a single ID. POST returns data as an object.
  • You write queueIds (Array[String]); you read queueDetails (Array[Queue], with queueId as int64). Do not use a GET response 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 data against requested IDs and always inspect errors.
  • The specification does not state a maximum number of IDs per call. Verify the supported bulk-request limit before sending large batches.

Custom properties

  • customProperties is a map of custom-field ID → Array[String]. Even single-valued fields take a list.
  • spr_task_status and spr_task_type are platform-maintained mirrors of taskStatus and taskType. Set the top-level fields, not the mirrors.

Error handling

  • Always inspect the errors array even on a 200 response.
  • A 403 response typically indicates insufficient permissions.
  • Code defensively for 429 and 5xx even though the specification does not declare them for these operations.