لوكلاء AI: ملف markdown لهذه الصفحة — /docs-content-en/storage.md فهرس التوثيق — /llms.txt
مقالات التوثيق متاحة حاليًا باللغة الإنجليزية فقط.
Storage
File storage for Bitrix24 apps: upload, manage visibility, and download via temporary links.
Scope: vibe:storage | Base URL: https://vibecode.bitrix24.com/v1 | Authorization: API key (X-Api-Key)
Authorization · Lifecycle · Upload paths · Visibility · Object key · Limits · Pricing · Quick start · Full example · Endpoints · Errors
When to use
Suitable for:
- User avatars and app logos
- Form attachments: photos, scans, receipts
- Exports and generated reports (CSV, PDF)
- Video attachments in CRM deals
- App configuration backups
- Intermediate files in a data-processing pipeline
Not suitable for:
- Publishing files to your Bitrix24 account's Drive — use the Drive section for that; storage is isolated per app and is not part of the Bitrix24 account's Drive tree
- Storing secrets and keys — use server environment variables
- App logging and audit
- Scenarios not tied to an app — storage is always isolated per key
Documentation sections
| Section | Description |
|---|---|
| SectionUpload | DescriptionThree upload paths: direct form (≤10 MB), presigned PUT (temporarily disabled), multipart (≤5 TB) |
| SectionObjects | DescriptionList, retrieve, metadata, delete objects, and public access |
Authorization
All requests to /v1/storage/* require an API key in the X-Api-Key header with the vibe:storage scope.
The vibe:storage scope is pre-selected in the key creation form. If a key does not have it, add the scope in the API Keys section of your Vibecode account.
Without the scope, any /v1/storage/* call returns 403 STORAGE_SCOPE_REQUIRED.
Key types
| Key type | Access | Storage area |
|---|---|---|
Key typevibe_api_* — personal API key |
Accessyes | Storage areanamespace of the key owner in the Bitrix24 account |
Key typevibe_app_* — app key |
Accessyes | Storage areanamespace of the app the key is bound to |
Management keys (vibe_live_*) are not suitable for /v1/storage/* — they have no Bitrix24 account binding. Any call returns 403 STORAGE_REQUIRES_PORTAL_BINDING.
Isolation. Objects are isolated per key owner: a key from one Bitrix24 account cannot access objects that belong to another — such a request returns 404 STORAGE_OBJECT_NOT_FOUND, not 403, so it does not reveal that another account's resources exist.
Object lifecycle
PENDING — created, awaiting file upload (first upload through Paths B and C)
│
▼ (path A → immediately COMPLETED)
COMPLETED — file uploaded and readable; app-bound Path C may stage a replacement on the same row
│
▼ (DELETE)
deletedAt — soft delete: read → 410; data retained for another 30 days
│
├──► (Path A or personal Path C after DELETE, new bytes) → COMPLETED with the same id and visibility
│
▼ (+30 days, scheduler)
deleted — data erased from storage
Direct upload (Path A) creates the object immediately in the COMPLETED state. A first upload through Paths B and C creates a PENDING row until /complete is called. During replacement of an app-bound object, Path C keeps the row COMPLETED: 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 happened but its result was not confirmed. A confirmed abort before publication preserves the old version. Automatic cleanup releases parts that are confirmed incomplete or finalizes a recorded publication of the new version, while an unconfirmed outcome retains the session for reconciliation.
An eligible soft-deleted object can be reused for new bytes: the row must have COMPLETED status, have no multipart session, and belong to the same owner tuple. Path A supports app-bound and personal keys up to 10 MB. Path C supports only a personal row after an explicit DELETE and is suitable for a large file. Both paths preserve id, createdAt, key, physical address, and visibility, and clear deletedAt only after a successful upload. This does not restore the old content. Path C is non-atomic: the object is unavailable between DELETE and successful complete. Path B is disabled and returns 503 STORAGE_PRESIGNED_UPLOAD_DISABLED.
After 24 hours, the background scheduler starts cleanup of unfinished PENDING objects. For a multipart session, the object and its 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.
Upload paths
| Path | File size | Steps | When to use |
|---|---|---|---|
| PathA — direct upload | File sizeup to 10 MB | Steps1 | When to useNew objects, live-content replacement, and revival of an eligible tombstone with the same id and visibility |
| PathB — presigned PUT | File sizeup to 5 GB | Steps3 | When to useTemporarily disabled: both steps return 503 STORAGE_PRESIGNED_UPLOAD_DISABLED without a URL. Does not revive deleted objects |
| PathC — multipart upload | File sizeup to 5 TB | Steps3 + N parts | When to useLarge new files, replacement of a live app-bound object, and a new personal-key upload after explicit DELETE. The last operation is non-atomic: the object is unavailable until complete |
For Path A direct uploads and all Path C 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 write ownership cannot be safely obtained, the server returns 409 STORAGE_KEY_CONFLICT. For Path A, this conflict occurs before writing to object storage. Retry the entire request. On Path C, a 409 from complete or abort can occur after parts already occupy storage; unfinished parts are released by a confirmed abort, while a journalled complete response means the provider has already assembled the object. Path A also covers a personal key (appId = null); Path C replaces a live object only for an app-bound key and accepts a personal key after explicit DELETE. Path B is excluded entirely.
A detailed description of each path — Upload.
Visibility and access
Each object has a visibility attribute:
PRIVATE(default) — download via an authorizedGET /v1/storage/objects/:keyrequest, which returns a 302 redirect to a presigned URL valid for 10 minutes.PUBLIC— after the upload is completed (COMPLETED), additionally available via a permanent URL of the formhttps://vibecode.bitrix24.com/v1/public-storage/:portalId/:objectIdwithout authorization. While the object isPENDING, the public URL returns404 STORAGE_OBJECT_NOT_FOUND.
For PUBLIC objects, serving files with content types text/html, application/javascript, application/x-javascript, image/svg+xml is blocked. An attempt to upload a file with such a type and visibility=PUBLIC returns 415 STORAGE_FORBIDDEN_CONTENT_TYPE.
Anonymous access via public-storage must be enabled for your Bitrix24 account and is off by default. While it is off, requests to the public URL return 503 STORAGE_PUBLIC_GET_DISABLED_FOR_PORTAL.
A permanent source for external tools. The presigned URL from GET /v1/storage/objects/:key is valid for 10 minutes and must be requested with an X-Api-Key header, so it does not work as a permanent source for tools that do not send authorization headers — spreadsheet data imports, report schedulers, external dashboards. For that scenario, complete the upload of a PUBLIC object and use its permanent URL https://vibecode.bitrix24.com/v1/public-storage/:portalId/:objectId, which is served without authorization while anonymous access is enabled for the Bitrix24 account. For PUBLIC reports, content types such as text/csv and application/json are suitable. The types text/html, application/javascript, and image/svg+xml are blocked.
Object key
The object key is the logical name of a file. It is unique per OWNER: the personal namespace of the key owner, the app-shared namespace, or an employee's personal namespace in the Bitrix24 account. So two employees of one app claim the same name independently of each other and get two distinct objects, while an app-shared file and a per-employee file share one namespace and cannot both hold one name. It follows that a name does NOT address an object unambiguously: use the object identifier for exact access. Format rules:
- Length: 1–1024 characters.
- Allowed characters:
a-z,A-Z,0-9,.,_,/,-. - Cannot start with
/or.. - Cannot contain the sequence
... - The
/character is a path separator, used for grouping by prefix.
In URL path parameters, slashes in the key must be encoded: encodeURIComponent('users/42/report.csv') returns 'users%2F42%2Freport.csv'.
Source code snapshots. The platform saves an app's sources into this same storage — they appear in GET /v1/storage/objects under the source/ prefix. A personal key sees all of its owner's objects in the Bitrix24 account. An app key without a user token lists only the app's shared objects, while a key with a user token lists only the objects of the corresponding Bitrix24 employee. Snapshots are removed through the source operations: DELETE /v1/infra/servers/:id/sources/:versionId for a single version and POST /v1/infra/servers/:id/sources/cleanup for bulk cleanup, with the same operations under the /v1/apps/:id/sources prefix for apps. What lives under that prefix and why the same key appears in the listing several times — Source code storage.
Limits
| Parameter | Value |
|---|---|
| ParameterMaximum file size (path A) | Value10 MB |
| ParameterMaximum file size (path B, temporarily disabled) | Value5 GB |
| ParameterMaximum file size (path C) | Value5 TB |
| ParameterObject key length | Value1–1024 characters |
| ParameterPresigned download URL validity | Value10 minutes |
| ParameterSoft-delete period | Value30 days |
| ParameterObjects in the PENDING state | Valueafter 24 hours the scheduler starts cleanup; multipart deletion follows only a confirmed abort |
| ParameterPublic URL request limit | Value60 requests/min from a single IP, platform-wide. The exact value arrives in the x-ratelimit-limit header (the cap is divided across replicas) |
Pricing
Storage is billed on a pay-as-you-go basis. Charges apply to three resources.
| Resource | Unit | Field in GET /v1/me |
|---|---|---|
| ResourceStorage | UnitꝞ per GB-month | Field in GET /v1/mecostRateGbMonthVibes |
| ResourceOutbound traffic | UnitꝞ per GB | Field in GET /v1/mecostRateGbEgressVibes |
| ResourceWrite operations | UnitꝞ per 1,000 operations | Field in GET /v1/mecostRateOps1kVibes |
Rates are set by the platform and differ from instance to instance. The current values are returned in the storage block of the GET /v1/me response.
When the balance is zero, write requests are blocked for 24 hours. Reading objects continues to work. Topping up the balance unblocks writing immediately.
Current usage and a cost forecast are in the storage block of the GET /v1/me response. The Bitrix24 account owner also sees aggregate spending across apps and can export a report on the Storage page in the admin section of the Vibecode account.
Use in AI agents
Storage is available to AI models and agents without reading this page:
- The
storageblock in theGET /v1/meresponse — storage state, rates, upload paths, and the list of endpoints. - The MCP server's
vibe_storage_*tools (upload, presigned links, list, delete, spending control) — see MCP for AI.
Quick start
Upload a file (path A):
curl -X POST https://vibecode.bitrix24.com/v1/storage/objects/upload \
-H "X-Api-Key: YOUR_API_KEY" \
-F "key=reports/march.csv" \
-F "visibility=PRIVATE" \
-F "file=@march.csv;type=text/csv"
Get a download link:
curl -I \
-H "X-Api-Key: YOUR_API_KEY" \
"https://vibecode.bitrix24.com/v1/storage/objects/reports%2Fmarch.csv"
# HTTP/1.1 302 Found
# Location: <temporary link to the object, valid for 10 minutes>
Full example
Full cycle: upload → list → get link → delete.
const KEY = 'YOUR_API_KEY'
const BASE = 'https://vibecode.bitrix24.com/v1'
const form = new FormData()
form.append('key', 'users/42/report.csv')
form.append('visibility', 'PRIVATE')
form.append('file', new Blob(['id,name\n1,Alice\n2,Bob'], { type: 'text/csv' }), 'report.csv')
const uploadRes = await fetch(`${BASE}/storage/objects/upload`, {
method: 'POST',
headers: { 'X-Api-Key': KEY },
body: form,
})
const { object } = await uploadRes.json()
console.log('Uploaded:', object.key, '| uploadStatus:', object.uploadStatus)
const listRes = await fetch(`${BASE}/storage/objects?prefix=users/42&withTotal=true`, {
headers: { 'X-Api-Key': KEY },
})
const { data, total } = await listRes.json()
console.log('Objects:', total, '| first:', data[0]?.key)
const getRes = await fetch(`${BASE}/storage/objects/${encodeURIComponent(object.key)}`, {
headers: { 'X-Api-Key': KEY },
redirect: 'manual',
})
const downloadUrl = getRes.headers.get('location')
console.log('Link (10 min):', downloadUrl)
const delRes = await fetch(`${BASE}/storage/objects/${encodeURIComponent(object.key)}`, {
method: 'DELETE',
headers: { 'X-Api-Key': KEY },
})
const { object: deleted } = await delRes.json()
console.log('Deleted:', deleted.key, '| deletedAt:', deleted.deletedAt)
Common scenarios
Large file via multipart upload
Uploading a large file in parts with cancellation on error (to avoid paying for unfinished parts):
const KEY = 'YOUR_API_KEY'
const BASE = 'https://vibecode.bitrix24.com/v1'
const PART_SIZE = 8 * 1024 * 1024 // 8 MB
// file — File or Blob, e.g. from <input type="file">
const init = await fetch(`${BASE}/storage/objects/multipart/create`, {
method: 'POST',
headers: { 'X-Api-Key': KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
key: 'videos/demo.mp4',
contentType: 'video/mp4',
totalSize: file.size,
partSize: PART_SIZE,
visibility: 'PRIVATE',
}),
})
const { objectId, parts } = await init.json()
try {
const uploaded = []
for (const part of parts) {
const chunk = file.slice((part.partNumber - 1) * PART_SIZE, part.partNumber * PART_SIZE)
const res = await fetch(part.uploadUrl, { method: 'PUT', body: chunk })
uploaded.push({ partNumber: part.partNumber, etag: res.headers.get('etag') })
}
const done = await fetch(`${BASE}/storage/objects/multipart/complete`, {
method: 'POST',
headers: { 'X-Api-Key': KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ objectId, parts: uploaded }),
})
const { object } = await done.json()
console.log('Uploaded:', object.key, object.sizeBytes)
} catch (err) {
await fetch(`${BASE}/storage/objects/multipart/abort`, {
method: 'POST',
headers: { 'X-Api-Key': KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ objectId }),
})
throw err
}
Bulk cleanup by prefix
Deleting all of a user's files when that user is deactivated: paginated iteration by prefix and deletion of each object.
const KEY = 'YOUR_API_KEY'
const BASE = 'https://vibecode.bitrix24.com/v1'
async function deleteByPrefix(prefix) {
let cursor = null
let removed = 0
while (true) {
const url = new URL(`${BASE}/storage/objects`)
url.searchParams.set('prefix', prefix)
url.searchParams.set('limit', '500')
if (cursor !== null) url.searchParams.set('cursor', cursor)
const page = await fetch(url, { headers: { 'X-Api-Key': KEY } })
const { data, cursor: next } = await page.json()
for (const obj of data) {
await fetch(`${BASE}/storage/objects/${encodeURIComponent(obj.key)}`, {
method: 'DELETE',
headers: { 'X-Api-Key': KEY },
})
removed++
}
if (next === null) return removed
cursor = next
}
}
const count = await deleteByPrefix('users/42/')
console.log('Objects deleted:', count)
Endpoint reference
Upload
| Method | Path | Description |
|---|---|---|
MethodPOST |
Path/v1/storage/objects/upload |
DescriptionDirect upload of a file up to 10 MB in a single multipart/form-data request |
MethodPOST |
Path/v1/storage/objects |
DescriptionCreate a presigned URL to upload a file up to 5 GB directly to storage. Temporarily disabled |
MethodPOST |
Path/v1/storage/objects/complete |
DescriptionConfirm completion of a presigned-URL upload. Temporarily disabled |
MethodPOST |
Path/v1/storage/objects/multipart/create |
DescriptionInitiate a multipart upload: obtain a session identifier and URLs for the parts |
MethodPOST |
Path/v1/storage/objects/multipart/complete |
DescriptionAssemble the object from uploaded parts by their partNumber and etag |
MethodPOST |
Path/v1/storage/objects/multipart/abort |
DescriptionCancel a multipart upload and remove unfinished parts |
Objects
| Method | Path | Description |
|---|---|---|
MethodGET |
Path/v1/storage/objects |
DescriptionList objects with prefix filtering, an opaque cursor, and exact counting only with withTotal=true |
MethodGET |
Path/v1/storage/objects/:key |
DescriptionDownload an object (302 redirect → presigned URL) |
MethodHEAD |
Path/v1/storage/objects/:key |
DescriptionObject metadata without a response body |
MethodDELETE |
Path/v1/storage/objects/:key |
DescriptionSoft-delete an object |
Public access
| Method | Path | Description |
|---|---|---|
MethodGET |
Path/v1/public-storage/:portalId/:objectId |
DescriptionDownload a completed PUBLIC object without an API key |
MethodHEAD |
Path/v1/public-storage/:portalId/:objectId |
DescriptionMetadata of a completed PUBLIC object without an API key |
Error codes
| Code | HTTP | When it occurs |
|---|---|---|
CodeSTORAGE_SCOPE_REQUIRED |
HTTP403 | When it occursAPI key does not have the vibe:storage scope |
CodeSTORAGE_NO_AUTH_CONTEXT |
HTTP401 | When it occursRequest without usable app credentials |
CodeSTORAGE_KEY_REQUIRED |
HTTP400 | When it occursThe key field is not passed or is empty |
CodeSTORAGE_INVALID_KEY |
HTTP400 | When it occursThe key contains invalid characters, starts with / or ., or contains .. |
CodeSTORAGE_INVALID_PATH |
HTTP400 | When it occursThe key forms a path injection |
CodeSTORAGE_CONTENT_TYPE_REQUIRED |
HTTP400 | When it occursThe contentType field is not passed (paths B and C) |
CodeSTORAGE_INVALID_VISIBILITY |
HTTP400 | When it occursvisibility is not equal to PUBLIC or PRIVATE |
CodeSTORAGE_INVALID_SIZE |
HTTP400 | When it occurssizeBytes is not a non-negative integer |
CodeSTORAGE_INVALID_TTL |
HTTP400 | When it occursttlSeconds is outside the 60–86400 range |
CodeSTORAGE_FILE_REQUIRED |
HTTP400 | When it occursThe file part of the form is not passed (path A) |
CodeSTORAGE_MULTIPART_PARSE_FAILED |
HTTP400 | When it occursFailed to parse multipart/form-data |
CodeSTORAGE_OBJECT_ID_REQUIRED |
HTTP400 | When it occursThe objectId parameter is not passed |
CodeSTORAGE_INVALID_TOTAL_SIZE |
HTTP400 | When it occurstotalSize is not a positive integer (path C) |
CodeSTORAGE_INVALID_PART_SIZE |
HTTP400 | When it occurspartSize is not a positive integer (path C) |
CodeSTORAGE_PARTS_REQUIRED |
HTTP400 | When it occursThe parts array is not passed or is empty |
CodeSTORAGE_INVALID_PART |
HTTP400 | When it occursAn element of the parts array does not match the { partNumber, etag } format |
CodeSTORAGE_INVALID_PARTS |
HTTP400 | When it occursThe set of parts was rejected by storage when assembling the multipart upload |
CodeSTORAGE_TOO_MANY_PARTS |
HTTP400 | When it occursToo many parts for a multipart upload; increase partSize |
CodeSTORAGE_INVALID_LIMIT |
HTTP400 | When it occurslimit is not a positive integer |
CodeSTORAGE_INVALID_CURSOR |
HTTP400 | When it occursA new opaque cursor is corrupted, modified, oversized, or belongs to another owner, prefix, or ordering mode; a legacy CUID cursor has an invalid shape, is oversized, or is used with prefix |
CodeSTORAGE_UPLOAD_TOO_LARGE |
HTTP413 | When it occursFile size exceeds 10 MB (path A) |
CodeSTORAGE_OBJECT_TOO_LARGE |
HTTP413 | When it occurstotalSize exceeds 5 TB (path C) |
CodeSTORAGE_FORBIDDEN_CONTENT_TYPE |
HTTP415 | When it occursContent type is not allowed for PUBLIC objects |
CodeSTORAGE_OBJECT_NOT_FOUND |
HTTP404 | When it occursObject not found or does not belong to the calling app; public GET/HEAD also returns this code for PENDING and PRIVATE objects |
CodeSTORAGE_UPLOAD_NOT_PENDING |
HTTP409 | When it occursThe object is already uploaded or is in an invalid state |
CodeSTORAGE_UPLOAD_PENDING |
HTTP409 | When it occursThe object upload is not complete: on a read the object is not ready yet; on an upload the key holds an unfinished presigned reservation |
CodeSTORAGE_UPLOAD_NOT_FOUND_IN_BUCKET |
HTTP409 | When it occursThe PUT request to the presigned URL was never made |
CodeSTORAGE_MULTIPART_IN_PROGRESS |
HTTP409 | When it occursThe object has an unfinished multipart upload; call /multipart/abort — it cannot be replaced or deleted until then |
CodeSTORAGE_KEY_DELETED |
HTTP409 | When it occursThis path cannot reuse the deleted key. Path A revives only a COMPLETED object without a multipart session, preserving its id and visibility; Path B currently returns 503 without a URL; Path C accepts only an eligible personal-key COMPLETED tombstone after explicit DELETE, while other states remain 409 |
CodeSTORAGE_KEY_OWNED_ELSEWHERE |
HTTP409 | When it occursThe name is taken by an object of the other ownership kind. Employees of an app claim names independently of each other, while an app-shared file and a per-employee file share one namespace |
CodeSTORAGE_KEY_EXISTS |
HTTP409 | When it occursThe object already exists. Replacing content through a presigned URL is not supported; Path B is temporarily disabled with 503 STORAGE_PRESIGNED_UPLOAD_DISABLED. Use Path A up to 10 MB; for a larger app-bound file use Path C. For a live personal object, DELETE first and then start Path C. This is non-atomic: the object is unavailable until complete succeeds |
CodeSTORAGE_KEY_CONFLICT |
HTTP409 | When it occursThe object is in a conflicting state, or a concurrent Path A or Path C upload did not obtain write ownership within 60 seconds or the concurrency limit. For Path A, object storage is untouched until confirmation; retry the entire request. On Path C, a 409 from complete or abort can arrive after parts were uploaded: unfinished parts occupy storage until a confirmed abort, while a journalled complete means the provider has already assembled the object. Serialization also covers a personal key (appId = null); Path B is excluded |
CodeSTORAGE_VISIBILITY_MISMATCH |
HTTP400 | When it occursReplacing content cannot change object visibility. Omit the visibility field or pass the current value |
CodeSTORAGE_OBJECT_DELETED |
HTTP410 | When it occursThe object is marked as deleted. Without an eligible subsequent upload, reads and a repeated DELETE stay 410 until physical purge |
CodeBILLING_INSUFFICIENT |
HTTP402 | When it occursPortal balance is zero — writing is paused for 24 hours (reading works) |
CodeSTORAGE_QUOTA_EXCEEDED |
HTTP507 | When it occursThe write exceeds the portal storage volume cap. 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. Usage is counted across the whole portal, not per app. Path A rejects the write when the file size does not fit the remaining cap, Path C on the declared totalSize at /multipart/create, before any part is uploaded. The response message names the cap in effect |
CodeSTORAGE_RATE_LIMIT_EXCEEDED |
HTTP429 | When it occursExceeded the limit of 60 requests/min to the public URL from a single IP. The exact value arrives in the x-ratelimit-limit header (the cap is divided across replicas) |
CodeSTORAGE_PUBLIC_GET_DISABLED_FOR_PORTAL |
HTTP503 | When it occursPublic access is disabled at the portal level |
CodeSTORAGE_FEATURE_DISABLED |
HTTP503 | When it occursStorage is temporarily disabled |
CodeSTORAGE_PRESIGNED_UPLOAD_DISABLED |
HTTP503 | When it occursPath B is temporarily disabled. Use direct upload (Path A) or multipart upload (Path C) |
CodeSTORAGE_STS_UNAVAILABLE |
HTTP503 | When it occursThe temporary-credentials issuance service is unavailable |
CodeSTORAGE_BUCKET_ERROR |
HTTP502 | When it occursError accessing the object storage |
CodeSTORAGE_OBJECT_MISSING_IN_BUCKET |
HTTP502 | When it occursThe object is recorded in the database but absent from storage |
CodeSTORAGE_OBJECT_STREAM_FAILED |
HTTP502 | When it occursError while transferring object data |
CodeSTORAGE_OBJECT_HEAD_FAILED |
HTTP502 | When it occursError while retrieving object metadata from storage |
General API error codes — Error codes.