For AI agents: markdown of this page — /docs-content-en/storage/upload/presigned.md documentation index — /llms.txt
Create presigned URL
POST /v1/storage/objects
The first step of uploading a file from 10 MB to 5 GB — returns a temporary URL the client uses to send the file directly via PUT; after sending, you call upload completion.
Request fields (body)
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
key |
string | yes | — | Logical object key: from 1 to 1024 characters, allowed a-z, A-Z, 0-9, ., _, /, -; must not start with / or ., must not contain .. |
contentType |
string | yes | — | File MIME type; must match the Content-Type header on the PUT request to uploadUrl |
visibility |
string | no | PRIVATE |
Object visibility: PRIVATE or PUBLIC |
sizeBytes |
number | no | — | Expected file size in bytes; an optional hint, the actual size is captured at the completion step |
ttlSeconds |
number | no | 3600 |
Presigned URL lifetime in seconds: from 60 to 86400 |
Examples
curl — personal key
# Step 1 — get a presigned URL
curl -X POST https://vibecode.bitrix24.com/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"
}'
# Step 2 — send the file to the received uploadUrl
curl -X PUT "https://storage.example.com/upload/..." \
-H "Content-Type: application/pdf" \
--data-binary @report.pdf
curl — OAuth application
# Step 1 — get a presigned URL
curl -X POST https://vibecode.bitrix24.com/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"
}'
# Step 2 — send the file to the received uploadUrl
curl -X PUT "https://storage.example.com/upload/..." \
-H "Content-Type: application/pdf" \
--data-binary @report.pdf
JavaScript — personal key
// Step 1 — get a presigned URL
const res = await fetch('https://vibecode.bitrix24.com/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()
// Step 2 — send the file to the presigned URL
const put = await fetch(result.uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': 'application/pdf' },
body: file, // File or Blob
})
if (!put.ok) throw new Error(`PUT failed: ${put.status}`)
JavaScript — OAuth application
// Step 1 — get a presigned URL
const res = await fetch('https://vibecode.bitrix24.com/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()
// Step 2 — send the file to the presigned URL
await fetch(result.uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': 'application/pdf' },
body: file, // File or Blob
})
Response fields
| Field | Type | Description |
|---|---|---|
objectId |
string | Object identifier; pass it to POST /v1/storage/objects/complete |
uploadUrl |
string | Presigned URL for sending the file via the PUT method |
bucket |
string | Internal container name |
objectKey |
string | Internal object key in storage |
expiresAt |
string | Presigned URL expiry (ISO 8601) |
Response example
{
"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"
}
Error response example
400 — ttlSeconds is outside the allowed range:
{
"success": false,
"error": {
"code": "STORAGE_INVALID_TTL",
"message": "ttlSeconds must be between 60 and 86400"
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 403 | STORAGE_SCOPE_REQUIRED |
The API key lacks the vibe:storage scope |
| 401 | STORAGE_NO_AUTH_CONTEXT |
The request was made without authorization |
| 400 | STORAGE_KEY_REQUIRED |
The key field was not provided |
| 400 | STORAGE_CONTENT_TYPE_REQUIRED |
The contentType field was not provided |
| 400 | STORAGE_INVALID_KEY |
The key value violates the format rules |
| 400 | STORAGE_INVALID_VISIBILITY |
Invalid visibility value |
| 400 | STORAGE_INVALID_SIZE |
Invalid sizeBytes value |
| 400 | STORAGE_INVALID_TTL |
ttlSeconds is outside the range 60–86400 |
| 402 | BILLING_INSUFFICIENT |
Insufficient balance |
| 503 | STORAGE_FEATURE_DISABLED |
Storage is disabled for the portal |
| 503 | STORAGE_STS_UNAVAILABLE |
The temporary credentials issuance service is unavailable |
| 502 | STORAGE_BUCKET_ERROR |
Error while interacting with storage |
Full list of common API errors — Errors.
Known specifics
Mandatory second step. After sending the file to uploadUrl you must call upload completion with the objectId from this response. Until completion, the object stays in PENDING status and cannot be downloaded.
URL lifetime. ttlSeconds limits the lifetime of the presigned upload URL, not the retention period of the finished object.