For AI agents: markdown of this page — /docs-content-en/storage/upload.md documentation index — /llms.txt
Upload
Three file upload paths into storage: a direct form for small files, a presigned PUT for browser uploads, and multipart upload for large files.
Scope: vibe:storage | Base URL: https://vibecode.bitrix24.com/v1 | Authorization: API key (X-Api-Key)
Direct file upload
POST /v1/storage/objects/upload
Uploads a file up to 10 MB in a single multipart/form-data request — the server accepts the file and returns the resulting storage object.
Uploading again under the same key replaces the object content. This also applies to a soft-deleted object when it belongs to the same owner tuple, has COMPLETED status, and has no open multipart session. The row is revived with the same object.id, createdAt, key, physical address, owner tuple, and visibility; deletedAt is cleared, while sizeBytes, sha256, contentType, and contentUpdatedAt are updated to match the new bytes.
This does not restore the deleted content: Path A writes new bytes over the old ones. For a PUBLIC object, a previously issued anonymous URL with the same object.id starts working again and serves the new bytes. To replace or revive an object, omit visibility or pass the stored value — visibility cannot change (STORAGE_VISIBILITY_MISMATCH). Deleted PENDING rows and rows with a multipart session stay occupied and return 409 STORAGE_KEY_DELETED.
Replacement and revival work the same way for an app-bound key and for a personal developer key: uploading again under the same key does not create a second object.
For Path A direct uploads, concurrent requests to the same physical address run one at a time. A request waits for its turn for no more than 60 seconds; if safe write ownership cannot be confirmed, the server returns 409 STORAGE_KEY_CONFLICT before writing to object storage. Retry the entire request. The guarantee also covers a personal key (appId = null). Path C serializes every key, but replaces a live object only for an app-bound key; it accepts a personal key after explicit DELETE. Path B is excluded.
Request fields (body)
The request body must be in multipart/form-data format.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file |
file | yes | — | Binary file no larger than 10 MB. The MIME type is determined from the Content-Type header of the form part. If the header is absent, application/octet-stream is used |
key |
string | yes | — | Logical object key: 1 to 1024 characters. Allowed characters: a-z, A-Z, 0-9, ., _, /, -. Must not start with / or . and must not contain .. |
visibility |
string | no | PRIVATE |
Object visibility: PRIVATE or PUBLIC. On a new object an omitted field means PRIVATE. When replacing or reviving an existing object visibility does not change: omit the field or pass the stored value |
Examples
curl — personal key
curl -X POST https://vibecode.bitrix24.com/v1/storage/objects/upload \
-H "X-Api-Key: YOUR_API_KEY" \
-F "file=@avatar.png;type=image/png" \
-F "key=users/42/avatar.png" \
-F "visibility=PRIVATE"
curl — OAuth application
curl -X POST https://vibecode.bitrix24.com/v1/storage/objects/upload \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-F "file=@avatar.png;type=image/png" \
-F "key=users/42/avatar.png" \
-F "visibility=PRIVATE"
JavaScript — personal key
const formData = new FormData()
formData.append('file', fileInput.files[0])
formData.append('key', 'users/42/avatar.png')
formData.append('visibility', 'PRIVATE')
const res = await fetch('https://vibecode.bitrix24.com/v1/storage/objects/upload', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
},
body: formData,
})
if (!res.ok) {
const { error } = await res.json()
console.error(error.code, error.message)
return
}
const { object } = await res.json()
console.log('Object ID:', object.id)
JavaScript — OAuth application
const formData = new FormData()
formData.append('file', fileInput.files[0])
formData.append('key', 'users/42/avatar.png')
formData.append('visibility', 'PRIVATE')
const res = await fetch('https://vibecode.bitrix24.com/v1/storage/objects/upload', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
body: formData,
})
const { object } = await res.json()
Response fields
| Field | Type | Description |
|---|---|---|
object.id |
string | Object identifier |
object.key |
string | Logical object key |
object.contentType |
string | File MIME type |
object.sizeBytes |
string | File size in bytes (string) |
object.sha256 |
string | File SHA-256 hash. Always computed on direct upload |
object.visibility |
string | Visibility: PRIVATE or PUBLIC |
object.uploadStatus |
string | Upload status: COMPLETED |
object.createdAt |
string | Creation date (ISO 8601) |
object.deletedAt |
string | Deletion date or null |
object.contentUpdatedAt |
string (ISO 8601) | When the content last became current: a replacement, or the first completion of the upload. Service updates do not move it. null for objects last written before this field existed |
Response example
{
"object": {
"id": "cmpf9b1gp003omr0zkc0qjojn",
"key": "users/42/avatar.png",
"contentType": "image/png",
"sizeBytes": "20480",
"sha256": "10068f1908d45621f11b50a1d23ddc0720a669c15f45a43d883604e7793537cd",
"visibility": "PRIVATE",
"uploadStatus": "COMPLETED",
"createdAt": "2026-05-21T08:56:32.809Z",
"deletedAt": null
}
}
Error response example
415 — the request passed a MIME type that is not allowed for PUBLIC objects because of cross-site scripting risks:
{
"success": false,
"error": {
"code": "STORAGE_FORBIDDEN_CONTENT_TYPE",
"message": "Content-Type text/html is not allowed for PUBLIC objects (XSS-prone)"
}
}
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_FILE_REQUIRED |
The file field was not provided |
| 400 | STORAGE_KEY_REQUIRED |
The key field was not provided |
| 400 | STORAGE_INVALID_KEY |
The key value violates the format rules |
| 400 | STORAGE_INVALID_VISIBILITY |
Invalid visibility value |
| 400 | STORAGE_VISIBILITY_MISMATCH |
Replacing content cannot change object visibility. Omit the field or pass the current value — it is named in the error message |
| 409 | STORAGE_KEY_DELETED |
The deleted row is not eligible for Path A revival: it is not a COMPLETED object without a multipart session. An eligible deleted object returns 200 with the same id and visibility instead |
| 409 | STORAGE_MULTIPART_IN_PROGRESS |
The object has a multipart upload in progress — complete or abort it |
| 409 | STORAGE_UPLOAD_PENDING |
A presigned URL was issued for this key and the upload is not confirmed yet |
| 409 | STORAGE_KEY_OWNED_ELSEWHERE |
The name is taken by an object of the other ownership kind (app-shared vs per-employee). Another employee does not block the same name |
| 409 | STORAGE_KEY_CONFLICT |
The object state changed, or a concurrent direct upload (Path A) did not obtain write ownership within 60 seconds or the concurrency limit. Object storage is untouched until confirmation; retry the entire request. Serialization also covers a personal key (appId = null). Path C serializes every key, but replaces a live object only for an app-bound key; it accepts a personal key after explicit DELETE. Path B is excluded |
| 400 | STORAGE_MULTIPART_PARSE_FAILED |
Failed to parse the request body as multipart/form-data |
| 413 | STORAGE_UPLOAD_TOO_LARGE |
The file exceeds 10 MB — use multipart upload (path B is temporarily disabled) |
| 415 | STORAGE_FORBIDDEN_CONTENT_TYPE |
For PUBLIC objects the types text/html, application/javascript, application/x-javascript, image/svg+xml are forbidden |
| 402 | BILLING_INSUFFICIENT |
Insufficient balance |
| 503 | STORAGE_FEATURE_DISABLED |
Storage is disabled for the portal |
| 502 | STORAGE_BUCKET_ERROR |
Error while interacting with storage |
Full list of common API errors — Errors.
Known specifics
Files larger than 10 MB. Path B is temporarily disabled and answers 503 STORAGE_PRESIGNED_UPLOAD_DISABLED, so upload anything above 10 MB with multipart upload (Path C).
Type check for PUBLIC objects. The check runs against the declared Content-Type of the form part before the file is written — if it fails, the request is rejected without accessing storage.