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.

On an unused key, Path C creates a new object. It replaces, in place, a live app-bound COMPLETED object owned by the same tuple: the response returns the existing objectId, while reads return the old content until /multipart/complete publishes the new bytes. A successful complete response confirms the new version; 502 STORAGE_BUCKET_ERROR can mean publication without a confirmed result. A confirmed abort before publication preserves the old content.

Path C does not replace a live object owned by a personal key (appId = null). Delete the object with DELETE /v1/storage/objects/{key} first, then start the multipart upload again under the same key. This operation is non-atomic: the object is unavailable between deletion and successful /multipart/complete. An eligible deleted personal-key COMPLETED row is reused with its existing objectId and visibility; other deleted states return 409 STORAGE_KEY_DELETED.

Request fields (body)

The request body is JSON.

Parameter Type Required Default Description
Parameterkey Typestring Requiredyes Default— DescriptionLogical object key: from 1 to 1024 characters, allowed a-z, A-Z, 0-9, ., _, /, -; must not start with / or ., must not contain ..
ParametercontentType Typestring Requiredyes Default— DescriptionFile MIME type, e.g. video/mp4 or application/zip
ParametertotalSize Typenumber Requiredyes Default— DescriptionTotal file size in bytes; positive integer; no more than 5 TB (5,497,558,138,880 bytes)
ParameterpartSize Typenumber Requiredno Default8388608 DescriptionSize 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
Parametervisibility Typestring Requiredno DefaultPRIVATE for a new object DescriptionObject visibility: PRIVATE or PUBLIC. On replacement, omit the field or pass the stored value

Examples

curl — personal key

Terminal
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

Terminal
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
FieldobjectId Typestring DescriptionObject identifier in storage
FielduploadId Typestring DescriptionMultipart upload session identifier
FieldpartCount Typenumber DescriptionNumber of parts, computed from totalSize and partSize
Fieldparts Typearray DescriptionArray of part descriptors
Fieldparts[].partNumber Typenumber DescriptionPart number (starting from 1)
Fieldparts[].uploadUrl Typestring DescriptionPresigned URL for uploading the part via PUT; its signature binds the exact size of this part, including the final remainder
Fieldparts[].expiresAt Typestring DescriptionPart URL expiry (ISO 8601), taken from the URL signature. May be shorter than the 24-hour session

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
HTTP403 CodeSTORAGE_SCOPE_REQUIRED DescriptionThe API key lacks the vibe:storage scope
HTTP409 CodeSTORAGE_KEY_EXISTS DescriptionA personal-key (appId = null) address already has a live object. Delete the object, then start the upload again. The operation is non-atomic: the new object is unavailable until completion succeeds
HTTP409 CodeSTORAGE_KEY_DELETED DescriptionThe deleted row is not eligible for a Path C upload. Only a personal-key COMPLETED row with no active multipart session can be reused
HTTP409 CodeSTORAGE_MULTIPART_IN_PROGRESS DescriptionA multipart upload for this key is already in progress — complete or abort it
HTTP409 CodeSTORAGE_UPLOAD_PENDING DescriptionThe key holds an unfinished presigned reservation
HTTP409 CodeSTORAGE_KEY_OWNED_ELSEWHERE DescriptionThe name is taken by an object of the other ownership kind (app-shared vs per-employee). Another employee does not block the same name
HTTP409 CodeSTORAGE_KEY_CONFLICT DescriptionRetryable conflict: a concurrent write owns the address, or the object changed while the session was being created. Retry the same create operation
HTTP401 CodeSTORAGE_NO_AUTH_CONTEXT DescriptionThe request was made without authorization
HTTP400 CodeSTORAGE_INVALID_PATH DescriptionThe caller identifier contains invalid path characters
HTTP400 CodeSTORAGE_KEY_REQUIRED DescriptionThe key field was not provided
HTTP400 CodeSTORAGE_INVALID_KEY DescriptionThe key value violates the format rules
HTTP400 CodeSTORAGE_CONTENT_TYPE_REQUIRED DescriptionThe contentType field was not provided
HTTP400 CodeSTORAGE_INVALID_CONTENT_TYPE DescriptioncontentType is not a single type/subtype media type with optional ;name=value parameters, for example it is a comma-separated list
HTTP400 CodeSTORAGE_INVALID_VISIBILITY DescriptionInvalid visibility value
HTTP400 CodeSTORAGE_VISIBILITY_MISMATCH DescriptionA replacement cannot change object visibility — omit visibility or pass the stored value
HTTP400 CodeSTORAGE_INVALID_TOTAL_SIZE DescriptiontotalSize is not a positive integer
HTTP400 CodeSTORAGE_INVALID_PART_SIZE DescriptionpartSize is outside the allowed range (5 MB – 5 GB) or is not a positive integer
HTTP413 CodeSTORAGE_OBJECT_TOO_LARGE DescriptiontotalSize exceeds 5 TB
HTTP415 CodeSTORAGE_FORBIDDEN_CONTENT_TYPE DescriptionFor PUBLIC objects the types a browser renders as a page, runs or sniffs are forbidden: HTML and XHTML, SVG, JavaScript, any XML (*/xml, *+xml), XSL, multipart/*, message/*, unknown/unknown, application/unknown, */*, with or without parameters — Error codes
HTTP400 CodeSTORAGE_TOO_MANY_PARTS DescriptionThe computed number of parts exceeds 10,000 — increase partSize
HTTP402 CodeBILLING_INSUFFICIENT DescriptionInsufficient balance
HTTP507 CodeSTORAGE_QUOTA_EXCEEDED DescriptionThe declared totalSize does not fit the remaining portal storage volume cap. The refusal is returned before the session is created, so no parts are uploaded. The cap is optional, is switched on by the platform, and applies only to portals that have no commercial Bitrix24 plan and never had one
HTTP503 CodeSTORAGE_FEATURE_DISABLED DescriptionStorage is disabled for the portal
HTTP503 CodeSTORAGE_STS_UNAVAILABLE DescriptionThe temporary credentials issuance service is unavailable
HTTP502 CodeSTORAGE_BUCKET_ERROR DescriptionError 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.

Automatic cleanup — after 24 hours. After 24 hours, the scheduler attempts to abort an incomplete session. Unuploaded parts are deleted only after the provider confirms the abort. If the abort is not confirmed, the session is retained and releasing it requires another abort attempt or operator action. Each presigned part URL has its own lifetime: it is taken from the URL signature, returned in parts[].expiresAt and may be shorter than 24 hours. Upload parts as early as you can, going by parts[].expiresAt, not by the automatic-cleanup deadline. To free resources early, call POST /v1/storage/objects/multipart/abort.

Publishing the new version. During a multipart replacement, reads return the previous version until /multipart/complete publishes the new bytes. A successful complete response confirms the new version under the same objectId; 502 STORAGE_BUCKET_ERROR can mean publication happened without a confirmed result. A confirmed /multipart/abort before publication preserves the previous version. Automatic cleanup releases parts that are confirmed incomplete or finalizes a recorded publication of the new version; an unconfirmed outcome retains the session for operator reconciliation.

Smaller files. For files up to 10 MB use direct upload (Path A). Path B is temporarily disabled and answers 503 STORAGE_PRESIGNED_UPLOAD_DISABLED, so the intermediate size range goes through multipart upload as well.

See also