POSThttps://wabapi.com/api/v1/media
A template with a document, image or video header needs a public link to the file at send time. If you have no file server or CDN of your own, upload the file here and use the url that comes back as the header's link.
Request
Send the file as multipart/form-data, not JSON, with the same Authorization header as a send.
| Field | Type | Required | Description |
|---|---|---|---|
| file | file | yes | The file itself, as a multipart/form-data part. Its type and size must be in the accepted-files table. |
| filename | string | no | The name to store the file under, for example invoice-8821.pdf. Defaults to the uploaded file's own name. Anything other than letters, digits, dots, dashes and underscores becomes _, and the name is cut to 120 characters. Its extension decides the file type. |
Example
curl -X POST https://wabapi.com/api/v1/media \
-H "Authorization: Bearer wab_live_0123456789abcdef0123456789abcdef" \
-F file=@invoice-8821.pdf \
-F filename=invoice-8821.pdf{
"id": "Yk3t9QpL2vN8rD5wZa1cXg",
"url": "https://sin1.contabostorage.com/0a1b2c3d4e5f:wabapi/api-media/ten_7c2e91a04b3f4d8e9a1b5c6d/Yk3t9QpL2vN8rD5wZa1cXg/invoice-8821.pdf",
"filename": "invoice-8821.pdf",
"content_type": "application/pdf",
"size_bytes": 48213,
"created_at": "2026-09-01T09:59:30.000Z"
}| Field | Type | Required | Description |
|---|---|---|---|
| id | string | yes | The upload's id — 22 random characters, part of the url. |
| url | string | yes | The file's public address. Pass it as the link of a document, image or video header. |
| filename | string | yes | The name the file was stored under, after the clean-up described above. |
| content_type | string | yes | The file's media type, decided by its extension. |
| size_bytes | integer | yes | The file's size. |
| created_at | string | yes | When it was uploaded, ISO 8601 in UTC. |
Accepted files
| Kind | Extensions | Largest file |
|---|---|---|
| Image | jpg, jpeg, png | 5 MB |
| Video | mp4, 3gp | 16 MB |
| Audio | mp3, ogg, aac, amr, m4a, wav | 16 MB |
| Document | pdf, doc, docx, xls, xlsx, ppt, pptx, txt, csv | 50 MB |
The extension of the final filename decides the type, and the file's contents must match it: a .pdf that is not really a PDF is refused. The size limits are WhatsApp's own, so a file that uploads here is not then refused at send time for its size. GIF is not accepted; WhatsApp does not take GIF headers.
Use the url in a send
{
"to": "917021948630",
"template_name": "ticket_confirmation",
"template_language": "en",
"components": [
{
"type": "header",
"parameters": [
{
"type": "document",
"document": {
"link": "https://sin1.contabostorage.com/0a1b2c3d4e5f:wabapi/api-media/ten_7c2e91a04b3f4d8e9a1b5c6d/Yk3t9QpL2vN8rD5wZa1cXg/invoice-8821.pdf",
"filename": "invoice-8821.pdf"
}
}
]
},
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "Prajesh"
},
{
"type": "text",
"text": "Thane"
},
{
"type": "text",
"text": "04:00 PM"
}
]
}
]
}Cost and limits
Uploading is free: no credits are used, and it works before your first payment. Each API key may upload 30 files a minute; above that the API answers 429 rate_limited with a Retry-After header. The file is served from our file storage, so the url is not on wabapi.com.
Links are public and kept indefinitely: anyone with the url can open the file. The random id in the path makes the url unguessable, but it is not a password — so do not upload anything you would not send to that customer anyway.
Errors
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_request | The body is not valid JSON or fails the schema. |
| 401 | invalid_api_key | The Authorization header is missing, malformed, unknown or revoked. |
| 400 | unsupported_media_type | The uploaded file's type is not accepted, or its contents do not match its extension. Images: jpg, jpeg, png. Video: mp4, 3gp. Audio: mp3, ogg, aac, amr, m4a, wav. Documents: pdf, doc, docx, xls, xlsx, ppt, pptx, txt, csv. |
| 413 | file_too_large | The uploaded file is larger than WhatsApp allows for its type: 5 MB for images, 16 MB for video and audio, 50 MB for documents. |
| 429 | rate_limited | Too many sends on this key in the last minute. Retry after the `Retry-After` seconds. |
| 503 | storage_unavailable | File storage is unavailable right now, so the file was not saved. Nothing was charged; try again shortly. |
A failed upload stores nothing. Retrying the same file after an error is safe; each successful upload gets its own id and url.