Mídia

Dois caminhos: subir um arquivo para a Meta e usar o media_id no cabeçalho de template, e baixar o arquivo que o contato mandou.

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
CampoO que é
phone_number_idObrigató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.
deliverymeta_media (padrão) devolve um media_id; meta_resumable_asset devolve um handle para criar template.
filenameOpcional. Sem ele, vale o nome do arquivo enviado ou o último trecho do caminho da URL.
mime_typeOpcional, 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.

TipoTamanho 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, com delivery: "meta_resumable_asset":
"target": { "kind": "meta_resumable_asset", "handle": "4::aW1hZ2UvanBlZw==:ARb…" }
  • resource — o que foi recebido: nome, MIME, tamanho, sha256 dos bytes e a URL de origem (null no 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.components do 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_handle de POST /api/v1/templates. A Meta exige esse exemplo para analisar o template.

Erros

HTTPcodeQuando
400invalid_json / invalid_multipartCorpo ilegível no formato declarado.
422missing_phone_number_idSem phone_number_id.
422missing_source / missing_fileJSON sem source; multipart sem arquivo ou com arquivo vazio.
422invalid_deliverydelivery diferente de meta_media e meta_resumable_asset.
422blocked_sourceA URL aponta para um destino não permitido (rede interna, endereço privado, http://).
422too_many_redirects / fetch_failedA origem redirecionou demais, ou o download falhou.
413too_largeAcima de 100 MB em qualquer caso, ou acima do teto do tipo.
422empty_sourceA origem respondeu sem conteúdo.
422invalid_phone_numberO phone_number_id não é de um Número da conta (um UUID interno também cai aqui).
409not_connectedO Número não tem token de acesso: refaça a conexão.
422 / 502meta_errorA Meta recusou (422) ou falhou (502) ao receber o arquivo.
502upload_failedFalha 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.

HTTPcodeO que fazer
202media_not_readyA cópia ainda está em andamento. Espere o Retry-After (5 s) e consulte de novo.
404media_not_foundO id não é de uma mídia recebida pela conta.
413too_largeO arquivo passou de 100 MB e não foi copiado.
502media_fetch_failedA Meta não entregou o arquivo agora. Tente de novo mais tarde.
503storage_unavailableArmazenamento 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.