## Download object

`GET /v1/storage/objects/:key`

Returns the object's content or a temporary download link. The query parameters determine the behavior: a redirect by default, or the content in the response body with `?inline=true`.

## Parameters

**Path**

| Parameter | Type | Description |
|----------|-----|---------|
| `key` | string | Object key. The `/` characters in the key must be encoded: `users%2F42%2Favatar.png` |

**Query**

| Parameter | Type | Default | Description |
|----------|-----|-----------|---------|
| `inline` | boolean | `false` | `true` or `1` — return the content directly in the response body (HTTP 200) instead of a redirect |
| `inline_render` | boolean | `false` | Only together with `?inline=true`. `true` or `1` — removes the `Content-Disposition` header so the browser displays the file instead of downloading it |
| `download` | string | — | File name. When following the link, the browser will prompt to save the file with the given name |

## Examples

### curl — personal key

```bash
curl -L "https://vibecode.bitrix24.com/v1/storage/objects/reports%2F2024%2Fannual.pdf" \
  -H "X-Api-Key: YOUR_API_KEY" \
  --output annual.pdf
```

### curl — OAuth application

```bash
curl -L "https://vibecode.bitrix24.com/v1/storage/objects/reports%2F2024%2Fannual.pdf" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  --output annual.pdf
```

### JavaScript — personal key

```javascript
// Get a temporary link (without following the redirect)
const res = await fetch(
  'https://vibecode.bitrix24.com/v1/storage/objects/reports%2F2024%2Fannual.pdf',
  {
    redirect: 'manual',
    headers: { 'X-Api-Key': 'YOUR_API_KEY' },
  }
)
const url = res.headers.get('location') // temporary link: 10 minutes requested, the signature may expire earlier
```

### JavaScript — OAuth application

```javascript
const res = await fetch(
  'https://vibecode.bitrix24.com/v1/storage/objects/reports%2F2024%2Fannual.pdf',
  {
    redirect: 'manual',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
    },
  }
)
const url = res.headers.get('location')
```

## Response fields

The response format depends on the query parameters.

**Default — redirect**

Returns `302 Found` with a temporary link in the `Location` header. The requested link lifetime is 10 minutes and the signature may expire earlier: follow it right away instead of storing it. Browsers follow the redirect automatically; in server-side code use `redirect: 'manual'` and read `response.headers.get('location')`.

**`?inline=true` — content in the response body**

Returns `200 OK` with the object's content directly in the response body. By default the browser saves the file (the `Content-Disposition: attachment` header). To have the browser display the file instead of downloading it, add `&inline_render=true`.

**`?download=<name>` — download with a given name**

Returns `302 Found`. When the client follows the link from the `Location` header, the storage serves the file with the `Content-Disposition: attachment; filename="<name>"` header, so the browser prompts the user to save it under the given name.

## Response example

Default — a redirect with no response body:

```http
HTTP/1.1 302 Found
Location: <temporary link>
Cache-Control: no-store
Content-Length: 0
```

`?inline=true` — the object's content in the response body:

```http
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 303
ETag: "dbaa5d4c5e4a709133830a8facd9fec8"
X-Content-Type-Options: nosniff
Content-Disposition: attachment; filename="annual.pdf"

<object content, 303 bytes>
```

`?download=annual-report.pdf` — a redirect with the file name carried in the link:

```http
HTTP/1.1 302 Found
Location: <temporary link with the file name annual-report.pdf>
Cache-Control: no-store
Content-Length: 0
```

## Error response example

404 — object not found:

```json
{
  "success": false,
  "error": {
    "code": "STORAGE_OBJECT_NOT_FOUND",
    "message": "Object reports/2024/annual.pdf not found"
  }
}
```

## Errors

The method is limited to 600 requests per minute per Bitrix24 account. All API keys of
the account share this budget.
The current limit is returned in `x-ratelimit-limit`; after a `429`, retry
after the delay in `Retry-After`.

| HTTP | Code | Description |
|------|-----|---------|
| 401 | `MISSING_API_KEY` | API key not passed |
| 403 | `STORAGE_SCOPE_REQUIRED` | The API key has no `vibe:storage` scope |
| 404 | `STORAGE_OBJECT_NOT_FOUND` | No object with the given key was found |
| 410 | `STORAGE_OBJECT_DELETED` | The object was deleted |
| 429 | `RATE_LIMITED` | The portal object-read budget is exhausted |
| 502 | `STORAGE_OBJECT_STREAM_FAILED` | Error while transferring the object's content (only for `?inline=true` mode) |

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

- **An app key with no employee session sees only the app-shared file.** Employees of an app claim
  logical names independently of each other, so several objects may share the same name. A request made with
  an app key and NO `Authorization` header addresses the app-shared file; if the name is held solely by
  per-employee files, the response is `404 STORAGE_OBJECT_NOT_FOUND`. To reach an employee's file, pass
  that employee's session in `Authorization: Bearer`, or address the object by its identifier.

## See also

- [Objects](/docs/storage/objects)
- [List objects](/docs/storage/objects/list)
- [Object metadata](/docs/storage/objects/head)
- [Upload](/docs/storage/upload)
