Como consumir API de CPF em Ruby on Rails com Faraday

Aprenda a consumir a API de consulta de CPF em Ruby on Rails usando Faraday. Guia completo com exemplos de código, service objects e boas práticas.

Redação CPFHub.io
Redação CPFHub.io
··7 min de leitura
Como consumir API de CPF em Ruby on Rails com Faraday

Para consumir a API de CPF da CPFHub.io em Ruby on Rails com Faraday, crie um initializer que configura o cliente HTTP com timeout, retry para erros 5xx e a chave de API via Rails.application.credentials. Em seguida, encapsule a lógica em um service object e adicione tratamento de exceções granular — a API nunca retorna HTTP 429, então o retry deve ser reservado apenas para falhas transitórias do servidor. A ANPD recomenda que credenciais de APIs que acessam dados pessoais sejam armazenadas de forma criptografada, o que as credentials do Rails atendem nativamente.

Introdução

Ruby on Rails continua sendo um framework popular para desenvolvimento web rápido, especialmente em startups e empresas que valorizam produtividade e convenção sobre configuração. Para aplicações Rails que operam no mercado brasileiro, a validação de CPF é um requisito recorrente em processos de cadastro, checkout e onboarding.

A gem Faraday é a biblioteca HTTP mais utilizada no ecossistema Ruby para consumo de APIs REST, oferecendo uma interface flexível com suporte a middleware, retry e timeout.


Pré-requisitos

  • Ruby 3.2+ — versão recomendada.
  • Rails 7.1+ — framework web.
  • Conta na CPFHub.io — para obter a chave de API (50 consultas/mês no plano gratuito).

Adicionando a gem Faraday

No Gemfile:

gem 'faraday', '~> 2.9'
gem 'faraday-retry', '~> 2.2'

Execute bundle install.


Configuração da chave de API

Usando credentials do Rails

rails credentials:edit

Adicione:

cpfhub:
  api_key: SUA_CHAVE_DE_API

Ou via variável de ambiente

No arquivo .env (com a gem dotenv-rails):

CPFHUB_API_KEY=SUA_CHAVE_DE_API

Criando o cliente HTTP com Faraday

Crie um initializer para configurar o cliente Faraday:

# config/initializers/cpfhub.rb
module CpfHub
  BASE_URL = 'https://api.cpfhub.io'

  def self.client
    @client ||= Faraday.new(url: BASE_URL) do |conn|
      conn.request :retry, max: 2, interval: 1, backoff_factor: 2,
                  retry_statuses: [500, 502, 503]
      conn.headers['x-api-key'] = api_key
      conn.headers['Accept'] = 'application/json'
      conn.options.timeout = 10
      conn.options.open_timeout = 5
      conn.adapter Faraday.default_adapter
    end
  end

  def self.api_key
    Rails.application.credentials.dig(:cpfhub, :api_key) ||
      ENV.fetch('CPFHUB_API_KEY', '')
  end
end

Note que retry_statuses inclui apenas erros 5xx — a CPFHub.io nunca retorna HTTP 429. Ao ultrapassar a cota do plano, cada consulta adicional é cobrada a R$0,15 e a API continua respondendo normalmente.


Criando o service object

Seguindo as convenções do Rails, encapsule a lógica de consulta em um service object:

# app/services/cpf_validation_service.rb
class CpfValidationService
  class CpfInvalidoError < StandardError; end
  class CpfNaoEncontradoError < StandardError; end
  class ApiIndisponivel < StandardError; end
  class TimeoutError < StandardError; end

  def initialize(cpf)
    @cpf = cpf.to_s.gsub(/\D/, '')
  end

  def call
    validar_formato!
    consultar_api
  end

  private

  def validar_formato!
    unless @cpf.match?(/\A\d{11}\z/)
      raise CpfInvalidoError, 'CPF inválido. Informe 11 dígitos numéricos.'
    end
  end

  def consultar_api
    response = CpfHub.client.get("/cpf/#{@cpf}")

    case response.status
    when 200
      processar_resposta(response.body)
    when 401
      raise StandardError, 'Chave de API inválida ou ausente.'
    when 500, 503
      raise ApiIndisponivel, "Serviço temporariamente indisponível (HTTP #{response.status})."
    else
      raise StandardError, "Erro HTTP inesperado: #{response.status}"
    end
  rescue Faraday::TimeoutError
    raise TimeoutError, 'A consulta excedeu o tempo limite.'
  rescue Faraday::ConnectionFailed => e
    raise StandardError, "Erro de conexão: #{e.message}"
  end

  def processar_resposta(body)
    dados = JSON.parse(body)

    if dados['success'] && dados['data']
      OpenStruct.new(
        cpf: dados['data']['cpf'],
        nome: dados['data']['name'],
        nome_upper: dados['data']['nameUpper'],
        genero: dados['data']['gender'],
        data_nascimento: dados['data']['birthDate'],
        dia: dados['data']['day'],
        mes: dados['data']['month'],
        ano: dados['data']['year']
      )
    else
      raise CpfNaoEncontradoError, 'CPF não encontrado na base de dados.'
    end
  end
end

Criando o controller

# app/controllers/cpf_controller.rb
class CpfController < ApplicationController
  def show
    resultado = CpfValidationService.new(params[:cpf]).call

    render json: {
      valido: true,
      dados: {
        cpf: resultado.cpf,
        nome: resultado.nome,
        genero: resultado.genero,
        data_nascimento: resultado.data_nascimento
      }
    }
  rescue CpfValidationService::CpfInvalidoError => e
    render json: { erro: e.message }, status: :bad_request
  rescue CpfValidationService::CpfNaoEncontradoError => e
    render json: { erro: e.message }, status: :not_found
  rescue CpfValidationService::TimeoutError => e
    render json: { erro: e.message }, status: :gateway_timeout
  rescue StandardError => e
    render json: { erro: e.message }, status: :internal_server_error
  end
end

Rota

# config/routes.rb
Rails.application.routes.draw do
  get 'cpf/:cpf', to: 'cpf#show', constraints: { cpf: /\d{11}/ }
end

Usando em um model com validação customizada

Para validar o CPF ao salvar um registro no banco de dados:

# app/models/cliente.rb
class Cliente < ApplicationRecord
  validate :cpf_valido_na_api, if: -> { cpf_changed? && cpf.present? }

  private

  def cpf_valido_na_api
    resultado = CpfValidationService.new(cpf).call
    self.nome_receita = resultado.nome
  rescue CpfValidationService::CpfInvalidoError
    errors.add(:cpf, 'é sintaticamente inválido')
  rescue CpfValidationService::CpfNaoEncontradoError
    errors.add(:cpf, 'não foi encontrado na base de dados')
  rescue CpfValidationService::TimeoutError
    errors.add(:base, 'Não foi possível validar o CPF no momento.')
  rescue StandardError
    errors.add(:base, 'Erro ao validar CPF.')
  end
end

Exemplo de resposta da API

{
  "success": true,
  "data": {
    "cpf": "12345678900",
    "name": "João da Silva",
    "nameUpper": "JOÃO DA SILVA",
    "gender": "M",
    "birthDate": "15/06/1990",
    "day": 15,
    "month": 6,
    "year": 1990
  }
}

Testando com cURL

curl -X GET https://api.cpfhub.io/cpf/12345678900 \
  -H "x-api-key: SUA_CHAVE_DE_API" \
  -H "Accept: application/json" \
  --max-time 10

Para testar o endpoint Rails:

curl -X GET http://localhost:3000/cpf/12345678900 \
  -H "Accept: application/json" \
  --max-time 10

Escrevendo testes com RSpec

# spec/services/cpf_validation_service_spec.rb
require 'rails_helper'

RSpec.describe CpfValidationService do
  describe '#call' do
    context 'com CPF válido' do
      before do
        stub_request(:get, 'https://api.cpfhub.io/cpf/12345678900')
          .to_return(
            status: 200,
            body: {
              success: true,
              data: {
                cpf: '12345678900',
                name: 'João da Silva',
                nameUpper: 'JOÃO DA SILVA',
                gender: 'M',
                birthDate: '15/06/1990',
                day: 15,
                month: 6,
                year: 1990
              }
            }.to_json,
            headers: { 'Content-Type' => 'application/json' }
          )
      end

      it 'retorna os dados do CPF' do
        resultado = described_class.new('12345678900').call
        expect(resultado.nome).to eq('João da Silva')
        expect(resultado.cpf).to eq('12345678900')
      end
    end

    context 'com CPF inválido' do
      it 'levanta CpfInvalidoError' do
        expect {
          described_class.new('123').call
        }.to raise_error(CpfValidationService::CpfInvalidoError)
      end
    end
  end
end

Boas práticas

  • Service objects — isole a lógica de integração em service objects para facilitar testes e manutenção.

  • Timeout — configure timeout e open_timeout no Faraday para evitar requisições travadas.

  • Retry — use o middleware faraday-retry para lidar com falhas transitórias 5xx automaticamente. Não é necessário retry por cota: a CPFHub.io nunca bloqueia, cobra R$0,15/extra ao ultrapassar o plano.

  • Credentials — armazene a chave de API nas credentials criptografadas do Rails ou em variáveis de ambiente.

  • Testes — use WebMock ou VCR para mockar requisições HTTP nos testes, evitando chamadas reais à API.


Perguntas frequentes

O Faraday é obrigatório para consumir a API de CPF em Rails?

Não. Qualquer biblioteca HTTP funciona — Net::HTTP, HTTParty, Typhoeus. O Faraday é recomendado porque seu sistema de middleware torna simples adicionar retry, logging e autenticação de forma desacoplada. Para projetos que já usam outra biblioteca, adaptar os exemplos deste guia é direto.

Como configurar o retry do Faraday corretamente para a CPFHub.io?

Configure retry_statuses: [500, 502, 503] e omita o código 429, já que a CPFHub.io nunca o retorna. Um max: 2 com backoff_factor: 2 cobre bem instabilidades transitórias sem atrasar o fluxo da aplicação. O intervalo resultante é 1s na primeira tentativa e 2s na segunda.

É seguro validar o CPF dentro de um callback do ActiveRecord?

Sim, mas com ressalvas. Callbacks before_validation ou validate que fazem chamadas externas aumentam o tempo de resposta do save. Para operações em lote ou importações, prefira validar CPFs em background jobs e usar o resultado armazenado no model. Para cadastros em tempo real, a chamada síncrona é adequada dado que a latência da API é ~900ms.

Como lidar com o timeout da API em um fluxo de checkout?

Defina um timeout de 5 a 10 segundos no Faraday e trate CpfValidationService::TimeoutError no controller com uma resposta que permita ao usuário tentar novamente. Em checkouts críticos, considere validar o CPF antes do início do fluxo de pagamento para não interromper a transação no momento do processamento.


Conclusão

Consumir a API de consulta de CPF da CPFHub.io em Ruby on Rails com Faraday é simples quando a arquitetura segue as convenções do framework: initializer para o cliente, service object para a lógica de negócio e RSpec com WebMock para os testes. O resultado é uma integração testável, resiliente e fácil de manter.

Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e adicione validação de CPF à sua aplicação Rails em menos de uma hora.

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