## Download a task comment attachment

`GET /v1/tasks/:taskId/comments/:id/files/:fileId/download`

Downloads a file attached to a task comment. The response is the file content as bytes rather than JSON, with the file name in a response header.

This endpoint exists because the file address is never exposed: it carries an authorization code, so requesting such an address from a third-party application returns the sign-in page with code `200` instead of the file. The endpoint adds the authorization itself. The `task` scope is enough — there is no need to extend the key's permissions to all of Drive, and the endpoint returns only a file from a comment the key can already read.

## Parameters

| Parameter | Type | Req. | Description |
|----------|-----|:-----:|----------|
| `taskId` (path) | number | yes | Task ID. Get it from [`GET /v1/tasks`](/docs/entities/tasks) |
| `id` (path) | number | yes | Comment ID. Get it from [`GET /v1/tasks/:taskId/comments`](./list.md) or from the [`POST /v1/tasks/:taskId/comments`](./create.md) response |
| `fileId` (path) | number | yes | The `attachments[].fileId` value from the comment response — [`GET /v1/tasks/:taskId/comments/:id`](./get.md). The same full path is already in `attachments[].downloadUrl` — use it instead of assembling the address piece by piece |


## Examples

### curl — personal key

```bash
# Save under the original name from the response header
curl -OJ -H "X-Api-Key: YOUR_API_KEY" \
  https://vibecode.bitrix24.com/v1/tasks/3711/comments/9393/files/6687/download

# Set the file name explicitly
curl -H "X-Api-Key: YOUR_API_KEY" \
  https://vibecode.bitrix24.com/v1/tasks/3711/comments/9393/files/6687/download \
  -o attachment.jpg
```

### curl — OAuth app

```bash
curl -OJ \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  https://vibecode.bitrix24.com/v1/tasks/3711/comments/9393/files/6687/download
```

### JavaScript — personal key

```javascript
const res = await fetch(
  'https://vibecode.bitrix24.com/v1/tasks/3711/comments/9393/files/6687/download',
  { headers: { 'X-Api-Key': 'YOUR_API_KEY' } },
)

if (!res.ok) {
  const { error } = await res.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const bytes = await res.arrayBuffer()
console.log('bytes received:', bytes.byteLength)
```

### JavaScript — OAuth app

```javascript
const res = await fetch(
  'https://vibecode.bitrix24.com/v1/tasks/3711/comments/9393/files/6687/download',
  {
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
    },
  },
)

const bytes = await res.arrayBuffer()
```

## Response headers

On success the file content is returned with code `200`. The response body is the file bytes rather than JSON, so the headers are described instead of response fields.

| Header | Example value | Description |
|-----------|-----------------|----------|
| `Content-Type` | `image/jpeg` | Content type as Bitrix24 returned it |
| `Content-Disposition` | `attachment; filename="ava555.jpg"` | File name to save under |
| `Content-Length` | `405559` | Size in bytes when Bitrix24 reported it |

## Error response example

404 — the file does not belong to this comment:

```json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "File 6687 does not belong to comment 9395"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_PARAMS` | `taskId`, `id` or `fileId` is not a positive integer. No request reaches Bitrix24 in this case |
| 401 | `TOKEN_MISSING` | The key has no configured Bitrix24 tokens |
| 403 | `SCOPE_DENIED` | The key lacks the `task` scope |
| 404 | `NOT_FOUND` | No comment with this `id` exists in the named task. The same response is returned when the task itself does not exist |
| 404 | `NOT_FOUND` | The file exists but belongs to another comment, or it has no download address |
| 422 | `BITRIX_ERROR` | Bitrix24 refused one of the calls behind this operation and named no more specific condition |
| 429 | `RATE_LIMITED` | This code comes from two sources. The first is the operation's own limit, 30 requests per minute per key: take the effective value from the `X-RateLimit-Limit` header, and the remaining count and the reset time from `X-RateLimit-Remaining` and `X-RateLimit-Reset` — they arrive with every response of this operation, refusals included, and the refusal itself also carries `Retry-After` with the seconds until the window resets. The second is Bitrix24 rejecting a call under its own rate limit: that response carries the `Retry-After: 2` header |
| 503 | `BITRIX_TIMEOUT` | Bitrix24 accepted a call behind this operation but did not answer within the time limit. This operation is a read, so retrying is safe. Header `Retry-After: 10` |
| 502 | `DOWNLOAD_FAILED` | The request for the file bytes returned a non-2xx status (including when Bitrix24 is temporarily unavailable for the file delivery itself), or Bitrix24 named an address outside the account domain — no request is sent to such an address |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 answered a REST call behind this operation — the comment lookup or the file details request — with a 5xx. The status is the same as for `DOWNLOAD_FAILED`; tell them apart by `error.code`. Both outcomes are temporary and safe to retry |

The full list of common API errors — [Errors](/docs/errors).

## Known specifics

- **The file must belong to the named comment.** Otherwise you get a `404`, even when the file exists and is reachable in another comment. The check is not pedantry: without it a numeric match on the identifier would hand over someone else's file.
- **A matching number means nothing.** Chat comments and records in the comments block of the old card keep their identifiers in separate spaces, and those spaces overlap numerically. So the reliable source of `fileId` is the response for that same comment, not a number taken from somewhere else.
- **The path in `downloadUrl` arrives without a domain.** The field points at this operation rather than at the file in Bitrix24. Join the path with the same API base address you called — the response does not repeat the domain.

## See also

- [Get comment](./get.md)
- [List comments](./list.md)
- [Comment fields](./fields.md)
- [Task comments](/docs/entities/task-comments)
- [Tasks](/docs/entities/tasks)
