Códigos de Erro

19. Códigos de Erro#

Formato unificado de resposta#

Todos os erros seguem o mesmo envelope JSON, com error_code estável para handling tipado e trace_id para casamento exato com o log do backend:

json
{
  "error_code": "INSTANCE_NOT_CONNECTED",
  "message": "instance is not connected (status: DISCONNECTED)",
  "trace_id": "2a62142c-f55f-4db4-8c7b-060018e3ef48",
  "error":   "instance is not connected (status: DISCONNECTED)"
}
Campo Descricao
error_code Código estável em SCREAMING_SNAKE_CASE. Use isso pra lógica condicional no cliente.
message Mensagem legivel para humanos.
trace_id UUID que identifica unicamente o request. Também disponível no header de resposta X-Trace-ID. Use para correlacionar com GET /v1/admin/logs?trace_id=<uuid>.
error Campo legacy, identico a message. Mantido por uma release para integradores antigos. Migre para message.

O header X-Trace-ID está exposto via CORS e pode ser lido pelo navegador (error.response.headers['x-trace-id']).

Códigos HTTP#

Código error_code padrão Descricao
400 BAD_REQUEST Corpo inválido, campos faltando ou inválidos
401 UNAUTHORIZED / TOKEN_INVALID Token ausente, expirado ou inválido
403 FORBIDDEN / COMPANY_SUSPENDED / INSUFFICIENT_PERMISSIONS Sem permissão, empresa suspensa
404 NOT_FOUND / INSTANCE_NOT_FOUND / MESSAGE_NOT_FOUND / WEBHOOK_NOT_FOUND Recurso não encontrado
409 CONFLICT / DUPLICATE_INSTANCE_NAME / INSTANCE_NOT_CONNECTED Estado conflitante
422 UNPROCESSABLE_ENTITY Dados válidos mas não processaveis
429 RATE_LIMITED / ACCOUNT_LOCKED Rate limit excedido (RATE_LIMITED) ou conta travada por brute-force (ACCOUNT_LOCKED). Verifique os headers Retry-After e X-RateLimit-*
500 INTERNAL_ERROR Erro interno do servidor
503 SERVICE_UNAVAILABLE Serviço degradado (MySQL/Redis/S3, lookup de empresa no JWT)

Códigos de erro estáveis#

A lista canonica vive em whats-api/internal/api/error_codes.go (códigos auth/role de middleware em whats-api/internal/middleware/error_response.go). Lista completa:

error_code Quando ocorre
Genericos (fallback por status HTTP)
BAD_REQUEST Fallback de 400 quando nenhum código explicito foi setado
UNAUTHORIZED Fallback de 401 — autenticação ausente
FORBIDDEN Fallback de 403 — sem permissão
NOT_FOUND Fallback de 404 — recurso não encontrado
CONFLICT Fallback de 409 — estado conflitante
UNPROCESSABLE_ENTITY Fallback de 422 — dados válidos mas não processaveis
RATE_LIMITED Fallback de 429 — rate limit excedido
SERVICE_UNAVAILABLE Fallback de 503 — serviço degradado
INTERNAL_ERROR Fallback de 500+ — erro interno
Auth / sessão
INVALID_CREDENTIALS Email ou senha errados no login
ACCOUNT_LOCKED Mais de 5 tentativas falhas em 15min
TOKEN_EXPIRED JWT expirado
TOKEN_INVALID JWT malformado, expirado ou não encontrado / API key inválida
CSRF_INVALID Token CSRF ausente ou não bate em request unsafe do navegador
REFRESH_FAILED Refresh token ausente ou já consumido
REGISTRATION_FAILED Falha interna durante o cadastro
EMAIL_ALREADY_REGISTERED Email já em uso no register
RESET_TOKEN_INVALID Token de reset não encontrado ou já usado
RESET_TOKEN_EXPIRED Token de reset passou da janela de 30min (PASSWORD_RESET_TTL)
PASSWORD_CHANGED JWT emitido antes da senha ser alterada. O refresh-token também já foi revogado — cliente descarta o token e vai pro /login
OAUTH_PROVIDER_INVALID Provider social desconhecido
OAUTH_PROVIDER_DISABLED Provider social desabilitado ou sem credenciais
OAUTH_STATE_INVALID State OAuth ausente, expirado ou inválido
OAUTH_EMAIL_UNVERIFIED Provider não confirmou email verificado
OAUTH_PENDING_INVALID Token de conclusao de cadastro social inválido ou expirado
OAUTH_UPSTREAM_FAILED Falha ao conversar com Google/GitHub
Roles / permissão (middleware)
INSUFFICIENT_PERMISSIONS Role autenticada sem permissão para a rota
SUPERADMIN_REQUIRED Rota /v1/admin/* acessada sem is_superadmin
Verificação de email
EMAIL_NOT_VERIFIED Ação gated exige email confirmado (criar instância/token/webhook)
EMAIL_ALREADY_VERIFIED Email já confirmado
EMAIL_VERIFICATION_CODE_INVALID Código de 6 digitos errado
EMAIL_VERIFICATION_CODE_EXPIRED Código de verificação expirado
EMAIL_VERIFICATION_RESEND_COOLDOWN Reenvio de código solicitado dentro do cooldown
EMAIL_VERIFICATION_MAX_ATTEMPTS Excedeu o número de tentativas de código
Tenancy / plano
COMPANY_SUSPENDED Empresa está com status != active
PLAN_LIMIT_REACHED Limite do plano atingido
Instância
INSTANCE_NOT_FOUND Instância não existe ou não pertence a empresa
INSTANCE_NOT_CONNECTED Tentativa de send em instância que não está CONNECTED
INSTANCE_BANNED Instância banida pelo WhatsApp
INSTANCE_HARD_PAUSED Throttler em hard pause após rate limits repetidos
DUPLICATE_INSTANCE_NAME Nome de instância já em uso na empresa
INVALID_INSTANCE_NAME Nome de instância inválido (3-80 chars)
INVALID_INSTANCE_TRANSITION Transicao inválida (ex: connect em CREATED)
Mensageria
INVALID_JSON Corpo não e JSON válido
MISSING_FIELD Campo obrigatório ausente
INVALID_PHONE Número brasileiro com formato errado
INVALID_RECIPIENT Formato de destinatário inválido
IDEMPOTENCY_KEY_REQUIRED Header Idempotency-Key ausente em endpoint async
MEDIA_TOO_LARGE Arquivo excede limite de upload
UNSUPPORTED_MEDIA_TYPE MIME type não aceito
MEDIA_FETCH_FAILED API tentou baixar media_url no request e falhou (DNS, 4xx/5xx do origem, SSRF, timeout, oversized). HTTP 502. Sem retry — request inteiro falha sincronamente. Retry com URL válida ou faca upload prévio em /media
MESSAGE_NOT_FOUND Mensagem não existe na tenant DB
SEND_FAILED Falha ao criar/enfileirar a task
BLOCKED_DUPLICATE_CONTENT Anti-spam guard bloqueou conteúdo identico já enviado em 24h. Bypass: header X-Force-Send: true
BLOCKED_NO_RECIPROCITY Anti-spam guard bloqueou envio consecutivo sem inbound do contato. Bypass: header X-Force-Send: true
BLOCKED_TEMPERATURE_LIMIT Regra de temperatura bloqueou outbound — max_consecutive excedido para o tier do contato (cold=2, warm=3, engaged=4, hot=5, very_hot=7). Bypass: header X-Force-Send: true (audit-logged)
PASSIVE_MODE_ENABLED Instância em modo passivo (passive_mode_enabled=true) — so recebe mensagens; todo endpoint outbound responde 409 antes de qualquer throttle/fila. Bypass: header X-Force-Send: true (audit-logged)
OpenAI Playground
PLAYGROUND_DISABLED Playground OpenAI desabilitado neste binario
PLAYGROUND_BUDGET_EXCEEDED Orcamento diário em USD esgotado
PLAYGROUND_RATE_LIMITED Rate limit por usuário do playground excedido
PLAYGROUND_UNSUPPORTED_KIND Tipo de geração não suportado
Webhook
WEBHOOK_NOT_FOUND Webhook não existe
WEBHOOK_URL_INVALID URL inválida (HTTP em prod, scheme não suportado, SSRF)
WEBHOOK_URL_BLOCKED URL em dominio bloqueado
Feedback
FEEDBACK_INVALID Conteúdo de feedback/bug/request inválido
FEEDBACK_FAILED Falha interna ao registrar o feedback
Conta (self-service)
INVALID_CURRENT_PASSWORD Senha atual errada em PATCH /v1/auth/me/password
PASSWORD_TOO_SHORT Nova senha abaixo do mínimo
OWNER_ONLY Ação restrita ao owner da empresa
EMAIL_ALREADY_TAKEN Novo email já em uso por outra conta
EMAIL_CHANGE_NOT_FOUND Pedido de troca de email não encontrado
EMAIL_CHANGE_CODE_INVALID Código de confirmação de troca de email inválido
EMAIL_CHANGE_LOCKED Troca de email travada por excesso de tentativas
Reputacao de IP
REPUTATION_NOT_AVAILABLE IP nunca foi checado, ou serviço de reputacao não configurado
REPUTATION_RATE_LIMITED Budget de 5/min por superadmin do check on-demand esgotado

Casando o erro do frontend com o log do backend#

Quando o frontend recebe um erro, ele também recebe o trace_id. Para encontrar a linha exata no log:

bash
TRACE_ID="2a62142c-f55f-4db4-8c7b-060018e3ef48"
TOKEN=$(curl -sf https://api.catcher.one/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@biazap.test","password":"<senha>"}' | jq -r .token)

curl -sf "https://api.catcher.one/v1/admin/logs?trace_id=$TRACE_ID&limit=20" \
  -H "Authorization: Bearer $TOKEN" | jq '.lines[]' -r

O endpoint /v1/admin/logs aceita os filtros: file, limit, skip, level, contains, instance_id, trace_id, error_code. Use trace_id para uma única request, error_code para encontrar todas as ocorrências de um erro especifico.

A linha de log do request completed inclui os campos trace_id, error_code, status, duration_ms, path, method, remote_addr — tudo o que você precisa para diagnosticar o problema.