Media
Endpoints para gerenciamento de mídia: download de inbound, upload para Meta, transcrição (Whisper) e OCR.
Base path: /v1/media
Modelo Media
Quando o gateway recebe uma mensagem com mídia (image, video, audio, document), ele:
- Baixa do servidor de origem (Meta CDN ou Baileys S3).
- Faz upload para o S3 interno (bucket configurado por env).
- Cria registro
MediaObjectcom TTL 7 dias. - No evento
message:received, devolve omediaUrlapontando paraGET /v1/media/:id(proxy do gateway, não o link cru).
{
"id": "med-1234-5678-...",
"messageId": "ABCD1234...",
"contentType": "image/jpeg",
"sizeBytes": 234567,
"filename": "foto.jpg",
"expiresAt": "2026-05-28T14:00:00.000Z",
"ackedAt": null,
"deletedAt": null
}
TTL e ACK pattern:
- Mídia que não recebe ACK em 7 dias é deletada automaticamente (S3 lifecycle policy).
- Cliente pode fazer ACK explícito (
POST /:id/ack) após baixar — gateway deleta na hora. - Vencimento dispara evento
media:expiredno webhook.
GET /v1/media/:id
Redireciona para signed URL S3 (TTL 10min). Use em <img src>, <audio src>, ou clientes HTTP normais.
curl -L https://wpp.ogmma.com.br/v1/media/med-1234-... \
-H "X-API-Key: ak_live_..." \
-o foto.jpg
Response 302 — Location: https://s3.amazonaws.com/...?X-Amz-Signature=...
Errors
| HTTP | Code | Quando |
|---|---|---|
| 404 | MEDIA_NOT_FOUND | ID inexistente ou de outra account |
| 410 | MEDIA_EXPIRED | TTL 7d passou |
| 410 | MEDIA_DELETED | ACK já fez delete explícito |
GET /v1/media/:id/info
Metadata sem fazer download.
curl https://wpp.ogmma.com.br/v1/media/med-1234-.../info \
-H "X-API-Key: ak_live_..."
Response 200 — mesmo objeto Media descrito acima.
POST /v1/media/:id/ack
ACK explícito: marca como ackedAt + dispara delete S3. Use depois de baixar e persistir do seu lado.
curl -X POST https://wpp.ogmma.com.br/v1/media/med-1234-.../ack \
-H "X-API-Key: ak_live_..."
Response 204 (idempotente — chamadas múltiplas retornam 204)
POST /v1/media/upload
Upload de mídia para o Meta (gera metaMediaId reutilizável). Use quando você quer mandar a mesma mídia para vários destinatários sem rebaixar — cache de envio.
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
X-API-Key | sim | — |
X-Phone-Id | sim | UUID do phone WABA |
Content-Type | sim | application/json |
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
mediaUrl | URL | sim | URL pública HTTPS de onde baixar |
mimeType | string | sim | Ex.: image/jpeg |
filename | string | não | 255 chars |
curl -X POST https://wpp.ogmma.com.br/v1/media/upload \
-H "X-API-Key: ak_live_..." \
-H "X-Phone-Id: 5f7b2e1c-..." \
-H "Content-Type: application/json" \
-d '{
"mediaUrl": "https://cdn.acme.com/foto.jpg",
"mimeType": "image/jpeg",
"filename": "foto.jpg"
}'
Response 201
{ "metaMediaId": "1234567890" }
Use o metaMediaId no content.metaMediaId de POST /v1/messages em vez de mediaUrl.
Limites Meta: 5MB image, 16MB video/audio, 100MB document. Para arquivos maiores em templates, use Resumable Upload abaixo.
POST /v1/media/upload/resumable
Upload reumível Meta. Necessário para arquivos ≥5MB usados como header_handle em templates ou em uploads grandes. Retorna um handle opaco (h:...) para usar em template.components.
Headers
| Header | Obrigatório |
|---|---|
X-API-Key | sim |
X-Phone-Id | sim |
Body
| Campo | Tipo | Obrigatório |
|---|---|---|
mediaUrl | URL | sim |
mimeType | string | sim |
fileName | string | sim |
appId | string | sim (Meta App ID — geralmente o mesmo de META_APP_ID) |
curl -X POST https://wpp.ogmma.com.br/v1/media/upload/resumable \
-H "X-API-Key: ak_live_..." \
-H "X-Phone-Id: 5f7b2e1c-..." \
-H "Content-Type: application/json" \
-d '{
"mediaUrl": "https://cdn.acme.com/video.mp4",
"mimeType": "video/mp4",
"fileName": "promo.mp4",
"appId": "1234567890"
}'
Response 201
{ "handle": "h:ABCD1234...", "sessionId": "...:abc" }
DELETE /v1/media/meta/:metaId
Deleta uma mídia no Meta (libera storage da WABA).
Headers: X-Phone-Id obrigatório.
curl -X DELETE https://wpp.ogmma.com.br/v1/media/meta/1234567890 \
-H "X-API-Key: ak_live_..." \
-H "X-Phone-Id: 5f7b2e1c-..."
Response 204
Transcrição (Whisper)
Transcreve áudios via OpenAI Whisper. BYO-key: account precisa ter configurado openaiApiKey via PATCH /v1/account/integrations/openai.
POST /v1/media/:id/transcribe
Enfileira transcrição. Idempotente — cache hit retorna direto.
curl -X POST https://wpp.ogmma.com.br/v1/media/med-1234-.../transcribe \
-H "X-API-Key: ak_live_..."
Response 202 (primeira chamada)
{ "status": "PENDING", "mediaId": "med-1234-..." }
Response 200 (cache hit — já transcrito antes)
{
"status": "COMPLETED",
"text": "Olá, gostaria de saber...",
"language": "pt",
"durationSeconds": 12.5,
"transcribedAt": "2026-05-21T14:00:30.000Z",
"cached": true
}
Response 202 (já em progresso)
{ "status": "PROCESSING", "mediaId": "med-1234-..." }
Errors
| HTTP | Code | Quando |
|---|---|---|
| 400 | MEDIA_NOT_AUDIO | contentType não é audio/* |
| 410 | MEDIA_EXPIRED | TTL 7d passou |
| 410 | MEDIA_DELETED | ACK já deletou |
GET /v1/media/:id/transcription
Estado atual. Polling-friendly.
curl https://wpp.ogmma.com.br/v1/media/med-1234-.../transcription \
-H "X-API-Key: ak_live_..."
Response 200
{
"status": "COMPLETED",
"text": "Olá, gostaria de saber...",
"language": "pt",
"durationSeconds": 12.5,
"transcribedAt": "2026-05-21T14:00:30.000Z",
"error": null
}
Status: PENDING → PROCESSING → COMPLETED ou FAILED (com error populado).
Errors
| HTTP | Code | Quando |
|---|---|---|
| 404 | TRANSCRIPTION_NOT_REQUESTED | Você não chamou POST /transcribe ainda |
OCR (extração de texto de imagem)
Igual ao transcribe, mas para image/*. Usa OpenAI Vision (gpt-4o-mini com prompt OCR).
POST /v1/media/:id/ocr
curl -X POST https://wpp.ogmma.com.br/v1/media/med-1234-.../ocr \
-H "X-API-Key: ak_live_..."
Response 202 — { "status": "PENDING", "mediaId": "..." }
Response 200 (cache hit)
{ "status": "COMPLETED", "text": "TEXTO EXTRAÍDO...", "extractedAt": "...", "cached": true }
Errors
| HTTP | Code | Quando |
|---|---|---|
| 400 | MEDIA_NOT_IMAGE | contentType não é image/* (PDFs ainda não suportados) |
| 410 | MEDIA_EXPIRED | — |
GET /v1/media/:id/ocr
Estado atual.
{
"status": "COMPLETED",
"text": "...",
"extractedAt": "2026-05-21T14:00:30.000Z",
"error": null
}
Custos de IA
OpenAI cobra por:
- Whisper (
whisper-1): ~$0.006/min de áudio. - OCR (
gpt-4o-minivision): ~$0.00015/imagem.
Cada chamada é registrada em ai_usage_events (acessível via /v1/admin/ai-usage). Os tokens consumidos são deduzidos da sua key OpenAI direto (gateway não cobra markup).