## Создать предподписанный URL

`POST /v1/storage/objects`

> **Временно отключено.** Метод fail-closed возвращает `503
> STORAGE_PRESIGNED_UPLOAD_DISABLED`. Используйте [прямую
> загрузку](/docs/storage/upload/direct) или [multipart](/docs/storage/upload/multipart-create).

Первый шаг загрузки файла от 10 МБ до 5 ГБ — возвращает временный URL, по которому клиент отправляет файл напрямую методом PUT; после отправки вызывается подтверждение загрузки.

## Поля запроса (body)

| Поле | Тип | Обяз. | По умолч. | Описание |
|------|-----|:-----:|-----------|---------|
| `key` | string | да | — | Логический ключ объекта: от 1 до 1024 символов, допустимы `a-z`, `A-Z`, `0-9`, `.`, `_`, `/`, `-`; не начинать с `/` или `.`, не содержать `..` |
| `contentType` | string | да | — | MIME-тип файла; должен совпасть с заголовком `Content-Type` при PUT-запросе на `uploadUrl` |
| `visibility` | string | нет | `PRIVATE` | Видимость объекта: `PRIVATE` или `PUBLIC` |
| `sizeBytes` | number | нет | — | Ожидаемый размер файла в байтах; необязательная подсказка, фактический размер фиксируется на шаге подтверждения |
| `ttlSeconds` | number | нет | `3600` | Срок действия предподписанного URL в секундах: от 60 до 86400 |

## Примеры

### curl — личный ключ

```bash
# Шаг 1 — получить предподписанный URL
curl -X POST https://vibecode.bitrix24.tech/v1/storage/objects \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "users/42/report.pdf",
    "contentType": "application/pdf",
    "visibility": "PRIVATE"
  }'

# Шаг 2 — отправить файл на полученный uploadUrl
curl -X PUT "https://storage.example.com/upload/..." \
  -H "Content-Type: application/pdf" \
  --data-binary @report.pdf
```

### curl — OAuth-приложение

```bash
# Шаг 1 — получить предподписанный URL
curl -X POST https://vibecode.bitrix24.tech/v1/storage/objects \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "users/42/report.pdf",
    "contentType": "application/pdf",
    "visibility": "PRIVATE"
  }'

# Шаг 2 — отправить файл на полученный uploadUrl
curl -X PUT "https://storage.example.com/upload/..." \
  -H "Content-Type: application/pdf" \
  --data-binary @report.pdf
```

### JavaScript — личный ключ

```javascript
// Шаг 1 — получить предподписанный URL
const res = await fetch('https://vibecode.bitrix24.tech/v1/storage/objects', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    key: 'users/42/report.pdf',
    contentType: 'application/pdf',
    visibility: 'PRIVATE',
  }),
})
if (!res.ok) {
  const { error } = await res.json()
  console.error(error.code, error.message)
  return
}
const result = await res.json()

// Шаг 2 — отправить файл на предподписанный URL
const put = await fetch(result.uploadUrl, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/pdf' },
  body: file, // File или Blob
})
if (!put.ok) throw new Error(`PUT failed: ${put.status}`)
```

### JavaScript — OAuth-приложение

```javascript
// Шаг 1 — получить предподписанный URL
const res = await fetch('https://vibecode.bitrix24.tech/v1/storage/objects', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    key: 'users/42/report.pdf',
    contentType: 'application/pdf',
    visibility: 'PRIVATE',
  }),
})
const result = await res.json()

// Шаг 2 — отправить файл на предподписанный URL
await fetch(result.uploadUrl, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/pdf' },
  body: file, // File или Blob
})
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|---------|
| `objectId` | string | Идентификатор объекта; передать в [`POST /v1/storage/objects/complete`](/docs/storage/upload/complete) |
| `uploadUrl` | string | Предподписанный URL для отправки файла методом PUT |
| `bucket` | string | Служебное название контейнера |
| `objectKey` | string | Служебный ключ объекта в хранилище |
| `expiresAt` | string | Срок действия предподписанного URL (ISO 8601) |

## Пример ответа

```json
{
  "objectId": "cmpf9b1gp003omr0zkc0qjojn",
  "uploadUrl": "https://storage.example.com/upload/portals/example-portal/users/42/report.pdf?X-Amz-Signature=abc123",
  "bucket": "example-storage-bucket",
  "objectKey": "portals/example-portal/users/42/report.pdf",
  "expiresAt": "2026-05-21T09:57:43.675Z"
}
```

## Пример ответа при ошибке

400 — `ttlSeconds` вне допустимого диапазона:

```json
{
  "success": false,
  "error": {
    "code": "STORAGE_INVALID_TTL",
    "message": "ttlSeconds must be between 60 and 86400"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 403 | `STORAGE_SCOPE_REQUIRED` | API-ключу не хватает скоупа `vibe:storage` |
| 409 | `STORAGE_KEY_EXISTS` | Объект под этим ключом уже существует. Заменить содержимое через предподписанную ссылку нельзя — используйте [прямую загрузку](/docs/storage/upload/direct) |
| 409 | `STORAGE_KEY_DELETED` | Объект под этим ключом удалён и держит имя до уборки |
| 409 | `STORAGE_MULTIPART_IN_PROGRESS` | По этому ключу идёт составная загрузка — завершите или отмените её |
| 409 | `STORAGE_KEY_OWNED_ELSEWHERE` | Имя занято другим объектом этого приложения |
| 409 | `STORAGE_KEY_CONFLICT` | Бронь изменилась, пока выдавалась ссылка — повторите запрос |
| 400 | `STORAGE_VISIBILITY_MISMATCH` | Повторная ссылка на свою незавершённую бронь не может менять её видимость — не передавайте `visibility` либо передайте текущее значение |
| 401 | `STORAGE_NO_AUTH_CONTEXT` | Запрос выполнен без авторизации |
| 400 | `STORAGE_KEY_REQUIRED` | Не передано поле `key` |
| 400 | `STORAGE_CONTENT_TYPE_REQUIRED` | Не передано поле `contentType` |
| 400 | `STORAGE_INVALID_KEY` | Значение `key` нарушает правила формата |
| 400 | `STORAGE_INVALID_VISIBILITY` | Недопустимое значение `visibility` |
| 400 | `STORAGE_INVALID_SIZE` | Недопустимое значение `sizeBytes` |
| 400 | `STORAGE_INVALID_TTL` | `ttlSeconds` вне диапазона 60–86400 |
| 402 | `BILLING_INSUFFICIENT` | Недостаточно средств на балансе |
| 503 | `STORAGE_FEATURE_DISABLED` | Хранилище отключено для портала |
| 503 | `STORAGE_PRESIGNED_UPLOAD_DISABLED` | Путь B временно отключён; используйте путь A или C |
| 503 | `STORAGE_STS_UNAVAILABLE` | Сервис выдачи временных учётных данных недоступен |
| 502 | `STORAGE_BUCKET_ERROR` | Ошибка при взаимодействии с хранилищем |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

**Обязательный второй шаг.** После отправки файла на `uploadUrl` необходимо вызвать [подтверждение загрузки](/docs/storage/upload/complete) с `objectId` из этого ответа. До подтверждения объект находится в статусе `PENDING` и недоступен для скачивания.

**Срок действия URL.** `ttlSeconds` ограничивает срок действия предподписанного URL загрузки, а не срок хранения готового объекта.

## Смотрите также

- [Загрузка](/docs/storage/upload)
- [Прямая загрузка](/docs/storage/upload/direct)
- [Подтвердить загрузку](/docs/storage/upload/complete)
- [Составная загрузка](/docs/storage/upload/multipart-create)
- [Хранилище](/docs/storage)
