## Загрузить содержимое файла на Диск

`POST /v1/disk/uploads`

Записывает файл на Диск Битрикс24 по ранее зарезервированной операции — создаёт новый файл или новую версию существующего, как задано при резерве.

Тело плоское, без обёртки `fields`.

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

| Поле | Тип | Обяз. | Описание |
|------|-----|:-----:|----------|
| `operationId` | string | да | Идентификатор операции из ответа [`POST /v1/disk/uploads/reserve`](/docs/disk/uploads-reserve). Потерянный идентификатор находится в [`GET /v1/disk/uploads`](/docs/disk/uploads-list) |
| `intentHash` | string | да | Поле `intentHash` из ответа резерва — 64 символа в нижнем регистре |
| `effectiveFilename` | string | да | Имя файла, совпадающее с `intent[6]` резерва символ в символ |
| `content` | string | да | Байты файла в base64 по RFC 4648: алфавит `A-Z`, `a-z`, `0-9`, `+`, `/`, дополнение `=`, без переносов строк и без префикса `data:`. Не больше `26214400` байт после декодирования (25 МиБ) |
| `contentHash` | string | да | SHA-256 байтов файла до кодирования в base64 — 64 символа в нижнем регистре. Совпадает с `intent[3]` резерва |

Других полей тело не принимает: неизвестное поле даёт `400 INVALID_PARAMS`. Общий размер тела запроса — не больше 35 МиБ.

## Примеры

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

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/disk/uploads \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "operationId": "xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU",
    "intentHash": "3e10ae1152a0e4b4e89d3b312bfacafc1f3e0cf22d8a5eae3e231fa8bc274498",
    "effectiveFilename": "vibe-disk-docs-check-2.txt",
    "content": "VmliZSBEaXNrIGFnZW50IGRvY3MgY2hlY2sgMgo=",
    "contentHash": "6be5cdb4cdbf136301c74adf2fd5a380fe5ceda1e998ce4b62f01cf7d20589a5"
  }'
```

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

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/disk/uploads \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "operationId": "xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU",
    "intentHash": "3e10ae1152a0e4b4e89d3b312bfacafc1f3e0cf22d8a5eae3e231fa8bc274498",
    "effectiveFilename": "vibe-disk-docs-check-2.txt",
    "content": "VmliZSBEaXNrIGFnZW50IGRvY3MgY2hlY2sgMgo=",
    "contentHash": "6be5cdb4cdbf136301c74adf2fd5a380fe5ceda1e998ce4b62f01cf7d20589a5"
  }'
```

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

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/disk/uploads', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    operationId: 'xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU',
    intentHash: '3e10ae1152a0e4b4e89d3b312bfacafc1f3e0cf22d8a5eae3e231fa8bc274498',
    effectiveFilename: 'vibe-disk-docs-check-2.txt',
    content: 'VmliZSBEaXNrIGFnZW50IGRvY3MgY2hlY2sgMgo=',
    contentHash: '6be5cdb4cdbf136301c74adf2fd5a380fe5ceda1e998ce4b62f01cf7d20589a5',
  }),
})

const { success, data, error } = await res.json()
if (!success) throw new Error(error.code)
if (data.application === 'unknown') {
  // Не повторяйте запись: сначала проверьте папку или файл на Диске
  console.warn('Результат записи неизвестен:', data.operationId)
} else if (data.application === 'applied') {
  console.log('Файл записан:', data.fileId, data.safeLink)
}
```

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

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/disk/uploads', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    operationId: 'xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU',
    intentHash: '3e10ae1152a0e4b4e89d3b312bfacafc1f3e0cf22d8a5eae3e231fa8bc274498',
    effectiveFilename: 'vibe-disk-docs-check-2.txt',
    content: 'VmliZSBEaXNrIGFnZW50IGRvY3MgY2hlY2sgMgo=',
    contentHash: '6be5cdb4cdbf136301c74adf2fd5a380fe5ceda1e998ce4b62f01cf7d20589a5',
  }),
})

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

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

| Поле | Тип | Описание |
|------|-----|----------|
| `success` | boolean | `true`, если запрос обработан. Записан ли файл, показывает `data.application` |
| `data.operationId` | string | Идентификатор операции |
| `data.phase` | string | Этап операции: `prepared` — зарезервирована, `submitting` — запись передана в Битрикс24, `settled` — завершена |
| `data.application` | string | Результат записи: `applied` — Битрикс24 подтвердил запись, `not_applied` — запись в Битрикс24 не отправлялась, `unknown` — запись отправлена, но подтверждения нет |
| `data.verification` | string | Сверка с текущим состоянием файла: `matched` — файл совпадает с записанным, `superseded` — файл с тех пор изменился, `unavailable` — сверка не выполнена |
| `data.fileId` | string | ID файла на Диске. Приходит при `application: "applied"`. Используется как `intent[2]` в режиме `version` |
| `data.contentFileId` | string | ID записанного содержимого. Приходит при `application: "applied"`. Первый элемент ожидаемого состояния `intent[5]` для следующей версии |
| `data.contentGeneration` | string | Номер версии содержимого файла. Приходит при `application: "applied"`. Второй элемент `intent[5]` для следующей версии |
| `data.safeLink` | string | HTTPS-адрес файла в Битрикс24. Приходит, когда Битрикс24 его вернул. Доступ ограничен правами сотрудника в Битрикс24 |
| `data.atomicConcurrency` | boolean | Всегда `false`: запись не защищена от одновременного изменения файла другим пользователем |
| `data.conflictDetected` | boolean | `true`, если в режиме `version` текущее состояние файла не совпало с `intent[5]` и запись не выполнялась |
| `data.outcomeUnknown` | boolean | `true` при `application: "unknown"` |

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

Файл записан:

```json
{
  "success": true,
  "data": {
    "operationId": "xykPPgOlNBiKMTlAV3SfBoGJhG6Hi6AX57Ede4rAFoU",
    "phase": "settled",
    "application": "applied",
    "verification": "matched",
    "fileId": "10901",
    "contentFileId": "39571",
    "contentGeneration": "1",
    "safeLink": "https://example.bitrix24.ru/company/personal/user/1/disk/file/vibe-disk-docs-check-2.txt",
    "atomicConcurrency": false,
    "conflictDetected": false,
    "outcomeUnknown": false
  }
}
```

Запись отправлена, подтверждения нет — в папке уже был файл с таким именем:

```json
{
  "success": true,
  "data": {
    "operationId": "zQl-VU0ZeSeCYnH14kibBVeiYRM7iihEu6-QDSTPJFQ",
    "phase": "settled",
    "application": "unknown",
    "verification": "unavailable",
    "atomicConcurrency": false,
    "conflictDetected": false,
    "outcomeUnknown": true
  }
}
```

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

409 — `contentHash` не совпадает с SHA-256 переданных байтов:

```json
{
  "success": false,
  "error": {
    "code": "DISK_OPERATION_CONFLICT",
    "message": "Disk request unavailable",
    "retrySafe": false
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `INVALID_PARAMS` | Тело не прошло проверку: нет обязательного поля, `intentHash` или `contentHash` не из 64 шестнадцатеричных символов, недопустимое имя файла, неизвестное поле |
| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |
| 401 | `INVALID_API_KEY` | Ключ не опознан |
| 401 | `TOKEN_MISSING` | У ключа нет подключения к Битрикс24 |
| 401 | `DISK_AUTH_UNAVAILABLE` | Не удалось подтвердить пользователя Битрикс24: ключ приложения передан без `Authorization: Bearer`, сессия не принадлежит приложению ключа или ключ не поддерживается агентом Диска |
| 401 | `DISK_AUTH_UNAVAILABLE` | Режим `version`: не удалось прочитать текущее состояние файла. Запись не выполнялась, операция завершена с `application: "not_applied"` |
| 401 | `TOKEN_REFRESH_FAILED` | Не удалось обновить авторизацию портала для ключа. Повтор не поможет, пока ключ не авторизован заново |
| 403 | `SCOPE_DENIED` | У ключа нет скоупа `disk` |
| 403 | `WRITE_BLOCKED_READONLY` | Ключ в режиме «только чтение» |
| 403 | `DISK_ACCESS_DENIED` | Битрикс24 отказал в доступе при проверке пользователя |
| 409 | `DISK_OPERATION_CONFLICT` | `content` не является base64 по RFC 4648, `contentHash` не совпадает с SHA-256 байтов, либо `intentHash`, `effectiveFilename`, хеш или размер байтов не совпадают с резервом |
| 409 | `DISK_OPERATION_EXPIRED` | Операция `operationId` не найдена в контексте ключа или срок `reservationExpiresAt` истёк. Создайте новый резерв |
| 409 | `DISK_VERSION_CONFLICT` | Режим `version`: текущее состояние файла не совпало с `intent[5]`. Запись не выполнялась, операция завершена с `application: "not_applied"` и `conflictDetected: true` |
| 413 | `DISK_UPLOAD_TOO_LARGE` | Содержимое после декодирования больше `26214400` байт |
| 413 | `PAYLOAD_TOO_LARGE` | Тело запроса больше 35 МиБ |
| 429 | `LARGE_BODY_BACKEND_BUSY` | Сервер обрабатывает много больших запросов одновременно. В ответе заголовок `Retry-After` с паузой в секундах |
| 502 | `DISK_READ_UNAVAILABLE` | Битрикс24 вернул ошибку, которую Вайбкод не распознал |
| 503 | `QUERY_LIMIT_EXCEEDED` | Битрикс24 ограничил частоту запросов. Повторите позже |
| 503 | `BITRIX_TIMEOUT` | Битрикс24 не ответил вовремя |
| 503 | `QUEUE_OVERFLOW`, `QUEUE_TIMEOUT` | Очередь вызовов портала переполнена. Заголовок `Retry-After` называет паузу перед повтором |
| 503 | `RATE_LIMITED` | Портал ответил HTTP 429. Паузу перед повтором называет заголовок `Retry-After` |
| 503 | `ERROR_LOOP_DETECTED` | Повторяющиеся ошибки этого ключа на этом методе временно остановили вызовы. Паузу называет заголовок `Retry-After` |
| 503 | `user_self_deletion_pending` | Владелец ключа запросил удаление своей учётной записи — обращения к Диску остановлены |

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

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

- **Запись отправляется в Битрикс24 один раз.** Вайбкод не повторяет её сам ни при таймауте, ни при сбое соединения. Если Битрикс24 не подтвердил отправленную запись — отказал, не ответил или ответил неполно, — операция завершается с `application: "unknown"`, а повторный вызов с тем же `operationId` возвращает это состояние без новой записи.
- **При `application: "unknown"` проверьте Диск, прежде чем писать снова.** Новый резерв с `lineage.kind: "retry"` от такой операции отклоняется с `409 DISK_OUTCOME_UNKNOWN`. Найдите файл через [`POST /v1/disk/search`](/docs/disk/search) или [`POST /v1/disk/sync/check`](/docs/disk/sync-check) и решите, нужна ли новая запись с `lineage.kind: "initial"` и другим `intent`.
- **Режим `create` не перезаписывает файл.** Если в папке уже есть файл с тем же именем, существующий файл остаётся без изменений, а операция завершается с `application: "unknown"`. Для замены содержимого используйте режим `version`.
- **Повтор завершённой операции пересчитывает `verification`.** Для `application: "applied"` поле сверяется с текущим файлом заново.
- **Проверка `intent[5]` в режиме `version` выполняется перед записью и не блокирует файл.** Изменение файла между проверкой и записью не обнаруживается. После `409 DISK_VERSION_CONFLICT` возьмите актуальные `contentFileId`, `contentGeneration` и `size` и зарезервируйте новую операцию.

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

- [Зарезервировать загрузку файла на Диск](/docs/disk/uploads-reserve)
- [Получить операцию загрузки](/docs/disk/uploads-get)
- [Список операций загрузки](/docs/disk/uploads-list)
- [Поиск по Диску](/docs/disk/search)
- [Проверить видимость объектов Диска](/docs/disk/sync-check)
- [Ошибки](/docs/errors)
