## Download an activity file

`GET /v1/activities/:activityId/files/:fileId/download`

Downloads a file attached to a CRM activity, including a call recording. The response is a binary stream with a `Content-Disposition` header.

This endpoint exists because an activity file is **not a Disk file**. `GET /v1/activities/:id` returns each file as `{ id, url }`, where the `id` lives in a separate identifier space that overlaps Disk object ids. Passing such an `id` to [Disk file download](/docs/entities/files/download) is not safe: it may well return a different file. And the address in the `url` field arrives with an empty authorization parameter, so requesting it returns the sign-in page with code `200` — not a file. This endpoint adds the authorization itself and returns the content.

## Parameters

| Parameter | In | Type | Req. | Description |
|----------|---|-----|:-----:|----------|
| `activityId` | path | number | yes | Activity ID. Get it from [`GET /v1/activities`](/docs/entities/activities/list) |
| `fileId` | path | number | yes | File ID from the activity `files` array. Get it from [`GET /v1/activities/:id`](/docs/entities/activities/get) with `files` included in `select` |

The request body is empty.

## Examples

### curl — personal key

```bash
# Save under the original name from the Content-Disposition header
curl -OJ -H "X-Api-Key: YOUR_API_KEY" \
  https://vibecode.bitrix24.com/v1/activities/4257/files/5387/download

# Set the file name explicitly
curl -H "X-Api-Key: YOUR_API_KEY" \
  https://vibecode.bitrix24.com/v1/activities/4257/files/5387/download \
  -o call-record.mp3
```

### curl — OAuth application

```bash
curl -OJ \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  https://vibecode.bitrix24.com/v1/activities/4257/files/5387/download
```

### JavaScript — personal key

```javascript
const res = await fetch(
  'https://vibecode.bitrix24.com/v1/activities/4257/files/5387/download',
  { headers: { 'X-Api-Key': 'YOUR_API_KEY' } },
)

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

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

### JavaScript — OAuth application

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

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

## Response headers

On success the binary file content is returned (HTTP 200). There is no `## Response fields` section — the response body is not JSON.

| Header | Example value | Description |
|-----------|-----------------|----------|
| `Content-Type` | `audio/mpeg` | Content type exactly as Bitrix24 returned it |
| `Content-Disposition` | `attachment; filename="call-record.mp3"` | File name to save under |
| `Content-Length` | `22509` | Size in bytes when Bitrix24 reported it |

## Error response example

404 — the file does not belong to this activity:

```json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "File 999999 does not belong to activity 4257"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_PARAMS` | `activityId` or `fileId` is not a positive integer |
| 401 | `TOKEN_MISSING` | The key has no configured Bitrix24 tokens |
| 403 | `SCOPE_DENIED` | The key lacks the `crm` scope |
| 404 | `NOT_FOUND` | The activity does not exist, the file does not belong to it, or the file has no download address |
| 502 | `DOWNLOAD_FAILED` | Bitrix24 named an address outside the account domain (no request is sent to such an address), or did not return the file. This includes answering with a page instead of the content, which means the key could not be authorized for that file |

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

## Known specifics

- **The file must belong to the named activity.** Otherwise `404`, even when the file exists. The check is not pedantry: Bitrix24 answers a foreign file with `200` and a sign-in page, so without it you would receive HTML posing as the file.
- **Permissions are not bypassed.** Reading the activity IS the access check: a key without access to the activity gets a refusal. Bitrix24 additionally verifies the user's access to the activity on its side.
- **The address is never returned.** The download address contains an authorization code, so the endpoint returns bytes only.
- **Files are not always present in the activity response.** In the [activity list](/docs/entities/activities/list) the `files` field is returned only when named in `select`. For a [single activity](/docs/entities/activities/get) it arrives with no extra conditions.

## See also

- [Get an activity](/docs/entities/activities/get)
- [Download a timeline comment attachment](/docs/entities/timelines/file-download)
- [Download a Disk file](/docs/entities/files/download)
- [Activities](/docs/entities/activities)
