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:
{
"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:
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.