Consumindo a SmartCPF em Supabase Edge Functions

Tutorial técnico: integre a API SmartCPF v2 em Supabase Edge Functions com TypeScript, autenticação X-API-KEY e tratamento de erros.

Redação Smart CPFRedação Smart CPF
·· 7 min de leitura
Consumindo a SmartCPF em Supabase Edge Functions

Validar e consultar CPFs em tempo real é um requisito comum em sistemas brasileiros — onboarding de usuários, compliance KYC, prevenção de fraude. A API SmartCPF resolve isso com uma consulta única que retorna dados cadastrais, situação cadastral, e-mails, telefones e endereços associados ao CPF. Este artigo mostra como consumir o endpoint GET /v2/individuals/{cpf} diretamente de uma Supabase Edge Function, mantendo a chave de API segura no servidor e lidando com respostas de erro de forma robusta.

Supabase Edge Functions rodam no runtime Deno, o que significa TypeScript nativo, fetch global disponível e implantação via CLI sem configuração de containers. A combinação é ideal para chamadas de API sensíveis: a X-API-KEY nunca expõe no frontend, a função escala automaticamente e o custo por execução permanece previsível.

O que são Supabase Edge Functions

Edge Functions são funções serverless executadas na borda da rede (Deno Deploy), com latência baixa e cold-start mínimo. Diferente de Functions tradicionais (Node.js), o runtime é Deno: sem node_modules, imports via URL (esm.sh, jsr.io), tipagem TypeScript out-of-the-box e APIs web padrão (fetch, Request, Response, Headers). Para integrações externas como a SmartCPF, isso elimina bundlers e permite escrever o cliente HTTP direto com tipagem estrita.

Vantagens de usar Edge Functions com a SmartCPF

1. Segredo no servidor
A X-API-KEY fica apenas em variáveis de ambiente da Edge Function; o frontend chama sua função sem credenciais.

2. Tipagem compartilhada
Defina interfaces TypeScript para request/response da SmartCPF e reutilize no frontend via supabase functions invoke — contratos sincronizados sem OpenAPI extra.

3. Escala automática
Picos de cadastro (Black Friday, campanhas) não exigem provisionamento; o Deno Deploy escala horizontalmente.

4. Logs centralizados
console.log/console.error aparecem no dashboard do Supabase e no CLI (supabase functions logs), facilitando debug de 422, 401, 429.

5. Custo por execução
Modelo pay-per-invocation alinha com o modelo de créditos da SmartCPF: cada consulta consome 1 crédito, cada invocação da Edge Function tem custo fixo conhecido.

6. CORS nativo
Retorne headers Access-Control-Allow-Origin direto na Response — sem configurar gateway extra.

Desafios ao integrar APIs externas em Edge Functions

1. Timeout padrão de 150s
A SmartCPF responde em ~100ms, mas se a latência subir, a Edge Function pode estourar o limite. Configure export const config = { runtime: 'edge', maxDuration: 30 } se precisar de margem.

2. Sem node:crypto nativo
Deno usa crypto.subtle (Web Crypto API). Para assinaturas HMAC ou hash de CPF antes de enviar, use crypto.subtle.digest ou bibliotecas compatíveis (ex: @std/crypto).

3. Variáveis de ambiente no deploy
supabase secrets set SMARTCP_API_KEY=... é obrigatório antes do supabase functions deploy. Esquecer isso gera 500 silencioso — teste local com supabase functions serve --env-file=.env.local.

4. Rate limit da SmartCPF (429)
A API retorna Retry-After em segundos. Implemente backoff exponencial ou fila no frontend; a Edge Function pode apenas propagar o status e header.

5. Cold start em regiões distantes
Deno Deploy roda em ~35 regiões. Usuários no Brasil batem em São Paulo (GRU) ou Rio (GIG) — latência < 50ms. Fora do Brasil, adicione fly.io ou Cloudflare Workers como camada extra se necessário.

Como implementar com a SmartCPF

Abaixo, uma Edge Function completa que valida o CPF recebido, chama a SmartCPF com o header X-API-KEY, trata códigos de erro e retorna JSON padronizado para o frontend. O arquivo deve ficar em supabase/functions/consultar-cpf/index.ts.

supabase/functions/consultar-cpf/index.ts
import { serve } from "https://deno.land/[email protected]/http/server.ts";

// ============================================================
// Tipos da resposta SmartCPF (subset relevante para o frontend)
// ============================================================
interface SmartCpfMeta {
  searchType: string;
  consultId: string;
  user: string;
  message: string;
  ip: string;
  success: boolean;
  apiVersion: string;
  date: string; // ISO 8601
  executionTimeMs: number;
  balance: number; // créditos restantes
}

interface SmartCpfPhone {
  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: SmartCpfPhone[];
  addresses: unknown[]; // estrutura omitida por brevidade
}

interface SmartCpfResponse {
  meta: SmartCpfMeta;
  result: SmartCpfResult;
  success: boolean;
  errors: string[];
}

// ============================================================
// Resposta padronizada que nosso frontend vai consumir
// ============================================================
interface ApiResponse<T> {
  ok: boolean;
  data?: T;
  error?: {
    code: string;
    message: string;
    details?: unknown;
    consultId?: string; // rastreabilidade SmartCPF
  };
}

// ============================================================
// Validação simples de CPF (apenas formato, não dígito verificador)
// ============================================================
function isValidCpfFormat(cpf: string): boolean {
  const cleaned = cpf.replace(/\D/g, "");
  return cleaned.length === 11 && /^\d{11}$/.test(cleaned);
}

// ============================================================
// Handler principal
// ============================================================
serve(async (req: Request): Promise<Response> => {
  // CORS pré-voo
  if (req.method === "OPTIONS") {
    return new Response(null, {
      status: 204,
      headers: corsHeaders(),
    });
  }

  if (req.method !== "POST") {
    return jsonResponse(
      { ok: false, error: { code: "METHOD_NOT_ALLOWED", message: "Use POST" } },
      405
    );
  }

  // Lê body JSON
  let body: { cpf?: string; package?: string } = {};
  try {
    body = await req.json();
  } catch {
    return jsonResponse(
      { ok: false, error: { code: "INVALID_JSON", message: "Body deve ser JSON válido" } },
      400
    );
  }

  const { cpf, package: pkg = "complete" } = body;

  // Validação de entrada
  if (!cpf || !isValidCpfFormat(cpf)) {
    return jsonResponse(
      { ok: false, error: { code: "INVALID_CPF", message: "CPF inválido. Envie 11 dígitos numéricos." } },
      422
    );
  }

  const apiKey = Deno.env.get("SMARTCP_API_KEY");
  if (!apiKey) {
    console.error("SMARTCP_API_KEY não configurada nas secrets da Edge Function");
    return jsonResponse(
      { ok: false, error: { code: "SERVER_CONFIG_ERROR", message: "Chave de API não configurada no servidor" } },
      500
    );
  }

  const cleanedCpf = cpf.replace(/\D/g, "");
  const url = `https://api.smartcpf.com.br/v2/individuals/${cleanedCpf}?package=${encodeURIComponent(pkg)}`;

  // Chamada à SmartCPF
  let smartRes: Response;
  try {
    smartRes = await fetch(url, {
      method: "GET",
      headers: {
        "X-API-KEY": apiKey,
        "Accept": "application/json",
        "User-Agent": "supabase-edge-function/1.0",
      },
    });
  } catch (err) {
    console.error("Erro de rede ao chamar SmartCPF:", err);
    return jsonResponse(
      { ok: false, error: { code: "UPSTREAM_NETWORK_ERROR", message: "Falha de comunicação com a API SmartCPF" } },
      502
    );
  }

  // Parse seguro da resposta
  let smartData: SmartCpfResponse;
  try {
    smartData = await smartRes.json();
  } catch {
    return jsonResponse(
      { ok: false, error: { code: "UPSTREAM_INVALID_JSON", message: "Resposta inválida da API SmartCPF" } },
      502
    );
  }

  // Mapeia códigos de status HTTP da SmartCPF para nossa resposta
  if (!smartRes.ok) {
    const errorMap: Record<number, { code: string; message: string }> = {
      400: { code: "BAD_REQUEST", message: "Parâmetros inválidos enviados à SmartCPF" },
      401: { code: "UNAUTHORIZED", message: "Chave de API inválida ou expirada" },
      403: { code: "FORBIDDEN", message: "Acesso negado — verifique permissões do plano" },
      404: { code: "NOT_FOUND", message: "CPF não encontrado na base" },
      422: { code: "UNPROCESSABLE_ENTITY", message: "CPF inválido ou mal formatado" },
      429: { code: "RATE_LIMITED", message: "Limite de requisições excedido" },
      500: { code: "UPSTREAM_ERROR", message: "Erro interno da API SmartCPF" },
      503: { code: "SERVICE_UNAVAILABLE", message: "API SmartCPF indisponível no momento" },
    };

    const mapped = errorMap[smartRes.status] ?? {
      code: "UPSTREAM_ERROR",
      message: `Erro na API SmartCPF (HTTP ${smartRes.status})`,
    };

    return jsonResponse(
      {
        ok: false,
        error: {
          ...mapped,
          details: smartData.errors?.length ? smartData.errors : smartData.meta?.message,
          consultId: smartData.meta?.consultId,
        },
      },
      smartRes.status
    );
  }

  // Sucesso: retorna campos úteis para o frontend
  return jsonResponse({
    ok: true,
    data: {
      consultId: smartData.meta.consultId,
      balance: smartData.meta.balance,
      executionTimeMs: smartData.meta.executionTimeMs,
      cpf: smartData.result.cpf,
      name: smartData.result.name,
      birthDate: smartData.result.birthDate,
      age: smartData.result.age,
      motherName: smartData.result.motherName,
      gender: smartData.result.gender,
      status: smartData.result.status,
      emails: smartData.result.emails,
      phones: smartData.result.phones.map((p) => ({
        number: p.number,
        operator: p.operator,
        type: p.type,
        whatsapp: p.whatsapp,
        blockedTelemarketing: p.blockedTelemarketing,
      })),
      addresses: smartData.result.addresses,
    },
  });
});

// ============================================================
// Helpers
// ============================================================
function corsHeaders(): HeadersInit {
  return {
    "Access-Control-Allow-Origin": "*", // ajuste para seu domínio em produção
    "Access-Control-Allow-Methods": "POST, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization",
    "Access-Control-Max-Age": "86400",
  };
}

function jsonResponse(data: unknown, status = 200): Response {
  return new Response(JSON.stringify(data), {
    status,
    headers: {
      "Content-Type": "application/json; charset=utf-8",
      ...corsHeaders(),
    },
  });
}

Resposta da API

O exemplo abaixo mostra a estrutura real retornada pela SmartCPF v2 (package complete). Os campos consultId e balance são essenciais para rastreabilidade e controle de créditos.

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 implementado resolve na prática:

  • Rastreabilidade: consultId único por consulta permite auditoria e suporte ("qual foi a consulta #a1b2c3d4?").
  • Controle de custos: balance (9.77 créditos) retorna em toda resposta — seu dashboard pode alertar quando cair abaixo de um threshold.
  • Performance observável: executionTimeMs (74.1ms) expõe latência real da SmartCPF; útil para SLOs internos.
  • Dados enriquecidos prontos: phones[].operator, phones[].whatsapp, emails[] eliminam chamadas adicionais a provedores de enriquecimento.
  • Erro acionável: mapeamento de 401→UNAUTHORIZED, 429→RATE_LIMITED com Retry-After propagado evita retry loops cegos no frontend.

Por que usar a SmartCPF

  • Latência previsível: P95 < 150ms na região GRU/GIG, ideal para fluxos síncronos de cadastro.
  • Integração direta: REST + header X-API-KEY — sem OAuth flows, sem SDKs pesados, funciona em qualquer runtime (Deno, Node, Go, Python).
  • Modelo de créditos transparente: 1 consulta = 1 crédito; balance em cada resposta elimina surpresas na fatura.
  • Dados completos no package complete: situação cadastral, nome da mãe, e-mails, telefones com operadora/WhatsApp, endereços — tudo em uma chamada.
  • Escalabilidade nativa: A API suporta milhares de RPM; combinada com Edge Functions, o gargalo passa a ser seu rate limit, não a infra.

Conclusão

Consumir a SmartCPF de uma Supabase Edge Function mantém a credencial segura, tipa o contrato ponta-a-ponta e escala sem ops. O código acima cobre validação, autenticação correta (X-API-KEY), mapeamento de erros e resposta enxuta para o frontend. 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.