---
title: Attachments | API Docs
description: Send images, video, audio, documents and stickers by uploading bytes or naming a public URL, and download what customers send.
---

A `media` part carries one image, video, audio file, document or sticker. On a send you tell the API where the bytes are in one of two ways; on a read the part tells you where to fetch them. The three identifiers are separate namespaces and never overlap:

| Field        | Direction | Meaning                                                                                   |
| ------------ | --------- | ----------------------------------------------------------------------------------------- |
| `upload_ref` | send      | Your durable handle from `POST /v1/media`, reusable in this account and partition.        |
| `source_url` | send      | A public `https` address the API fetches once, before accepting the send.                 |
| `media_id`   | read only | The channel’s transient inbound identifier. Never accepted on a send.                     |
| `url`        | read only | The authenticated download address for a retained inbound file. Never accepted on a send. |

A sent part carries exactly one of `upload_ref` or `source_url`. Both, or neither, is refused with HTTP `422` and a JSON Pointer at the offending field.

## Option 1: upload, then send the handle

Post the exact bytes to `POST /v1/media` with their media type, then reference the returned `upload_ref` from the part. The handle is reusable across sends in the same account and test/live partition; it has no fixed expiry but is not permanent.

Terminal window

```
UPLOAD_REF=$(curl -sS -X POST "https://whatsapp.messages.api.linqapp.com/v1/media?kind=image" \
  -H "Authorization: Bearer ${LINQ_WHATSAPP_API_KEY}" \
  -H "Content-Type: image/png" \
  --data-binary @photo.png | jq -r .upload_ref)
```

```
{
  "to": "+15551234567",
  "parts": [{ "type": "media", "kind": "image", "upload_ref": "upload.0123456789abcdef0123456789abcdef" }]
}
```

Use this route for large files, for bytes you will send more than once, and whenever the file is not already reachable on the public internet.

## Option 2: name a public URL

If the file is already served from a public `https` host, put its address in `source_url` and skip the upload. The API fetches the address exactly once, before it accepts the send, and stores the exact bytes as an upload in your account. The accepted message — and every later transcript read and webhook — carries that upload’s `upload_ref`, never the address. No retry fetches the address again, and the address is never forwarded to the channel.

```
{
  "to": "+15551234567",
  "parts": [{ "type": "media", "kind": "image", "source_url": "https://cdn.example.com/menu.jpg" }]
}
```

The response’s part shows what was stored:

```
{
  "id": "msg_158ff7124ea56816e5006438177a773c",
  "status": "accepted",
  "parts": [{ "type": "media", "kind": "image", "upload_ref": "upload.4293e657b84aa6a0c561219cdee74b71" }]
}
```

Send with an `Idempotency-Key`: a replay of the same request under the same key returns the same message and the same `upload_ref` without fetching again, which is what makes a timed-out send safe to retry.

### What the address must satisfy

- Absolute `https` on the default port, with no credentials and no fragment.
- Resolves to a public destination — on every redirect hop, at most three. Private, loopback, link-local, multicast and cloud-metadata destinations are refused, whatever name resolves to them.
- Answers `200` with the complete bytes, their exact `Content-Type`, and no `Content-Encoding`, within the fetch deadline. The API never sniffs a media type from bytes and never decodes a compressed answer.
- The bytes satisfy the same media-type allowlist and size ceiling that `POST /v1/media` applies to the part’s `kind`. Nothing is converted or truncated.

### Refusals

Every refusal is HTTP `422` with `error.param` at `/parts/0/source_url`, except capacity, which is a `503` you may retry shortly.

| Code                                                                                       | Meaning                                                                                                                    |
| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| [5007](/channel/whatsapp/error/codes/5xxx/5007/index.md) `media_source_invalid`            | The address failed the policy: scheme, port, credentials, a non-public destination or too many redirects. Fix the address. |
| [5008](/channel/whatsapp/error/codes/5xxx/5008/index.md) `media_source_unavailable`        | The host was unreachable, timed out, answered something other than `200`, or delivered fewer bytes than promised.          |
| [5009](/channel/whatsapp/error/codes/5xxx/5009/index.md) `media_source_unsupported`        | The bytes do not fit the kind: wrong media type, over the ceiling, an unreadable sticker, or an encoded or untyped body.   |
| [5010](/channel/whatsapp/error/codes/5xxx/5010/index.md) `media_source_capacity_exhausted` | No fetch capacity was free. Retry shortly, or upload the bytes instead.                                                    |

For large or slowly served files, prefer the upload route: the fetch runs inside the send request and has a bounded deadline.

## Send a voice note

Set `"voice": true` on an outbound `audio` part to send it as a voice note, shown with a waveform and a play button. `false`, or leaving it out, sends an ordinary audio file.

Terminal window

```
curl -sS -X POST "https://whatsapp.messages.api.linqapp.com/v1/chats/${CHAT_ID}/messages" \
  -H "Authorization: Bearer ${LINQ_WHATSAPP_API_KEY}" \
  -H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"parts":[{"type":"media","kind":"audio","upload_ref":"upload.0123456789abcdef0123456789abcdef","voice":true}]}'
```

- The audio must be mono Opus in an OGG container, stored as `audio/ogg`: upload it to `POST /v1/media` with `kind=audio` and `Content-Type: audio/ogg`, or name a `source_url` that serves `audio/ogg`.
- The bytes are sent exactly as supplied — never converted or inspected — so encode them as mono Opus before uploading.
- A voice note of 512 KB or less shows a play button. A larger one still arrives as a voice note, but the recipient must download it before it plays. The ordinary audio size ceiling still applies.
- Voice notes are supported on sends to individual chats.

`voice: true` on an upload that is not stored as `audio/ogg`, `voice` on any kind other than `audio`, and `"voice": null` are all refused with HTTP `422`, `error.code` [1004](/channel/whatsapp/error/codes/1xxx/1004/index.md) `invalid_content`, and a JSON Pointer at the field, such as `/parts/0/voice`.

## Kinds, types and ceilings

`kind` selects the rules. Captions apply to image, video and document; a filename applies to documents only. The allowed media types and byte ceilings per kind are the upload operation’s, published in its `x-media-upload-profiles` extension in the [API reference](/channel/whatsapp/api/resources/media/index.md).

## Download incoming attachments

An incoming `media` part carries a response-only `url` that requires your account’s Bearer key. Its response-only `availability` says whether the download answers the bytes: `available`, or `unavailable` with an `unavailable_reason` (`timed_out`, `capture_failed` or `expired` today). A `message.received` event can be recorded once its attachments are captured or have failed, or ten minutes after the message arrived, so the file can be ready when the event arrives. This is being enabled gradually; a part with no `availability` may still be capturing.

The full procedure — including what each value means and the pending, expired and not-found answers — is in [Sending messages → Download incoming attachments](/channel/whatsapp/guides/messaging/sending-messages#download-incoming-attachments/index.md).

## Related

- [Sending messages](/channel/whatsapp/guides/messaging/sending-messages/index.md) — routes, the customer window and idempotency
- [Errors](/channel/whatsapp/guides/platform/errors/index.md) — the envelope and the code ranges
- [API reference: media](/channel/whatsapp/api/resources/media/index.md) and [attachments](/channel/whatsapp/api/resources/attachments/index.md)
