# Upload a file

> Uploads a picture, video, audio file, PDF or contact card (.vcf) of up to 25 MB to send.

`POST /v1/files`

Uploads a picture, video, audio file, PDF or contact card (.vcf) of up to 25 MB to send. Send the answer’s `id` in `files` on a send. A file nobody sends is deleted after a day.

Needs a key with the `messages:send` scope. [Keys and scopes](https://usebluebot.com/docs/concepts/api-keys)

Send an `Idempotency-Key` so a retry never acts twice. [Idempotency](https://usebluebot.com/docs/concepts/idempotency)

## Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | No | Any string of up to 255 characters, new for each request. A retry with the same key gets the first answer back for 24 hours, with `Idempotent-Replayed: true`. Up to 255 characters |

## Request body

Send it as `multipart/form-data`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file` | file | Yes | The file, up to 25 MB: a picture, video, audio, a PDF or a contact card (.vcf). |
| `name` | string | No | A name to send it under, if not the file’s own. Up to 255 characters |

## Example

```bash
curl -X POST "https://usebluebot.com/api/v1/files" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Idempotency-Key: 0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70" \
  -F "file=@house.jpg"
```

```ts
import BlueBot from '@usebluebot/sdk';
import { openAsBlob } from 'node:fs';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const file = await client.files.upload(await openAsBlob('house.jpg'), { filename: 'house.jpg' }, { idempotencyKey: '0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70' });
console.log(file);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

file = client.files.upload(
    open("house.jpg", "rb"),
    idempotency_key="0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70",
)
print(file)
```

## Response

`201`: The file.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | A file_ id. Starts with `file_` |
| `object` | "file" | Yes |   |
| `name` | string or null | Yes | The file name, as it was uploaded or as Messages named it. |
| `content_type` | string | Yes | The media type. |
| `size` | integer | Yes | Bytes. |
| `kind` | string | Yes | What the file is, for showing it: an image, a video, audio, a voice memo, a PDF, a contact card or another file. One of `image`, `video`, `audio`, `voice_memo`, `pdf`, `contact_card`, `file` |
| `status` | string | Yes | `ready` when BlueBot has the bytes; `waiting` while the Mac uploads them (usually seconds); `too_large` for an attachment over 25 MB, which the Mac never uploads; `deleted` once 30 days have passed. One of `ready`, `waiting`, `too_large`, `deleted` |
| `url` | string or null | Yes | Reads the file for an hour. Null unless `status` is `ready`. |
| `url_expires_at` | timestamp or null | Yes | When `url` stops working. |
| `transcript` | string or null | Yes | For a voice memo, the words the Mac transcribed on the device, when the owner turned transcription on. |
| `transcript_language` | string or null | Yes | The transcript’s language, such as `en-US`. |
| `created_at` | timestamp | Yes | When BlueBot first knew of the file. |

The answer, `201`:

```json
{
  "id": "file_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "file",
  "name": "listing-412-oak.jpg",
  "content_type": "image/jpeg",
  "size": 482113,
  "kind": "image",
  "status": "ready",
  "url": "https://usebluebot.com/files/f/2Bf8QzD4kJmA6nXwEuTr1c?sig=…",
  "url_expires_at": "2026-11-13T20:41:00.000Z",
  "transcript": null,
  "transcript_language": null,
  "created_at": "2026-11-13T19:40:00.000Z"
}
```

## Errors

Every refusal has the same envelope and a page of its own. These are the ones this operation can give.

| Code                                                            | Status | What it means                                                                                                                                    |
| --------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`unauthorized`](https://usebluebot.com/docs/errors/unauthorized)                     | 401    | The request carried no key, or a key BlueBot doesn’t know: mistyped, revoked, or a test key sent to the live API.                                |
| [`insufficient_scope`](https://usebluebot.com/docs/errors/insufficient_scope)         | 403    | The key doesn’t carry the scope the endpoint needs. Each endpoint’s reference names its scope.                                                   |
| [`rate_limited`](https://usebluebot.com/docs/errors/rate_limited)                     | 429    | Too many requests. A key may make 120 requests a minute. The `RateLimit` header says how many are left, and `Retry-After` how long to wait.      |
| [`internal`](https://usebluebot.com/docs/errors/internal)                             | 500    | Something went wrong on BlueBot’s side. It was reported. Send the request again; with an Idempotency-Key that is always safe.                    |
| [`invalid_request`](https://usebluebot.com/docs/errors/invalid_request)               | 400    | Something in the request doesn’t fit: a missing or malformed field, a bad id, or a combination the endpoint refuses. `details` names each field. |
| [`payload_too_large`](https://usebluebot.com/docs/errors/payload_too_large)           | 413    | The request is bigger than the endpoint takes.                                                                                                   |
| [`unsupported_media_type`](https://usebluebot.com/docs/errors/unsupported_media_type) | 415    | The body isn’t a type the endpoint reads: JSON for most, a multipart form for file uploads, and only the file types listed for files.            |
| [`plan_lapsed`](https://usebluebot.com/docs/errors/plan_lapsed)                       | 402    | The account’s plan isn’t active, so BlueBot isn’t taking new texts. Texts already queued wait and go once the plan is active again.              |
| [`idempotency_conflict`](https://usebluebot.com/docs/errors/idempotency_conflict)     | 409    | The Idempotency-Key was used in the last 24 hours with a different request. A key stands for one request.                                        |
| [`conflict`](https://usebluebot.com/docs/errors/conflict)                             | 409    | The request doesn’t fit the thing’s current state, or another request with the same Idempotency-Key is still running.                            |

## Try it
