Attachments
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
Section titled “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.
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
Section titled “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
Section titled “What the address must satisfy”- Absolute
httpson 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
200with the complete bytes, their exactContent-Type, and noContent-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/mediaapplies to the part’skind. Nothing is converted or truncated.
Refusals
Section titled “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 media_source_invalid |
The address failed the policy: scheme, port, credentials, a non-public destination or too many redirects. Fix the address. |
5008 media_source_unavailable |
The host was unreachable, timed out, answered something other than 200, or delivered fewer bytes than promised. |
5009 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 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.
Kinds, types and ceilings
Section titled “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.
Download incoming attachments
Section titled “Download incoming attachments”An incoming media part carries a response-only url that requires your
account’s Bearer key. The full procedure — including the pending, expired and
not-found answers — is in
Sending messages → Download incoming attachments.
Related
Section titled “Related”- Sending messages — routes, the customer window and idempotency
- Errors — the envelope and the code ranges
- API reference: media and attachments