Skip to content

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:

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

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

FormatoExemplo 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 campo error.


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:

HTTPerrorCausaSolução
400MISSING_CLIENT_IDO 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.
402ACCOUNT_SUSPENDEDTenant suspenso por inadimplência (billingStatus: SUSPENDED).Regularizar pagamentos junto à Axia.
402ACCOUNT_RESTRICTEDTenant com restrições por pagamentos pendentes — só consultas liberadas.Regularizar pagamentos junto à Axia.
403PRODUCT_NOT_ENABLEDFamília de produto (BANKING, CRYPTO, CRYPTO_BRIDGE, KYC, SALARY_ADVANCE) não habilitada para o tenant.Contatar suporte para habilitar o produto.
403OPERATION_NOT_ALLOWEDTenant restrito a um subconjunto de operações (ex.: apenas PIX).Contatar suporte se precisar de mais operações.
403TRANSACTION_BLOCKEDTransação bloqueada pelo antifraude.Contatar suporte com o antifraudId retornado.
202TRANSACTION_PENDING_APPROVALTransação retida para aprovação manual (antifraude). Não é erro — aguarde.Acompanhar status; a resposta traz antifraudId e pendingExpiresAt.
503SECURITY_SERVICE_UNAVAILABLEServiço de antifraude temporariamente indisponível.Retry com backoff em alguns minutos.

Categorias de Erro ​

CategoriaHTTPDescriçãoRetryableAção Recomendada
Validação400Request inválido (schema, formato, checksum)NãoValidar antes de enviar; revisar documentação da API
Autenticação401Token expirado, inválido ou revogadoSim (refresh)Renovar token ou re-autenticar
Autorização403Sem permissão para recurso (produto, tenant, antifraude)NãoVerificar credenciais e produtos habilitados
Billing do tenant402Tenant suspenso/restrito por inadimplênciaNãoRegularizar pagamentos
Não encontrado404Recurso não existe ou foi deletadoNãoVerificar ID e fazer requisição novamente
Conflito409Duplicata, state mismatch, violação únicaNãoUsar idempotência; verificar estado atual
Pré-condição412Falha reportada pelo serviço interno (validação de negócio)NãoLer message; corrigir o request
Rate limit429Limite de requisições excedidoSim (backoff)Aguardar + respeitar header Retry-After
Servidor5xxErro interno, serviço temporariamente indisponívelSim (backoff)Retry com exponential backoff; logar e alertar

HTTP Status Codes ​

CodeTipoDescrição
200OKSucesso
202AcceptedRequisição aceita/retida para processamento assíncrono (ex.: antifraude)
204No ContentSucesso sem corpo de resposta
400Bad RequestValidação falhou (formato, campo obrigatório, tenant não identificado)
401UnauthorizedToken expirado, inválido ou credenciais rejeitadas
402Payment RequiredTenant suspenso/restrito por billing (ACCOUNT_SUSPENDED / ACCOUNT_RESTRICTED)
403ForbiddenAcesso negado: produto não habilitado, operação restrita ou antifraude
404Not FoundRecurso não existe ou foi removido
405Method Not AllowedVerb HTTP não suportado para endpoint
409ConflictDuplicata, inconsistência de estado ou violação de constraint única
412Precondition FailedServiço interno reportou falha de negócio — ler message
422Unprocessable EntityRequest válida, mas falha na lógica de negócio (KYC, saldo insuficiente)
429Too Many RequestsRate limit excedido; respeitar Retry-After
500Internal Server ErrorErro não-esperado no servidor
503Service UnavailableServiç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çãoHTTPmessage
Header Authorization ausente4xxYou must send an Authorization header
Authorization presente mas não Basic (token endpoints)4xxExpected a Basic token
Authorization presente mas não Bearer (rotas de API)4xxExpected a Bearer token
Header token ausente (introspect / revoke)4xxYou must send the bearer token on header
Token expirado/rejeitado na introspecção4xxInvalid token

Tratamento por Tipo ​

4.1 Validação (400 / 412) ​

Característica: Erro do lado cliente; nunca retryar.

Ação:

  1. Validar input ANTES de enviar para API
  2. Usar biblioteca de validação (ex: Joi, Yup, Zod)
  3. Ler o campo message da resposta — ele descreve a causa
  4. Logar para auditoria

Exemplo:

typescript
// 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:

  1. Extrair header Retry-After (segundos)
  2. Aguardar + retry automático
  3. Implementar circuit breaker após 5+ retries
  4. Logar para alertar DevOps se persistir

Exemplo:

typescript
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:

  1. Em 401: renovar o token via POST /v1/authentication/oauth/access-token e repetir uma vez
  2. Se o 401 persistir após renovar: erro crítico de credenciais (revisar .env)
  3. Implementar refresh automático antes de expiração (TTL - 5 min)

Exemplo:

typescript
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:

  1. Retry com exponential backoff (1s, 2s, 4s, 8s, 16s, max 30s)
  2. Logar a resposta completa (message) para correlação com o suporte
  3. Alertar após 3 falhas consecutivas
  4. Contatar suporte se > 5 min persistindo

Exemplo:

typescript
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:

  1. Polling GET /v1/bank-accounts/checkOnboarding?onBoardingId=<CPF/CNPJ do titular> a cada 30s (máximo 5 min)
  2. Se data.body.biometricUrl vier preenchida, o titular precisa completar a etapa de biometria — encaminhe a URL
  3. Verificar no BackOffice > Auditoria > Onboarding se há rejection_reason
  4. Se timeout após 5 min: webhook deveria ter chegado; verificar logs do cliente

Exemplo:

typescript
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:

  1. Verificar se chave PIX é válida no banco do destinatário (use GET /v1/pix/getAccountInfoByPixKey antes de transferir)
  2. Tentar novamente após 5 min (geralmente delay de replicação)
  3. 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:

  1. Verificar se URL é HTTPS com certificado válido
  2. Verificar se endpoint está respondendo 200–299 em até 30s
  3. Conferir o log de entregas no BackOffice (Auditoria → Webhooks) — cada tentativa registra URL, status HTTP e erro
  4. 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})
  5. Verificar a inscrição em GET /v1/webhooks (URL, events, status: active)

Configuração de Webhook:

typescript
// 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) ​

ErroComo identificarCausaSolução
Tenant não identificado400 + error: MISSING_CLIENT_IDHeader x-client-id ausenteEnviar x-client-id: <Client_ID> em toda chamada
Token inválido/expirado401 ou message: Invalid tokenToken expirou (TTL 1h) ou credenciais erradasRenovar token; revisar credenciais
Tenant suspenso402 + error: ACCOUNT_SUSPENDEDInadimplênciaRegularizar pagamentos
Produto não habilitado403 + error: PRODUCT_NOT_ENABLEDFamília de produto desligada para o tenantContatar suporte
Onboarding não encontrado404 + message: Banking account not found...onBoardingId errado (formato/dígitos) ou conta criada fora desta APIConsultar pelo CPF/CNPJ do titular
Falha de negócio412 + message descritivaServiço interno rejeitou a operaçãoLer message e corrigir o request
Rate limit excedido429Muitas requisiçõesAguardar + implementar backoff
Serviço indisponível503Manutenção ou dependency downRetry com backoff; contatar suporte