# Send media (image · document · audio · video)

`POST /v1/messages`

**Connect does not store files.** To send a file of your own, **host it and send its link** (`link`) — **Meta** downloads it, not us. That is the main path, not the fallback.

**Media identifiers (`id`) are the ones of files you RECEIVED**: they arrive inside an inbound message event (`data.message.image.id`, `.document.id`, …). **There is no upload endpoint** — `GET /v1/media/:mediaId` is download-only — so **there is no way to obtain an `id` for a file of yours**.

⚠ And the uncomfortable part, stated as it is: Meta **usually** lets you reuse an inbound identifier to reply **from the same number**, but **it does not guarantee it** and **we have not verified it against real Meta yet**. We neither claim it nor deny it. If you need it to work every time, use the link.

**Need to send ALWAYS the same file** — a price list, a how-to, a catalogue? **Host it and send the link.** That is the whole answer: there is no `id` of your own to store and reuse. And this is not a limitation being papered over — **we don't store files because we don't store content** (not your message text either). It is exactly what our privacy policy promises, applied here.

**How to send it.** POST /v1/messages with `type` `image`/`document`/`audio`/`video` and, inside that type's object, **`link` or `id` — exactly one of the two**. `caption` for image/document/video; `audio` takes **no** caption; `document` may add a `filename` (the name the recipient sees).

**What the link needs for Meta to accept it:** be **https** and **point straight at the file** (the response must be the bytes — no login, no interstitial page); carry the **right `Content-Type`** — if it doesn't match the file, Meta rejects it; and stay within **Meta's limits**: image JPEG/PNG **5 MB**, audio (aac · amr · mp3 · m4a · ogg _OPUS only_) **16 MB**, video mp4/3gpp **16 MB**, document (pdf · doc/docx · xls/xlsx · ppt/pptx · txt) **100 MB**.

⚠ **The most common trap: Google Drive, Dropbox and friends do NOT work.** A "share" link returns an **HTML interstitial**, not the file bytes: Meta downloads that page and rejects the send. Serve it from a URL that answers with the file itself.

## Examples

### cURL

```bash
curl -X POST https://api.waiaconnect.com/v1/messages \
  -H "Authorization: Bearer wc_live_YOUR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to":"5493511234567","type":"image","image":{"link":"https://example.com/product.jpg","caption":"New product 📦"}}'
# document: {"type":"document","document":{"link":"https://…/invoice.pdf","filename":"invoice.pdf","caption":"Your invoice"}}
# audio (no caption): {"type":"audio","audio":{"link":"https://…/note.ogg"}}
# video: {"type":"video","video":{"link":"https://…/demo.mp4","caption":"Demo"}}
# by media id instead of a link: {"image":{"id":"1234567890"}}
# ...but the only ids that exist are the ones you RECEIVED (from a message.received
# event); there is no upload endpoint. To send a file of your own, host it and link it.
```

### Node.js

```javascript
// Media is sent by { link } (Meta fetches the URL) or { id }. The only media ids that
// exist are the ones you RECEIVED: there is no upload endpoint. Host your own files.
const res = await fetch("https://api.waiaconnect.com/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": "Bearer wc_live_YOUR_API_KEY",
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
  "connectionId": "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "to": "5493511234567",
  "type": "image",
  "image": {
    "link": "https://example.com/product.jpg",
    "caption": "New product 📦"
  }
})
});
console.log(await res.json()); // { id: "msg_…", status: "queued" }
```

### PHP

```php
<?php
// Media is sent by ["link" => …] (Meta fetches it) or ["id" => …]. The only media ids
// that exist are the ones you RECEIVED: there is no upload endpoint. Host your own files.
$ch = curl_init("https://api.waiaconnect.com/v1/messages");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer wc_live_YOUR_API_KEY",
    "Idempotency-Key: " . bin2hex(random_bytes(16)),
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode(["connectionId" => "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to" => "5493511234567","type" => "image","image" => ["link" => "https://example.com/product.jpg","caption" => "New product 📦"]]),
]);
echo curl_exec($ch);
```

### Python

```python
import requests, uuid
# Media is sent by {"link": …} (Meta fetches it) or {"id": …}. The only media ids that
# exist are the ones you RECEIVED: there is no upload endpoint. Host your own files.
res = requests.post("https://api.waiaconnect.com/v1/messages",
  headers={
    "Authorization": "Bearer wc_live_YOUR_API_KEY",
    "Idempotency-Key": str(uuid.uuid4()),
  },
  json={"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to":"5493511234567","type":"image","image":{"link":"https://example.com/product.jpg","caption":"New product 📦"}})
print(res.json())  # {"id": "msg_…", "status": "queued"}
```

## Notes

- ⚠ An identifier that arrives in a webhook **expires after 7 days** (the ones Meta's upload API returns last 30). Once expired it is good neither for downloading nor for resending.
- Meta **caches your link for 10 minutes**. If you change the file behind the SAME URL, append a random query string (`?v=…`) or you'll send the old one.
- **If the link doesn't work you find out at two moments.** (1) On accept, as a 400 with its `code`: `MEDIA_LINK_OR_ID_REQUIRED` (you sent neither), `MEDIA_LINK_AND_ID` (you sent both), `MEDIA_LINK_INVALID` (not a well-formed https URL), `AUDIO_CAPTION_NOT_ALLOWED`.
- (2) If it passed validation but **Meta could not fetch it**, the rejection lands AFTER the 202: check `GET /v1/messages/:id` → `lastErrorCode` = `MEDIA_DOWNLOAD_FAILED` (unreachable link or interstitial page), `MEDIA_FILE_REJECTED` (format or size) or `MEDIA_TYPE_UNSUPPORTED`. What to do: serve the file directly, with its `Content-Type`, within the limits above.
- To **download** a file someone sent you, see "Download inbound media".
