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
Temporarily disabled. This fail-closed method returns
503 STORAGE_PRESIGNED_UPLOAD_DISABLED. Use direct upload or multipart upload.
Path B does not replace or revive soft-deleted objects. While it is disabled, every request returns 503 STORAGE_PRESIGNED_UPLOAD_DISABLED before a URL is minted. If occupied-key handling becomes reachable again, a deleted object will still return 409 STORAGE_KEY_DELETED without an uploadUrl. New bytes can be written into an eligible deleted row by direct upload (Path A), or for a personal key after explicit DELETE, by multipart upload (Path C).
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 |
|---|---|---|---|---|
Fieldkey |
Typestring | Requiredyes | Default— | DescriptionLogical object key: from 1 to 1024 characters, allowed a-z, A-Z, 0-9, ., _, /, -; must not start with / or ., must not contain .. |
FieldcontentType |
Typestring | Requiredyes | Default— | DescriptionFile MIME type; must match the Content-Type header on the PUT request to uploadUrl |
Fieldvisibility |
Typestring | Requiredno | DefaultPRIVATE |
DescriptionObject visibility: PRIVATE or PUBLIC |
FieldsizeBytes |
Typenumber | Requiredno | Default— | DescriptionExpected file size in bytes; an optional hint, the actual size is captured at the completion step |
FieldttlSeconds |
Typenumber | Requiredno | Default3600 |
DescriptionPresigned 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 |
|---|---|---|
FieldobjectId |
Typestring | DescriptionObject identifier; pass it to POST /v1/storage/objects/complete |
FielduploadUrl |
Typestring | DescriptionPresigned URL for sending the file via the PUT method |
Fieldbucket |
Typestring | DescriptionInternal container name |
FieldobjectKey |
Typestring | DescriptionInternal object key in storage |
FieldexpiresAt |
Typestring | DescriptionPresigned URL expiry (ISO 8601), taken from the URL signature. May come earlier than the requested ttlSeconds |
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 |
|---|---|---|
| HTTP403 | CodeSTORAGE_SCOPE_REQUIRED |
DescriptionThe API key lacks the vibe:storage scope |
| HTTP409 | CodeSTORAGE_KEY_EXISTS |
DescriptionAn object under this key already exists. Replacing content through a presigned URL is not supported — use a direct upload |
| HTTP409 | CodeSTORAGE_KEY_DELETED |
DescriptionPath B does not revive a deleted object: the key stays occupied until purge and no uploadUrl is minted. While Path B is disabled, 503 STORAGE_PRESIGNED_UPLOAD_DISABLED is returned before this refusal |
| HTTP409 | CodeSTORAGE_MULTIPART_IN_PROGRESS |
DescriptionA multipart upload is in progress for this key — complete or abort it |
| HTTP409 | CodeSTORAGE_KEY_OWNED_ELSEWHERE |
DescriptionThe name belongs to an object of the other ownership kind (app-shared vs per-employee) |
| HTTP409 | CodeSTORAGE_KEY_CONFLICT |
DescriptionThe reservation changed while the URL was being minted — retry |
| HTTP400 | CodeSTORAGE_VISIBILITY_MISMATCH |
DescriptionRe-minting a URL for your own unfinished reservation cannot change its visibility — omit visibility or pass the current value |
| HTTP401 | CodeSTORAGE_NO_AUTH_CONTEXT |
DescriptionThe request was made without authorization |
| HTTP400 | CodeSTORAGE_KEY_REQUIRED |
DescriptionThe key field was not provided |
| HTTP400 | CodeSTORAGE_CONTENT_TYPE_REQUIRED |
DescriptionThe contentType field was not provided |
| HTTP400 | CodeSTORAGE_INVALID_KEY |
DescriptionThe key value violates the format rules |
| HTTP400 | CodeSTORAGE_INVALID_VISIBILITY |
DescriptionInvalid visibility value |
| HTTP400 | CodeSTORAGE_INVALID_SIZE |
DescriptionInvalid sizeBytes value |
| HTTP400 | CodeSTORAGE_INVALID_TTL |
DescriptionttlSeconds is outside the range 60–86400 |
| HTTP402 | CodeBILLING_INSUFFICIENT |
DescriptionInsufficient balance |
| HTTP503 | CodeSTORAGE_FEATURE_DISABLED |
DescriptionStorage is disabled for the portal |
| HTTP503 | CodeSTORAGE_PRESIGNED_UPLOAD_DISABLED |
DescriptionPath B is temporarily disabled; use Path A or C |
| HTTP503 | CodeSTORAGE_STS_UNAVAILABLE |
DescriptionThe temporary credentials issuance service is unavailable |
| HTTP502 | CodeSTORAGE_BUCKET_ERROR |
DescriptionError 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. It is the requested lifetime: the effective one is returned in expiresAt and may be shorter. Schedule the file upload by expiresAt, not by ttlSeconds, and upload as early as you can.