## Create multipart upload

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

Opens a multipart upload session for files up to 5 TB. Returns the object identifier, the session identifier, and presigned URLs for uploading parts. After all parts are uploaded, call [`POST /v1/storage/objects/multipart/complete`](/docs/storage/upload/multipart-complete) to assemble the object.

## Request fields (body)

The request body is JSON.

| Parameter | Type | Required | Default | Description |
|----------|-----|:-----:|-----------|---------|
| `key` | string | yes | — | Logical object key: from 1 to 1024 characters, allowed `a-z`, `A-Z`, `0-9`, `.`, `_`, `/`, `-`; must not start with `/` or `.`, must not contain `..` |
| `contentType` | string | yes | — | File MIME type, e.g. `video/mp4` or `application/zip` |
| `totalSize` | number | yes | — | Total file size in bytes; positive integer; no more than 5 TB (5,497,558,138,880 bytes) |
| `partSize` | number | no | `8388608` | Size of one part in bytes; from 5 MB (5,242,880) to 5 GB (5,368,709,120); the last part may be smaller than the minimum |
| `visibility` | string | no | `PRIVATE` | Object visibility: `PRIVATE` or `PUBLIC` |

## Examples

### curl — personal key

```bash
curl -X POST https://vibecode.bitrix24.com/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 application

```bash
curl -X POST https://vibecode.bitrix24.com/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 — personal key

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

// Step 1 — open a multipart upload session
const createRes = await fetch(
  'https://vibecode.bitrix24.com/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()

// Step 2 — upload the parts in parallel, save the ETag from the response headers
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 }
  })
)

// Step 3 — finalize object assembly
const completeRes = await fetch(
  'https://vibecode.bitrix24.com/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 application

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

const createRes = await fetch(
  'https://vibecode.bitrix24.com/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.com/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()
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `objectId` | string | Object identifier in storage |
| `uploadId` | string | Multipart upload session identifier |
| `partCount` | number | Number of parts, computed from `totalSize` and `partSize` |
| `parts` | array | Array of part descriptors |
| `parts[].partNumber` | number | Part number (starting from 1) |
| `parts[].uploadUrl` | string | Presigned URL for uploading the part via the `PUT` method |
| `parts[].expiresAt` | string | URL expiry (ISO 8601); matches the session lifetime — 24 hours |

## Response example

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

## Error response example

413 — the file exceeds the 5 TB limit:

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

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 403 | `STORAGE_SCOPE_REQUIRED` | The API key lacks the `vibe:storage` scope |
| 401 | `STORAGE_NO_AUTH_CONTEXT` | The request was made without authorization |
| 400 | `STORAGE_INVALID_PATH` | The caller identifier contains invalid path characters |
| 400 | `STORAGE_KEY_REQUIRED` | The `key` field was not provided |
| 400 | `STORAGE_INVALID_KEY` | The `key` value violates the format rules |
| 400 | `STORAGE_CONTENT_TYPE_REQUIRED` | The `contentType` field was not provided |
| 400 | `STORAGE_INVALID_VISIBILITY` | Invalid `visibility` value |
| 400 | `STORAGE_INVALID_TOTAL_SIZE` | `totalSize` is not a positive integer |
| 400 | `STORAGE_INVALID_PART_SIZE` | `partSize` is outside the allowed range (5 MB – 5 GB) or is not a positive integer |
| 413 | `STORAGE_OBJECT_TOO_LARGE` | `totalSize` exceeds 5 TB |
| 415 | `STORAGE_FORBIDDEN_CONTENT_TYPE` | For `PUBLIC` objects the types `text/html`, `application/javascript`, `application/x-javascript`, `image/svg+xml` are forbidden — [Error codes](/docs/errors) |
| 400 | `STORAGE_TOO_MANY_PARTS` | The computed number of parts exceeds 10,000 — increase `partSize` |
| 402 | `BILLING_INSUFFICIENT` | Insufficient balance |
| 503 | `STORAGE_FEATURE_DISABLED` | Storage is disabled for the portal |
| 503 | `STORAGE_STS_UNAVAILABLE` | The temporary credentials issuance service is unavailable |
| 502 | `STORAGE_BUCKET_ERROR` | Error while interacting with storage |

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

## Known specifics

**Presigned URLs for parts do not require authorization headers.** `PUT` requests to these URLs are sent directly to storage without `X-Api-Key` or `Authorization` — all necessary credentials are already signed into the URL itself.

**Session lifetime — 24 hours.** After it expires, the presigned URLs become invalid and the unuploaded parts are deleted automatically. To free resources early, call [`POST /v1/storage/objects/multipart/abort`](/docs/storage/upload/multipart-abort).

**Smaller files.** For files up to 10 MB use [direct upload](/docs/storage/upload/direct) (Path A). For files up to 5 GB — a [presigned URL](/docs/storage/upload/presigned) (Path B).

## See also

- [Upload](/docs/storage/upload)
- [Complete multipart upload](/docs/storage/upload/multipart-complete)
- [Abort multipart upload](/docs/storage/upload/multipart-abort)
- [Direct file upload](/docs/storage/upload/direct)
- [Storage](/docs/storage)
