## Создать составную загрузку

`POST /v1/storage/objects/multipart/create`

Открывает сессию составной загрузки для файлов до 5 ТБ. Возвращает идентификатор объекта, идентификатор сессии и предподписанные URL для загрузки частей. После загрузки всех частей вызовите [`POST /v1/storage/objects/multipart/complete`](/docs/storage/upload/multipart-complete), чтобы собрать объект.

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

Тело запроса — JSON.

| Параметр | Тип | Обяз. | По умолч. | Описание |
|----------|-----|:-----:|-----------|---------|
| `key` | string | да | — | Логический ключ объекта: от 1 до 1024 символов, допустимы `a-z`, `A-Z`, `0-9`, `.`, `_`, `/`, `-`; не начинать с `/` или `.`, не содержать `..` |
| `contentType` | string | да | — | MIME-тип файла, например `video/mp4` или `application/zip` |
| `totalSize` | number | да | — | Общий размер файла в байтах; положительное целое число; не более 5 ТБ (5 497 558 138 880 байт) |
| `partSize` | number | нет | `8388608` | Размер одной части в байтах; от 5 МБ (5 242 880) до 5 ГБ (5 368 709 120); последняя часть может быть меньше минимума |
| `visibility` | string | нет | `PRIVATE` | Видимость объекта: `PRIVATE` или `PUBLIC` |

## Примеры

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

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/storage/objects/multipart/create \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "videos/lecture-01.mp4",
    "contentType": "video/mp4",
    "totalSize": 209715200
  }'
```

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

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/storage/objects/multipart/create \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "videos/lecture-01.mp4",
    "contentType": "video/mp4",
    "totalSize": 209715200
  }'
```

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

```javascript
const file = fileInput.files[0]

// Шаг 1 — открыть сессию составной загрузки
const createRes = await fetch(
  'https://vibecode.bitrix24.tech/v1/storage/objects/multipart/create',
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      key: 'videos/lecture-01.mp4',
      contentType: file.type,
      totalSize: file.size,
    }),
  }
)
const { objectId, parts } = await createRes.json()

// Шаг 2 — загрузить части параллельно, сохранить ETag из заголовков ответов
const PART_SIZE = 8 * 1024 * 1024
const uploadedParts = await Promise.all(
  parts.map(async ({ partNumber, uploadUrl }) => {
    const start = (partNumber - 1) * PART_SIZE
    const end = Math.min(start + PART_SIZE, file.size)

    const putRes = await fetch(uploadUrl, {
      method: 'PUT',
      body: file.slice(start, end),
    })
    const etag = putRes.headers.get('ETag')
    return { partNumber, etag }
  })
)

// Шаг 3 — завершить сборку объекта
const completeRes = await fetch(
  'https://vibecode.bitrix24.tech/v1/storage/objects/multipart/complete',
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ objectId, parts: uploadedParts }),
  }
)
const { object } = await completeRes.json()
console.log('Object ID:', object.id)
```

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

```javascript
const file = fileInput.files[0]

const createRes = await fetch(
  'https://vibecode.bitrix24.tech/v1/storage/objects/multipart/create',
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      key: 'videos/lecture-01.mp4',
      contentType: file.type,
      totalSize: file.size,
    }),
  }
)
const { objectId, parts } = await createRes.json()

const PART_SIZE = 8 * 1024 * 1024
const uploadedParts = await Promise.all(
  parts.map(async ({ partNumber, uploadUrl }) => {
    const start = (partNumber - 1) * PART_SIZE
    const end = Math.min(start + PART_SIZE, file.size)

    const putRes = await fetch(uploadUrl, { method: 'PUT', body: file.slice(start, end) })
    const etag = putRes.headers.get('ETag')
    return { partNumber, etag }
  })
)

const completeRes = await fetch(
  'https://vibecode.bitrix24.tech/v1/storage/objects/multipart/complete',
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ objectId, parts: uploadedParts }),
  }
)
const { object } = await completeRes.json()
```

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

| Поле | Тип | Описание |
|------|-----|---------|
| `objectId` | string | Идентификатор объекта в хранилище |
| `uploadId` | string | Идентификатор сессии составной загрузки |
| `partCount` | number | Количество частей, рассчитанное из `totalSize` и `partSize` |
| `parts` | array | Массив описателей частей |
| `parts[].partNumber` | number | Номер части (начиная с 1) |
| `parts[].uploadUrl` | string | Предподписанный URL для загрузки части методом `PUT` |
| `parts[].expiresAt` | string | Срок действия URL (ISO 8601); совпадает со сроком сессии — 24 часа |

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

```json
{
  "objectId": "cmpfg012a01aco510mrr6u632",
  "uploadId": "00065252B4EA33D1",
  "partCount": 2,
  "parts": [
    {
      "partNumber": 1,
      "uploadUrl": "https://storage.example.com/upload?partNumber=1&uploadId=00065252B4EA33D1&...",
      "expiresAt": "2026-05-22T12:03:56.398Z"
    },
    {
      "partNumber": 2,
      "uploadUrl": "https://storage.example.com/upload?partNumber=2&uploadId=00065252B4EA33D1&...",
      "expiresAt": "2026-05-22T12:03:56.398Z"
    }
  ]
}
```

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

413 — файл превышает ограничение 5 ТБ:

```json
{
  "success": false,
  "error": {
    "code": "STORAGE_OBJECT_TOO_LARGE",
    "message": "Object exceeds 5 TB cap"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 403 | `STORAGE_SCOPE_REQUIRED` | API-ключу не хватает скоупа `vibe:storage` |
| 409 | `STORAGE_KEY_EXISTS` | Объект под этим ключом уже существует. Заменить содержимое через составную загрузку нельзя — используйте другой ключ |
| 409 | `STORAGE_KEY_DELETED` | Объект под этим ключом удалён и держит имя до уборки |
| 409 | `STORAGE_MULTIPART_IN_PROGRESS` | По этому ключу уже идёт составная загрузка — завершите или отмените её |
| 409 | `STORAGE_UPLOAD_PENDING` | По этому ключу висит незавершённая бронь предподписанной ссылки |
| 409 | `STORAGE_KEY_CONFLICT` | Объект под этим ключом создал параллельный запрос — повторите запрос |
| 401 | `STORAGE_NO_AUTH_CONTEXT` | Запрос выполнен без авторизации |
| 400 | `STORAGE_INVALID_PATH` | Идентификатор вызывающего содержит недопустимые символы пути |
| 400 | `STORAGE_KEY_REQUIRED` | Не передано поле `key` |
| 400 | `STORAGE_INVALID_KEY` | Значение `key` нарушает правила формата |
| 400 | `STORAGE_CONTENT_TYPE_REQUIRED` | Не передано поле `contentType` |
| 400 | `STORAGE_INVALID_VISIBILITY` | Недопустимое значение `visibility` |
| 400 | `STORAGE_INVALID_TOTAL_SIZE` | `totalSize` не является положительным целым числом |
| 400 | `STORAGE_INVALID_PART_SIZE` | `partSize` выходит за пределы допустимого диапазона (5 МБ – 5 ГБ) или не является положительным целым числом |
| 413 | `STORAGE_OBJECT_TOO_LARGE` | `totalSize` превышает 5 ТБ |
| 415 | `STORAGE_FORBIDDEN_CONTENT_TYPE` | Для `PUBLIC`-объектов запрещены типы `text/html`, `application/javascript`, `application/x-javascript`, `image/svg+xml` — [Коды ошибок](/docs/errors) |
| 400 | `STORAGE_TOO_MANY_PARTS` | Рассчитанное количество частей превышает 10 000 — увеличьте `partSize` |
| 402 | `BILLING_INSUFFICIENT` | Недостаточно средств на балансе |
| 503 | `STORAGE_FEATURE_DISABLED` | Хранилище отключено для портала |
| 503 | `STORAGE_STS_UNAVAILABLE` | Служба выдачи временных учётных данных недоступна |
| 502 | `STORAGE_BUCKET_ERROR` | Ошибка при взаимодействии с хранилищем |

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

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

**Предподписанные URL для частей не требуют заголовков авторизации.** Запросы `PUT` на эти URL отправляются напрямую в хранилище без `X-Api-Key` или `Authorization` — все необходимые учётные данные уже подписаны в самом URL.

**Срок действия сессии — 24 часа.** По истечении срока предподписанные URL становятся недействительными, незагруженные части удаляются автоматически. Для досрочного освобождения ресурсов вызовите [`POST /v1/storage/objects/multipart/abort`](/docs/storage/upload/multipart-abort).

**Файлы меньшего размера.** Для файлов до 10 МБ используйте [прямую загрузку](/docs/storage/upload/direct) (Путь A). Путь B временно отключён и отвечает `503 STORAGE_PRESIGNED_UPLOAD_DISABLED`, поэтому промежуточный размер тоже идёт составной загрузкой.

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

- [Загрузка](/docs/storage/upload)
- [Завершить составную загрузку](/docs/storage/upload/multipart-complete)
- [Отменить составную загрузку](/docs/storage/upload/multipart-abort)
- [Прямая загрузка файла](/docs/storage/upload/direct)
- [Хранилище](/docs/storage)
