## Связи нескольких задач

`POST /v1/tasks/dependencies/bulk`

Возвращает предшественников сразу для нескольких задач, до 50 за вызов.

## Поля запроса (body)

| Поле | Тип | Обяз. | Описание |
|------|-----|:-----:|----------|
| `taskIds` | array | да | ID задач — числа или десятичные строки: `"4203"` равно `4203`. До 500 элементов и до 50 уникальных ID, повторы учитываются один раз. Список задач: [`GET /v1/tasks`](/docs/entities/tasks/list) |

## Примеры

### curl — личный ключ

```bash
curl -X POST "https://vibecode.bitrix24.tech/v1/tasks/dependencies/bulk" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "taskIds": [4203, "4201", 4203, 999999999]
  }'
```

### curl — OAuth-приложение

```bash
curl -X POST "https://vibecode.bitrix24.tech/v1/tasks/dependencies/bulk" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "taskIds": [4203, "4201", 4203, 999999999]
  }'
```

### JavaScript — личный ключ

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/tasks/dependencies/bulk', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    taskIds: [4203, '4201', 4203, 999999999],
  }),
})

const { success, data, meta } = await res.json()
for (const [taskId, deps] of Object.entries(data.results)) {
  console.log(taskId, 'зависит от', deps.map((dep) => dep.id))
}
for (const [taskId, error] of Object.entries(data.errors)) {
  console.log(taskId, 'не прочитана:', error.code)
}
```

### JavaScript — OAuth-приложение

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/tasks/dependencies/bulk', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    taskIds: [4203, '4201', 4203, 999999999],
  }),
})

const { success, data, meta } = await res.json()
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|----------|
| `success` | boolean | `true`, если запрос принят. Отказы по отдельным задачам — в `data.errors` |
| `data.results` | object | Связи задач: ключ — ID задачи строкой, значение — массив предшественников |
| `data.results.<taskId>[].id` | number | ID задачи-предшественника. Получить: [`GET /v1/tasks/:id`](/docs/entities/tasks/get) |
| `data.results.<taskId>[].title` | string \| null | Название задачи-предшественника. `null`, если портал его не отдал |
| `data.errors` | object | Отказы по задачам: ключ — ID задачи строкой, значение — объект ошибки |
| `data.errors.<taskId>.code` | string | Код отказа — один из пяти, см. «Отказы по отдельным задачам» ниже |
| `data.errors.<taskId>.message` | string | Текст отказа |
| `meta.requested` | number | Число уникальных ID в запросе: повторы учитываются один раз. Всегда равно `succeeded + failed` |
| `meta.succeeded` | number | Сколько задач прочитано |
| `meta.failed` | number | Сколько задач попало в `data.errors` |
| `meta.direction` | string | Всегда `predecessors` |
| `meta.source` | string | Всегда `RELATED_TASKS` |

## Пример ответа

Запрос из примеров: повтор `4203` учтён один раз, строка `"4201"` прочитана как число, задачи `999999999` нет.

```json
{
  "success": true,
  "data": {
    "results": {
      "4201": [],
      "4203": [
        {
          "id": 4199,
          "title": "Подготовить смету"
        },
        {
          "id": 4201,
          "title": "Согласовать бюджет"
        }
      ]
    },
    "errors": {
      "999999999": {
        "code": "TASK_NOT_FOUND",
        "message": "Task not found."
      }
    }
  },
  "meta": {
    "requested": 3,
    "succeeded": 2,
    "failed": 1,
    "direction": "predecessors",
    "source": "RELATED_TASKS"
  }
}
```

## Пример ответа при ошибке

400 — в `taskIds` больше 50 уникальных ID:

```json
{
  "success": false,
  "error": {
    "code": "BATCH_LIMIT_EXCEEDED",
    "message": "taskIds carries 51 unique ids; at most 50 are accepted"
  }
}
```

## Ошибки

Отказы всего запроса:

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `BATCH_LIMIT_EXCEEDED` | Больше 50 уникальных ID. Сообщение `taskIds carries N unique ids; at most 50 are accepted` |
| 400 | `BATCH_LIMIT_EXCEEDED` | Больше 500 элементов в исходном массиве, проверяется до разбора элементов. Сообщение `taskIds carries N elements; at most 500 are read, and at most 50 unique ids are accepted` |
| 400 | `INVALID_PARAMS` | Элемент — не целое положительное число. Сообщение называет позицию в исходном массиве, отсчёт с нуля: `taskIds[1] must be a positive integer id …` |
| 400 | `INVALID_REQUEST` | Тело — не JSON-объект. Сообщение `Request body must be an object with a taskIds array` |
| 400 | `INVALID_REQUEST` | Нет `taskIds`, это не массив или массив пустой. Так же отвечает запрос без тела. Сообщение `taskIds must be a non-empty array` |
| 401 | `MISSING_API_KEY` | Не передан `X-Api-Key` |
| 401 | `TOKEN_MISSING` | У ключа нет токенов Битрикс24 |
| 403 | `SCOPE_DENIED` | У ключа нет скоупа `task` |
| 415 | `FST_ERR_CTP_INVALID_MEDIA_TYPE` | Непустое тело с `Content-Type`, отличным от `application/json` |
| 429 | `RATE_LIMITED` | Превышена частота пакетного вызова. Повторите запрос через время из заголовка `retry-after` |

Отказы по отдельным задачам приходят в `data.errors` при ответе `200` — даже когда отказ пришёл по всем задачам. Проверяйте `data.errors`, а не только `data.results`:

| Код | Описание |
|-----|----------|
| `TASK_NOT_FOUND` | Задачи нет или она не видна ключу |
| `RATE_LIMITED` | Битрикс24 ограничил частоту для этого подвызова. Повторите позже |
| `OPERATION_TIME_LIMIT` | Битрикс24 попросил снизить нагрузку. Повторите позже |
| `UPSTREAM_INCOMPLETE` | Пригодного ответа по этой задаче не получено |
| `DEPENDENCY_LIMIT_EXCEEDED` | У задачи больше 1000 связей |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

**Лимит частоты — на портал, а не на ключ.** Пакетный вызов ограничен 30 запросами в минуту на портал. Все ключи одного портала расходуют общий лимит, отказы тоже засчитываются. Лимит делится между репликами платформы: действующее значение — в заголовке `x-ratelimit-limit`, остаток — в `x-ratelimit-remaining`.

## Смотрите также

- [Связи задач](/docs/entities/tasks/dependencies)
- [Связи задачи](/docs/entities/tasks/dependencies-list)
- [Список задач](/docs/entities/tasks/list)
- [Обновить задачу](/docs/entities/tasks/update)
