wapp-api — envio de mídia

Complementa docs/api.md. Autenticação, códigos de erro e consulta de status são os mesmos de lá.

Como mandar

Mesma rota do texto, POST /api/mensagens, mas em multipart/form-data em vez de JSON. O arquivo vai na parte arquivo; os demais campos vão como campos de formulário.

Multipart e não base64 no JSON de propósito: o upload é derramado para arquivo temporário e a memória do servidor não acompanha o tamanho do anexo. O base64 acontece só no último salto, do worker para a Evolution — o arquivo nunca é exposto por URL.

Campo Obrigatório Observação
arquivo sim a parte com o arquivo em si
instancia sim nome da instância
numero sim mesma normalização do texto
tipo sim imagem, documento, video ou audio
conteudo não legenda (imagem/vídeo/documento)
nome_arquivo não nome exibido; sem ele usa o nome do upload
nota_de_voz não só para tipo=audio; padrão true
agendado_para não ISO 8601

O tipo precisa bater com o mimetype real do arquivo. Se mandar um PDF dizendo tipo=imagem, a resposta é 400 e nada é gravado.

Tipos aceitos

mimetype tipo Extensão
application/pdf documento .pdf
image/jpeg imagem .jpg
image/png imagem .png
video/mp4 video .mp4
audio/ogg, audio/mpeg, audio/mp4 audio .ogg, .mp3, .m4a

Qualquer outro mimetype: 400.

Áudio: nota de voz ou anexo

  • nota_de_voz=true (padrão) → bolha de nota de voz, com onda sonora. A conversão para ogg/opus é feita pela Evolution.
  • nota_de_voz=false → anexo de áudio comum, com player de arquivo.

Tamanho

Teto de 16 MB por arquivo, que é o limite do WhatsApp para mídia tocada dentro da conversa. Acima disso: 400.

O corpo inteiro da requisição é limitado a 20 MB (a folga é o overhead do multipart). Passar disso devolve 413, com limite_bytes na resposta.

Vídeo acima de 16 MB no WhatsApp só existe enviado como documento, e aí perde o player — vira anexo para baixar. Se precisar disso, fale comigo antes: muda o desenho.

Exemplo em Python

import os
import requests

API = 'http://wapp-api.local'
CABECALHOS = {'X-API-Key': os.environ['WAPP_API_KEY']}

with open('boleto.pdf', 'rb') as arquivo:
    resposta = requests.post(
        f'{API}/api/mensagens',
        headers=CABECALHOS,
        data={
            'instancia': 'principal',
            'numero': '(11) 99999-8888',
            'tipo': 'documento',
            'conteudo': 'Segue seu boleto.',
            'nome_arquivo': 'boleto-agosto.pdf',
        },
        files={'arquivo': ('boleto.pdf', arquivo, 'application/pdf')},
        timeout=60,
    )

resposta.raise_for_status()
print(resposta.json()['id'])

Nota de voz é a mesma chamada com 'tipo': 'audio' e o arquivo de áudio.

Exemplo em curl

curl -X POST http://wapp-api.local/api/mensagens \
  -H "X-API-Key: $WAPP_API_KEY" \
  -F 'instancia=principal' \
  -F 'numero=(11) 99999-8888' \
  -F 'tipo=documento' \
  -F 'conteudo=Segue seu boleto.' \
  -F 'arquivo=@boleto.pdf;type=application/pdf'

Onde o arquivo fica

Em disco no servidor, sob MIDIA_DIR/AAAA/MM/<uuid>.<ext>, modo 0640 e nunca servido pelo Apache. O banco guarda só os metadados (arquivo_caminho, mimetype, tamanho_bytes).

Ainda não existe rotina de expurgo: os arquivos ficam até serem apagados à mão. A coluna arquivo_expurgado_em já está no schema para quando ela for escrita — é o que vai distinguir "a limpeza apagou" de "o arquivo se perdeu". Se o worker tentar enviar um anexo que sumiu, a mensagem vai para falhou com o motivo explícito.