Photos and documents are sent in three steps: ask for an upload link, upload the file straight to storage, then send the message. Your file never passes through our API servers, so uploads are fast even for large documents.
| Photos | Documents | |
|---|---|---|
| File types | JPG, PNG | Any (PDF, Word, Excel, ZIP, …) |
| Largest file | 16 MB | 100 MB |
| Cost | $0.007 | $0.007 |
Ask for an upload link#
https://api.waconnect.ai/v1/media/uploadsTell us what you're about to upload. You get back an uploadId and a one-time uploadUrl, valid for 10 minutes, that accepts exactly this file: the same type and the same size.
curl https://api.waconnect.ai/v1/media/uploads \
-H "Authorization: Bearer wc_live_your_key" \
-H "Content-Type: application/json" \
-d '{"kind":"DOCUMENT","mimeType":"application/pdf","size":84213}'{
"uploadId": "1e6e1b59-6ef6-4a8e-8aeb-8a21b5aa3c06",
"uploadUrl": "https://waconnect-media-prod.s3.us-east-1.amazonaws.com/pending/…",
"expiresAt": "2026-10-04T10:10:00.000Z"
}Body
kindIMAGE | DOCUMENTrequired- IMAGE shows as a photo in the chat; DOCUMENT as a file the recipient downloads.
mimeTypestringrequired- The file's type: image/jpeg or image/png for photos; any type for documents (application/octet-stream if unsure).
sizenumberrequired- The file's size in bytes.
Upload the file#
PUT the file's bytes to uploadUrl, with the same Content-Type you sent as mimeType. Don't add an Authorization header: the link itself is the permission.
curl -X PUT "UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @invoice-1042.pdfA 200 means the file is stored. A 403 means the link expired or the type or size didn't match what you asked for; start again from step 1.
Send the message#
Send it like a text, with media added. text is optional and becomes the caption.
curl https://api.waconnect.ai/v1/messages \
-H "Authorization: Bearer wc_live_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-1042" \
-d '{
"from": "+12125550147",
"to": "+14155550123",
"text": "Here is your invoice for order #1042.",
"media": { "uploadId": "UPLOAD_ID", "kind": "DOCUMENT", "fileName": "Invoice-1042.pdf" }
}'media
uploadIdstringrequired- From step 1. Each upload can be sent once.
kindIMAGE | DOCUMENTrequired- The same kind you asked the upload link for.
fileNamestringoptional- Documents: the name the recipient sees, e.g. Invoice-1042.pdf.
The answer is the usual message object, in PENDING status, with kind set to IMAGE or DOCUMENT and the file described in media. Delivery is reported by the same message.sent and message.status webhooks as texts.
{
"id": "01a0f4c2-8b1e-7a0c-9d3f-2e6b5a4c1d00",
"numberId": "01a0f44d-c969-75b1-955d-3841d817f711",
"direction": "OUTBOUND",
"contact": { "phone": "+14155550123", "name": null },
"kind": "DOCUMENT",
"text": "Here is your invoice for order #1042.",
"media": { "status": "READY", "mimeType": "application/pdf", "fileName": "Invoice-1042.pdf", "size": 84213, "width": null, "height": null },
"status": "PENDING",
"error": null,
"sentVia": "api",
"sentAt": "2026-10-04T10:00:00.000Z"
}Receive photos and documents#
Photos and documents your customers send arrive in the message.received webhook with kind set to IMAGE or DOCUMENT and the file described in media. The webhook is sent once the file is stored, so you can download it straight away:
https://api.waconnect.ai/v1/messages/:id/mediaIt redirects to a download link that works for 5 minutes. Add ?download=1 to download the file under its original name. Most HTTP clients follow the redirect for you; with cURL, add -L:
curl -L https://api.waconnect.ai/v1/messages/MESSAGE_ID/media \
-H "Authorization: Bearer wc_live_your_key" \
-o photo.jpgCall the endpoint each time you need the file, rather than saving the link: the address stays the same, and the link behind it is always fresh. If WhatsApp couldn't provide the file, media.status is FAILED and the endpoint answers 404. Videos, voice messages and stickers arrive as messages without a file (media is null).
Common errors#
| Status | Why |
|---|---|
400 | The photo isn't JPG or PNG, the upload expired or never finished, or neither text nor media was sent. |
402 | Not enough credits (CREDITS_REQUIRED). |
409 | That number isn't connected right now. |
413 | The file is larger than 16 MB (photos) or 100 MB (documents). |
422 | The recipient isn't on WhatsApp. Nothing is sent or charged. |
An upload that's never sent is deleted automatically after a day.