Como implementar retry com backoff exponencial em consultas de API de CPF

Implemente retry com backoff exponencial em consultas de CPF via API. Evite sobrecarregar o servidor e aumente a resiliência.

Redação CPFHub.io
Redação CPFHub.io
··8 min de leitura
Como implementar retry com backoff exponencial em consultas de API de CPF

Retry com backoff exponencial é o padrão correto para lidar com falhas transitórias em chamadas à API de CPF. Em vez de repetir a requisição imediatamente -- o que pode sobrecarregar o servidor -- o backoff espaça as tentativas de forma crescente: 1s, 2s, 4s, 8s. Com jitter (variação aleatória), múltiplos clientes evitam retomar no mesmo instante, distribuindo a carga e aumentando a chance de recuperação.


Por que backoff exponencial e não retry simples

Retry simples (ingênuo)

Falha -> retry imediato -> retry imediato -> retry imediato

Se 100 clientes fazem isso simultaneamente, o servidor recebe 400 requisições em poucos segundos -- exatamente quando ele menos pode lidar com essa carga.

Backoff exponencial

Falha -> espera 1s -> retry -> espera 2s -> retry -> espera 4s -> retry

As tentativas se espaçam, dando tempo para o servidor se recuperar.

Backoff exponencial com jitter

Falha -> espera 1.3s -> retry -> espera 2.7s -> retry -> espera 3.9s -> retry

O jitter (variação aleatória) evita que múltiplos clientes retentam no mesmo instante, distribuindo a carga de forma mais uniforme.


Quando fazer retry e quando não fazer

Nem todo erro justifica uma nova tentativa. A regra geral é:

Erros que justificam retry

  • Timeout: a requisição não completou no tempo esperado.
  • 5xx (Server Error): o servidor está com problema temporário.
  • Erros de conexão: problemas de rede transitórios.

Erros que NÃO justificam retry

  • 400 (Bad Request): a requisição está malformada -- repetir vai dar o mesmo resultado.
  • 401 (Unauthorized): a chave de API está errada -- repetir não vai resolver.
  • 404 (Not Found): o recurso não existe.

Implementação em Python

import time
import random
import requests
import logging
from typing import Optional, Dict

logger = logging.getLogger(__name__)

# Códigos HTTP que justificam retry
RETRYABLE_STATUS_CODES = {500, 502, 503, 504}

def consultar_cpf_com_retry(
    cpf: str,
    api_key: str,
    max_retries: int = 4,
    base_delay: float = 1.0,
    max_delay: float = 30.0,
    timeout: int = 30
) -> Optional[Dict]:
    """
    Consulta CPF com retry e backoff exponencial.

    Args:
    cpf: número do CPF (apenas dígitos)
    api_key: chave de API da CPFHub
    max_retries: número máximo de tentativas
    base_delay: delay inicial em segundos
    max_delay: delay máximo em segundos
    timeout: timeout da requisição HTTP em segundos
    """
    headers = {
    "x-api-key": api_key,
    "Accept": "application/json"
    }
    url = f"https://api.cpfhub.io/cpf/{cpf}"

    for tentativa in range(max_retries + 1):
    try:
    response = requests.get(url, headers=headers, timeout=timeout)

    # Sucesso -- retornar imediatamente
    if response.status_code == 200:
    dados = response.json()
    if tentativa > 0:
    logger.info(
    f"Consulta bem-sucedida na tentativa {tentativa + 1}"
    )
    return dados

    # Erro que justifica retry
    if response.status_code in RETRYABLE_STATUS_CODES:
    if tentativa < max_retries:
    delay = _calcular_delay(tentativa, base_delay, max_delay)
    logger.warning(
    f"HTTP {response.status_code}. "
    f"Retry {tentativa + 1}/{max_retries} em {delay:.1f}s"
    )
    time.sleep(delay)
    continue
    else:
    logger.error(
    f"HTTP {response.status_code} apos {max_retries} retries"
    )
    return None

    # Erro que NAO justifica retry
    logger.error(
    f"HTTP {response.status_code} -- erro nao-retentavel"
    )
    return None

    except requests.exceptions.Timeout:
    if tentativa < max_retries:
    delay = _calcular_delay(tentativa, base_delay, max_delay)
    logger.warning(
    f"Timeout. Retry {tentativa + 1}/{max_retries} em {delay:.1f}s"
    )
    time.sleep(delay)
    else:
    logger.error(f"Timeout apos {max_retries} retries")
    return None

    except requests.exceptions.ConnectionError:
    if tentativa < max_retries:
    delay = _calcular_delay(tentativa, base_delay, max_delay)
    logger.warning(
    f"Erro de conexao. Retry {tentativa + 1}/{max_retries} em {delay:.1f}s"
    )
    time.sleep(delay)
    else:
    logger.error(f"Erro de conexao apos {max_retries} retries")
    return None

    return None

def _calcular_delay(tentativa: int, base_delay: float, max_delay: float) -> float:
    """Calcula delay com backoff exponencial e jitter."""
    delay_exponencial = base_delay * (2 ** tentativa)
    delay_com_jitter = delay_exponencial * (0.5 + random.random())
    return min(delay_com_jitter, max_delay)

# Uso
resultado = consultar_cpf_com_retry(
    cpf="12345678900",
    api_key="SUA_CHAVE_API"
)

if resultado and resultado.get("success"):
    print(f"Nome: {resultado['data']['name']}")
else:
    print("Consulta falhou apos todas as tentativas.")

Implementação em Node.js

const axios = require("axios");

const RETRYABLE_STATUS_CODES = new Set([500, 502, 503, 504]);
const API_KEY = "SUA_CHAVE_API";
const BASE_URL = "https://api.cpfhub.io/cpf";
const TIMEOUT_MS = 30000;

function calcularDelay(tentativa, baseDelay = 1000, maxDelay = 30000) {
    const delayExponencial = baseDelay * Math.pow(2, tentativa);
    const jitter = delayExponencial * (0.5 + Math.random());
    return Math.min(jitter, maxDelay);
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function consultarCPFComRetry(cpf, maxRetries = 4) {
    const cpfLimpo = cpf.replace(/\D/g, "");

    for (let tentativa = 0; tentativa <= maxRetries; tentativa++) {
    try {
    const response = await axios.get(`${BASE_URL}/${cpfLimpo}`, {
    headers: {
    "x-api-key": API_KEY,
    "Accept": "application/json"
    },
    timeout: TIMEOUT_MS
    });

    if (tentativa > 0) {
    console.log(`Sucesso na tentativa ${tentativa + 1}`);
    }
    return response.data;

    } catch (error) {
    const status = error.response ? error.response.status : null;
    const isRetryable = status
    ? RETRYABLE_STATUS_CODES.has(status)
    : error.code === "ECONNABORTED" || error.code === "ECONNREFUSED";

    if (isRetryable && tentativa < maxRetries) {
    const delay = calcularDelay(tentativa);
    console.warn(
    `Erro ${status || error.code}. Retry ${tentativa + 1}/${maxRetries} em ${Math.round(delay)}ms`
    );
    await sleep(delay);
    } else if (!isRetryable) {
    console.error(`Erro ${status} -- nao retentavel`);
    return null;
    } else {
    console.error(`Falha apos ${maxRetries} retries`);
    return null;
    }
    }
    }

    return null;
}

// Uso
consultarCPFComRetry("123.456.789-00").then(resultado => {
    if (resultado && resultado.success) {
    console.log(`Nome: ${resultado.data.name}`);
    } else {
    console.log("Consulta falhou.");
    }
});

Visualizando os intervalos de retry

Para entender concretamente os tempos de espera, veja a progressão típica:

TentativaDelay baseCom jitter (exemplo)Tempo acumulado
11s1.3s1.3s
22s2.7s4.0s
34s5.1s9.1s
48s9.8s18.9s

Após 4 tentativas com backoff exponencial, o tempo total é de aproximadamente 19 segundos -- tempo suficiente para a maioria dos problemas transitórios se resolverem.


Tratamento especial para erros de servidor

Para erros 5xx, o header Retry-After pode indicar o tempo recomendado de espera. Quando presente, priorize esse valor:

def tratar_erro_servidor(response, tentativa, base_delay, max_delay):
    """Trata erros 5xx respeitando o header Retry-After quando presente."""
    retry_after = response.headers.get("Retry-After")

    if retry_after:
    try:
    delay = float(retry_after)
    logger.info(f"Retry-After: {delay}s")
    return delay
    except ValueError:
    pass

    # Fallback para backoff exponencial
    return _calcular_delay(tentativa, base_delay, max_delay)

Configurações recomendadas para API de CPF

ParâmetroValor recomendadoJustificativa
max_retries3-4Equilibra resiliência e tempo de espera total
base_delay1 segundoSuficiente para problemas transitórios rápidos
max_delay30 segundosEvita esperas excessivas
timeout30 segundosMargem sobre a latência média de ~900 ms
jitter50-100% do delayDistribui retries de múltiplos clientes

Testando a implementação

import unittest
from unittest.mock import patch, MagicMock

class TestRetryBackoff(unittest.TestCase):

    @patch("requests.get")
    def test_sucesso_na_primeira_tentativa(self, mock_get):
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"success": True, "data": {"name": "Teste"}}
    mock_get.return_value = mock_response

    resultado = consultar_cpf_com_retry("12345678900", "chave_teste")
    self.assertTrue(resultado["success"])
    self.assertEqual(mock_get.call_count, 1)

    @patch("requests.get")
    def test_sucesso_apos_retry(self, mock_get):
    erro_response = MagicMock()
    erro_response.status_code = 503

    sucesso_response = MagicMock()
    sucesso_response.status_code = 200
    sucesso_response.json.return_value = {"success": True, "data": {"name": "Teste"}}

    mock_get.side_effect = [erro_response, erro_response, sucesso_response]

    resultado = consultar_cpf_com_retry(
    "12345678900", "chave_teste", base_delay=0.01
    )
    self.assertTrue(resultado["success"])
    self.assertEqual(mock_get.call_count, 3)

Perguntas frequentes

Qual é a latência esperada da API de CPF e como isso afeta o timeout do retry?

A latência média da API da CPFHub.io é de aproximadamente 900ms. Configure o timeout de cada tentativa em 30 segundos para evitar falsos timeouts antes do servidor responder. Com 4 retries e backoff exponencial, o tempo total máximo de espera é de aproximadamente 19 segundos entre tentativas.

A API CPFHub.io bloqueia as requisições quando a cota mensal é atingida?

Não. A CPFHub.io não bloqueia quando a cota é atingida -- cada consulta extra é cobrada a R$0,15 automaticamente. O plano Gratuito inclui 50 consultas/mês e o plano Pro inclui 1.000 consultas por R$149/mês. O serviço permanece disponível sem interrupção.

Como garantir conformidade com a LGPD ao usar uma API de CPF?

Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não guarde o CPF cru se um token bastar), implemente controle de acesso aos logs de consulta e documente a base legal para o tratamento. A ANPD orienta que dados de identificação devem ser tratados com o princípio da necessidade.

Quanto tempo leva para integrar a API CPFHub.io?

A integração básica leva menos de 30 minutos: crie uma conta em cpfhub.io, gere a API key no painel e faça uma chamada GET para https://api.cpfhub.io/cpf/{CPF} com o header x-api-key. A documentação inclui exemplos em Python, Node.js, PHP, Java e outras linguagens.


Conclusão

O retry com backoff exponencial e jitter é o padrão de ouro para lidar com falhas transitórias em chamadas de API. Ele protege tanto a sua aplicação -- que não fica presa em loops de retry infinitos -- quanto o servidor da API -- que não é sobrecarregado com requisições repetidas durante momentos de instabilidade.

A API da CPFHub.io

Cadastre-se em cpfhub.io

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

Sobre a redação

Redação CPFHub.io

Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.

WhatsAppFale conosco via WhatsApp