## Save a task checklist

`POST /v1/tasks/:taskId/checklist/tree`

Saves a task's entire checklist in one call: the flat array of items you pass replaces the current checklist. Item field names are in upper case.

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|----------|
| `taskId` (path) | number | yes | Task ID, a positive integer. List: [`GET /v1/tasks`](/docs/entities/tasks/list) |

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `items` | array | yes | All checklist items in one flat array. An item missing from the array is deleted. An empty array deletes the whole checklist. The order of items with the same parent in the array sets their `sortIndex` |
| `items[].ID` | number | no | ID of an existing item. List: [`GET /v1/tasks/:taskId/checklist`](/docs/entities/tasks/checklist/list). With `ID`, the item is saved under its existing ID. Without it, the item is re-created with a new ID, and the old item is deleted |
| `items[].NODE_ID` | string | no | Item key within the request, for example `root-1`. Sub-items reference it in `PARENT_NODE_ID`, and the item comes back under it in the response. Give each item its own `NODE_ID`: if several new items lack one, only the last of them is saved, and the rest are lost without an error |
| `items[].PARENT_NODE_ID` | string \| number | no | `NODE_ID` of the parent item. `0` or omitted — a top-level item, and its `TITLE` becomes the checklist name |
| `items[].TITLE` | string | yes | Item text. Required for every item, including an item with `ID` |
| `items[].IS_COMPLETE` | boolean | no | `true` — the item is complete. Without the field, a new item is saved as incomplete. The string `"Y"` instead of `true` saves the item as incomplete, with no error |
| `items[].IS_IMPORTANT` | boolean | no | `true` — the item is important. Defaults to `false`: an item with `ID` loses its previous importance flag |
| `parameters` | object | no | Additional save parameters. They do not affect the result |

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/tasks/4218/checklist/tree" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "NODE_ID": "root-1", "PARENT_NODE_ID": 0, "TITLE": "Launch preparation", "IS_COMPLETE": false, "IS_IMPORTANT": false },
      { "NODE_ID": "child-1", "PARENT_NODE_ID": "root-1", "TITLE": "Check the build", "IS_COMPLETE": false, "IS_IMPORTANT": true },
      { "NODE_ID": "child-2", "PARENT_NODE_ID": "root-1", "TITLE": "Update the description", "IS_COMPLETE": false, "IS_IMPORTANT": false }
    ]
  }'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/tasks/4218/checklist/tree" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "NODE_ID": "root-1", "PARENT_NODE_ID": 0, "TITLE": "Launch preparation", "IS_COMPLETE": false, "IS_IMPORTANT": false },
      { "NODE_ID": "child-1", "PARENT_NODE_ID": "root-1", "TITLE": "Check the build", "IS_COMPLETE": false, "IS_IMPORTANT": true },
      { "NODE_ID": "child-2", "PARENT_NODE_ID": "root-1", "TITLE": "Update the description", "IS_COMPLETE": false, "IS_IMPORTANT": false }
    ]
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/tasks/4218/checklist/tree', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      { NODE_ID: 'root-1', PARENT_NODE_ID: 0, TITLE: 'Launch preparation', IS_COMPLETE: false, IS_IMPORTANT: false },
      { NODE_ID: 'child-1', PARENT_NODE_ID: 'root-1', TITLE: 'Check the build', IS_COMPLETE: false, IS_IMPORTANT: true },
      { NODE_ID: 'child-2', PARENT_NODE_ID: 'root-1', TITLE: 'Update the description', IS_COMPLETE: false, IS_IMPORTANT: false },
    ],
  }),
})

const { data } = await res.json()
const saved = data?.checkListItem?.traversedItems ?? {}
for (const [nodeId, item] of Object.entries(saved)) {
  console.log(nodeId, '→ item ID', item.id)
}
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/tasks/4218/checklist/tree', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      { NODE_ID: 'root-1', PARENT_NODE_ID: 0, TITLE: 'Launch preparation', IS_COMPLETE: false, IS_IMPORTANT: false },
      { NODE_ID: 'child-1', PARENT_NODE_ID: 'root-1', TITLE: 'Check the build', IS_COMPLETE: false, IS_IMPORTANT: true },
      { NODE_ID: 'child-2', PARENT_NODE_ID: 'root-1', TITLE: 'Update the description', IS_COMPLETE: false, IS_IMPORTANT: false },
    ],
  }),
})

const { data } = await res.json()
const saved = data?.checkListItem?.traversedItems ?? {}
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` if the request succeeded |
| `data.checkListItem` | object \| null | The saved checklist. `null` if an empty `items` array was passed |
| `data.checkListItem.traversedItems` | object | Saved items. The key is the item's `NODE_ID` from the request; for an item without `NODE_ID` it is `"0"` |
| `data.checkListItem.traversedItems.<NODE_ID>.id` | number | Item ID. An item without `ID` in the request gets a new ID |
| `data.checkListItem.traversedItems.<NODE_ID>.copiedId` | number \| null | ID of the original item if the item was copied. The method returns `null` |
| `data.checkListItem.traversedItems.<NODE_ID>.entityId` | number | Task ID from the path |
| `data.checkListItem.traversedItems.<NODE_ID>.userId` | number | ID of the employee on whose behalf the call is made |
| `data.checkListItem.traversedItems.<NODE_ID>.createdBy` | number \| null | ID of the item author. For an item saved by `ID` it is `null`; the author of such an item is returned by [`GET /v1/tasks/:taskId/checklist`](/docs/entities/tasks/checklist/list) |
| `data.checkListItem.traversedItems.<NODE_ID>.parentId` | number | ID of the parent item. `0` — a top-level item |
| `data.checkListItem.traversedItems.<NODE_ID>.title` | string | Item text |
| `data.checkListItem.traversedItems.<NODE_ID>.sortIndex` | number | The item's position among items with the same parent: the smaller the number, the higher the item |
| `data.checkListItem.traversedItems.<NODE_ID>.displaySortIndex` | string | Item number for display. The method returns an empty string |
| `data.checkListItem.traversedItems.<NODE_ID>.isComplete` | boolean | Whether the item is complete |
| `data.checkListItem.traversedItems.<NODE_ID>.isImportant` | boolean | Whether the item is important |
| `data.checkListItem.traversedItems.<NODE_ID>.completedCount` | number | Number of completed sub-items. The method returns `0` even when a sub-item is complete |
| `data.checkListItem.traversedItems.<NODE_ID>.members` | array | Item members. An empty array if there are none |
| `data.checkListItem.traversedItems.<NODE_ID>.attachments` | array \| object | Item attachments. An empty array if there are none |
| `data.checkListItem.traversedItems.<NODE_ID>.nodeId` | string \| null | The item's `NODE_ID` from the request. For an item without `NODE_ID` it is `null` |
| `data.checkListItem.taskId` | null | Always `null`. The task ID is in the items' `entityId` |

## Response example

Checklist saved:

```json
{
  "success": true,
  "data": {
    "checkListItem": {
      "traversedItems": {
        "root-1": {
          "id": 268,
          "copiedId": null,
          "entityId": 4218,
          "userId": 1295,
          "createdBy": 1295,
          "parentId": 0,
          "title": "Launch preparation",
          "sortIndex": 0,
          "displaySortIndex": "",
          "isComplete": false,
          "isImportant": false,
          "completedCount": 0,
          "members": [],
          "attachments": [],
          "nodeId": "root-1"
        },
        "child-1": {
          "id": 270,
          "copiedId": null,
          "entityId": 4218,
          "userId": 1295,
          "createdBy": 1295,
          "parentId": 268,
          "title": "Check the build",
          "sortIndex": 0,
          "displaySortIndex": "",
          "isComplete": false,
          "isImportant": true,
          "completedCount": 0,
          "members": [],
          "attachments": [],
          "nodeId": "child-1"
        },
        "child-2": {
          "id": 272,
          "copiedId": null,
          "entityId": 4218,
          "userId": 1295,
          "createdBy": 1295,
          "parentId": 268,
          "title": "Update the description",
          "sortIndex": 1,
          "displaySortIndex": "",
          "isComplete": false,
          "isImportant": false,
          "completedCount": 0,
          "members": [],
          "attachments": [],
          "nodeId": "child-2"
        }
      },
      "taskId": null
    }
  }
}
```

An empty `items` array was passed, and the task checklist was deleted:

```json
{
  "success": true,
  "data": {
    "checkListItem": null
  }
}
```

## Error response example

422 — a new item has no `TITLE`:

```json
{
  "success": false,
  "error": {
    "code": "BITRIX_ERROR",
    "message": "Incorrect value [] specified for field [TITLE] in item [, ]",
    "b24Code": "0"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_PARAMS` | `taskId` is not a positive integer. Message `taskId must be a positive integer` |
| 400 | `INVALID_PARAMS` | The body is not a JSON object, for example an array. Message `JSON object body is required` |
| 400 | `INVALID_PARAMS` | The `items` field is missing, or `items` is not an array. Message `items must be an array` |
| 400 | `INVALID_PARAMS` | `parameters` is not an object. Message `parameters must be an object` |
| 401 | `MISSING_API_KEY` | `X-Api-Key` is missing |
| 401 | `TOKEN_MISSING` | The key has no Bitrix24 tokens |
| 403 | `SCOPE_DENIED` | The key has no `task` scope |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is in read-only mode. `error.details.switchUrl` contains the URL of the section where the mode is changed |
| 415 | `FST_ERR_CTP_INVALID_MEDIA_TYPE` | A body with a `Content-Type` the API does not parse, for example `application/xml` |
| 422 | `BITRIX_ERROR` | An item has no `TITLE`, including an item with `ID`. The message is in the Bitrix24 account language and refers to the item as `[283, ]`, where `283` is the item's `ID`. For a new item the ID slot in the message is empty: `[, ]` |
| 422 | `BITRIX_ERROR` | The employee on whose behalf the call is made has no access to the task. This employee gets the same response for the `taskId` of a nonexistent task. The message is in the Bitrix24 account language; on an English-language account it is `Access to task was denied`. The API specification declares `404 TASK_NOT_FOUND` for this case, but the Bitrix24 accounts we tested return this response instead |
| 429 | `RATE_LIMITED` | Request rate exceeded. The effective cap is in the `x-ratelimit-limit` header |

Full list of common API errors — [Errors](/docs/errors).

## Known specifics

**An error does not roll back what is already saved.** A `422` response does not mean the checklist stayed as it was. A request that renames an item with `ID` and adds a new item without `TITLE` returns `422`, but the item is renamed anyway. Items missing from the request are not deleted in this case. After an error, check the result with [`GET /v1/tasks/:taskId/checklist`](/docs/entities/tasks/checklist/list).

**A nonexistent `taskId` does not always return an error.** The response depends on the employee on whose behalf the call is made: one gets `422 BITRIX_ERROR`, another gets `200`. In the second case the items are saved with `entityId` equal to the passed `taskId` and are not visible in any task. Before saving, check the task with [`GET /v1/tasks/:id`](/docs/entities/tasks/get).

## See also

- [Checklist actions and task file](/docs/entities/tasks/checklist-actions)
- [List checklist items](/docs/entities/tasks/checklist/list)
- [Update a checklist item](/docs/entities/tasks/checklist/update)
- [Delete a checklist item](/docs/entities/tasks/checklist/delete)
- [Mark an item complete with its sub-items](/docs/entities/tasks/checklist-actions/complete-all)
- [Task checklist](/docs/entities/tasks/checklist)
- [Tasks](/docs/entities/tasks)
