Validação de CPF em Ruby on Rails: guia completo de integração

Como validar e consultar CPF em Rails usando a API SmartCPF v2 com autenticação X-API-KEY, tratamento de erros e exemplos práticos.

Redação Smart CPFRedação Smart CPF
·· 8 min de leitura
Validação de CPF em Ruby on Rails: guia completo de integração

Validar CPF é um requisito recorrente em sistemas brasileiros — cadastros, onboarding, compliance, antifraude. Implementar a validação apenas no frontend ou com algoritmos locais resolve o formato, mas não garante que o CPF exista, esteja regular na Receita Federal ou pertença à pessoa informada. A API SmartCPF resolve essa lacuna consultando bases oficiais em tempo real, retornando nome, data de nascimento, situação cadastral, e-mails, telefones e endereços vinculados, tudo via uma única chamada HTTPS com autenticação por header X-API-KEY.

Este artigo mostra como integrar a SmartCPF v2 em uma aplicação Ruby on Rails de forma robusta: configurando o cliente HTTP, tratando códigos de erro, parseando a resposta e expondo um service object reutilizável. O foco é código de produção — logs, timeouts, retentativas e fallback para não travar o fluxo principal.

Como a SmartCPF valida CPF na prática

A SmartCPF não roda apenas o algoritmo de dígito verificador. Ao receber um CPF no endpoint GET /v2/individuals/{cpf}?package=X, a API consulta fontes oficiais (Receita Federal, bureaus de crédito, bases de telefonia) e monta um dossiê consolidado. O parâmetro package define o nível de detalhe: basic (apenas situação cadastral), standard (dados pessoais + contatos) ou complete (inclui endereços e score). A resposta chega em JSON padronizado com metadados de auditoria (consultId, executionTimeMs, balance de créditos) e o objeto result com os dados da pessoa.

A autenticação é obrigatória via header X-API-KEY — não use Authorization: Bearer, query string nem cookies. Cada requisição consome créditos do plano contratado; o campo balance no retorno mostra o saldo restante em tempo real, permitindo monitoramento proativo sem painel administrativo.

Vantagens de usar a SmartCPF em Rails

1. Validação real, não apenas sintática
O algoritmo de dígito verificador aceita CPFs inexistentes. A SmartCPF confirma existência na base da Receita e situação cadastral (Regular, Suspenso, Cancelado, Nulo).

2. Dados enriquecidos em uma chamada
Nome completo, data de nascimento, idade, nome da mãe, sexo, e-mails, telefones com operadora/tipo/WhatsApp e endereços — tudo no mesmo result, sem necessidade de múltiplos provedores.

3. Auditoria nativa via consultId
Cada consulta gera UUID único rastreável nos logs da SmartCPF. Em caso de disputa ou auditoria LGPD, basta informar o consultId para obter o log completo da requisição.

4. Controle de custo transparente
O campo balance retorna o saldo de créditos após cada chamada. Integre alertas (ex.: saldo < 100) no seu monitoramento para evitar interrupção silenciosa.

5. Latência previsível
executionTimeMs médio de 50–120 ms na região Brasil. Com timeout de 5 s no cliente HTTP, a integração não degrada a experiência do usuário mesmo em picos.

6. Modelo de créditos sem assinatura mínima
Paga-se por consulta realizada. Ideal para projetos com volume variável — desenvolvimento, staging e produção compartilham o mesmo pool de créditos.

Desafios e o que considerar antes de integrar

1. Dependência de rede externa
Se a SmartCPF estiver indisponível, sua validação falha. Implemente circuit breaker e fallback (ex.: validação apenas de formato + fila para reprocessamento assíncrono).

2. Rate limiting por API Key
Planos têm limite de requisições por segundo/minuto. Exceder retorna 429. Configure Retry-After no cliente e exponha métrica de rate_limit_remaining nos logs.

3. LGPD e consentimento
A consulta retorna dados pessoais sensíveis. Garanta base legal (legítimo interesse, consentimento ou obrigação legal) antes de chamar a API e não logue o result completo em produção.

4. CPFs inválidos ou não encontrados
A API retorna success: false com errors descrevendo o motivo (formato inválido, não encontrado, CPF nulo). Trate cada caso: UX amigável para o usuário, alerta para fraude tentada.

5. Variação de pacotes (package)
Escolher basic economiza créditos mas não traz contatos. Defina o pacote por caso de uso (cadastro simples vs. KYC completo) e documente a decisão no código.

Como implementar com a SmartCPF

O exemplo abaixo encapsula a chamada em um CpfValidationService com Faraday, timeouts configuráveis, retentativa exponencial em erros 5xx/429 e parsing seguro da resposta. Ajuste PACKAGE e BASE_URL via variáveis de ambiente.

app/services/cpf_validation_service.rb
# frozen_string_literal: true

# Service object para validar e consultar CPF via SmartCPF v2.
# Uso:
#   result = CpfValidationService.call(cpf: "111.111.111-11", package: "standard")
#   result.success? # => true/false
#   result.data     # => Hash com :cpf, :name, :birth_date, :status, :emails, :phones, :addresses
#   result.meta     # => Hash com :consult_id, :balance, :execution_time_ms
#   result.errors   # => Array de strings (vazio se success?)
class CpfValidationService
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :cpf, :string
  attribute :package, :string, default: -> { ENV.fetch("SMARTCPF_DEFAULT_PACKAGE", "standard") }

  validates :cpf, presence: true
  validates :package, inclusion: { in: %w[basic standard complete] }

  BASE_URL = ENV.fetch("SMARTCPF_BASE_URL", "https://api.smartcpf.com.br/v2")
  API_KEY  = ENV.fetch("SMARTCPF_API_KEY") { raise "SMARTCPF_API_KEY não configurado" }
  TIMEOUT  = ENV.fetch("SMARTCPF_TIMEOUT", 5).to_i
  RETRIES  = ENV.fetch("SMARTCPF_MAX_RETRIES", 2).to_i

  # Estrutura de retorno padronizada
  Result = Struct.new(:success?, :data, :meta, :errors, keyword_init: true)

  def self.call(cpf:, package: nil)
    new(cpf: cpf, package: package).call
  end

  def call
    return failure(errors: ["CPF inválido"]) unless valid?

    normalized_cpf = normalize_cpf(cpf)
    response = http_get("/individuals/#{normalized_cpf}", params: { package: package })

    case response.status
    when 200
      parse_success(response.body)
    when 400, 404, 422
      parse_client_error(response.body)
    when 401, 403
      failure(errors: ["Credenciais inválidas ou sem permissão"], meta: { status: response.status })
    when 429
      failure(errors: ["Rate limit excedido"], meta: { retry_after: response.headers["Retry-After"] })
    when 500..599
      failure(errors: ["Erro interno da SmartCPF"], meta: { status: response.status })
    else
      failure(errors: ["Resposta inesperada: #{response.status}"])
    end
  rescue Faraday::TimeoutError
    failure(errors: ["Timeout ao consultar SmartCPF (#{TIMEOUT}s)"])
  rescue Faraday::ConnectionFailed => e
    failure(errors: ["Falha de conexão: #{e.message}"])
  rescue JSON::ParserError => e
    failure(errors: ["Resposta inválida da API: #{e.message}"])
  end

  private

  def http_get(path, params: {})
    connection.get(path, params) do |req|
      req.options.timeout = TIMEOUT
      req.options.open_timeout = 2
    end
  end

  def connection
    @connection ||= Faraday.new(url: BASE_URL) do |f|
      f.request :retry,
                max: RETRIES,
                interval: 0.5,
                interval_randomness: 0.5,
                backoff_factor: 2,
                exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed],
                retry_statuses: [429, 500, 502, 503, 504]
      f.headers["X-API-KEY"] = API_KEY
      f.headers["Accept"] = "application/json"
      f.headers["User-Agent"] = "SmartCPF-Ruby/#{Smartcpf::VERSION rescue '1.0'}"
      f.response :raise_error # lança em 4xx/5xx para o rescue externo
      f.adapter Faraday.default_adapter
    end
  end

  def normalize_cpf(raw)
    raw.gsub(/\D/, "")
  end

  def parse_success(body)
    json = JSON.parse(body, symbolize_names: true)

    # A API retorna success: true no envelope E no result
    unless json[:success] && json.dig(:result, :cpf)
      return failure(errors: ["Resposta de sucesso sem dados válidos"], meta: { raw: json })
    end

    result = json[:result]
    meta   = json[:meta] || {}

    data = {
      cpf:          result[:cpf],
      name:         result[:name],
      birth_date:   result[:birthDate],
      age:          result[:age],
      mother_name:  result[:motherName],
      gender:       result[:gender],
      status:       result[:status],
      emails:       result[:emails] || [],
      phones:       result[:phones] || [],
      addresses:    result[:addresses] || []
    }

    meta_data = {
      consult_id:       meta[:consultId],
      balance:          meta[:balance],
      execution_time_ms: meta[:executionTimeMs],
      search_type:      meta[:searchType],
      api_version:      meta[:apiVersion],
      date:             meta[:date]
    }

    Result.new(success?: true, data: data, meta: meta_data, errors: [])
  end

  def parse_client_error(body)
    json = JSON.parse(body, symbolize_names: true)
    errors = Array(json[:errors] || json[:message] || "Erro desconhecido")
    failure(errors: errors, meta: { raw: json })
  rescue JSON::ParserError
    failure(errors: ["Erro de validação (resposta não-JSON)"])
  end

  def failure(errors:, meta: {})
    Result.new(success?: false, data: nil, meta: meta, errors: Array(errors))
  end
end

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": "2025-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 acima resolve na prática:

  • Identificação única — consultId permite rastrear a consulta nos logs da SmartCPF e no seu próprio sistema para auditoria LGPD.
  • Controle de custo em tempo real — balance: 9.77 mostra o saldo restante; integre alerta quando < 50 para evitar interrupção.
  • Validação de situação cadastral — status: "Regular" confirma que o CPF está ativo na Receita Federal (outros valores: Suspenso, Cancelado, Nulo, Falecido).
  • Dados de contato prontos para uso — emails, phones (com operator, type, whatsapp) e addresses eliminam necessidade de bases separadas.
  • Performance observável — executionTimeMs: 74.1 permite SLA interno e alerta se latência subir acima do esperado.

Por que usar a SmartCPF no seu Rails

  • Integração em minutos — Apenas Faraday + header X-API-KEY; sem SDK pesado, sem geração de token, sem OAuth.
  • Latência baixa e previsível — Infraestrutura no Brasil, tempo médio < 100 ms, timeout configurável no cliente.
  • Modelo pay-per-use — Créditos não expiram, compartilhados entre ambientes, sem taxa mínima mensal.
  • Dados completos e atualizados — Fontes oficiais + bureaus + telefonia em uma resposta, com versionamento de API (apiVersion: "v2").
  • Escalabilidade nativa — Rate limits generosos por plano; Retry-After no header 429 facilita backoff automático.

Conclusão

Validar CPF apenas pelo dígito verificador expõe seu sistema a fraudes, cadastros fantasmas e problemas de compliance. A SmartCPF v2 entrega a validação real — existência, regularidade e dados enriquecidos — com uma integração HTTP simples, autenticação por header X-API-KEY e resposta JSON padronizada. O service object mostrado aqui isola a complexidade de rede, retries, parsing e tratamento de erro, devolvendo um objeto Result tipado que o resto da aplicação consome sem conhecer detalhes da API. 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.