For AI agents: markdown of this page — /docs-content-en/feedback/attachments.md documentation index — /llms.txt
Upload an attachment
POST /v1/feedback/attachments
Uploads an image or a document and returns its ID for later linking to a ticket or comment. The upload is two-step: first the file is sent here, then the returned id is passed in the attachmentIds field when creating a ticket or adding a comment.
The file is passed as multipart/form-data in the file field; its type is determined by the extension in the file name. PNG, JPEG, WebP, and GIF images are re-encoded on the server: PNG, JPEG, and WebP are converted to WebP, and GIF is re-encoded as GIF, preserving animation. TXT, MD, CSV, HTML, XLSX, and ZIP documents are stored unchanged. Limits: up to 10 MB per file, up to 24 million pixels per image, up to 5 attachments per message and up to 25 per ticket.
Request fields (multipart/form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file |
file | yes | A file up to 10 MB. An image — extension .png, .jpg, .jpeg, .webp, or .gif. A document — .txt, .md, .csv, .html, .htm, .xlsx, or .zip |
Examples
curl — personal key
curl -X POST https://vibecode.bitrix24.com/v1/feedback/attachments \
-H "X-Api-Key: YOUR_API_KEY" \
-F "file=@screenshot.png"
curl — OAuth application
curl -X POST https://vibecode.bitrix24.com/v1/feedback/attachments \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-F "file=@screenshot.png"
JavaScript — personal key
const form = new FormData()
form.append('file', fileInput.files[0])
const res = await fetch('https://vibecode.bitrix24.com/v1/feedback/attachments', {
method: 'POST',
headers: { 'X-Api-Key': 'YOUR_API_KEY' },
body: form,
})
const { data } = await res.json()
// pass data.id in attachmentIds when creating a ticket or comment
JavaScript — OAuth application
const form = new FormData()
form.append('file', fileInput.files[0])
const res = await fetch('https://vibecode.bitrix24.com/v1/feedback/attachments', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
body: form,
})
const { data } = await res.json()
Response fields
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on success |
data |
object | The uploaded attachment |
data.id |
string | Attachment UUID. Passed in attachmentIds when creating a ticket or comment |
data.kind |
string | Attachment class: IMAGE or DOCUMENT |
data.mime |
string | Type after processing. For an image — image/webp for PNG, JPEG, and WebP, image/gif for GIF. For a document — the type by extension, for example text/plain or application/zip |
data.sizeBytes |
number | Size of the stored file in bytes |
data.width |
number | Width in pixels. 0 for a document |
data.height |
number | Height in pixels. 0 for a document |
data.thumbnailUrl |
string · null | Path to the attachment thumbnail. null for a document |
data.originalName |
string · null | Original file name. null if the image was uploaded without a file name. A document without a name gets attachment.<extension> |
data.expiresAt |
string | The time after which an unlinked attachment is deleted (ISO 8601) |
Response example
The image screenshot.png was uploaded:
{
"success": true,
"data": {
"id": "15ae6203-e67c-4378-912c-9b0465360c79",
"kind": "IMAGE",
"mime": "image/webp",
"sizeBytes": 560,
"width": 640,
"height": 360,
"thumbnailUrl": "/v1/feedback/_orphan/15ae6203-e67c-4378-912c-9b0465360c79/thumb",
"originalName": "screenshot.png",
"expiresAt": "2026-10-09T09:27:47.506Z"
}
}
The document note.txt was uploaded — no thumbnail and no dimensions:
{
"success": true,
"data": {
"id": "f4ec1f18-431b-49ec-85bb-bedcfce6da7d",
"kind": "DOCUMENT",
"mime": "text/plain",
"sizeBytes": 16,
"width": 0,
"height": 0,
"thumbnailUrl": null,
"originalName": "note.txt",
"expiresAt": "2026-10-09T09:25:40.292Z"
}
}
Error response example
413 — file larger than 10 MB:
{
"success": false,
"error": {
"code": "IMAGE_TOO_LARGE",
"message": "File too large: a feedback attachment is capped at 10 MB (10485760 bytes)",
"hint": "Shrink the image below 10 MB (lower the resolution or re-encode as JPEG or WebP) and upload it again. The cap applies to each file and is not raised by retrying."
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 400 | NO_FILE |
No file was passed in the file field |
| 400 | INVALID_MIME |
The file name extension is not on the allowed list. A file without an extension is rejected unless its Content-Type is image/png, image/jpeg, image/webp, or image/gif |
| 400 | MIME_MISMATCH |
The contents do not match the type: an image signature is not recognized as PNG, JPEG, WebP, or GIF, or a .zip or .xlsx file lacks the ZIP archive signature. A mismatch between two image formats is not an error — processing follows the actual contents |
| 400 | INVALID_FILE_EMPTY |
The file is empty |
| 400 | IMAGE_DECODE_FAILED |
The file could not be parsed as an image: the data is corrupted, the image has no dimensions, or processing did not finish within 5 seconds |
| 400 | IMAGE_TOO_MANY_PIXELS |
More than 24 million pixels |
| 400 | TOO_MANY_FRAMES |
The animation has more than 30 frames |
| 402 | ACCOUNT_FROZEN |
The balance is frozen — uploading attachments is unavailable while it is frozen |
| 403 | MANAGEMENT_KEY_READ_ONLY |
A management key cannot upload attachments |
| 403 | WRITE_BLOCKED_READONLY_KEY |
The key is read-only — see access rights |
| 413 | IMAGE_TOO_LARGE |
File larger than 10 MB. The same code applies to images and documents |
| 401 | MISSING_API_KEY |
The X-Api-Key header was not passed |
| 429 | RATE_LIMITED |
More than 5 uploads per minute from one key. This is the platform-wide cap and it is divided across the backend replicas, so the effective value arrives in the X-RateLimit-Limit header |
For the full list of common API errors, see Errors.
Known specifics
An attachment lives for an hour before it is linked. An uploaded attachment is initially not linked to any ticket and is deleted after expiresAt (in one hour) if its id is not passed in attachmentIds when creating a ticket or comment. Once linked, the attachment is stored together with the ticket.
Format after processing. PNG, JPEG, and WebP are converted to WebP, so their mime in the response is image/webp. GIF is re-encoded as GIF (image/gif), preserving animation, up to 30 frames; beyond that the upload is rejected with 400 TOO_MANY_FRAMES. The sizeBytes, width, and height fields refer to the processed file. A document is stored byte for byte; its mime is determined by the extension, not the contents. An archive is never unpacked — for .zip and .xlsx only the ZIP signature is checked.
The attachment class is chosen by the file name extension, not by Content-Type. An image is processed by its actual contents, so a JPEG saved as image.png is accepted, and the mime in the response is the result of re-encoding. A file without an extension — for example blob, the default name FormData.append assigns to a Blob object — is accepted only as an image and only when its Content-Type is image/png, image/jpeg, image/webp, or image/gif.
Operations on a linked attachment
Once linked to a ticket, an attachment is available for download and deletion by its id (the data.attachments[].id field in Get a ticket). These operations work with binary data, so they have no JSON body and no standard set of 4 examples.
Download an attachment
GET /v1/feedback/:id/attachments/:attId/file
Returns the attachment file. Available to a key that has access to the ticket. An image arrives with its own type — image/webp or image/gif — and supports ETag and If-None-Match: a conditional request returns 304 Not Modified. A document always arrives as application/octet-stream with the Content-Disposition: attachment header and the original file name, without ETag.
| Parameter | Type | Required | Description |
|---|---|---|---|
id (path) |
string | yes | Ticket UUID |
attId (path) |
string | yes | Attachment UUID from data.attachments[].id |
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://vibecode.bitrix24.com/v1/feedback/a1b2c3d4-1111-2222-3333-444455556666/attachments/1209c405-1f99-4240-95e5-d5cc0cd43560/file" \
-o attachment.webp
If the attachment does not exist, is not accessible to the key, or does not belong to the specified ticket, the request returns 404 NOT_FOUND. If the balance is frozen, it returns 402 ACCOUNT_FROZEN.
Attachment thumbnail
GET /v1/feedback/:id/attachments/:attId/thumb
Returns a scaled-down thumbnail of an image. Content-Type is always image/jpeg. Parameters and access rules are the same as for the file download. A document has no thumbnail — the request returns 404 NOT_FOUND.
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://vibecode.bitrix24.com/v1/feedback/a1b2c3d4-1111-2222-3333-444455556666/attachments/1209c405-1f99-4240-95e5-d5cc0cd43560/thumb" \
-o thumb.jpg
Delete an attachment
DELETE /v1/feedback/:id/attachments/:attId
Deletes the attachment. Available to whoever uploaded it. On success, returns 204 No Content with an empty body. Deletion is not allowed if the other side has already replied after the attachment was uploaded.
| Parameter | Type | Required | Description |
|---|---|---|---|
id (path) |
string | yes | Ticket UUID |
attId (path) |
string | yes | Attachment UUID |
reason (body) |
string | no | Deletion reason, 3–500 characters |
curl -X DELETE -H "X-Api-Key: YOUR_API_KEY" \
"https://vibecode.bitrix24.com/v1/feedback/a1b2c3d4-1111-2222-3333-444455556666/attachments/1209c405-1f99-4240-95e5-d5cc0cd43560"
| HTTP | Code | Description |
|---|---|---|
| 402 | ACCOUNT_FROZEN |
The balance is frozen |
| 403 | DELETE_NOT_ALLOWED |
The other side has already replied after the attachment was uploaded |
| 403 | WRITE_BLOCKED_READONLY_KEY |
The key is read-only — see access rights |
| 400 | REASON_INVALID |
reason shorter than 3 or longer than 500 characters |
| 404 | NOT_FOUND |
The attachment was not found, is not owned by the key, or does not belong to the ticket |