Para enviar uma imagem, um vídeo, um áudio ou um documento numa mensagem comum, você não precisa desta página: mande o link https do arquivo em POST /api/v1/messages, e a Meta o busca no envio. POST /api/v1/media existe para o que pede um arquivo já hospedado na Meta: o cabeçalho de mídia de um template (por media_id) e o exemplo de cabeçalho na criação de um template (por handle). Mensagem de mídia com media_id é recusada com 422 invalid_request.
Subir um arquivo: POST /api/v1/media
Escopo media:write, só com chave de produção (uma chave sandbox recebe 403 sandbox_forbidden). Dois formatos de entrada, com o mesmo resultado:
Por URL (JSON)
A BotoZap baixa o arquivo de source por você — o caminho para arquivos grandes, sem limite de corpo na requisição.
POST /api/v1/media
Authorization: Bearer bz_live_...
Content-Type: application/json
{
"phone_number_id": "106540352242922",
"source": "https://cdn.exemplo.com/catalogo-setembro.pdf",
"filename": "catalogo-setembro.pdf"
}Upload direto (multipart)
Para quem não tem onde hospedar: multipart/form-data com o arquivo em file. O corpo da requisição é limitado a cerca de 4,5 MB pela plataforma de hospedagem; acima disso, use source.
curl https://botozap.com.br/api/v1/media \
-H "Authorization: Bearer bz_live_..." \
-F phone_number_id=106540352242922 \
-F [email protected] \
-F filename=catalogo-setembro.pdf| Campo | O que é |
|---|---|
phone_number_id | Obrigatório. O id do Número na Meta (o phone_number_id de GET /api/v1/phone_numbers), não o UUID interno. O media_id fica atrelado a esse número: use-o em envios que saem dele. |
source (JSON) | URL https:// pública do arquivo. Destino interno ou privado é recusado (proteção contra SSRF), e cada redirecionamento é conferido de novo. |
file (multipart) | O arquivo, não vazio. |
delivery | meta_media (padrão) devolve um media_id; meta_resumable_asset devolve um handle para criar template. |
filename | Opcional. Sem ele, vale o nome do arquivo enviado ou o último trecho do caminho da URL. |
mime_type | Opcional, e só um reserva. Por URL manda o Content-Type que a origem respondeu; no multipart, o tipo declarado na parte do arquivo. Sem nenhum dos dois, o arquivo é tratado como application/octet-stream (documento). |
Limites por tipo
O tipo sai do MIME, e cada tipo tem o seu teto — os mesmos da Cloud API. Acima dele, 413 too_large.
| Tipo | Tamanho máximo |
|---|---|
| Imagem (image/*) | 5 MB |
| Áudio (audio/*) | 16 MB |
| Vídeo (video/*) | 16 MB |
| Documento (qualquer outro MIME) | 100 MB |
Resposta
HTTP/1.1 201 Created
{
"data": {
"ingest_id": "5d7c1a9e-2f0b-4c8e-9a61-3b2e7f4d1c05",
"target": { "kind": "meta_media", "media_id": "1234567890123456" },
"resource": {
"filename": "catalogo-setembro.pdf",
"mime_type": "application/pdf",
"size_bytes": 482133,
"sha256": "9f2c…",
"source_url": "https://cdn.exemplo.com/catalogo-setembro.pdf"
}
}
}target— o que você usa depois:{ "kind": "meta_media", "media_id": "..." }, ou, comdelivery: "meta_resumable_asset":
"target": { "kind": "meta_resumable_asset", "handle": "4::aW1hZ2UvanBlZw==:ARb…" }resource— o que foi recebido: nome, MIME, tamanho,sha256dos bytes e a URL de origem (nullno upload direto);ingest_id— um id para os seus logs. Não há rota para consultá-lo.
A Meta mantém o media_id por cerca de 30 dias. Guarde-o se for reusar o mesmo arquivo em vários envios; depois disso, suba de novo.
Onde o media_id e o handle servem
- media_id — no cabeçalho de mídia de um template, em
template.componentsdo envio (veja Template com variáveis):
{
"type": "header",
"parameters": [
{ "type": "document", "document": { "id": "1234567890123456", "filename": "catalogo-setembro.pdf" } }
]
}- handle — no exemplo do cabeçalho ao criar um template com cabeçalho de mídia, em
components[].example.header_handlede POST /api/v1/templates. A Meta exige esse exemplo para analisar o template.
Erros
| HTTP | code | Quando |
|---|---|---|
| 400 | invalid_json / invalid_multipart | Corpo ilegível no formato declarado. |
| 422 | missing_phone_number_id | Sem phone_number_id. |
| 422 | missing_source / missing_file | JSON sem source; multipart sem arquivo ou com arquivo vazio. |
| 422 | invalid_delivery | delivery diferente de meta_media e meta_resumable_asset. |
| 422 | blocked_source | A URL aponta para um destino não permitido (rede interna, endereço privado, http://). |
| 422 | too_many_redirects / fetch_failed | A origem redirecionou demais, ou o download falhou. |
| 413 | too_large | Acima de 100 MB em qualquer caso, ou acima do teto do tipo. |
| 422 | empty_source | A origem respondeu sem conteúdo. |
| 422 | invalid_phone_number | O phone_number_id não é de um Número da conta (um UUID interno também cai aqui). |
| 409 | not_connected | O Número não tem token de acesso: refaça a conexão. |
| 422 / 502 | meta_error | A Meta recusou (422) ou falhou (502) ao receber o arquivo. |
| 502 | upload_failed | Falha inesperada no envio à Meta. Pode repetir. |
Validação e download acontecem antes de olhar o número: um arquivo grande demais responde 413 mesmo com phone_number_id errado. Os erros comuns a toda a API estão em Erros.
Baixar a mídia que o contato mandou
Uma mensagem recebida de imagem, vídeo, áudio, documento ou sticker traz o id da mídia na Meta: no evento whatsapp.message.received ele vem no objeto do tipo (ex.: message.image.id), e em GET /api/v1/messages, em content.id. A BotoZap copia o arquivo para o próprio armazenamento logo depois que a mensagem chega; você o baixa por esse id, com escopo media:read:
GET /api/v1/media/1234567890123456
HTTP/1.1 200 OK
Cache-Control: private, no-store
{
"data": {
"id": "1234567890123456",
"mime_type": "image/jpeg",
"file_size": 184233,
"sha256": "2b8e…",
"download_url": "https://…assinada…",
"expires_at": "2026-09-25T15:02:11.000Z"
}
}download_url é assinada e vale 1 hora (expires_at); peça de novo quando precisar. Para baixar direto, GET /api/v1/media/{id}/download responde 302 para essa mesma URL. Nenhuma das duas respostas deve ser guardada em cache.
| HTTP | code | O que fazer |
|---|---|---|
| 202 | media_not_ready | A cópia ainda está em andamento. Espere o Retry-After (5 s) e consulte de novo. |
| 404 | media_not_found | O id não é de uma mídia recebida pela conta. |
| 413 | too_large | O arquivo passou de 100 MB e não foi copiado. |
| 502 | media_fetch_failed | A Meta não entregou o arquivo agora. Tente de novo mais tarde. |
| 503 | storage_unavailable | Armazenamento indisponível no momento. Tente de novo. |
Sandbox
Com chave bz_sandbox_, POST /api/v1/media não está disponível. As duas rotas de leitura respondem só para o id sintético sandbox-media-0001 — uma imagem image/svg+xml de 417 bytes, para você testar o download sem um contato real; qualquer outro id é 404 media_not_found.
