# 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](#authorization) · [Lifecycle](#object-lifecycle) · [Upload paths](#upload-paths) · [Visibility](#visibility-and-access) · [Object key](#object-key) · [Limits](#limits) · [Pricing](#pricing) · [Quick start](#quick-start) · [Full example](#full-example) · [Endpoints](#endpoint-reference) · [Errors](#error-codes)

## 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](/docs/entities/storages) 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](/docs/storage/upload) | Three upload paths: direct form (≤10 MB), presigned PUT (temporarily disabled), multipart (≤5 TB) |
| [Objects](/docs/storage/objects) | Object listing, retrieval, metadata, deletion, 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.

For a detailed description of each path, see [Upload](/docs/storage/upload).

## Visibility and access

Each object has a `visibility` attribute:

- `PRIVATE` (default) — download via an authorized `GET /v1/storage/objects/:key` request, 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 form `https://vibecode.bitrix24.com/v1/public-storage/:portalId/:objectId` without authorization. While the object is `PENDING`, the public URL returns `404 STORAGE_OBJECT_NOT_FOUND`.

For `PUBLIC` objects, serving files with types a browser renders as a page, runs or sniffs is blocked: HTML and XHTML, SVG, JavaScript, any XML (`*/xml`, `*+xml`), XSL, `multipart/*`, `message/*`, `unknown/unknown`, `application/unknown`, `*/*`, with or without parameters. An attempt to upload a file with such a type and `visibility=PUBLIC` returns `415 STORAGE_FORBIDDEN_CONTENT_TYPE`. The `contentType` field accepts exactly one `type/subtype` media type with optional `;name=value` parameters and is stored in canonical form: lowercase, with `charset` as the only parameter kept. Any other value, such as a comma-separated list, returns `400 STORAGE_INVALID_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 a browser renders as a page, runs or sniffs are blocked: HTML and XHTML, SVG, JavaScript, any XML (`*/xml`, `*+xml`), XSL, `multipart/*`, `message/*`, `unknown/unknown`, `application/unknown`, `*/*`, with or without parameters. In particular, a report uploaded as `application/xml`, `text/xml`, or `application/xslt+xml` returns `415 STORAGE_FORBIDDEN_CONTENT_TYPE`.

## 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. For details on what lives under that prefix and why the same key appears in the listing several times, see [Source code storage](/docs/source-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 current limit is returned in the `x-ratelimit-limit` header |

## 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 `storage` block in the `GET /v1/me` response — 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](/docs/mcp).

## Quick start

**Upload a file (path A):**

```bash
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:**

```bash
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.

```javascript
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):

```javascript
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.

```javascript
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`](/docs/storage/upload/direct) | Direct upload of a file up to 10 MB in a single `multipart/form-data` request |
| `POST` | [`/v1/storage/objects`](/docs/storage/upload/presigned) | Create a presigned URL to upload a file up to 5 GB directly to storage. Temporarily disabled |
| `POST` | [`/v1/storage/objects/complete`](/docs/storage/upload/complete) | Confirm completion of a presigned-URL upload. Temporarily disabled |
| `POST` | [`/v1/storage/objects/multipart/create`](/docs/storage/upload/multipart-create) | Initiate a multipart upload: obtain a session identifier and URLs for the parts |
| `POST` | [`/v1/storage/objects/multipart/complete`](/docs/storage/upload/multipart-complete) | Assemble the object from uploaded parts by their `partNumber` and `etag` |
| `POST` | [`/v1/storage/objects/multipart/abort`](/docs/storage/upload/multipart-abort) | Cancel a multipart upload and remove unfinished parts |

### Objects

| Method | Path | Description |
|---|---|---|
| `GET` | [`/v1/storage/objects`](/docs/storage/objects/list) | List objects with prefix filtering, an opaque cursor, and exact counting only with `withTotal=true` |
| `GET` | [`/v1/storage/objects/:key`](/docs/storage/objects/get) | Download an object (302 redirect → presigned URL) |
| `HEAD` | [`/v1/storage/objects/:key`](/docs/storage/objects/head) | Object metadata without a response body |
| `DELETE` | [`/v1/storage/objects/:key`](/docs/storage/objects/delete) | Soft-delete an object |

### Public access

| Method | Path | Description |
|---|---|---|
| `GET` | [`/v1/public-storage/:portalId/:objectId`](/docs/storage/objects/public-get) | Download a completed `PUBLIC` object without an API key |
| `HEAD` | [`/v1/public-storage/:portalId/:objectId`](/docs/storage/objects/public-head) | 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_CONTENT_TYPE` | 400 | `contentType` or the content type of the `file` part is not a single `type/subtype` media type with optional parameters (paths A, 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 exceeds the remaining capacity. Path C checks the declared `totalSize` at `/multipart/create` and rejects the write before any part is uploaded if that size exceeds the remaining capacity. 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 current limit is returned in the `x-ratelimit-limit` header |
| `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 |

For general API error codes, see [Error codes](/docs/errors).

## See also

- [Upload](/docs/storage/upload)
- [Objects](/docs/storage/objects)
- [Source code storage](/docs/source-storage)
- [Keys and authorization](/docs/keys-auth)
- [Scopes](/docs/scopes)
