Validação de CPF com Deno: consumo nativo da API SmartCPF via fetch

Como validar CPFs em Deno usando fetch nativo para consumir a API SmartCPF v2 com autenticação X-API-KEY.

Redação Smart CPFRedação Smart CPF
·· 8 min de leitura
Validação de CPF com Deno: consumo nativo da API SmartCPF via fetch

Validar CPFs em aplicações backend é uma necessidade recorrente em sistemas brasileiros — onboarding, KYC, prevenção a fraude, conformidade. A API SmartCPF resolve isso devolvendo não apenas a validade do dígito verificador, mas dados cadastrais completos (nome, data de nascimento, situação cadastral, e-mails, telefones, endereços) com uma única chamada HTTP. Este artigo mostra como consumir o endpoint GET /v2/individuals/{cpf} diretamente com o fetch nativo do Deno, sem dependências externas, mantendo o runtime leve e a superfície de ataque mínima.

O Deno inclui fetch global compatível com o padrão Web Fetch API, o que elimina a necessidade de instalar axios, node-fetch ou undici. A autenticação na SmartCPF é feita exclusivamente via header X-API-KEY — nada de Authorization: Bearer, query string ou cookies. O exemplo abaixo cobre montagem da URL com o parâmetro package, envio do header obrigatório, tratamento de códigos de erro HTTP (401, 403, 404, 422, 5xx) e parsing tipado da resposta JSON.

Conceito central: fetch nativo + tipagem estrita

Deno roda TypeScript out-of-the-box e expõe fetch como API global, igual ao navegador. Isso significa que você escreve código que roda tanto no edge quanto no servidor sem polyfills. A tipagem da resposta da SmartCPF permite validar o contrato em tempo de compilação e evitar any espalhado pelo código. O endpoint aceita o parâmetro package (ex: package=complete) para controlar o nível de detalhe do retorno — o que evita over-fetching e economiza créditos.

Vantagens de usar fetch nativo no Deno

1. Zero dependências externas Não há package.json, node_modules ou lockfile. O binário único do Deno já traz tudo o que você precisa para requisições HTTP/HTTPS com TLS moderno.

2. Tipagem TypeScript nativa Interfaces como SmartCPFResponse e SmartCPFMeta são verificadas em tempo de compilação (deno check), reduzindo bugs de runtime causados por mudanças no contrato da API.

3. Compatível com Web Standards O mesmo código roda em Cloudflare Workers, Deno Deploy, Vercel Edge Functions e browsers — portabilidade real sem adaptações.

4. Controle fino de timeout e redirectfetch aceita signal (AbortController) para timeouts customizados e redirect: 'manual' para lidar com 3xx sem seguir cegamente — útil em ambientes com proxy corporativo.

5. Streaming de resposta para payloads grandesresponse.body é um ReadableStream; você pode processar JSON incrementalmente com TextDecoderStream se o pacote complete trouxer muitos registros.

6. Segurança por padrão Deno exige flags explícitas (--allow-net=api.smartcpf.com.br) para acesso de rede. Isso impede vazamento acidental de credenciais em scripts maliciosos ou dependências comprometidas.

Desafios do fetch nativo no Deno

1. Sem retry automático Diferente de axios-retry ou ky, o fetch nativo não tem retry built-in. Você precisa implementar backoff exponencial manualmente se a API retornar 429 ou 5xx transitório.

2. Tratamento de erro HTTP não é exceçãofetch só rejeita em falha de rede (DNS, TLS, conexão recusada). Códigos 4xx/5xx resolvem a Promise com ok: false. Esquecer de checar response.ok é a causa mais comum de bugs silenciosos.

3. JSON parsing falha silenciosamente em corpo vazioawait response.json() lança SyntaxError se o corpo estiver vazio ou não for JSON válido (ex: HTML de página de erro do WAF). Sempre valide content-type antes de parsear.

4. Ausência de interceptors globais Não há equivalente a axios.interceptors.request.use para injetar X-API-KEY automaticamente em todas as chamadas. O padrão recomendado é uma wrapper function ou classe client tipada.

5. Certificados customizados exigem flag CLI Em ambientes com CA corporativa, você precisa passar --ca-file ou --unsafely-ignore-certificate-errors na invocação do Deno — não dá para configurar via código.

Como implementar com a SmartCPF

Abaixo, um client mínimo tipado que encapsula a chamada, injeta o header X-API-KEY, trata erros HTTP e retorna um discriminated union (Success | Error) para forçar o consumidor a lidar com os dois casos. O parâmetro package é opcional e defaulta a complete.

smartcpf-client.ts
// smartcpf-client.ts
// Client tipado para consumir GET /v2/individuals/{cpf} da SmartCPF
// Requer flag --allow-net=api.smartcpf.com.br ao rodar com Deno

// ============================================
// Tipos da resposta (subconjunto relevante)
// ============================================

interface SmartCPFMeta {
  searchType: string;
  consultId: string;
  user: string;
  message: string;
  ip: string;
  success: boolean;
  apiVersion: string;
  date: string; // ISO 8601
  executionTimeMs: number;
  balance: number;
}

interface Phone {
  number: string;
  blockedTelemarketing: boolean;
  operator: string;
  type: string;
  whatsapp: boolean;
}

interface SmartCPFResult {
  cpf: string;
  name: string;
  birthDate: string; // DD/MM/YYYY
  age: number;
  motherName: string;
  gender: "M" | "F";
  status: string;
  emails: string[];
  phones: Phone[];
  addresses: unknown[]; // estrutura omitida para brevidade
}

interface SmartCPFSuccessResponse {
  meta: SmartCPFMeta;
  result: SmartCPFResult;
  success: true;
  errors: never[];
}

interface SmartCPFErrorResponse {
  meta: SmartCPFMeta;
  result: null;
  success: false;
  errors: string[];
}

// Union discriminada pelo campo `success`
type SmartCPFResponse = SmartCPFSuccessResponse | SmartCPFErrorResponse;

// ============================================
// Erros de domínio tipados
// ============================================

class SmartCPFHttpError extends Error {
  constructor(
    public readonly status: number,
    public readonly body: unknown,
    message: string
  ) {
    super(message);
    this.name = "SmartCPFHttpError";
  }
}

class SmartCPFNetworkError extends Error {
  constructor(public readonly cause: Error) {
    super(`Falha de rede ao chamar SmartCPF: ${cause.message}`);
    this.name = "SmartCPFNetworkError";
  }
}

// ============================================
// Client
// ============================================

const BASE_URL = "https://api.smartcpf.com.br/v2";

interface ClientOptions {
  apiKey: string;
  /** Timeout total em ms (default: 10_000) */
  timeoutMs?: number;
  /** Pacote de dados: 'basic' | 'complete' | 'custom' (default: 'complete') */
  defaultPackage?: "basic" | "complete" | "custom";
}

export function createSmartCPFClient(options: ClientOptions) {
  const { apiKey, timeoutMs = 10_000, defaultPackage = "complete" } = options;

  const headers = new Headers({
    "X-API-KEY": apiKey,
    "Accept": "application/json",
    "User-Agent": `smartcpf-deno-client/1.0 (deno/${Deno.version.deno})`,
  });

  async function fetchWithTimeout(
    url: string,
    init: RequestInit,
    timeout: number
  ): Promise<Response> {
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), timeout);
    try {
      return await fetch(url, { ...init, signal: controller.signal });
    } finally {
      clearTimeout(id);
    }
  }

  return {
    /**
     * Consulta um CPF na SmartCPF.
     * @param cpf CPF apenas dígitos (ex: '11111111111') ou formatado
     * @param pkg Override do pacote para esta chamada
     */
    async consultarCPF(
      cpf: string,
      pkg: "basic" | "complete" | "custom" = defaultPackage
    ): Promise<SmartCPFResponse> {
      // Normaliza CPF: remove pontuação
      const cleanCpf = cpf.replace(/\D/g, "");

      if (cleanCpf.length !== 11) {
        throw new Error(`CPF inválido: deve conter 11 dígitos (recebido: ${cleanCpf.length})`);
      }

      const url = `${BASE_URL}/individuals/${cleanCpf}?package=${pkg}`;

      let response: Response;
      try {
        response = await fetchWithTimeout(url, { method: "GET", headers }, timeoutMs);
      } catch (err) {
        if (err instanceof DOMException && err.name === "AbortError") {
          throw new SmartCPFNetworkError(new Error(`Timeout após ${timeoutMs}ms`));
        }
        throw new SmartCPFNetworkError(err as Error);
      }

      // Content-Type guard: evita parsear HTML de WAF/página de erro
      const contentType = response.headers.get("content-type") ?? "";
      if (!contentType.includes("application/json")) {
        const text = await response.text().catch(() => "");
        throw new SmartCPFHttpError(
          response.status,
          text,
          `Resposta não-JSON (content-type: ${contentType || "ausente"})`
        );
      }

      let data: SmartCPFResponse;
      try {
        data = (await response.json()) as SmartCPFResponse;
      } catch (err) {
        throw new SmartCPFHttpError(
          response.status,
          null,
          `Falha ao parsear JSON: ${(err as Error).message}`
        );
      }

      // SmartCPF retorna success: false no body para 404/422/etc, mas HTTP 200
      // Também pode vir HTTP 401/403/500 com body de erro
      if (!response.ok || data.success === false) {
        const msg = data.errors?.join("; ") ?? data.meta?.message ?? "Erro desconhecido";
        throw new SmartCPFHttpError(response.status, data, msg);
      }

      return data;
    },

    /** Validação rápida de dígito verificador (algoritmo local, sem rede) */
    validarDigitos(cpf: string): boolean {
      const nums = cpf.replace(/\D/g, "");
      if (nums.length !== 11 || /^(\d)\1{10}$/.test(nums)) return false;

      const calc = (base: number) =>
        nums
          .slice(0, base)
          .split("")
          .reduce((sum, d, i) => sum + Number(d) * (base + 1 - i), 0) % 11;

      const d1 = calc(9) < 2 ? 0 : 11 - calc(9);
      const d2 = calc(10) < 2 ? 0 : 11 - calc(10);

      return d1 === Number(nums[9]) && d2 === Number(nums[10]);
    },
  };
}

// ============================================
// Exemplo de uso (rode com: deno run --allow-net=api.smartcpf.com.br --allow-env smartcpf-client.ts)
// ============================================

if (import.meta.main) {
  const apiKey = Deno.env.get("SMARTCPf_API_KEY");
  if (!apiKey) {
    console.error("Defina a variável de ambiente SMARTCPf_API_KEY");
    Deno.exit(1);
  }

  const client = createSmartCPFClient({ apiKey });

  // Exemplo: CPF de teste (formato livre)
  const cpfTeste = "111.111.111-11";

  // Validação local rápida antes de gastar crédito
  if (!client.validarDigitos(cpfTeste)) {
    console.log("CPF inválido (dígito verificador) — não consultando API");
    Deno.exit(0);
  }

  try {
    const resposta = await client.consultarCPF(cpfTeste, "complete");

    // TypeScript estreita o tipo para SmartCPFSuccessResponse aqui
    console.log("=== Consulta realizada ===");
    console.log(`ConsultID: ${resposta.meta.consultId}`);
    console.log(`Saldo restante: ${resposta.meta.balance} créditos`);
    console.log(`Tempo de execução: ${resposta.meta.executionTimeMs} ms`);
    console.log(`Nome: ${resposta.result.name}`);
    console.log(`CPF: ${resposta.result.cpf}`);
    console.log(`Status: ${resposta.result.status}`);
    console.log(`E-mails: ${resposta.result.emails.join(", ") || "—"}`);
    console.log(
      `Telefones: ${resposta.result.phones.map((p) => p.number).join(", ") || "—"}`
    );
  } catch (err) {
    if (err instanceof SmartCPFHttpError) {
      console.error(`Erro HTTP ${err.status}: ${err.message}`);
      // Exemplo: err.status === 401 -> chave inválida/expirada
      //          err.status === 403 -> pacote não permitido para esta chave
      //          err.status === 404 -> CPF não encontrado na base
      //          err.status === 422 -> CPF malformado
      //          err.status === 429 -> rate limit (implemente retry com backoff)
    } else if (err instanceof SmartCPFNetworkError) {
      console.error(`Erro de rede: ${err.message}`);
      // Exemplo: DNS, TLS, timeout, conexão recusada
    } else {
      console.error(`Erro inesperado: ${(err as Error).message}`);
    }
    Deno.exit(1);
  }
}

Resposta da API

response.json
{
  "meta": {
    "searchType": "CPF G",
    "consultId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "user": "Fulano de Tal",
    "message": "CPF encontrado com sucesso",
    "ip": "127.0.0.1",
    "success": true,
    "apiVersion": "v2",
    "date": "2026-08-17T14:32:10.123Z",
    "executionTimeMs": 74.1,
    "balance": 9.77
  },
  "result": {
    "cpf": "111.111.111-11",
    "name": "João da Silva",
    "birthDate": "10/09/1992",
    "age": 33,
    "motherName": "Maria da Silva",
    "gender": "M",
    "status": "Regular",
    "emails": ["[email protected]"],
    "phones": [
      {
        "number": "(55) 99999-9999",
        "blockedTelemarketing": false,
        "operator": "VIVO",
        "type": "CELULAR",
        "whatsapp": false
      }
    ],
    "addresses": []
  },
  "success": true,
  "errors": []
}

O fluxo demonstrado resolve na prática:

  • Validação local de dígito (validarDigitos) evita gastar crédito em CPFs obviamente inválidos antes de chamar a rede.
  • Header X-API-KEY injetado automaticamente pelo client elimina erro humano de esquecer autenticação ou usar método errado.
  • consultId no meta rastreia cada consulta nos logs da SmartCPF — essencial para auditoria e suporte.
  • balance devolvido em tempo real permite implementar alerta de saldo baixo (ex: < 5 créditos) no seu painel administrativo.
  • executionTimeMs expõe latência real da API para SLOs internos e monitoramento de performance.
  • Union discriminada (success: true | false) força o TypeScript a exigir tratamento dos dois ramos, eliminando acessos a result nulo em caso de erro.

Por que usar a SmartCPF

  • Latência previsível: média sub-100 ms no endpoint complete com infra no Brasil, adequado para fluxos síncronos de onboarding.
  • Integração sem SDK: REST puro sobre HTTPS, compatível com qualquer runtime (Deno, Node, Go, Python, edge functions) — zero vendor lock-in.
  • Modelo de créditos transparente: balance no response e dashboard com extrato detalhado; você paga apenas pelo que consulta, sem mensalidade mínima.
  • Escalabilidade elástica: a API absorve picos de Black Friday ou campanhas de marketing sem necessidade de provisionar capacidade antecipada.
  • Dados enriquecidos em uma chamada: além do status cadastral, retorna e-mails, telefones com operadora/tipo/WhatsApp e endereços — evita múltiplas integrações com bureaus distintos.

Conclusão

Consumir a SmartCPF com o fetch nativo do Deno entrega uma solução enxuta, tipada e portável para validação e enriquecimento de CPF. O client mostrado aqui cobre autenticação correta (X-API-KEY), timeout configurável, validação local de dígito, parsing seguro de JSON e tratamento estruturado de erros HTTP e de domínio — tudo em menos de 150 linhas sem dependências externas. Adapte o defaultPackage e o timeout ao seu SLA, implemente retry com backoff exponencial para 429/5xx se precisar de resiliência extra, e você tem uma integração pronta para produção. Crie sua conta na SmartCPF e faça sua primeira consulta em minutos.

Redação Smart CPF

Sobre a redação

Redação Smart CPF

Autor do blog

Pronto para validar CPFs em segundos?

Consultas gratuitas para testar agora. Acesso imediato à documentação.