For AI agents: markdown of this page — /docs-content-en/storage/upload/multipart-create.md documentation index — /llms.txt
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 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
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
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
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
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
{
"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:
{
"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 |
| 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.
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.
Smaller files. For files up to 10 MB use direct upload (Path A). For files up to 5 GB — a presigned URL (Path B).