BlendFi

Erros e retentativas

Cada código de erro mapeado para significado claro, quem causou, se deve retentar e com qual backoff.

Confiabilidade

Erros e retentativas

Quando algo falha na API da BlendFi, a resposta é sempre JSON e tem sempre o mesmo formato. Este guia mostra como ler, o que cada code significa e quais erros são seguros para retentar.

O envelope de erro

{
  "code": "invalid_transaction_state",
  "message": "Transaction cannot transition from 'buying_crypto' to 'completed'.",
  "request_id": "01KPR9F6MM8G147177J7ZQPJHG",
  "details": { /* opcional, presente em erros de validação */ }
}

Decida pelo `code`

`code` é estável, legível por máquina e nunca muda de significado. Toda sua lógica de erro deve se basear nesse campo.

Mostre `message` para humanos

`message` é a descrição legível por humanos. A gente pode reescrever ao longo do tempo, então não faça `match` em cima do texto.

Envie `request_id` quando pedir ajuda

Toda resposta carrega um `request_id`. Inclua nos e-mails de suporte — encontramos sua requisição em segundos.

Como tratar um erro

1. Faça parse do código primeiro

Não confie só no status HTTP. O mesmo 400 pode ser validation_error (você corrige), invalid_json (também você) ou idempotency_key_required (você esqueceu o cabeçalho). Decida pelo code, não pelo status.

const res = await fetch(url, options);

if (!res.ok) {
  const error = await res.json();

  switch (error.code) {
    case "validation_error":
      // Mostra erros de campo a partir de error.details.issues
      return showFieldErrors(error.details?.issues);
    case "idempotency_key_required":
      throw new Error("chave de idempotência ausente — bug na camada de retry");
    case "rate_limit_exceeded":
      return retryAfterBackoff(error);
    case "internal_error":
      return retryWithBackoff(error);
    default:
      return reportUnexpected(error);
  }
}

2. Decida se vai retentar

Use a política de retentativa abaixo. Versão curta: 5xx e 429 são sempre seguros para retentar com a mesma chave de idempotência. 4xx não — corrija a requisição e use uma chave nova.

3. Logue o request_id em tudo

Tendo a chamada dado certo ou errado, guarde o request_id junto com a operação no seu log de auditoria. Quando um parceiro reportar "a transação X tá presa", esse ID nos permite rastrear a requisição exata em segundos — sem ele, a gente fica caçando por timestamp e ID de conta, o que é lento e ambíguo.

log.info({
  request_id: res.headers.get("x-request-id"),
  transaction_id: body.id,
  status: res.status,
}, "blendfi.transaction.created");

Política de retentativa

Uma regra

Retente 5xx e 429. Não retente 4xx. Use a mesma chave de idempotência quando retentar; gere uma chave nova só quando você mudou a requisição para corrigir um 4xx.

Faixa de statusCausaSeguro retentar?Como
2xxSucesson/an/a
400, 401, 403, 404, 409, 422Sua requisiçãoNãoCorrija a requisição, use uma chave de idempotência nova
429 rate_limit_exceededVocê está enviando rápido demaisSimRespeite o cabeçalho Retry-After; backoff exponencial se ausente
500 internal_errorBug do lado da BlendFiSimMesma chave; backoff exponencial (250ms, 500ms, 1s, 2s, 4s, desiste)
502, 503, 504BlendFi ou provedor externo indisponívelSimMesma chave; backoff exponencial

Um loop de retentativa razoável

async function withRetry(fn) {
  const MAX_ATTEMPTS = 5;
  let lastError;

  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
    try {
      const res = await fn();
      if (res.status < 500 && res.status !== 429) return res;
      lastError = await res.json();
    } catch (networkError) {
      lastError = networkError;
    }

    const backoff = Math.min(2 ** attempt * 250, 4000);
    const jitter = Math.random() * 100;
    await sleep(backoff + jitter);
  }

  throw new Error("blendfi: retentativas esgotadas", { cause: lastError });
}

Encapsule fn() para que a mesma chave de idempotência seja reusada entre as tentativas — veja o guia de Idempotência para entender por quê.

Catálogo completo de erros

4xx — sua requisição

CódigoHTTPSignificadoComo resolver
validation_error400Validação de schema falhou (corpo, query ou parâmetros)Leia details.issues para mensagens por campo; corrija e reenvie com chave de idempotência nova
invalid_json400Corpo não pôde ser interpretado como JSONConfira vírgulas finais, aspas não escapadas, encoding
invalid_cursor400Cursor de paginação está malformado ou expirouReinicie a listagem do começo
idempotency_key_required400Cabeçalho Idempotency-Key ausente em POST ou PATCHAdicione o cabeçalho; veja o guia de Idempotência
authentication_required401Cabeçalho Authorization ausente ou malformadoAdicione Authorization: Bearer sk_test_…
authentication_failed401Chave desconhecida, revogada ou com prefixo de ambiente erradoCopie a chave de novo; verifique sandbox vs produção
unauthorized401Autenticado mas sem credenciais para esse recursoConfira se o recurso pertence ao seu tenant
missing_capability403Chave válida mas sem a capacidade para esse endpointManda e-mail pra gente ampliar as capacidades da chave
user_not_found404O user_id não existe ou está em outro tenantVerifique o ID; confira se pertence à sua organização
quote_not_found404O quote_id não existe ou está em outro tenantIdem
transaction_not_found404O transaction_id não existe ou está em outro tenantIdem
idempotency_key_reused409Essa Idempotency-Key foi usada com um corpo diferenteBug no seu código — gere uma chave nova por operação lógica
idempotency_key_in_progress409Uma requisição com a mesma chave ainda está rodando (até 60 s)Espere um pouco e retente com a mesma chave
invalid_transaction_state409Tentou avançar uma transação por uma transição de estado proibidaRe-busque a transação; respeite o ciclo de vida
quote_already_consumed409Cotação já foi usada para criar uma transaçãoCrie uma cotação nova
quote_expired409Cotação está mais velha que seu TTL (geralmente 30s)Crie uma cotação nova
duplicate_cpf409Outro usuário no seu tenant já tem esse CPFBusque o usuário existente com GET /v1/users?cpf=…
duplicate_external_id409Outro usuário no seu tenant já tem esse external_idIdem

429 — limite de taxa

CódigoHTTPSignificadoComo resolver
rate_limit_exceeded429Você excedeu o limite de taxa por chave para esse endpointRespeite Retry-After; faça backoff; considere throttling no cliente

5xx — problema nosso

CódigoHTTPSignificadoComo resolver
internal_error500Falha inesperada do lado do servidorRetente com a mesma chave de idempotência; se persistir, manda e-mail com o request_id
upstream_unavailable502Um provedor externo (banco PIX, corretora, blockchain) está inacessívelRetente com backoff; sinalizamos o identificador do upstream em details.upstream

Erros de validação em detalhe

Quando a resposta for 400 validation_error, details.issues lista cada campo com problema:

{
  "code": "validation_error",
  "message": "Request validation failed.",
  "request_id": "01KPR9F6MM8G147177J7ZQPJHG",
  "details": {
    "issues": [
      { "path": "cpf", "message": "must be 11 digits" },
      { "path": "external_id", "message": "is required" }
    ]
  }
}

path é o caminho JSON do campo problemático. Mostre essas mensagens junto aos campos do seu formulário — seus usuários finais vão agradecer.

O que ler em seguida

FAQ

Devo retentar num 400 validation_error? Não. A mesma requisição vai falhar do mesmo jeito toda vez. Corrija o corpo, gere uma chave de idempotência nova, envie de novo.

O campo code veio com um valor que eu nunca vi. O que faço? Trate como internal_error para fins de retentativa (não retente operações destrutivas; retente leituras idempotentes com backoff). Manda e-mail pra gente com o request_id e a gente documenta o código ou conserta o bug.

Meu orçamento de retentativas acabou e eu não vi resposta final. Qual o estado? Para POST e PATCH, busque o recurso direto com GET usando os parâmetros que você enviou. Se você usou os padrões recomendados de external_id/Idempotency-Key, seu dado é encontrável — você só não sabe se o create original deu certo. A busca te conta.

429 não veio com Retry-After. Quanto tempo eu espero? Use backoff exponencial começando em 1 segundo, com teto de 30 segundos. A gente sempre tenta incluir Retry-After, mas respostas degradadas podem omitir.

Qual a diferença entre authentication_failed e unauthorized? authentication_failed significa que a gente não consegue identificar você (chave ruim). unauthorized significa que a gente sabe quem você é mas você está tentando acessar um recurso que não é seu (por exemplo, uma transação de outra organização).

Nesta página