Erros
Toda falha gerada pelo Joinotify usa o mesmo envelope:
{
"error": {
"message": "Frase legível descrevendo o que aconteceu.",
"type": "codigo_estavel"
}
}
Programe contra o campo type — ele é estável. O message é escrito para humanos e
pode mudar de redação sem aviso.
Campos opcionais
Alguns campos aparecem quando fazem sentido.
issues — falhas de validação
{
"error": {
"message": "Invalid request payload.",
"type": "invalid_request",
"issues": [
{
"code": "too_small",
"path": ["body"],
"message": "String must contain at least 1 character(s)"
}
]
}
}
meta — erro repassado da Meta
Quando a recusa vem da Meta, devolvemos o corpo original dela intacto. É onde estão o
code, o error_subcode e o fbtrace_id que o suporte da Meta pede.
{
"error": {
"message": "The Meta Cloud API rejected the request.",
"type": "meta_error",
"meta": {
"error": {
"message": "(#131047) Re-engagement message",
"type": "OAuthException",
"code": 131047,
"error_subcode": 2494010,
"fbtrace_id": "AbCdEfGhIjK"
}
}
}
}
reason e action — sem direito de uso
Um 402 diz por que o acesso foi bloqueado e qual é a saída: o que fazer
(action.kind) e a página do painel onde se faz (action.url). Mostre as duas coisas a
quem administra a integração — num log de erro, "pagamento necessário" sem um link é um
beco sem saída.
{
"error": {
"message": "Your free trial has ended. Subscribe to keep using the API.",
"type": "payment_required",
"reason": "trial_ended",
"billable_numbers": 1,
"action": {
"kind": "subscribe",
"url": "https://app.joinotify.com/checkout"
}
}
}
action.kind | Quando |
|---|---|
subscribe | Teste encerrado, assinatura cancelada ou inexistente |
update_payment_method | O último pagamento falhou (past_due) |
pay_invoices | Há faturas em aberto (unpaid) |
finish_checkout | O primeiro pagamento ainda não foi confirmado (incomplete) |
resume_subscription | A assinatura está pausada (paused) |
renew_license | A licença expirou (license_expired) |
upgrade_plan | O teto mensal de mensagens do plano foi atingido (message_limit) |
manage_numbers | Há mais números conectados do que a conta paga (capacity_exceeded) |
O bloqueio não chega sem aviso. Durante a carência as requisições continuam passando, e a
resposta traz o header X-Joinotify-Billing-Warning com o motivo — past_due, por
exemplo. Registre esse header nos seus logs: é ele que avisa que o 402 está a caminho.
allowed — método não aceito
Um caminho que existe, chamado com o método errado, responde 405, e não 404. Os
métodos aceitos vêm no header Allow e em error.allowed, para você procurar o erro no
verbo e não na URL.
{
"error": {
"message": "PUT is not supported on this route. Allowed: DELETE, GET, HEAD.",
"type": "method_not_allowed",
"allowed": ["DELETE", "GET", "HEAD"]
}
}
request_id — falha inesperada
Um 500 não diz o que quebrou — a causa fica nos nossos registros, nunca na resposta. O
que ele traz é o request_id, o mesmo valor do header x-request-id. Mande esse id ao
suporte e nós achamos a falha.
{
"error": {
"message": "Internal server error.",
"type": "internal",
"request_id": "0d72872e-f70e-4c25-95c6-6bec4d33a800"
}
}
Tabela de códigos
| HTTP | type | Significado | O que fazer |
|---|---|---|---|
400 | invalid_request | Nenhum ID de recurso depois de /v1/. | Corrija o path. |
400 | invalid_request | O corpo não é JSON válido, ou o path tem uma sequência % malformada. | Corrija o corpo ou a codificação da URL. |
401 | authentication | Token ausente, malformado, desconhecido ou revogado. | Confira o header e a chave. |
402 | payment_required | Sem direito de uso: teste encerrado, pagamento pendente, teto do plano… | Siga error.action. |
403 | tenant_inactive | Conta suspensa ou cancelada. | Fale com o suporte. |
403 | forbidden | O recurso não pertence à sua conta. | Confirme o phone_number_id / waba_id. |
403 | forbidden_endpoint | Caminho na lista de bloqueio do proxy. | Veja Compatibilidade com a Meta. |
404 | not_found | Rota, chave, endpoint ou entrega inexistente. | Confira o ID. |
405 | method_not_allowed | O caminho existe, mas não com esse método. | Use um dos métodos de Allow. |
409 | no_phone_number | Nenhum número conectado e from omitido. | Conecte um número ou informe from. |
409 | no_waba | Nenhuma conta empresarial conectada e wabaId omitido. | Conecte uma WABA ou informe wabaId. |
409 | idempotency_in_progress | Uma requisição com o mesmo Idempotency-Key ainda está rodando. | Aguarde e repita com a mesma chave. |
422 | invalid_request | Payload reprovado na validação. | Leia issues. |
413 | payload_too_large | O corpo é maior do que a rota aceita. | Divida o lote ou reduza o corpo. |
415 | unsupported_media_type | Charset ou Content-Encoding do corpo não aceito. | Envie JSON em UTF-8, sem compressão ou com gzip. |
422 | meta_error | A Meta recusou (4xx). | Leia error.meta. |
422 | idempotency_key_reused | O Idempotency-Key já foi usado com outro corpo. | Use uma chave nova por operação. |
429 | rate_limit | Limite da categoria estourado. | Aguarde Retry-After. Veja Limites de uso. |
500 | internal | Falha inesperada do nosso lado. | Tente de novo; se persistir, envie o request_id. |
502 | meta_error | A Meta respondeu com erro 5xx. | Tente de novo com backoff. |
502 | upstream_error | Não conseguimos alcançar a Meta. | Tente de novo com backoff. |
504 | upstream_error | A Meta demorou mais de 30 segundos. | Tente de novo com backoff. |
Erro nosso ou erro da Meta?
Uma regra simples: se existe o campo error.meta, a recusa foi da Meta. O Joinotify
apenas repassou.
Isso importa na hora de agir. invalid_request significa que o seu payload está errado e
uma nova tentativa idêntica vai falhar de novo. Já meta_error costuma ser regra de
negócio do WhatsApp — janela de 24 horas fechada, template não aprovado, número inválido
— e a correção é outra.
No espelho /v1/* a regra muda: quando a Meta responde, você recebe a resposta dela,
no formato dela, sem o nosso envelope. O envelope do Joinotify só aparece em falhas que
acontecem antes da chamada.
Rastreando uma requisição
Toda resposta traz x-request-id. Se você mandar um x-request-id próprio, nós o
ecoamos — o que facilita casar os logs dos dois lados.
O id próprio só é aceito se tiver de 8 a 128 caracteres, entre letras, dígitos, ., _,
: e -. Fora disso geramos um nosso (um UUID) — confie sempre no que volta no header.
curl -i https://api.joinotify.com/templates \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'x-request-id: pedido-1042-tentativa-1'
Guarde esse identificador nos seus logs. É a primeira coisa que o suporte pede.
Quais erros vale repetir
| Situação | Repetir? |
|---|---|
429 rate_limit | Sim, depois de Retry-After. |
500 · 502 · 504 | Sim, com backoff exponencial e teto de tentativas. |
409 idempotency_in_progress | Sim, depois de alguns segundos, com a mesma chave. |
402 payment_required | Não. Resolva o que error.action indica. |
401 · 403 · 404 · 405 · outros 409 | Não. Repetir vai falhar igual. |
400 · 413 · 415 · 422 invalid_request | Não. Corrija o payload. |
422 meta_error | Depende do code da Meta. Erro de janela ou template não se resolve repetindo. |