Códigos de erro

Toda resposta de erro da API segue o mesmo formato:

{
  "error": "codigo_estavel",
  "message": "Frase em português explicando o problema",
  "details": { "campos extras, quando existem": "..." },
  "request_id": "req_...",
  "docs_url": "https://wevi.chat/docs/api/erros#codigo_estavel"
}

O campo error é um código estável, em snake_case, que você usa para ramificar a lógica no seu código. O message é para humanos e pode mudar. Vale para todos os endpoints, inclusive os nove send-* antigos: neles a frase que a integração já conhecia (connection_id is required) continua igual, só que em message, e o error virou código (missing_connection_id).

Toda resposta traz o header X-Request-Id. Guarde esse valor: com ele a gente acha a chamada exata no log e você mesmo a encontra em Conta → Desenvolvedores → Requisições recentes, com status, tempo de resposta e código do erro dos últimos 7 dias. Se preferir gerar o id do seu lado, mande no header X-Request-Id da requisição e ele é reaproveitado.

Códigos HTTP

Status Significado
200 Sucesso. Mesmo erros de "negócio" (ex: atribuição de carteira falhou mas contato foi criado) podem vir aqui com campo ..._error no payload.
400 Body inválido, falta campo obrigatório, formato errado.
401 Autenticação faltando ou inválida.
402 Plano não permite essa feature (trial expirado, plano não tem WhatsApp, etc).
403 Autenticado mas sem permissão (ex: contato fez opt-out, fluxo inativo).
404 Recurso não existe (connection_id errado, etc).
409 Conflito de estado (ex: contato já está numa run ativa de automação).
422 Validação semântica falhou (ex: número de telefone fora do formato).
429 Rate limit excedido. Veja rate limits.
500 Erro no nosso lado. Tente novamente; se persistir, abra um chamado.

Erros mais comuns

Em qualquer endpoint

error Status Quando
invalid_json 400 O corpo não é JSON válido, ou não é um objeto.
unknown_fields 400 O corpo tem uma chave que o endpoint não conhece. details.unknown_fields lista as chaves rejeitadas e details.allowed_fields as aceitas. Um typo em assigned_user_email deixa de virar contato sem dono em silêncio.
route_not_found 404 O caminho não existe.
method_not_allowed 405 O método não vale para este caminho.
rate_limit_exceeded 429 Veja rate limits. Vem com Retry-After.
internal_error 500 Erro nosso. Guarde o request_id e tente de novo.

/contacts

error Quando
missing_identifier Body não tem nem email nem phone (ou, nas sub-rotas, nenhum de id, email, phone, cpf).
invalid_fields Mandou fields como array, string ou null.
unknown_fields Chaves em fields não estão cadastradas em Campos personalizados, ou chave desconhecida no topo do corpo. details.unknown_fields traz a lista.
contact_not_found O identificador não bate com nenhum contato da organização.
plan_limit_exceeded Org com trial expirado ou plano cancelado.

Sucesso parcial: se assigned_user_email falhar (email não é membro da org, role inválida), o contato é criado/atualizado mesmo assim e a resposta inclui assignment_error: "..." em vez de assigned_user_id.

/contacts-schema

error Quando
invalid_api_key API key inválida ou revogada.
rate_limit_exceeded 60 req/min excedido.

/automation-trigger

error Quando
phone is required Body sem phone.
Invalid token Token errado ou fluxo não tem webhook habilitado.
opted_out Contato fez opt-out global; fluxo não dispara.
Webhook trigger disabled for this flow Fluxo não tem trigger webhook ativo.
Flow is not active Fluxo está em draft ou pausado.
Already in flow Contato já está numa run ativa desse fluxo.
cooldown Contato saiu desse fluxo há menos tempo que o cooldown configurado. Resposta inclui retry_at (ISO8601).

Erros de chave

error Quando
missing_authorization O header Authorization não veio.
invalid_api_key A chave não existe ou foi revogada. Uma chave revogada pode continuar válida por até 30 segundos, o tempo do cache de autorização.
expired_api_key A chave passou da data de expiração escolhida na criação.
ip_not_allowed A chamada veio de um endereço fora da lista de IPs configurada na chave. details.your_ip diz de onde ela veio.
insufficient_scope A chave não tem o escopo exigido pelo endpoint. details.required_scope diz qual. Veja autenticação.
plan_limit_exceeded com feature: "api" O plano não inclui escrita e envio pela API. Leitura continua liberada em qualquer plano.

POST /messages e os send-*

error Status Quando
missing_type 400 POST /messages sem type.
missing_connection_id, missing_to, missing_text, missing_body, missing_template 400 Falta campo obrigatório. Nos send-*, o message traz a frase antiga (connection_id is required).
missing_destination 400 Nem to nem conversation_id.
no_connection 400 A organização não tem número conectado.
connection_not_found 404 connection_id não pertence à sua org.
conversation_not_found 404 conversation_id não pertence à sua org.
not_a_whatsapp_conversation 400 A conversa é de outro canal.
missing_media, incompatible_mime 400 Mídia sem url/base64 ou mime_type, ou tipo que não combina.
invalid_buttons, invalid_sections, invalid_url, invalid_header 400 Interativo malformado. message explica o que faltou.
catalog_not_configured 403 product ou product_list sem catalog_id na conexão.
opted_out 403 O contato pediu para sair e a janela de 24 h está fechada.
idempotency_key_reused 422 A mesma Idempotency-Key veio com um corpo diferente. details.first_send_id aponta o envio original.
plan_limit_exceeded 402 Plano sem escrita pela API ou trial expirado. details.feature e details.current_plan.

Se o Meta rejeitar a mensagem (template não aprovado, fora da janela de 24h pra texto livre, etc), a resposta vem com HTTP 200 mas status: "failed" e error com a mensagem do Meta. Trate ambos os casos.

O /send-conversation-message segue essa mesma regra. Antes ele devolvia 500 para recusa do Meta, o que fazia cliente com retry automático reenviar a mensagem. Hoje recusa do Meta é sempre 200 com status: "failed", nos dois endpoints.

/webhooks

error Quando
invalid_url URL não é https, ou aponta para endereço local ou de rede privada.
unknown_event_type Algum tipo em events não existe. A resposta traz details.unknown com a lista.
invalid_events events não é lista, ou veio vazia.
webhook_not_found O id não é desta organização.
delivery_not_found A entrega não pertence a este endpoint.
admin_only A sessão do painel não é de um administrador.

Idempotência

Todo envio aceita o header Idempotency-Key (ou o campo idempotency_key no corpo). Se você repetir a chamada com a mesma chave e o mesmo corpo, recebe o resultado do envio original sem reenviar. Recomendado para não duplicar mensagens em caso de timeout de rede.

Se a mesma chave vier com um corpo diferente, a resposta é 422 idempotency_key_reused. Antes, a resposta antiga era devolvida em silêncio, e um bug no seu lado que reaproveitasse chaves mandava a mensagem errada com 200.

await fetch("/send-template", {
  method: "POST",
  headers: { /* ... */ },
  body: JSON.stringify({
    connection_id: "...",
    template_name: "boas_vindas",
    to: "+5511999999999",
    idempotency_key: "lead-12345-welcome",
  }),
});

Próximo

Ferramentas para dev