[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-storage":3,"docs-tabs-storage":6},{"content":4,"lastmod":5},"# Хранилище\n\nХранилище файлов для Битрикс24-приложений: загружайте, управляйте видимостью и скачивайте по временным ссылкам.\n\n**Скоуп:** `vibe:storage` | **Базовый URL:** `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1` | **Авторизация:** API-ключ (`X-Api-Key`)\n\n[Авторизация](#авторизация) · [Жизненный цикл](#жизненный-цикл-объекта) · [Пути загрузки](#пути-загрузки) · [Видимость](#видимость-и-доступ) · [Ключ объекта](#ключ-объекта) · [Лимиты](#лимиты) · [Тарификация](#тарификация) · [Быстрый старт](#быстрый-старт) · [Полный пример](#полный-пример) · [Эндпоинты](#справочник-эндпоинтов) · [Ошибки](#коды-ошибок)\n\n## Когда использовать\n\n**Подходит для:**\n\n- Аватаров пользователей и логотипов приложения\n- Вложений в формах: фотографии, сканы, чеки\n- Экспортов и сгенерированных отчётов (CSV, PDF)\n- Видеовложений в CRM-сделках\n- Резервных копий конфигурации приложения\n- Промежуточных файлов в цепочке обработки данных\n\n**Не подходит для:**\n\n- Публикации файлов в Диск Битрикс24 портала — для этого предназначен раздел [Диск](\u002Fdocs\u002Fentities\u002Fstorages); хранилище изолировано по приложению и не входит в дисковое дерево портала\n- Хранения секретов и ключей — используйте переменные окружения сервера\n- Журналирования и аудита приложения\n- Сценариев без привязки к приложению — хранилище всегда изолировано по ключу\n\n## Разделы документации\n\n| Раздел | Описание |\n|---|---|\n| [Загрузка](\u002Fdocs\u002Fstorage\u002Fupload) | Три пути загрузки: прямая форма (≤10 МБ), предподписанный PUT (≤5 ГБ), составные части (≤5 ТБ) |\n| [Объекты](\u002Fdocs\u002Fstorage\u002Fobjects) | Список, получение, метаданные, удаление объектов и публичный доступ |\n\n## Авторизация\n\nВсе запросы к `\u002Fv1\u002Fstorage\u002F*` требуют API-ключ в заголовке `X-Api-Key` со скоупом `vibe:storage`.\n\nНовые ключи получают скоуп `vibe:storage` автоматически. Если ключ создан до его появления — добавьте скоуп в разделе **API-ключи** личного кабинета.\n\nОтсутствие скоупа возвращает `403 STORAGE_SCOPE_REQUIRED` на любом вызове `\u002Fv1\u002Fstorage\u002F*`.\n\n**Типы ключей**\n\n| Тип ключа | Доступ | Область хранилища |\n|---|---|---|\n| `vibe_api_*` — личный API-ключ | да | пространство имён владельца ключа на портале |\n| `vibe_app_*` — ключ приложения | да | пространство имён приложения, к которому привязан ключ |\n\nМенеджмент-ключи (`vibe_live_*`) к `\u002Fv1\u002Fstorage\u002F*` **не подходят** — у них нет привязки к порталу. Любой вызов вернёт `403 STORAGE_REQUIRES_PORTAL_BINDING`.\n\n**Изоляция.** Объекты изолированы по владельцу ключа: ключ одного портала не получит доступ к объектам другого портала — такой запрос вернёт `404 STORAGE_OBJECT_NOT_FOUND`, а не 403, чтобы не раскрывать существование чужих ресурсов.\n\n## Жизненный цикл объекта\n\n```\nPENDING      — создан, ожидает загрузки файла (пути B и C)\n    │\n    ▼ (путь A → сразу COMPLETED)\nCOMPLETED    — файл загружен, доступен для чтения\n    │\n    ▼ (DELETE)\ndeletedAt    — мягкое удаление: чтение → 410; данные хранятся ещё 30 суток\n    │\n    ▼ (+30 суток, планировщик)\nудалён       — данные стёрты из хранилища, квота освобождена\n```\n\nПрямая загрузка (путь A) создаёт объект сразу в состоянии `COMPLETED`. Пути B и C создают объект в состоянии `PENDING` — до вызова `\u002Fcomplete`.\n\nОбъекты в состоянии `PENDING` без завершения удаляются фоновым планировщиком через 24 часа.\n\n## Пути загрузки\n\n| Путь | Размер файла | Шагов | Когда применять |\n|---|---|---|---|\n| **A — прямая загрузка** | до 10 МБ | 1 | Серверный код, небольшие файлы |\n| **B — предподписанный PUT** | до 5 ГБ | 3 | Загрузка из браузера без промежуточного сервера |\n| **C — составная загрузка** | до 5 ТБ | 3 + N частей | Крупные файлы, параллельная загрузка |\n\nПодробное описание каждого пути — [Загрузка](\u002Fdocs\u002Fstorage\u002Fupload).\n\n## Видимость и доступ\n\nКаждый объект имеет атрибут `visibility`:\n\n- `PRIVATE` (по умолчанию) — скачивание через авторизованный запрос `GET \u002Fv1\u002Fstorage\u002Fobjects\u002F:key`, который возвращает перенаправление 302 на предподписанный URL, действующий 10 минут.\n- `PUBLIC` — дополнительно доступен через постоянный URL вида `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fpublic-storage\u002F:portalId\u002F:objectId` без авторизации.\n\nДля `PUBLIC`-объектов заблокирована отдача файлов с типами содержимого `text\u002Fhtml`, `application\u002Fjavascript`, `application\u002Fx-javascript`, `image\u002Fsvg+xml`. Попытка загрузить файл с таким типом при `visibility=PUBLIC` возвращает `415 STORAGE_FORBIDDEN_CONTENT_TYPE`.\n\nАнонимный доступ через `public-storage` включается на стороне портала и по умолчанию выключен. Пока он выключен, запросы к публичному URL возвращают `503 STORAGE_PUBLIC_GET_DISABLED_FOR_PORTAL`.\n\n## Ключ объекта\n\nКлюч объекта — уникальный идентификатор в пространстве имён, привязанном к вашему ключу (личное пространство владельца или пространство приложения). Правила:\n\n- Длина: 1–1024 символа.\n- Допустимые символы: `a-z`, `A-Z`, `0-9`, `.`, `_`, `\u002F`, `-`.\n- Не может начинаться с `\u002F` или `.`.\n- Не может содержать последовательность `..`.\n- Символ `\u002F` — разделитель пути, используется для группировки по префиксу.\n\nВ параметрах пути URL слеши в ключе необходимо кодировать: `encodeURIComponent('users\u002F42\u002Freport.csv')` возвращает `'users%2F42%2Freport.csv'`.\n\n## Лимиты\n\n| Параметр | Значение |\n|---|---|\n| Максимальный размер файла (путь A) | 10 МБ |\n| Максимальный размер файла (путь B) | 5 ГБ |\n| Максимальный размер файла (путь C) | 5 ТБ |\n| Длина ключа объекта | 1–1024 символа |\n| Срок действия предподписанного URL | 10 минут |\n| Период мягкого удаления | 30 суток |\n| Объекты в состоянии PENDING | удаляются через 24 часа |\n| Лимит запросов к публичному URL | 60 запросов\u002Fмин с одного IP |\n\n## Тарификация\n\nХранилище работает по схеме оплаты по факту использования.\n\n| Ресурс | Ставка |\n|---|---|\n| Хранение | 2,21 Ꝟ\u002FГБ-месяц |\n| Исходящий трафик | 1,80 Ꝟ\u002FГБ |\n| Операции | 0,05 Ꝟ \u002F 1 000 операций записи |\n\nСтавки задаются платформой и могут меняться. Актуальные значения — в блоке `storage` ответа `GET \u002Fv1\u002Fme` (`costRateGbMonthVibes`, `costRateGbEgressVibes`, `costRateOps1kVibes`); таблица выше отражает их на момент публикации.\n\nПри нулевом балансе запросы на запись блокируются на 24 часа. Чтение объектов продолжает работать. Пополнение баланса разблокирует запись немедленно.\n\nТекущее использование и прогноз расходов — в блоке `storage` ответа `GET \u002Fv1\u002Fme`. Владелец портала также видит сводный расход по приложениям и может выгрузить отчёт на странице **Хранилище** в админ-панели портала.\n\n## Использование в AI-агентах\n\nAI-моделям и агентам хранилище доступно без чтения этой страницы:\n\n- Блок `storage` в ответе `GET \u002Fv1\u002Fme` — состояние хранилища, ставки, пути загрузки и список эндпоинтов.\n- Инструменты `vibe_storage_*` MCP-сервера (загрузка, предподписанные ссылки, список, удаление, контроль расхода) — см. [MCP для AI](\u002Fdocs\u002Fmcp).\n\n## Быстрый старт\n\n**Загрузить файл (путь A):**\n\n```bash\ncurl -X POST https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fstorage\u002Fobjects\u002Fupload \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -F \"key=reports\u002Fmarch.csv\" \\\n  -F \"visibility=PRIVATE\" \\\n  -F \"file=@march.csv;type=text\u002Fcsv\"\n```\n\n**Получить ссылку для скачивания:**\n\n```bash\ncurl -I \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  \"https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Fstorage\u002Fobjects\u002Freports%2Fmarch.csv\"\n# HTTP\u002F1.1 302 Found\n# Location: \u003Cвременная ссылка на объект, действует 10 минут>\n```\n\n## Полный пример\n\nПолный цикл: загрузка → список → получение ссылки → удаление.\n\n```javascript\nconst KEY = 'YOUR_API_KEY'\nconst BASE = 'https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1'\n\nconst form = new FormData()\nform.append('key', 'users\u002F42\u002Freport.csv')\nform.append('visibility', 'PRIVATE')\nform.append('file', new Blob(['id,name\\n1,Alice\\n2,Bob'], { type: 'text\u002Fcsv' }), 'report.csv')\n\nconst uploadRes = await fetch(`${BASE}\u002Fstorage\u002Fobjects\u002Fupload`, {\n  method: 'POST',\n  headers: { 'X-Api-Key': KEY },\n  body: form,\n})\nconst { object } = await uploadRes.json()\nconsole.log('Загружен:', object.key, '| uploadStatus:', object.uploadStatus)\n\nconst listRes = await fetch(`${BASE}\u002Fstorage\u002Fobjects?prefix=users\u002F42`, {\n  headers: { 'X-Api-Key': KEY },\n})\nconst { data, total } = await listRes.json()\nconsole.log('Объектов:', total, '| первый:', data[0]?.key)\n\nconst getRes = await fetch(`${BASE}\u002Fstorage\u002Fobjects\u002F${encodeURIComponent(object.key)}`, {\n  headers: { 'X-Api-Key': KEY },\n  redirect: 'manual',\n})\nconst downloadUrl = getRes.headers.get('location')\nconsole.log('Ссылка (10 мин):', downloadUrl)\n\nconst delRes = await fetch(`${BASE}\u002Fstorage\u002Fobjects\u002F${encodeURIComponent(object.key)}`, {\n  method: 'DELETE',\n  headers: { 'X-Api-Key': KEY },\n})\nconst { object: deleted } = await delRes.json()\nconsole.log('Удалён:', deleted.key, '| deletedAt:', deleted.deletedAt)\n```\n\n## Типовые сценарии\n\n### Большой файл составной загрузкой\n\nЗагрузка крупного файла по частям с отменой при ошибке (чтобы не оплачивать незавершённые части):\n\n```javascript\nconst KEY = 'YOUR_API_KEY'\nconst BASE = 'https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1'\nconst PART_SIZE = 8 * 1024 * 1024 \u002F\u002F 8 МБ\n\n\u002F\u002F file — File или Blob, например из \u003Cinput type=\"file\">\nconst init = await fetch(`${BASE}\u002Fstorage\u002Fobjects\u002Fmultipart\u002Fcreate`, {\n  method: 'POST',\n  headers: { 'X-Api-Key': KEY, 'Content-Type': 'application\u002Fjson' },\n  body: JSON.stringify({\n    key: 'videos\u002Fdemo.mp4',\n    contentType: 'video\u002Fmp4',\n    totalSize: file.size,\n    partSize: PART_SIZE,\n    visibility: 'PRIVATE',\n  }),\n})\nconst { objectId, parts } = await init.json()\n\ntry {\n  const uploaded = []\n  for (const part of parts) {\n    const chunk = file.slice((part.partNumber - 1) * PART_SIZE, part.partNumber * PART_SIZE)\n    const res = await fetch(part.uploadUrl, { method: 'PUT', body: chunk })\n    uploaded.push({ partNumber: part.partNumber, etag: res.headers.get('etag') })\n  }\n  const done = await fetch(`${BASE}\u002Fstorage\u002Fobjects\u002Fmultipart\u002Fcomplete`, {\n    method: 'POST',\n    headers: { 'X-Api-Key': KEY, 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ objectId, parts: uploaded }),\n  })\n  const { object } = await done.json()\n  console.log('Загружен:', object.key, object.sizeBytes)\n} catch (err) {\n  await fetch(`${BASE}\u002Fstorage\u002Fobjects\u002Fmultipart\u002Fabort`, {\n    method: 'POST',\n    headers: { 'X-Api-Key': KEY, 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ objectId }),\n  })\n  throw err\n}\n```\n\n### Массовая чистка по префиксу\n\nУдаление всех файлов пользователя при его отключении: постраничный обход по префиксу и удаление каждого объекта.\n\n```javascript\nconst KEY = 'YOUR_API_KEY'\nconst BASE = 'https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1'\n\nasync function deleteByPrefix(prefix) {\n  let cursor = null\n  let removed = 0\n  do {\n    const url = new URL(`${BASE}\u002Fstorage\u002Fobjects`)\n    url.searchParams.set('prefix', prefix)\n    url.searchParams.set('limit', '500')\n    if (cursor) url.searchParams.set('cursor', cursor)\n\n    const page = await fetch(url, { headers: { 'X-Api-Key': KEY } })\n    const { data, cursor: next } = await page.json()\n\n    for (const obj of data) {\n      await fetch(`${BASE}\u002Fstorage\u002Fobjects\u002F${encodeURIComponent(obj.key)}`, {\n        method: 'DELETE',\n        headers: { 'X-Api-Key': KEY },\n      })\n      removed++\n    }\n    cursor = next\n  } while (cursor)\n  return removed\n}\n\nconst count = await deleteByPrefix('users\u002F42\u002F')\nconsole.log('Удалено объектов:', count)\n```\n\n## Справочник эндпоинтов\n\n### Загрузка\n\n| Метод | Путь | Описание |\n|---|---|---|\n| `POST` | [`\u002Fv1\u002Fstorage\u002Fobjects\u002Fupload`](\u002Fdocs\u002Fstorage\u002Fupload\u002Fdirect) | Прямая загрузка файла до 10 МБ одним `multipart\u002Fform-data`-запросом |\n| `POST` | [`\u002Fv1\u002Fstorage\u002Fobjects`](\u002Fdocs\u002Fstorage\u002Fupload\u002Fpresigned) | Создать предподписанный URL для загрузки файла до 5 ГБ напрямую в хранилище |\n| `POST` | [`\u002Fv1\u002Fstorage\u002Fobjects\u002Fcomplete`](\u002Fdocs\u002Fstorage\u002Fupload\u002Fcomplete) | Подтвердить завершение загрузки по предподписанному URL |\n| `POST` | [`\u002Fv1\u002Fstorage\u002Fobjects\u002Fmultipart\u002Fcreate`](\u002Fdocs\u002Fstorage\u002Fupload\u002Fmultipart-create) | Инициировать составную загрузку: получить идентификатор сессии и URL для частей |\n| `POST` | [`\u002Fv1\u002Fstorage\u002Fobjects\u002Fmultipart\u002Fcomplete`](\u002Fdocs\u002Fstorage\u002Fupload\u002Fmultipart-complete) | Собрать объект из загруженных частей по их `partNumber` и `etag` |\n| `POST` | [`\u002Fv1\u002Fstorage\u002Fobjects\u002Fmultipart\u002Fabort`](\u002Fdocs\u002Fstorage\u002Fupload\u002Fmultipart-abort) | Отменить составную загрузку и удалить незавершённые части |\n\n### Объекты\n\n| Метод | Путь | Описание |\n|---|---|---|\n| `GET` | [`\u002Fv1\u002Fstorage\u002Fobjects`](\u002Fdocs\u002Fstorage\u002Fobjects\u002Flist) | Список объектов с фильтрацией по префиксу и курсорной пагинацией |\n| `GET` | [`\u002Fv1\u002Fstorage\u002Fobjects\u002F:key`](\u002Fdocs\u002Fstorage\u002Fobjects\u002Fget) | Скачать объект (перенаправление 302 → предподписанный URL) |\n| `HEAD` | [`\u002Fv1\u002Fstorage\u002Fobjects\u002F:key`](\u002Fdocs\u002Fstorage\u002Fobjects\u002Fhead) | Метаданные объекта без тела ответа |\n| `DELETE` | [`\u002Fv1\u002Fstorage\u002Fobjects\u002F:key`](\u002Fdocs\u002Fstorage\u002Fobjects\u002Fdelete) | Мягкое удаление объекта |\n\n### Публичный доступ\n\n| Метод | Путь | Описание |\n|---|---|---|\n| `GET` | [`\u002Fv1\u002Fpublic-storage\u002F:portalId\u002F:objectId`](\u002Fdocs\u002Fstorage\u002Fobjects\u002Fpublic-get) | Скачать `PUBLIC`-объект без API-ключа |\n| `HEAD` | [`\u002Fv1\u002Fpublic-storage\u002F:portalId\u002F:objectId`](\u002Fdocs\u002Fstorage\u002Fobjects\u002Fpublic-head) | Метаданные `PUBLIC`-объекта без API-ключа |\n\n## Коды ошибок\n\n| Код | HTTP | Когда возникает |\n|---|---|---|\n| `STORAGE_SCOPE_REQUIRED` | 403 | API-ключ не имеет скоупа `vibe:storage` |\n| `STORAGE_NO_AUTH_CONTEXT` | 401 | Запрос без пригодных учётных данных приложения |\n| `STORAGE_KEY_REQUIRED` | 400 | Поле `key` не передано или пустое |\n| `STORAGE_INVALID_KEY` | 400 | Ключ содержит недопустимые символы, начинается с `\u002F` или `.`, содержит `..` |\n| `STORAGE_INVALID_PATH` | 400 | Ключ формирует инъекцию пути |\n| `STORAGE_CONTENT_TYPE_REQUIRED` | 400 | Поле `contentType` не передано (пути B и C) |\n| `STORAGE_INVALID_VISIBILITY` | 400 | `visibility` не равен `PUBLIC` или `PRIVATE` |\n| `STORAGE_INVALID_SIZE` | 400 | `sizeBytes` не является неотрицательным целым числом |\n| `STORAGE_INVALID_TTL` | 400 | `ttlSeconds` вне диапазона 60–86400 |\n| `STORAGE_FILE_REQUIRED` | 400 | Файловая часть в форме не передана (путь A) |\n| `STORAGE_MULTIPART_PARSE_FAILED` | 400 | Ошибка разбора `multipart\u002Fform-data` |\n| `STORAGE_OBJECT_ID_REQUIRED` | 400 | Параметр `objectId` не передан |\n| `STORAGE_INVALID_TOTAL_SIZE` | 400 | `totalSize` не является положительным целым числом (путь C) |\n| `STORAGE_INVALID_PART_SIZE` | 400 | `partSize` не является положительным целым числом (путь C) |\n| `STORAGE_PARTS_REQUIRED` | 400 | Массив `parts` не передан или пуст |\n| `STORAGE_INVALID_PART` | 400 | Элемент массива `parts` не соответствует формату `{ partNumber, etag }` |\n| `STORAGE_INVALID_PARTS` | 400 | Набор частей отклонён хранилищем при сборке составной загрузки |\n| `STORAGE_TOO_MANY_PARTS` | 400 | Слишком много частей для составной загрузки; увеличьте `partSize` |\n| `STORAGE_INVALID_LIMIT` | 400 | `limit` не является положительным целым числом |\n| `STORAGE_UPLOAD_TOO_LARGE` | 413 | Размер файла превышает 10 МБ (путь A) |\n| `STORAGE_OBJECT_TOO_LARGE` | 413 | `totalSize` превышает 5 ТБ (путь C) |\n| `STORAGE_FORBIDDEN_CONTENT_TYPE` | 415 | Тип содержимого недопустим для `PUBLIC`-объектов |\n| `STORAGE_OBJECT_NOT_FOUND` | 404 | Объект не найден или не принадлежит вызывающему приложению |\n| `STORAGE_UPLOAD_NOT_PENDING` | 409 | Объект уже загружен или находится в недопустимом состоянии |\n| `STORAGE_UPLOAD_PENDING` | 409 | Попытка чтения объекта, загрузка которого не завершена |\n| `STORAGE_UPLOAD_NOT_FOUND_IN_BUCKET` | 409 | PUT-запрос к предподписанному URL не был выполнен |\n| `STORAGE_MULTIPART_IN_PROGRESS` | 409 | У объекта есть незавершённая составная загрузка; вызовите `\u002Fmultipart\u002Fabort` |\n| `STORAGE_OBJECT_DELETED` | 410 | Объект помечен как удалённый |\n| `STORAGE_QUOTA_EXCEEDED` | 507 | Превышена квота хранилища приложения |\n| `BILLING_INSUFFICIENT` | 402 | Нулевой баланс портала — запись приостановлена на 24 часа (чтение работает) |\n| `STORAGE_RATE_LIMIT_EXCEEDED` | 429 | Превышен лимит 60 запросов\u002Fмин к публичному URL с одного IP |\n| `STORAGE_PUBLIC_GET_DISABLED_FOR_PORTAL` | 503 | Публичный доступ отключён на уровне портала |\n| `STORAGE_FEATURE_DISABLED` | 503 | Хранилище временно отключено |\n| `STORAGE_STS_UNAVAILABLE` | 503 | Сервис выдачи временных учётных данных недоступен |\n| `STORAGE_BUCKET_ERROR` | 502 | Ошибка при обращении к объектному хранилищу |\n| `STORAGE_OBJECT_MISSING_IN_BUCKET` | 502 | Объект зафиксирован в базе, но отсутствует в хранилище |\n| `STORAGE_OBJECT_STREAM_FAILED` | 502 | Ошибка при передаче данных объекта |\n| `STORAGE_OBJECT_HEAD_FAILED` | 502 | Ошибка при получении метаданных объекта из хранилища |\n\nОбщие коды ошибок API — [Коды ошибок](\u002Fdocs\u002Ferrors).\n\n## Смотрите также\n\n- [Загрузка](\u002Fdocs\u002Fstorage\u002Fupload)\n- [Объекты](\u002Fdocs\u002Fstorage\u002Fobjects)\n- [Ключи и авторизация](\u002Fdocs\u002Fkeys-auth)\n- [Скоупы](\u002Fdocs\u002Fscopes)\n","2026-07-07",{}]