## Upload an attachment

`POST /v1/feedback/attachments`

Uploads an image 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](./submit.md) or [adding a comment](./comments.md).

The file is passed as `multipart/form-data` in the `file` field. PNG, JPEG, WebP, and GIF are accepted. PNG, JPEG, and WebP are converted to WebP on the server side, GIF is re-encoded as GIF preserving animation. Limits: up to 10 MB and up to 24 million pixels per file, 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 PNG, JPEG, WebP, or GIF image, up to 10 MB |

## Examples

### curl — personal key

```bash
curl -X POST https://vibecode.bitrix24.com/v1/feedback/attachments \
  -H "X-Api-Key: YOUR_API_KEY" \
  -F "file=@screenshot.png"
```

### curl — OAuth application

```bash
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

```javascript
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

```javascript
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.id` | string | Attachment UUID. Passed in `attachmentIds` when creating a ticket or comment |
| `data.mime` | string | Type after processing: `image/webp` for PNG, JPEG, and WebP, `image/gif` for GIF |
| `data.sizeBytes` | number | Size of the processed file in bytes |
| `data.width` | number | Width in pixels |
| `data.height` | number | Height in pixels |
| `data.thumbnailUrl` | string | Path to the attachment thumbnail |
| `data.originalName` | string | Original file name |
| `data.expiresAt` | string | The moment after which an unlinked attachment is deleted (ISO 8601) |

## Response example

```json
{
  "success": true,
  "data": {
    "id": "1209c405-1f99-4240-95e5-d5cc0cd43560",
    "mime": "image/webp",
    "sizeBytes": 20480,
    "width": 1280,
    "height": 720,
    "thumbnailUrl": "/v1/feedback/_orphan/1209c405-1f99-4240-95e5-d5cc0cd43560/thumb",
    "originalName": "screenshot.png",
    "expiresAt": "2026-04-19T11:30:00.000Z"
  }
}
```

## Error response example

413 — file larger than 10 MB:

```json
{
  "success": false,
  "error": {
    "code": "IMAGE_TOO_LARGE",
    "message": "File too large"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|------|-------------|
| 400 | `NO_FILE` | No file was passed in the `file` field |
| 400 | `INVALID_MIME` | The format is not one of PNG, JPEG, WebP, GIF |
| 400 | `IMAGE_TOO_MANY_PIXELS` | More than 24 million pixels |
| 413 | `IMAGE_TOO_LARGE` | File larger than 10 MB |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |
| 429 | `RATE_LIMITED` | More than 5 uploads per minute from one key |

For the full list of common API errors, see [Errors](/docs/errors).

## Known specifics

**An attachment lives for an hour before linking.** 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. The `sizeBytes`, `width`, and `height` fields refer to the processed file.

## 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](./get.md)). 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. `Content-Type` is the attachment type (`image/webp` or `image/gif`). Supports `ETag` and `If-None-Match` (a conditional request answers `304 Not Modified`).

| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `id` (path) | string | yes | Ticket UUID |
| `attId` (path) | string | yes | Attachment UUID from `data.attachments[].id` |

```bash
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 — `404 NOT_FOUND`.

### Attachment thumbnail

`GET /v1/feedback/:id/attachments/:attId/thumb`

Returns a scaled-down thumbnail of the attachment. `Content-Type` is always `image/jpeg`. Parameters and access rules are the same as for the file download.

```bash
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, `204 No Content` with an empty body is returned. 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 |

```bash
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 |
|------|------|-------------|
| 403 | `DELETE_NOT_ALLOWED` | The other side has already replied after the attachment was uploaded |
| 400 | `REASON_INVALID` | `reason` shorter than 3 or longer than 500 characters |
| 404 | `NOT_FOUND` | The attachment was not found, does not belong to the key, or does not belong to the ticket |

## See also

- [Submit a ticket](/docs/feedback/submit)
- [Add a comment](/docs/feedback/comments)
- [Feedback](/docs/feedback)
- [Errors](/docs/errors)
