Upload a file
POSThttps://api.wapito.com/v1/media
Send a channel token on every request: Authorization: Bearer wpt_YOUR_TOKEN
Uploads a file to Wapito storage and returns a media id you can reuse across sends, which is the right pattern when the same image or document goes to many recipients. The response also carries a signed link your own systems can fetch. Files larger than the plan cap are rejected with `payload_too_large` before anything is stored.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
data | string | Required | `data:<mimetype>;base64,<bytes>`. |
filename | string | Optional | File name shown to recipients. |
mimetype | string | Optional | Overrides the media type in the URI. |
{
"data": "data:application/pdf;base64,JVBERi0xLjQK…",
"filename": "Invoice-4182.pdf"
}Responses
201
The stored file.
| Field | Type | Required | Description |
|---|---|---|---|
channel_id | string | Required | Channel that owns the file. |
created_at | string | Required | ISO 8601 timestamp.date-time |
expires_at | string | Required | ISO 8601 timestamp after which the file and its signature are rejected.date-time |
filename | string | null | Required | Original file name. |
id | string | Required | Media id (`med_` + ULID). |
link | string | Required | Signed `/v1/media/{id}?exp&sig` URL.uri |
message_id | string | null | Required | Message the file was extracted from, when it came from an inbound message. |
mimetype | string | Required | IANA media type. |
size_bytes | integer | Required | File size in bytes. |
{
"channel_id": "ch_01JRQ8F4X9N2K7YB3C5V6W8H0T",
"created_at": "2026-09-15T07:22:01.000Z",
"expires_at": "2026-09-22T07:22:01.000Z",
"filename": "receipt.jpg",
"id": "med_01JRQ8F4X9N2K7YB3C5V6W8H0T",
"link": "https://api.wapito.com/v1/media/med_01JRQ8F4X9N2K7YB3C5V6W8H0T?exp=1790064121&sig=8f2c1d94b6a70e35",
"message_id": "false_15551234567@s.whatsapp.net_9F31A0C4D7E2B6081A55",
"mimetype": "image/jpeg",
"size_bytes": 184213
}400
The request body or query string is malformed, or the recipient cannot be parsed into a WhatsApp id.
Show 2 example bodies
invalid_recipient — `to` is not a dialable number or valid WhatsApp id
{
"error": {
"code": "invalid_recipient",
"details": {
"to": "+1555"
},
"message": "The recipient is not a valid WhatsApp address.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}invalid_request — schema validation failed
{
"error": {
"code": "invalid_request",
"details": {
"issues": [
{
"message": "Array must contain at least 2 element(s)",
"path": "body.options"
}
]
},
"message": "The request payload failed validation.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}401
The channel token is missing, malformed, revoked or belongs to a deleted channel.
Show 2 example bodies
token_revoked — the token was rotated in the dashboard
{
"error": {
"code": "token_revoked",
"message": "This channel token has been revoked.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}unauthorized — no or unusable Bearer token
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid channel token.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}403
The token is valid but the channel may not perform this action right now.
Show 2 example bodies
channel_locked — billing lapsed or the channel was locked by an operator
{
"error": {
"code": "channel_locked",
"details": {
"reason": "plan_required"
},
"message": "This channel is locked.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}forbidden — the channel does not own the target object
{
"error": {
"code": "forbidden",
"message": "You are not allowed to perform this action.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}413
The upload exceeds the media size cap for the plan.
Show the example body
payload_too_large — 16 MB on sandbox, 64 MB on premium
{
"error": {
"code": "payload_too_large",
"details": {
"max_bytes": 16777216
},
"message": "The request payload is too large.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}415
The file type cannot be sent as the requested message kind.
Show the example body
unsupported_media_type — wrong media type for the endpoint
{
"error": {
"code": "unsupported_media_type",
"details": {
"mimetype": "application/x-msdownload"
},
"message": "This media type is not supported.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}429
A rate limit, a plan quota or one of the anti-ban guards stopped the request. Every one of these is safe to retry later; read `Retry-After` when present.
Show 5 example bodies
cold_send_limit — too many first messages to new recipients this hour
{
"error": {
"code": "cold_send_limit",
"details": {
"resets_at": "2026-09-15T09:00:00.000Z",
"window_cap": 20
},
"message": "The hourly limit for messages to new recipients is reached.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}quota_exceeded — plan quota for the day or month
{
"error": {
"code": "quota_exceeded",
"details": {
"quota": "sent",
"resets_at": "2026-09-16T00:00:00.000Z",
"used": 150
},
"message": "The plan quota for this resource is exhausted.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}rate_limited — per-minute API rate limit
{
"error": {
"code": "rate_limited",
"details": {
"retry_after": 12
},
"message": "Too many requests.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}send_rate_limited — the send queue did not drain within 20 s
{
"error": {
"code": "send_rate_limited",
"details": {
"retry_after": 5
},
"message": "The send queue for this channel is saturated.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}warmup_limit — the warm-up ladder cap for today
{
"error": {
"code": "warmup_limit",
"details": {
"cap": 200,
"day": 2
},
"message": "The warm-up limit for this channel is reached.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}500
Something went wrong inside Wapito. Quote `request_id` when reporting it.
Show the example body
internal_error — unexpected failure
{
"error": {
"code": "internal_error",
"message": "Something went wrong on our side.",
"request_id": "req_01JRQ8F4X9N2K7YB3C5V6W8H0T"
}
}Errors
Code examples
import requests
url = "https://api.wapito.com/v1/media"
payload = {
"data": "data:application/pdf;base64,JVBERi0xLjQK…",
"filename": "Invoice-4182.pdf"
}
headers = {
"Authorization": "Bearer wpt_YOUR_TOKEN",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())Used in
Related
Other endpoints in this group
Reference
Try it on your own number
Create a channel, link a WhatsApp number by QR or pairing code, and call the API in a couple of minutes. The Sandbox plan is free and needs no card.