Error Handling
Introdução
Esta página documenta os formatos reais de erro da BaaS API e como tratá-los. Diferente de APIs que usam catálogos de códigos proprietários, a BaaS API sinaliza erros primariamente pelo HTTP status code + um corpo JSON com message (e, nos erros de gateway, um campo error estável para tratamento programático).
Formatos de resposta de erro
A API tem dois formatos de erro, conforme a camada que rejeitou a requisição:
1. Erros de negócio/validação (a maioria): corpo com message:
{
"message": "Banking account not found for the given onboardingId"
}2. Erros do gateway (validação de tenant, billing, antifraude): corpo com error (identificador estável, bom para switch) + message, às vezes campos extras:
{
"error": "MISSING_CLIENT_ID",
"message": "Client ID (x-client-id) is required"
}Formatos de resposta de sucesso
Também variam por endpoint — não assuma um envelope único:
| Formato | Exemplo de endpoints |
|---|---|
{ "message": ..., "data": ... } | balance, history, payBill, transferências, PIX |
{ "status": ..., "httpCode": ..., "when": ..., "data": { "status": ..., "body": ... } } | create (conta), checkOnboarding |
{ "body": ..., "when": ... } | revoke e endpoints mais antigos |
Consulte o exemplo de resposta na página de cada endpoint.
⚠️ Não trate erros por códigos
E#####. Versões antigas desta documentação descreviam um catálogo de códigos (E00101,E00201, …) que não existe na API. Lógica baseada nesses códigos nunca vai casar. Trate por HTTP status e, quando presente, pelo campoerror.
Erros do Gateway (validação de tenant)
Antes de chegar ao endpoint, toda requisição a /v1/* (exceto /v1/authentication/*) passa pela validação de tenant do gateway:
| HTTP | error | Causa | Solução |
|---|---|---|---|
400 | MISSING_CLIENT_ID | O gateway não conseguiu identificar seu tenant. O Bearer token sozinho não basta — o tenant é lido do campo application no body, ou dos headers client_id / x-client-id. | Envie o header x-client-id: <seu Client_ID> em toda chamada. Veja Tenant ID. |
402 | ACCOUNT_SUSPENDED | Tenant suspenso por inadimplência (billingStatus: SUSPENDED). | Regularizar pagamentos junto à Axia. |
402 | ACCOUNT_RESTRICTED | Tenant com restrições por pagamentos pendentes — só consultas liberadas. | Regularizar pagamentos junto à Axia. |
403 | PRODUCT_NOT_ENABLED | Família de produto (BANKING, CRYPTO, CRYPTO_BRIDGE, KYC, SALARY_ADVANCE) não habilitada para o tenant. | Contatar suporte para habilitar o produto. |
403 | OPERATION_NOT_ALLOWED | Tenant restrito a um subconjunto de operações (ex.: apenas PIX). | Contatar suporte se precisar de mais operações. |
403 | TRANSACTION_BLOCKED | Transação bloqueada pelo antifraude. | Contatar suporte com o antifraudId retornado. |
202 | TRANSACTION_PENDING_APPROVAL | Transação retida para aprovação manual (antifraude). Não é erro — aguarde. | Acompanhar status; a resposta traz antifraudId e pendingExpiresAt. |
503 | SECURITY_SERVICE_UNAVAILABLE | Serviço de antifraude temporariamente indisponível. | Retry com backoff em alguns minutos. |
Categorias de Erro
| Categoria | HTTP | Descrição | Retryable | Ação Recomendada |
|---|---|---|---|---|
| Validação | 400 | Request inválido (schema, formato, checksum) | Não | Validar antes de enviar; revisar documentação da API |
| Autenticação | 401 | Token expirado, inválido ou revogado | Sim (refresh) | Renovar token ou re-autenticar |
| Autorização | 403 | Sem permissão para recurso (produto, tenant, antifraude) | Não | Verificar credenciais e produtos habilitados |
| Billing do tenant | 402 | Tenant suspenso/restrito por inadimplência | Não | Regularizar pagamentos |
| Não encontrado | 404 | Recurso não existe ou foi deletado | Não | Verificar ID e fazer requisição novamente |
| Conflito | 409 | Duplicata, state mismatch, violação única | Não | Usar idempotência; verificar estado atual |
| Pré-condição | 412 | Falha reportada pelo serviço interno (validação de negócio) | Não | Ler message; corrigir o request |
| Rate limit | 429 | Limite de requisições excedido | Sim (backoff) | Aguardar + respeitar header Retry-After |
| Servidor | 5xx | Erro interno, serviço temporariamente indisponível | Sim (backoff) | Retry com exponential backoff; logar e alertar |
HTTP Status Codes
| Code | Tipo | Descrição |
|---|---|---|
200 | OK | Sucesso |
202 | Accepted | Requisição aceita/retida para processamento assíncrono (ex.: antifraude) |
204 | No Content | Sucesso sem corpo de resposta |
400 | Bad Request | Validação falhou (formato, campo obrigatório, tenant não identificado) |
401 | Unauthorized | Token expirado, inválido ou credenciais rejeitadas |
402 | Payment Required | Tenant suspenso/restrito por billing (ACCOUNT_SUSPENDED / ACCOUNT_RESTRICTED) |
403 | Forbidden | Acesso negado: produto não habilitado, operação restrita ou antifraude |
404 | Not Found | Recurso não existe ou foi removido |
405 | Method Not Allowed | Verb HTTP não suportado para endpoint |
409 | Conflict | Duplicata, inconsistência de estado ou violação de constraint única |
412 | Precondition Failed | Serviço interno reportou falha de negócio — ler message |
422 | Unprocessable Entity | Request válida, mas falha na lógica de negócio (KYC, saldo insuficiente) |
429 | Too Many Requests | Rate limit excedido; respeitar Retry-After |
500 | Internal Server Error | Erro não-esperado no servidor |
503 | Service Unavailable | Serviço temporariamente indisponível (manutenção, deps down) |
Mensagens de erro conhecidas (autenticação)
Os endpoints de token não usam envelope estruturado — validações de header lançam erro com mensagem descritiva:
| Situação | HTTP | message |
|---|---|---|
Header Authorization ausente | 4xx | You must send an Authorization header |
Authorization presente mas não Basic (token endpoints) | 4xx | Expected a Basic token |
Authorization presente mas não Bearer (rotas de API) | 4xx | Expected a Bearer token |
Header token ausente (introspect / revoke) | 4xx | You must send the bearer token on header |
| Token expirado/rejeitado na introspecção | 4xx | Invalid token |
Tratamento por Tipo
4.1 Validação (400 / 412)
Característica: Erro do lado cliente; nunca retryar.
Ação:
- Validar input ANTES de enviar para API
- Usar biblioteca de validação (ex: Joi, Yup, Zod)
- Ler o campo
messageda resposta — ele descreve a causa - Logar para auditoria
Exemplo:
// Validar antes de enviar
if (!isValidCPF(cpf)) {
throw new ValidationError('Invalid CPF format');
}
const response = await fetch(`${BAAS_URL}/v1/bank-accounts/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'x-client-id': CLIENT_ID,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
const err = await response.json();
// Erros do gateway trazem `error` (estável); os demais só `message`
if (err.error === 'MISSING_CLIENT_ID') {
throw new Error('x-client-id header ausente — configurar BAAS_CLIENT_ID');
}
throw new Error(`API error ${response.status}: ${err.message}`);
}4.2 Rate Limit (429)
Característica: Retryável; respeitar backoff exponencial.
Ação:
- Extrair header
Retry-After(segundos) - Aguardar + retry automático
- Implementar circuit breaker após 5+ retries
- Logar para alertar DevOps se persistir
Exemplo:
async function requestWithBackoff(url: string, options: RequestInit, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60') * 1000;
console.warn(`Rate limited. Retrying after ${retryAfter}ms`);
await new Promise(r => setTimeout(r, retryAfter));
continue;
}
if (response.ok) return response;
if (response.status >= 500) {
// Exponential backoff para 5xx
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await new Promise(r => setTimeout(r, delay));
continue;
}
return response; // 4xx não-retry
}
throw new Error('Max retries exceeded');
}4.3 Autenticação (401)
Característica: Retryável via renovação do token.
Ação:
- Em
401: renovar o token viaPOST /v1/authentication/oauth/access-tokene repetir uma vez - Se o
401persistir após renovar: erro crítico de credenciais (revisar.env) - Implementar refresh automático antes de expiração (TTL - 5 min)
Exemplo:
async function authenticatedRequest(endpoint: string, options: RequestInit) {
let token = await getToken(); // cache com renovação preventiva
let response = await fetch(endpoint, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'x-client-id': CLIENT_ID
}
});
if (response.status === 401) {
// Token pode ter expirado entre o cache e a chamada — renovar e repetir 1x
token = await refreshAccessToken();
response = await fetch(endpoint, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'x-client-id': CLIENT_ID
}
});
if (response.status === 401) {
throw new Error('Credenciais inválidas — revisar BAAS_CLIENT_ID/BAAS_CLIENT_SECRET');
}
}
return response;
}4.4 Servidor (5xx)
Característica: Retryável com backoff exponencial; alerta DevOps se persistir.
Ação:
- Retry com exponential backoff (1s, 2s, 4s, 8s, 16s, max 30s)
- Logar a resposta completa (
message) para correlação com o suporte - Alertar após 3 falhas consecutivas
- Contatar suporte se > 5 min persistindo
Exemplo:
async function robustRequest(url: string, options: RequestInit) {
const maxRetries = 5;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status < 500) return response;
const error = await response.json().catch(() => ({}));
if (attempt < maxRetries - 1) {
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
console.error(
`Server error (attempt ${attempt + 1}): ${error.message || response.status}. ` +
`Retrying in ${delay}ms`
);
await new Promise(r => setTimeout(r, delay));
continue;
} else {
console.error(
`Request failed after ${maxRetries} attempts: ${error.message || response.status}. ` +
`Contact support.`
);
throw new Error(`Server error: ${error.message || response.status}`);
}
} catch (err) {
if (attempt < maxRetries - 1) {
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await new Promise(r => setTimeout(r, delay));
} else {
throw err;
}
}
}
}Troubleshooting por Cenário
"400 MISSING_CLIENT_ID em toda chamada"
Causa: O gateway não conseguiu identificar seu tenant — o Bearer token sozinho não basta.
Ação: Envie o header x-client-id: <seu Client_ID> (o mesmo Client_ID do OAuth) em toda chamada fora de /v1/authentication/*. Detalhes em Tenant ID.
"Account creation stuck in PROCESSING"
Causa: KYC lento, documento rejeitado ou provider BaaS indisponível.
Ação:
- Polling GET
/v1/bank-accounts/checkOnboarding?onBoardingId=<CPF/CNPJ do titular>a cada 30s (máximo 5 min) - Se
data.body.biometricUrlvier preenchida, o titular precisa completar a etapa de biometria — encaminhe a URL - Verificar no BackOffice > Auditoria > Onboarding se há rejection_reason
- Se timeout após 5 min: webhook deveria ter chegado; verificar logs do cliente
Exemplo:
async function waitForOnboarding(documentNumber: string, maxWaitMs = 300000) {
const pollInterval = 30000;
const startTime = Date.now();
while (Date.now() - startTime < maxWaitMs) {
const response = await fetch(
`${BAAS_URL}/v1/bank-accounts/checkOnboarding?onBoardingId=${documentNumber}`,
{ headers: { 'Authorization': `Bearer ${token}`, 'x-client-id': CLIENT_ID } }
);
const { data } = await response.json();
if (data.status === 'CONFIRMED') {
console.log('Account onboarded successfully');
return data.body.account;
}
if (data.status === 'ERROR') {
throw new Error(`KYC rejected: ${data.body.messages?.[0]?.message || 'unknown'}`);
}
if (data.body?.biometricUrl) {
console.log('Biometric step pending — forward URL to the holder:', data.body.biometricUrl);
}
await new Promise(r => setTimeout(r, pollInterval));
}
throw new Error('Onboarding timeout after 5 min');
}"PIX payment rejected: receiver not found"
Causa: Chave PIX inválida no DICT (Diretório de Identificadores de Transações PIX do Banco Central).
Ação:
- Verificar se chave PIX é válida no banco do destinatário (use
GET /v1/pix/getAccountInfoByPixKeyantes de transferir) - Tentar novamente após 5 min (geralmente delay de replicação)
- Se persistir, contatar receiver para confirmar chave
Nota: Receiver precisa estar cadastrado no banco e ter chave ativa no DICT.
"Webhook não chegando"
Causa: HTTPS inválido, firewall, ou endpoint retornando erro.
Ação:
- Verificar se URL é HTTPS com certificado válido
- Verificar se endpoint está respondendo 200–299 em até 30s
- Conferir o log de entregas no BackOffice (Auditoria → Webhooks) — cada tentativa registra URL, status HTTP e erro
- Após 4 tentativas (backoff 1s → 2s → 4s), a entrega é marcada
FAILED— reconcilie o estado perdido pelas APIs de consulta (ex.:checkOnboarding,GET /v1/otc/trades/{id}) - Verificar a inscrição em
GET /v1/webhooks(URL,events,status: active)
Configuração de Webhook:
// Seu endpoint deve:
app.post('/webhook/axia', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8');
// Metadados chegam nos headers; o body é o evento cru
const eventType = req.header('X-Webhook-Event');
const eventId = req.header('X-Webhook-Event-Id');
const signature = req.header('X-Webhook-Signature'); // hex, sem prefixo "sha256="
// Validar assinatura (HMAC-SHA256 sobre o raw body)
const isValid = validateWebhookSignature(rawBody, signature, WEBHOOK_SECRET);
if (!isValid) return res.status(401).send('Unauthorized');
// Responder 200 IMEDIATAMENTE (não processar de forma síncrona)
res.status(200).send('OK');
// Processar de forma assíncrona + idempotência por X-Webhook-Event-Id
processEventAsync({ id: eventId, type: eventType, data: JSON.parse(rawBody) });
});Common Errors (Referência Rápida)
| Erro | Como identificar | Causa | Solução |
|---|---|---|---|
| Tenant não identificado | 400 + error: MISSING_CLIENT_ID | Header x-client-id ausente | Enviar x-client-id: <Client_ID> em toda chamada |
| Token inválido/expirado | 401 ou message: Invalid token | Token expirou (TTL 1h) ou credenciais erradas | Renovar token; revisar credenciais |
| Tenant suspenso | 402 + error: ACCOUNT_SUSPENDED | Inadimplência | Regularizar pagamentos |
| Produto não habilitado | 403 + error: PRODUCT_NOT_ENABLED | Família de produto desligada para o tenant | Contatar suporte |
| Onboarding não encontrado | 404 + message: Banking account not found... | onBoardingId errado (formato/dígitos) ou conta criada fora desta API | Consultar pelo CPF/CNPJ do titular |
| Falha de negócio | 412 + message descritiva | Serviço interno rejeitou a operação | Ler message e corrigir o request |
| Rate limit excedido | 429 | Muitas requisições | Aguardar + implementar backoff |
| Serviço indisponível | 503 | Manutenção ou dependency down | Retry com backoff; contatar suporte |