Dla agentów AI: markdown tej strony — /docs-content-en/storage.md indeks dokumentacji — /llms.txt
Artykuły dokumentacji są obecnie dostępne w języku angielskim.
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 |
|---|---|
| Upload | Three upload paths: direct form (≤10 MB), presigned PUT (temporarily disabled), multipart (≤5 TB) |
| Objects | List, 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 |
|---|---|---|
vibe_api_* — personal API key |
yes | namespace of the key owner in the Bitrix24 account |
vibe_app_* — app key |
yes | namespace 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 |
|---|---|---|---|
| A — direct upload | up to 10 MB | 1 | New objects, live-content replacement, and revival of an eligible tombstone with the same id and visibility |
| B — presigned PUT | up to 5 GB | 3 | Temporarily disabled: both steps return 503 STORAGE_PRESIGNED_UPLOAD_DISABLED without a URL. Does not revive deleted objects |
| C — multipart upload | up to 5 TB | 3 + N parts | Large 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 |
|---|---|
| Maximum file size (path A) | 10 MB |
| Maximum file size (path B, temporarily disabled) | 5 GB |
| Maximum file size (path C) | 5 TB |
| Object key length | 1–1024 characters |
| Presigned download URL validity | 10 minutes |
| Soft-delete period | 30 days |
| Objects in the PENDING state | after 24 hours the scheduler starts cleanup; multipart deletion follows only a confirmed abort |
| Public URL request limit | 60 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 |
|---|---|---|
| Storage | Ꝟ per GB-month | costRateGbMonthVibes |
| Outbound traffic | Ꝟ per GB | costRateGbEgressVibes |
| Write operations | Ꝟ per 1,000 operations | costRateOps1kVibes |
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 |
|---|---|---|
POST |
/v1/storage/objects/upload |
Direct upload of a file up to 10 MB in a single multipart/form-data request |
POST |
/v1/storage/objects |
Create a presigned URL to upload a file up to 5 GB directly to storage. Temporarily disabled |
POST |
/v1/storage/objects/complete |
Confirm completion of a presigned-URL upload. Temporarily disabled |
POST |
/v1/storage/objects/multipart/create |
Initiate a multipart upload: obtain a session identifier and URLs for the parts |
POST |
/v1/storage/objects/multipart/complete |
Assemble the object from uploaded parts by their partNumber and etag |
POST |
/v1/storage/objects/multipart/abort |
Cancel a multipart upload and remove unfinished parts |
Objects
| Method | Path | Description |
|---|---|---|
GET |
/v1/storage/objects |
List objects with prefix filtering, an opaque cursor, and exact counting only with withTotal=true |
GET |
/v1/storage/objects/:key |
Download an object (302 redirect → presigned URL) |
HEAD |
/v1/storage/objects/:key |
Object metadata without a response body |
DELETE |
/v1/storage/objects/:key |
Soft-delete an object |
Public access
| Method | Path | Description |
|---|---|---|
GET |
/v1/public-storage/:portalId/:objectId |
Download a completed PUBLIC object without an API key |
HEAD |
/v1/public-storage/:portalId/:objectId |
Metadata of a completed PUBLIC object without an API key |
Error codes
| Code | HTTP | When it occurs |
|---|---|---|
STORAGE_SCOPE_REQUIRED |
403 | API key does not have the vibe:storage scope |
STORAGE_NO_AUTH_CONTEXT |
401 | Request without usable app credentials |
STORAGE_KEY_REQUIRED |
400 | The key field is not passed or is empty |
STORAGE_INVALID_KEY |
400 | The key contains invalid characters, starts with / or ., or contains .. |
STORAGE_INVALID_PATH |
400 | The key forms a path injection |
STORAGE_CONTENT_TYPE_REQUIRED |
400 | The contentType field is not passed (paths B and C) |
STORAGE_INVALID_VISIBILITY |
400 | visibility is not equal to PUBLIC or PRIVATE |
STORAGE_INVALID_SIZE |
400 | sizeBytes is not a non-negative integer |
STORAGE_INVALID_TTL |
400 | ttlSeconds is outside the 60–86400 range |
STORAGE_FILE_REQUIRED |
400 | The file part of the form is not passed (path A) |
STORAGE_MULTIPART_PARSE_FAILED |
400 | Failed to parse multipart/form-data |
STORAGE_OBJECT_ID_REQUIRED |
400 | The objectId parameter is not passed |
STORAGE_INVALID_TOTAL_SIZE |
400 | totalSize is not a positive integer (path C) |
STORAGE_INVALID_PART_SIZE |
400 | partSize is not a positive integer (path C) |
STORAGE_PARTS_REQUIRED |
400 | The parts array is not passed or is empty |
STORAGE_INVALID_PART |
400 | An element of the parts array does not match the { partNumber, etag } format |
STORAGE_INVALID_PARTS |
400 | The set of parts was rejected by storage when assembling the multipart upload |
STORAGE_TOO_MANY_PARTS |
400 | Too many parts for a multipart upload; increase partSize |
STORAGE_INVALID_LIMIT |
400 | limit is not a positive integer |
STORAGE_INVALID_CURSOR |
400 | A 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 |
STORAGE_UPLOAD_TOO_LARGE |
413 | File size exceeds 10 MB (path A) |
STORAGE_OBJECT_TOO_LARGE |
413 | totalSize exceeds 5 TB (path C) |
STORAGE_FORBIDDEN_CONTENT_TYPE |
415 | Content type is not allowed for PUBLIC objects |
STORAGE_OBJECT_NOT_FOUND |
404 | Object not found or does not belong to the calling app; public GET/HEAD also returns this code for PENDING and PRIVATE objects |
STORAGE_UPLOAD_NOT_PENDING |
409 | The object is already uploaded or is in an invalid state |
STORAGE_UPLOAD_PENDING |
409 | The object upload is not complete: on a read the object is not ready yet; on an upload the key holds an unfinished presigned reservation |
STORAGE_UPLOAD_NOT_FOUND_IN_BUCKET |
409 | The PUT request to the presigned URL was never made |
STORAGE_MULTIPART_IN_PROGRESS |
409 | The object has an unfinished multipart upload; call /multipart/abort — it cannot be replaced or deleted until then |
STORAGE_KEY_DELETED |
409 | This 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 |
STORAGE_KEY_OWNED_ELSEWHERE |
409 | The 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 |
STORAGE_KEY_EXISTS |
409 | The 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 |
STORAGE_KEY_CONFLICT |
409 | The 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 |
STORAGE_VISIBILITY_MISMATCH |
400 | Replacing content cannot change object visibility. Omit the visibility field or pass the current value |
STORAGE_OBJECT_DELETED |
410 | The object is marked as deleted. Without an eligible subsequent upload, reads and a repeated DELETE stay 410 until physical purge |
BILLING_INSUFFICIENT |
402 | Portal balance is zero — writing is paused for 24 hours (reading works) |
STORAGE_QUOTA_EXCEEDED |
507 | The 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 |
STORAGE_RATE_LIMIT_EXCEEDED |
429 | Exceeded 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) |
STORAGE_PUBLIC_GET_DISABLED_FOR_PORTAL |
503 | Public access is disabled at the portal level |
STORAGE_FEATURE_DISABLED |
503 | Storage is temporarily disabled |
STORAGE_PRESIGNED_UPLOAD_DISABLED |
503 | Path B is temporarily disabled. Use direct upload (Path A) or multipart upload (Path C) |
STORAGE_STS_UNAVAILABLE |
503 | The temporary-credentials issuance service is unavailable |
STORAGE_BUCKET_ERROR |
502 | Error accessing the object storage |
STORAGE_OBJECT_MISSING_IN_BUCKET |
502 | The object is recorded in the database but absent from storage |
STORAGE_OBJECT_STREAM_FAILED |
502 | Error while transferring object data |
STORAGE_OBJECT_HEAD_FAILED |
502 | Error while retrieving object metadata from storage |
General API error codes — Error codes.