API v1https://api.ruvz.com.br/v1Baixar contexto para IA (.md)

API da Ruvz

API REST para enviar e receber mensagens de WhatsApp e Instagram. A Ruvz é responsável pela integração com a Meta — habilitação do número, credenciais, formato de payload e reenvio.

A integração tem duas direções. O seu sistema chama esta API para enviar e consultar; a Ruvz chama a URL cadastrada por você a cada evento — mensagem recebida, mudança de status de entrega, conversa aberta ou encerrada. A segunda direção está descrita em Webhook.

Versão dos eventos: 2026-09-01 · Todos os corpos são JSON em UTF-8.

Duas restrições que definem a integração

1. Janela de atendimento de 24 horas

Restrição da Meta. No WhatsApp, mensagens de texto livre só são aceitas até 24 horas após a última mensagem recebida do contato. Fora da janela, o envio retorna 403 window_expired e o único envio possível é um modelo aprovado, pelo mesmo POST /v1/messages com template_name. No Instagram a janela é a mesma de 24 horas, mas não há modelos: fora dela, é preciso esperar o contato escrever de novo. Cada conversa retornada informa window_expires_at, o instante em que a janela se encerra.

2. O envio é assíncrono

200 em POST /v1/messages significa mensagem registrada e enfileirada, não entregue. A entrega é reportada posteriormente pelo evento message.status. Não use a resposta do envio como confirmação de entrega.

Autenticação

Autenticação por token, enviado no header Authorization em todos os requests. Tokens são criados em Configurações → Conta → API, no painel da Ruvz, e exibidos uma única vez no momento da criação.

EXEMPLO
Authorization: Bearer rvz_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Cada token é restrito aos fluxos selecionados na criação e às permissões de leitura e de envio, concedidas separadamente. A validade é definida na criação; após expirar, o token é recusado.

A revogação tem efeito imediato no painel e em até 60 segundos nos servidores da API. Token revogado, expirado ou inexistente retorna 401 invalid_token, sem distinção entre os três casos.

Não há endpoint de criação de tokens. Para rotação sem interrupção: crie o novo token no painel, publique-o no seu sistema, valide o funcionamento e revogue o anterior.

Endpoints

POST/v1/messagesenvio — texto, mídia ou modelo, um modo por request
GET/v1/conversationslista conversas de um número; com destinatário, retorna uma
GET/v1/messageshistórico de uma conversa, do mais recente para o mais antigo
GET/v1/contactslista contatos de um número; com destinatário, retorna um
POST/v1/mediaupload multipart; retorna media_id reutilizável
GET/v1/media/:idnova URL assinada para uma mídia

Endereçamento

Nenhum endpoint recebe identificadores internos da Ruvz. Uma conversa é endereçada pelo par que as duas partes já possuem: o número conectado que envia e o identificador de quem recebe. Você não precisa armazenar identificadores da Ruvz no seu sistema.

fromstring · obrigatórionúmero conectado que envia, ou @usuario no Instagram. Aceita qualquer formatação: 5511988880000 e +55 11 98888-0000 são equivalentes
tostringtelefone do contato, em dígitos ou formatado
bsuidstringidentificador atribuído pela Meta quando o contato não expõe o telefone
instagram_idstringidentificador do contato no Instagram

from é obrigatório em todos os requests, inclusive quando o token alcança um único fluxo. Exatamente um entre to, bsuid e instagram_id deve ser informado; o canal é derivado dessa escolha e nunca é declarado. Informar dois retorna 400 invalid_request.

Nas rotas de leitura, os mesmos campos são enviados como parâmetros de query. Os três identificadores são devolvidos nos eventos do webhook com os mesmos nomes.

Falhas de endereçamento

404unknown_sendernenhum número conectado ao token corresponde a from
404unknown_recipiento destinatário nunca trocou mensagens com esse número
404no_conversationo contato existe, mas não há conversa neste canal — envie um modelo
409ambiguous_recipientmais de um cadastro corresponde ao identificador informado

ambiguous_recipient ocorre quando dois cadastros do mesmo número existem na conta. O envio é recusado em vez de resolvido por escolha arbitrária. Solicite a unificação dos cadastros ao suporte.

Enviar uma mensagem

POST /v1/messages é o único endpoint de envio. Além do endereçamento, o corpo deve conter exatamente um modo de conteúdo. Dois modos no mesmo request retornam 400 invalid_request.

textstringtexto livre. Sujeito à janela de 24 horas
mediastringURL https:// do arquivo, ou data URI em base64. Exclusivo com media_id
media_idstringidentificador devolvido por POST /v1/media. Exclusivo com media
template_namestringnome de um modelo aprovado no fluxo. Único modo aceito fora da janela
EXEMPLO
curl -X POST https://api.ruvz.com.br/v1/messages \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4f3c2b1a-..." \
  -d '{
    "from": "5511988880000",
    "to":   "5511999990000",
    "text": "Seu pedido saiu para entrega."
  }'
EXEMPLO
{ "message_id": "msg_456", "status": "pending" }

status na resposta é sempre pending. Os estados seguintes — sent, delivered, read, failed — chegam pelo evento message.status, correlacionados pelo message_id retornado aqui.

Enviar arquivos

Há três formas de informar o arquivo. Todas produzem o mesmo resultado: o arquivo é armazenado pela Ruvz antes do envio, o que garante que ele continue disponível na conversa, em reenvios e no media_id devolvido pelo webhook.

1. URL

Informe uma URL https:// pública. O download é feito pelo servidor, de forma assíncrona, após a resposta. Limite de 16MB.

EXEMPLO
curl -X POST https://api.ruvz.com.br/v1/messages \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-8842-nf" \
  -d '{
    "from": "5511988880000",
    "to":   "5511999990000",
    "media": "https://seu-sistema.com.br/notas/8842.pdf",
    "caption": "Segue a nota fiscal",
    "file_name": "nota-fiscal.pdf"
  }'

Como o download ocorre após a resposta, o 200 não confirma o acesso ao arquivo. URL indisponível, endereço de rede interna, arquivo acima do limite ou tipo não suportado resultam em message.status com failed e o motivo em error_message. Apenas https:// é aceito.

2. Base64

Informe o arquivo como data URI. Limite de 5MB já decodificados — acima disso, use URL ou upload, já que a codificação em base64 aumenta o corpo em aproximadamente um terço e é retransmitida a cada nova tentativa.

CURL
curl -X POST https://api.ruvz.com.br/v1/messages \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: avatar-cliente-42" \
  -d '{
    "from": "5511988880000",
    "to": "5511999990000",
    "media": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
    "media_type": "image",
    "caption": "Seu comprovante"
  }'

3. Upload prévio

Para o mesmo arquivo enviado a vários destinatários, faça o upload uma vez em POST /v1/media (multipart, campos file e from) e reutilize o media_id retornado. Limite de 16MB.

CURL — REUTILIZAR O UPLOAD
curl -X POST https://api.ruvz.com.br/v1/messages \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-8842-pdf" \
  -d '{
    "from": "5511988880000",
    "to": "5511999990000",
    "media_id": "YWNjXzEvZmxvdy...",
    "file_name": "nota-fiscal.pdf",
    "caption": "Segue a nota fiscal"
  }'

Campos e tipos

media_typestring · opcionalimage, video, audio ou document. Determinado pelo conteúdo do arquivo quando ausente
captionstring · opcionallegenda; não se aplica a áudio
file_namestring · opcionalnome exibido ao destinatário em documentos

O tipo é determinado pelo conteúdo do arquivo, não pela extensão da URL nem pelo cabeçalho do data URI. Um media_type divergente do conteúdo retorna 400 media_type_mismatch nos modos com arquivo já disponível, e é corrigido silenciosamente no modo URL após o download.

Tipos aceitos: image/jpeg, image/png, image/webp, image/gif, video/mp4, audio/ogg, audio/mpeg, audio/mp4 e application/pdf.

Enviar um modelo

Modelos aprovados pela Meta são o único envio aceito fora da janela de 24 horas e a única forma de iniciar uma conversa com um número que nunca enviou mensagem. Quando não existe conversa, ela é criada por este request.

EXEMPLO
curl -X POST https://api.ruvz.com.br/v1/messages \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "5511988880000",
    "to":   "5511999990000",
    "name": "Ana Souza",
    "template_name": "confirmacao_pedido",
    "parameters": ["8842"]
  }'
template_namestring · obrigatórionome do modelo aprovado neste fluxo
parametersarray de stringvalores das variáveis, na ordem. A quantidade deve corresponder à do modelo
namestringnome do contato. Obrigatório apenas no primeiro contato com o número

Quantidade de parâmetros diferente da esperada retorna 400 invalid_template_parameters. Modelo inexistente ou não aprovado no fluxo retorna 403 template_not_approved.

Apenas to abre conversa. bsuid e instagram_id identificam participantes de conversas existentes e retornam 404 no_conversation quando não há uma.

Idempotência

Envie o header Idempotency-Key em todo envio. Uma chave repetida retorna a resposta original com 200 e o header Idempotent-Replay: true, sem novo envio. A chave é retornada no evento message.sent no campo client_reference.

Use uma chave distinta por mensagem. A validade é de 24 horas. Como o envio é um único endpoint, uma chave corresponde a um envio, independentemente do modo de conteúdo.

Requisição com a mesma chave ainda em processamento retorna 409 idempotency_in_flight. A mesma chave com um corpo diferente retorna 409 idempotency_key_reuse: uma chave corresponde a uma mensagem, e reaproveitá-la para outra resultaria no envio da segunda ser substituído pela resposta da primeira.

Em indisponibilidade do cache de idempotência, a verificação passa a aceitar todos os requests. Nesse intervalo, uma retransmissão pode resultar em envio duplicado — o comportamento é deliberado, já que recusar por precaução descartaria mensagens legítimas.

Consultar dados e arquivos

Todas as leituras usam a permissão messages:read e a mesma autenticação Bearer. Listagens retornam data e, quando houver outra página, next_cursor. Passe esse cursor na próxima chamada; a ordem é da mais recente para a mais antiga.

GET/v1/conversations

Liste as conversas de um número conectado. Acrescente to, bsuid ou instagram_id para obter uma conversa específica.

CURL
curl -G https://api.ruvz.com.br/v1/conversations \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  --data-urlencode "from=5511988880000" \
  --data-urlencode "status=open" \
  --data-urlencode "limit=50"
RESPOSTA 200
{
  "data": [{
    "id": "cnv_123", "contact_id": "ct_123",
    "to": "5511999990000", "channel": "whatsapp", "status": "open",
    "window_expires_at": "2026-09-02T14:22:31Z",
    "last_message_at": "2026-09-01T14:22:31Z",
    "created_at": "2026-08-27T09:00:00Z"
  }],
  "next_cursor": "cnv_122"
}

GET/v1/messages

Obtenha o histórico de uma conversa. Além de from, informe exatamente um destinatário.

CURL
curl -G https://api.ruvz.com.br/v1/messages \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  --data-urlencode "from=5511988880000" \
  --data-urlencode "to=5511999990000" \
  --data-urlencode "limit=50"
RESPOSTA 200
{
  "data": [{
    "id": "msg_456", "conversation_id": "cnv_123",
    "direction": "outbound", "type": "text", "text": "Seu pedido saiu.",
    "status": "delivered", "origin": "agent",
    "created_at": "2026-09-01T14:23:02Z"
  }]
}

GET/v1/contacts

Liste contatos associados ao número conectado. Use search para filtrar por texto, ou um destinatário para consultar um contato específico.

CURL
curl -G https://api.ruvz.com.br/v1/contacts \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  --data-urlencode "from=5511988880000" \
  --data-urlencode "search=Ana" \
  --data-urlencode "limit=50"
RESPOSTA 200
{
  "data": [{
    "id": "ct_123", "name": "Ana Souza", "to": "5511999990000",
    "phone": "5511999990000", "email": "ana@exemplo.com",
    "blocked": false, "created_at": "2026-08-27T09:00:00Z"
  }]
}

POST/v1/media

Envie o arquivo uma vez e guarde o media_id retornado para usar em múltiplos envios. Aceita os tipos listados em Enviar arquivos, até 16MB.

CURL — MULTIPART
curl -X POST https://api.ruvz.com.br/v1/media \
  -H "Authorization: Bearer $RUVZ_TOKEN" \
  -F "from=5511988880000" \
  -F "file=@./nota-fiscal.pdf;type=application/pdf"
RESPOSTA 200
{
  "media_id": "YWNjXzEvZmxvdy...",
  "mime_type": "application/pdf",
  "file_name": "nota-fiscal.pdf",
  "size_bytes": 184320
}

GET/v1/media/:id

Gere uma URL assinada nova para uma mídia recebida no webhook ou enviada por upload. A URL vale por 24 horas.

CURL
curl https://api.ruvz.com.br/v1/media/YWNjXzEvZmxvdy... \
  -H "Authorization: Bearer $RUVZ_TOKEN"
RESPOSTA 200
{
  "media_id": "YWNjXzEvZmxvdy...",
  "url": "https://storage.googleapis.com/...",
  "expires_at": "2026-09-02T14:22:31Z"
}

Webhook

Cadastre a URL de recebimento em Fluxos → [fluxo] → Integração. A cada evento no fluxo, a Ruvz executa um POST nessa URL com um corpo JSON assinado.

Todos os eventos compartilham o mesmo envelope:

EXEMPLO
{
  "api_version": "2026-09-01",
  "event_id": "evt_msg_message.received_01J8...",
  "delivery_id": "dlv_evt_msg_message.received_01J8..._1",
  "type": "message.received",
  "occurred_at": "2026-09-01T14:22:31.412Z",
  "account_id": "acc_...",
  "flow_id": "flow_...",
  "data": { }
}
api_versionstringversão do formato, fixada no cadastro do webhook
event_idstringestável entre tentativas do mesmo evento; use para descartar repetições
delivery_idstringdistinto a cada tentativa; informe em chamados de suporte
typestringtipo do evento
occurred_atISO 8601instante do fato; ordene os eventos por este campo
dataobjectconteúdo específico do tipo

Responda 2xx em até 10 segundos e processe de forma assíncrona. Entregas sem resposta bem-sucedida são tentadas até 5 vezes, com intervalo crescente. Resposta 410 desativa o cadastro imediatamente. Após 100 falhas consecutivas, as entregas são pausadas e o estado é sinalizado no painel.

Catálogo de eventos

Estes são todos os eventos emitidos. Cada entrega usa o envelope de Webhook; os exemplos abaixo mostram apenas o objeto data para destacar o conteúdo específico.

message.received

Mensagem recebida do contato.

EXEMPLO
"data": {
  "message_id":      "01J8...",          // identificador da mensagem na Ruvz
  "from":            "5511988880000",    // número conectado que recebeu
  "to":              "5511999990000",    // contato; devolva este par para responder
  "bsuid":           "BR.1A2B3C",        // no lugar de "to" quando não há telefone
  "conversation_id": "01J8...",          // informativo; nenhum endpoint recebe este campo
  "contact_id":      "01J8...",          // idem
  "direction":       "inbound",
  "type":            "text",             // text | image | audio | video | document | sticker | location | template
  "status":          "received",
  "origin":          "contact",
  "text":            "Oi, meu pedido chegou?",
  "created_at":      "2026-09-01T14:22:31.412Z",

  // Presentes quando aplicável:
  "provider_message_id": "wamid.HBgN...", // identificador da Meta
  "reply_to":            "wamid.HBgN...", // mensagem citada pelo contato
  "transcript":          "...",           // transcrição de áudio, quando habilitada

  // Presentes em mensagens com mídia:
  "media_id":        "YWNjXzEvZmxvdy...",
  "media_url":       "https://storage.googleapis.com/...",  // assinada, validade de 24h
  "media_mime_type": "image/jpeg",
  "media_file_name": "nota-fiscal.pdf"
}

media_url é assinada e expira em 24 horas. Após a expiração, obtenha uma nova URL em GET /v1/media/:id a partir do media_id, que está sempre presente. Em falha de assinatura, o evento é entregue sem media_url.

message.sent

Mensagem enviada ao contato. Abrange envios por esta API, envios feitos na inbox da Ruvz e envios feitos pelo aplicativo WhatsApp Business.

EXEMPLO
"data": {
  "message_id":       "01J8...",
  "from":             "5511988880000",
  "to":               "5511999990000",
  "conversation_id":  "01J8...",
  "contact_id":       "01J8...",
  "direction":        "outbound",
  "type":             "text",
  "status":           "pending",         // estado inicial; acompanhe em message.status
  "origin":           "api",             // api | inbox | echo | campaign
  "text":             "Chegou sim!",
  "created_at":       "2026-09-01T14:23:02.100Z",
  "client_reference": "pedido-8842"      // Idempotency-Key informado no envio
}
apioriginenvio feito por esta API
inboxoriginenvio feito por um usuário na inbox da Ruvz
echooriginenvio feito pelo aplicativo WhatsApp Business
campaignorigindisparo de campanha feito por um usuário na Ruvz

client_reference preenchido indica que a mensagem foi originada pelo seu sistema; atualize o registro existente em vez de criar outro. Ausente, a mensagem foi originada na Ruvz e corresponde a um registro novo.

No modo URL de mídia, o evento é emitido antes do download e não inclui media_id. Nos demais modos o campo está presente.

message.status

Mudança de estado de entrega. Emitido uma vez por estado; nem todos os estados ocorrem em toda mensagem.

EXEMPLO
"data": {
  "message_id":      "01J8...",
  "conversation_id": "01J8...",
  "status":          "delivered",        // sent | delivered | read | failed
  "occurred_at":     "2026-09-01T14:23:04.900Z",

  // Presentes em failed:
  "error_code":    131047,
  "error_message": "Re-engagement message"
}

message.deleted

Mensagem apagada. O conteúdo não é retransmitido, pois deixa de existir também na Ruvz.

EXEMPLO
"data": {
  "message_id":      "01J8...",
  "conversation_id": "01J8...",
  "deleted_by":      "usr_..."   // identificador de usuário da Ruvz | "contact" | "whatsapp-business-app"
}

message.edited

Texto alterado dentro da janela de edição do WhatsApp. Não gera mensagem nova: atualize o texto do registro existente. Cada edição produz um evento.

EXEMPLO
"data": {
  "message_id":      "01J8...",
  "conversation_id": "01J8...",
  "text":            "Chegou sim, hoje de manhã!",
  "edited_by":       "contact"   // identificador de usuário da Ruvz | "contact" | "whatsapp-business-app"
}

message.reaction

Reação adicionada ou removida. emoji vazio indica remoção, conforme a sinalização do próprio WhatsApp.

EXEMPLO
"data": {
  "message_id":      "01J8...",
  "conversation_id": "01J8...",
  "emoji":           "👍",
  "by":              "usr_..."   // identificador de usuário da Ruvz | "contact" | "whatsapp-business-app"
}

contact.created · contact.updated

Primeiro contato de um número, ou alteração de cadastro. Mesmo formato para os dois tipos.

EXEMPLO
"data": {
  "contact_id":   "01J8...",
  "name":         "Ana Souza",
  "to":           "5511999990000",       // mesmo valor de "phone", sob o nome usado no envio
  "bsuid":        "BR.1A2B3C",           // quando não há telefone
  "phone":        "5511999990000",       // WhatsApp
  "instagram_id": "1784...",             // Instagram; excludente com phone
  "email":        "ana@exemplo.com",     // quando preenchido
  "created_at":   "2026-09-01T14:22:30.000Z"
}

conversation.opened · conversation.closed

Abertura, reabertura e encerramento de conversa. O encerramento é normalmente automático, ao fim da janela de 24 horas sem novas mensagens.

EXEMPLO
"data": {
  "conversation_id":   "01J8...",
  "from":              "5511988880000",
  "to":                "5511999990000",  // ou "bsuid" / "instagram_id"
  "contact_id":        "01J8...",
  "channel":           "whatsapp",       // whatsapp | instagram
  "status":            "open",           // open | closed
  "window_expires_at": "2026-09-02T14:22:31.412Z"   // ausente quando não há janela aberta
}

conversation.opened também é emitido na reabertura de uma conversa encerrada.

Coexistência com o aplicativo WhatsApp Business

Um número pode operar simultaneamente nesta API e no aplicativo WhatsApp Business. Respostas enviadas pelo aplicativo são entregues como message.sent com origin: "echo". Integrações que ignoram esse evento exibem a conversa sem as respostas enviadas pelo aplicativo.

Consequências para a integração:

  • contact.created e conversation.opened podem ser originados por um envio do aplicativo, antes de qualquer message.received.
  • Conversa iniciada pelo aplicativo não abre a janela de 24 horas — apenas mensagens recebidas do contato abrem. Até a resposta do contato, somente modelos aprovados são aceitos.
  • Exclusão, reação e edição feitas no aplicativo são entregues como message.deleted, message.reaction e message.edited, com o autor identificado como "whatsapp-business-app" ou "contact".

Mensagens enviadas por esta API não são duplicadas por esse caminho: são reconhecidas pelo identificador do WhatsApp e não geram um segundo evento.

Ordem e duplicatas

Os eventos não são entregues em ordem garantida. A escolha é deliberada: uma fila ordenada bloqueia na primeira falha, e uma indisponibilidade momentânea do endpoint interromperia as entregas seguintes daquela conversa.

Ordene os eventos por occurred_at antes de exibi-los e registre os event_id já processados para descartar repetições. A entrega é ao-menos-uma-vez: o mesmo event_id pode ser recebido mais de uma vez.

Eventos de mensagem são autossuficientes: carregam o endereçamento completo. Uma integração que indexa por contact_id deve criar o registro na primeira ocorrência, sem depender de ter recebido contact.created antes.

Verificar a assinatura

Cada entrega leva estes headers. O HMAC-SHA256 é sobre a string "<timestamp>.<corpo cru>" — o timestamp entra na assinatura, e não só no header, justamente para que uma entrega capturada não sirva de replay para sempre.

EXEMPLO
X-Ruvz-Event:          message.received
X-Ruvz-Delivery-Id:    dlv_...
X-Ruvz-Timestamp:      1756738951
X-Ruvz-Signature-256:  sha256=<hex>

Rejeite o request se a diferença entre X-Ruvz-Timestamp e o seu relógio passar de 5 minutos, e compare em tempo constante.

Node.js

EXEMPLO
const crypto = require("crypto");

// IMPORTANTE: use o corpo CRU, antes de qualquer JSON.parse.
// express.json() já consumiu o stream — configure
// express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } })
function verifyRuvz(rawBody, headers, secret) {
  const ts = Number(headers["x-ruvz-timestamp"]);
  if (!ts || Math.abs(Date.now() / 1000 - ts) > 300) return false;

  const expected =
    "sha256=" +
    crypto
      .createHmac("sha256", secret)
      .update(ts + "." )
      .update(rawBody)
      .digest("hex");

  const got = headers["x-ruvz-signature-256"] || "";
  const a = Buffer.from(expected);
  const b = Buffer.from(got);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

PHP

EXEMPLO
<?php
function verify_ruvz(string $rawBody, array $headers, string $secret): bool {
    $ts = (int) ($headers['X-Ruvz-Timestamp'] ?? 0);
    if ($ts === 0 || abs(time() - $ts) > 300) {
        return false;
    }

    $expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
    $got = $headers['X-Ruvz-Signature-256'] ?? '';

    return hash_equals($expected, $got);
}

Erros

Erros retornam um código estável, destinado ao tratamento programático. A mensagem é descritiva e pode mudar.

EXEMPLO
{ "error": { "code": "window_expired", "message": "The customer service window has expired. Send an approved template instead." } }
400invalid_requestcorpo inválido, nenhum modo de conteúdo ou mais de um
400invalid_phoneto não é um número em formato aceitável
400invalid_mediamedia não é URL https:// nem data URI válido
400invalid_media_idmedia_id malformado
400media_type_mismatchmedia_type diverge do conteúdo do arquivo
400unsupported_media_typetipo de arquivo não aceito
400invalid_template_parametersquantidade de parâmetros diferente da esperada pelo modelo
400name_requiredprimeiro contato com o número exige name
401invalid_tokentoken inválido, revogado ou expirado
403insufficient_scopetoken sem a permissão exigida pelo endpoint
403window_expiredfora da janela de atendimento; envie um modelo
403template_not_approvedmodelo inexistente ou não aprovado no fluxo
403channel_disconnectedfluxo sem canal conectado
404unknown_senderfrom não corresponde a nenhum número do token
404unknown_recipientdestinatário sem histórico com esse número
404no_conversationcontato sem conversa neste canal
404not_foundrecurso inexistente ou fora dos fluxos do token
409ambiguous_recipientmais de um cadastro corresponde ao identificador
409idempotency_in_flightrequest anterior com a mesma chave em processamento
409idempotency_key_reusemesma chave com corpo diferente do request original
413file_too_largearquivo acima do limite do modo utilizado
400invalid_idempotency_keyIdempotency-Key fora do formato aceito
403token_misconfiguredtoken sem fluxo algum atribuído; gere um novo no painel
422unsupported_channeloperação não existe neste canal — modelos são exclusivos do WhatsApp
429rate_limitedlimite de requisições do token excedido
500internal_errorfalha nossa; a mensagem não foi registrada, pode repetir com a mesma Idempotency-Key
503storage_unavailablearmazenamento de mídia indisponível; repita depois

404 é também a resposta para recursos existentes em fluxos que o token não alcança: 403 confirmaria a existência do recurso.

Limites

500 requisições por segundo por token, com pico de 1000. O limite é por token e não por conta, o que permite isolar cargas: um token de relatórios não consome a capacidade do token de envio.

Esse limite protege a infraestrutura da API e não corresponde à capacidade de envio. A capacidade de envio é definida pela Meta, por número de WhatsApp, e é respeitada pela Ruvz antes da entrega. Contas com muitos números conectados têm capacidade agregada superior a esse limite; solicite ajuste ao suporte se necessário.

Excedido o limite, a resposta é 429 rate_limited e nenhuma mensagem é enviada.

16MBupload e URLPOST /v1/media e media como URL
5MBbase64tamanho já decodificado, em media como data URI
24hmedia_urlvalidade da URL assinada nos eventos
24hIdempotency-Keyjanela de reconhecimento de chave repetida

Listagens são paginadas por cursor. Informe limit (padrão 50, máximo 200) e repita a chamada com o next_cursor retornado. Ausência de next_cursor indica fim da coleção.

Changelog

2026-09-01
Versão inicial. Endpoint único de envio para texto, mídia (URL, base64 ou upload) e modelos; leitura de conversas, mensagens e contatos; webhook com dez tipos de evento.

A versão dos eventos é fixada no cadastro do webhook. Alterações de formato são publicadas como versão nova; o endpoint cadastrado continua recebendo a versão anterior até a atualização do cadastro.