DEV Community

CPFHub
CPFHub

Posted on Originally published at tabnews.com.br

Como consultar CPF via API em Node.js e Python

Transparência: sou fundador da CPFHub, a API usada nos exemplos. Os conceitos (validação local, tratamento de erros, LGPD) valem para qualquer API de consulta de CPF.

Se você tem um formulário de cadastro, um checkout ou um backoffice no Brasil, cedo ou tarde precisa conferir se o CPF digitado existe e bate com o nome informado. Neste tutorial mostro como usar uma API de consulta de CPF em Node.js e Python: validar os dígitos localmente, fazer a consulta, tratar erros e decidir quando vale a pena consultar a Receita Federal em tempo real.

O que uma API de consulta de CPF retorna

Na consulta simples você envia o CPF e recebe os dados cadastrais básicos do titular:

  • nome completo (name e nameUpper, em maiúsculas, bom para comparar);
  • gênero (gender: "M", "F" ou null quando a base não tem a informação);
  • data de nascimento (birthDate no formato DD/MM/AAAA, mais day, month e year separados, úteis para calcular idade).

Exemplo de resposta (dados fictícios):

{
  "success": true,
  "data": {
    "cpf": "12345678909",
    "name": "Fulano de Tal",
    "nameUpper": "FULANO DE TAL",
    "gender": "M",
    "birthDate": "15/06/1990",
    "day": 15,
    "month": 6,
    "year": 1990
  }
}
Enter fullscreen mode Exit fullscreen mode

Passo 1: valide os dígitos antes de chamar a API

Todo CPF tem dois dígitos verificadores calculados pelo módulo 11. Validar isso localmente é gratuito, instantâneo e evita requisições inúteis (CPF digitado errado, tudo repetido como 111.111.111-11 etc.).

Node.js:

function cpfValido(entrada) {
  const cpf = String(entrada).replace(/\D/g, "");
  if (cpf.length !== 11 || /^(\d)\1{10}$/.test(cpf)) return false;

  const digito = (base) => {
    let soma = 0;
    for (let i = 0; i < base.length; i++) {
      soma += Number(base[i]) * (base.length + 1 - i);
    }
    const resto = (soma * 10) % 11;
    return resto === 10 ? 0 : resto;
  };

  const d1 = digito(cpf.slice(0, 9));
  const d2 = digito(cpf.slice(0, 10));
  return d1 === Number(cpf[9]) && d2 === Number(cpf[10]);
}
Enter fullscreen mode Exit fullscreen mode

Python:

import re

def cpf_valido(entrada: str) -> bool:
    cpf = re.sub(r"\D", "", str(entrada))
    if len(cpf) != 11 or cpf == cpf[0] * 11:
        return False

    def digito(base: str) -> int:
        soma = sum(int(n) * (len(base) + 1 - i) for i, n in enumerate(base))
        resto = (soma * 10) % 11
        return 0 if resto == 10 else resto

    return digito(cpf[:9]) == int(cpf[9]) and digito(cpf[:10]) == int(cpf[10])
Enter fullscreen mode Exit fullscreen mode

Atenção: dígito válido não significa que o CPF existe ou pertence a quem digitou. Para isso serve a consulta.

Passo 2: consulta simples (GET)

Na CPFHub, a consulta simples é GET https://api.cpfhub.io/cpf/{cpf}, autenticada pelo header x-api-key. O CPF pode ir com ou sem máscara. Custa 1 crédito por CPF encontrado; CPF não encontrado (404) não consome crédito. O tempo típico é de ~150 ms (tempo típico, não SLA), e a documentação recomenda timeout de cerca de 10 s.

Os erros que você deve tratar:

Status Significado O que fazer
400 CPF sem 11 dígitos Corrigir, não repetir
401 API Key ausente ou inválida Conferir o header x-api-key
403 Créditos esgotados ou conta bloqueada Recarregar créditos, não repetir
404 CPF não encontrado na base Resultado normal
422 Dígito verificador inválido Corrigir, não repetir
429 Limite por minuto (30/min no padrão) Esperar o header Retry-After
500/503 Falha passageira Repetir com espera crescente

Um detalhe: em erros de autenticação o campo error vem como string; nos demais, como objeto com message. Leia os dois formatos.

Node.js (18+, fetch nativo):

const API_KEY = process.env.CPFHUB_API_KEY; // nunca no front-end

async function consultarCpf(cpf) {
  if (!cpfValido(cpf)) throw new Error("CPF inválido (dígitos)");

  const res = await fetch(`https://api.cpfhub.io/cpf/${cpf.replace(/\D/g, "")}`, {
    headers: { "x-api-key": API_KEY },
    signal: AbortSignal.timeout(10_000),
  });
  const body = await res.json().catch(() => ({}));
  const msg = typeof body.error === "string" ? body.error : body.error?.message;

  if (res.ok) return body.data;
  if (res.status === 404) return null; // não encontrado, sem custo
  if (res.status === 429) {
    throw new Error(`Limite por minuto; tente em ${res.headers.get("retry-after")} s`);
  }
  if (res.status === 403) throw new Error(`Sem créditos ou conta bloqueada: ${msg}`);
  throw new Error(`Erro ${res.status}: ${msg}`);
}

const dados = await consultarCpf("123.456.789-09");
console.log(dados?.name, dados?.gender, dados?.birthDate);
Enter fullscreen mode Exit fullscreen mode

Python (requests):

import os
import requests

API_KEY = os.environ["CPFHUB_API_KEY"]

def consultar_cpf(cpf: str):
    if not cpf_valido(cpf):
        raise ValueError("CPF inválido (dígitos)")

    numero = "".join(c for c in cpf if c.isdigit())
    r = requests.get(
        f"https://api.cpfhub.io/cpf/{numero}",
        headers={"x-api-key": API_KEY},
        timeout=10,
    )
    try:
        body = r.json()
    except ValueError:
        body = {}
    err = body.get("error")
    msg = err if isinstance(err, str) else (err or {}).get("message")

    if r.ok:
        return body["data"]
    if r.status_code == 404:
        return None  # não encontrado, sem custo
    if r.status_code == 429:
        raise RuntimeError(f"Limite por minuto; tente em {r.headers.get('Retry-After')} s")
    if r.status_code == 403:
        raise RuntimeError(f"Sem créditos ou conta bloqueada: {msg}")
    raise RuntimeError(f"Erro {r.status_code}: {msg}")

dados = consultar_cpf("123.456.789-09")
if dados:
    print(dados["name"], dados["gender"], dados["birthDate"])
Enter fullscreen mode Exit fullscreen mode

Se o timeout estourar do seu lado, lembre que a consulta pode ter sido concluída e cobrada no servidor; repetir cobra de novo.

Passo 3: consulta em tempo real na Receita Federal (POST)

Quando você precisa da situação cadastral ou de um comprovante, use POST https://api.cpfhub.io/cpf/realtime. Ela consulta a Receita Federal no momento da chamada e exige CPF + data de nascimento (DD/MM/AAAA ou DDMMAAAA). Custa 1,5 crédito por consulta bem-sucedida, em qualquer situação cadastral. O tempo típico é de cerca de 1 segundo (não é SLA), mas a documentação pede timeout de pelo menos 60 s.

A resposta traz name, birthDate, situation (REGULAR, PENDENTE DE REGULARIZAÇÃO, SUSPENSA, CANCELADA, NULA ou TITULAR FALECIDO), deathYear e, quando disponíveis, controlCode, emissionDate, emissionTime, validationUrl (conferência no site da Receita) e validationHtmlUrl (comprovante).

Sobre deathYear: o campo sempre vem, como inteiro ou null. null significa que não há óbito registrado (ou o valor recebido era inválido). Não trate null como prova de que a pessoa está viva. E só REGULAR indica CPF sem pendências.

Node.js:

async function consultarReceita(cpf, dataNascimento) {
  const res = await fetch("https://api.cpfhub.io/cpf/realtime", {
    method: "POST",
    headers: { "x-api-key": API_KEY, "content-type": "application/json" },
    body: JSON.stringify({ cpf, birthDate: dataNascimento }),
    signal: AbortSignal.timeout(60_000),
  });
  const body = await res.json().catch(() => ({}));
  if (res.ok) return body.data; // { situation, deathYear, controlCode, ... }
  // 422: dígito inválido ou a Receita recusou (ex.: data divergente) — sem custo
  // 429: 5/min no padrão; 502/503: repetir com espera crescente — sem custo
  const msg = typeof body.error === "string" ? body.error : body.error?.message;
  throw new Error(`Erro ${res.status}: ${msg}`);
}
Enter fullscreen mode Exit fullscreen mode

Python:

def consultar_receita(cpf: str, data_nascimento: str):
    r = requests.post(
        "https://api.cpfhub.io/cpf/realtime",
        headers={"x-api-key": API_KEY},
        json={"cpf": cpf, "birthDate": data_nascimento},
        timeout=60,
    )
    body = r.json() if r.content else {}
    if r.ok:
        return body["data"]
    err = body.get("error")
    msg = err if isinstance(err, str) else (err or {}).get("message")
    raise RuntimeError(f"Erro {r.status_code}: {msg}")

d = consultar_receita("12345678909", "15/06/1990")
print(d["situation"], d["deathYear"])
Enter fullscreen mode Exit fullscreen mode

Duas cautelas: o POST não é idempotente, então depois de um timeout local confira o saldo em GET /quota antes de repetir; e o link validationHtmlUrl é público (abre nome, CPF e situação), então guarde-o só no back-end e fora dos logs.

Quando usar cada consulta

  • Simples (1 crédito): confirmar nome, gênero e idade no cadastro ou checkout. Rápida e barata.
  • Tempo real (1,5 crédito): quando você precisa da situação cadastral na Receita ou de um comprovante (crédito, contratos, cadastro regulado).
  • Lote (POST /cpf/bulk, 1 crédito por CPF encontrado): listas de até 10.000 CPFs, processadas em segundo plano — por exemplo, higienizar uma base antiga.

O plano gratuito inclui 50 créditos por mês, sem cartão, o que dá para testar tudo isso. Detalhes de cada endpoint estão na documentação da CPFHub.

LGPD: consulte com finalidade

CPF e data de nascimento são dados pessoais. Antes de integrar qualquer API de consulta de CPF:

  • tenha uma finalidade legítima e uma base legal (ex.: execução de contrato, prevenção a fraude no cadastro);
  • não guarde mais do que precisa: se só precisa saber se o nome confere, guarde o resultado da checagem, não a resposta inteira;
  • mantenha a API Key e as respostas no back-end, e não registre CPFs completos em logs.

Bônus: consulta de CPF por agentes de IA (MCP)

Se você usa Claude, Cursor ou outro cliente MCP, a CPFHub tem um servidor MCP remoto em https://api.cpfhub.io/mcp (autenticado pelo mesmo x-api-key), com as tools lookup_cpf, lookup_cpf_realtime e get_quota_info. Para clientes sem suporte a HTTP existe o pacote local @cpfhub/mcp (via npx), que hoje só tem lookup_cpf funcionando.

Resumo

  1. Valide os dígitos localmente.
  2. Use a consulta simples para nome, gênero e data de nascimento.
  3. Use a consulta em tempo real quando precisar da situação cadastral na Receita.
  4. Trate 404 como resultado normal, respeite 429 e não repita POST depois de timeout.
  5. Colete e guarde só o necessário.

Para testar, crie uma conta gratuita em cpfhub.io e gere sua chave. Dúvidas sobre a consulta CPF via API? Deixe nos comentários.

Top comments (0)