Consumindo a API SmartCPF em Java com Spring Boot

Tutorial prático de integração da API SmartCPF v2 em aplicações Spring Boot com autenticação X-API-KEY e tratamento de erros.

Redação Smart CPFRedação Smart CPF
·· 7 min de leitura
Consumindo a API SmartCPF em Java com Spring Boot

Validar e consultar CPFs é uma necessidade recorrente em sistemas brasileiros — desde cadastros de usuários até processos de KYC e compliance. A API SmartCPF resolve isso expondo um endpoint REST simples que retorna dados cadastrais, situação cadastral, e-mails, telefones e endereços associados a um CPF. Este artigo mostra como integrar a API v2 em uma aplicação Spring Boot de forma robusta, cobrindo autenticação, configuração, chamada HTTP e tratamento de respostas e erros.

A integração direta via RestClient (Spring Boot 3.2+) ou WebClient evita dependências extras e mantém o controle total sobre headers, timeouts e serialização. O foco aqui é uma implementação pronta para produção, com injeção de configuração, logs estruturados e mapeamento de erros da API para exceções de domínio.

Autenticação e endpoint da API SmartCPF

A SmartCPF v2 exige autenticação exclusivamente via header X-API-KEY — não use Authorization: Bearer, query string api_key nem nenhum outro método. O endpoint de consulta individual é:

GET https://api.smartcpf.com.br/v2/individuals/{cpf}?package={pacote}

O parâmetro package define o nível de detalhe do retorno (ex: basic, complete). O CPF na URL deve conter apenas dígitos (sem pontos ou traço). A resposta inclui metadados da consulta (consultId, balance, executionTimeMs) e o objeto result com os dados do titular.

Vantagens da integração nativa com Spring Boot

1. Controle total de headers e timeouts
RestClient e WebClient permitem configurar o header X-API-KEY globalmente via DefaultClientConfigurer ou ExchangeFilterFunction, evitando repetição em cada chamada.

2. Serialização tipada com Jackson
Mapeamento direto do JSON para records Java elimina parsing manual e garante validação de estrutura no compile-time.

3. Tratamento de erros HTTP mapeado a exceções de domínio
Códigos 401, 403, 404, 422 e 5xx podem ser convertidos em SmartCpfException hierárquica, facilitando ControllerAdvice global.

4. Configuração externalizada via application.yml
Base URL, API key, timeouts e pacote padrão ficam fora do código, suportando perfis dev, staging, prod.

5. Observabilidade nativa com Micrometer
Tempo de latência, taxa de erro e contagem de chamadas expostos automaticamente para Prometheus/Grafana sem instrumentação manual.

6. Testes de contrato com MockWebServer ou WireMock
Simulação de respostas da SmartCPF (sucesso, saldo insuficiente, CPF inválido) garante regressão zero em CI/CD.

Desafios e considerações antes de integrar

1. Gerenciamento seguro da API Key
Nunca commite a chave no repositório. Use variáveis de ambiente, Vault, AWS Secrets Manager ou Spring Cloud Config. Em desenvolvimento local, .env com spring.config.import resolve.

2. Rate limiting e retentativas
A API aplica limites por plano. Implemente Retry com backoff exponencial (ex: Resilience4j) apenas para erros 5xx e 429; não retente 4xx — são erros de requisição.

3. Formatação do CPF na requisição
O endpoint espera apenas dígitos. Remova máscara (replaceAll("\\D", "")) antes de montar a URL; a API rejeita CPF formatado com 400.

4. Saldo de créditos (balance)
O campo meta.balance indica créditos restantes. Monitore e alerte quando baixo; consultas falham com 402/403 quando zera.

5. LGPD e armazenamento de dados sensíveis
O retorno inclui nome, data de nascimento, e-mails, telefones. Armazene apenas o necessário, com criptografia em repouso e logs sem PII.

Como implementar com a SmartCPF

Abaixo, uma implementação completa usando RestClient (Spring Boot 3.2+), com configuração tipada, cliente imutável, mapeamento de resposta e exceções personalizadas. O exemplo assume Java 21+ (records, pattern matching) e Jackson no classpath.

SmartCpfClient.java
package br.com.smartcpf.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.client.RestClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;

import java.time.Instant;
import java.util.List;
import java.util.UUID;

/**
 * Cliente tipado para a API SmartCPF v2.
 * 
 * Autenticação: header X-API-KEY obrigatório em todas as requisições.
 * Endpoint: GET /v2/individuals/{cpf}?package={package}
 */
@Configuration
@ConfigurationProperties(prefix = "smartcpf")
public class SmartCpfClient {

    private String baseUrl = "https://api.smartcpf.com.br";
    private String apiKey;
    private String defaultPackage = "complete";
    private int connectTimeoutMs = 3000;
    private int readTimeoutMs = 10000;

    // Getters e setters para binding do Spring
    public String getBaseUrl() { return baseUrl; }
    public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
    public String getApiKey() { return apiKey; }
    public void setApiKey(String apiKey) { this.apiKey = apiKey; }
    public String getDefaultPackage() { return defaultPackage; }
    public void setDefaultPackage(String defaultPackage) { this.defaultPackage = defaultPackage; }
    public int getConnectTimeoutMs() { return connectTimeoutMs; }
    public void setConnectTimeoutMs(int connectTimeoutMs) { this.connectTimeoutMs = connectTimeoutMs; }
    public int getReadTimeoutMs() { return readTimeoutMs; }
    public void setReadTimeoutMs(int readTimeoutMs) { this.readTimeoutMs = readTimeoutMs; }

    /**
     * Bean RestClient configurado com base URL, timeouts e header X-API-KEY global.
     */
    @Bean
    public RestClient restClient(RestClientBuilder builder) {
        return builder
                .baseUrl(baseUrl)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader("X-API-KEY", apiKey) // Autenticação obrigatória
                .requestFactory(requestFactory -> {
                    requestFactory.setConnectTimeout(connectTimeoutMs);
                    requestFactory.setReadTimeout(readTimeoutMs);
                })
                .defaultStatusHandler(
                        HttpStatusCode::is4xxClientError,
                        (request, response) -> handleClientError(response)
                )
                .defaultStatusHandler(
                        HttpStatusCode::is5xxServerError,
                        (request, response) -> handleServerError(response)
                )
                .build();
    }

    /**
     * Consulta um CPF na SmartCPF.
     * 
     * @param cpf CPF apenas com dígitos (11 caracteres)
     * @param pkg Pacote de dados (opcional, usa defaultPackage se null)
     * @return Resposta tipada com metadados e resultado
     * @throws SmartCpfException em erros de autenticação, CPF não encontrado, saldo insuficiente, etc.
     */
    public SmartCpfResponse consultarCpf(String cpf, String pkg) {
        String cleanCpf = cpf.replaceAll("\\D", "");
        if (cleanCpf.length() != 11) {
            throw new SmartCpfException.InvalidCpfFormat("CPF deve conter 11 dígitos numéricos: " + cpf);
        }

        String pacote = pkg != null ? pkg : defaultPackage;

        return restClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/v2/individuals/{cpf}")
                        .queryParam("package", pacote)
                        .build(cleanCpf))
                .retrieve()
                .body(SmartCpfResponse.class);
    }

    /** Sobrecarga conveniente usando pacote padrão. */
    public SmartCpfResponse consultarCpf(String cpf) {
        return consultarCpf(cpf, null);
    }

    private void handleClientError(org.springframework.http.client.ClientHttpResponse response) {
        HttpStatusCode status = response.getStatusCode();
        String body = readBody(response);
        
        switch (status.value()) {
            case 401 -> throw new SmartCpfException.Unauthorized("API Key inválida ou ausente: " + body);
            case 403 -> throw new SmartCpfException.Forbidden("Acesso negado ou saldo insuficiente: " + body);
            case 404 -> throw new SmartCpfException.NotFound("CPF não encontrado na base: " + body);
            case 422 -> throw new SmartCpfException.UnprocessableEntity("CPF inválido ou parâmetros incorretos: " + body);
            default -> throw new SmartCpfException.ClientError("Erro cliente HTTP " + status + ": " + body);
        }
    }

    private void handleServerError(org.springframework.http.client.ClientHttpResponse response) {
        String body = readBody(response);
        throw new SmartCpfException.ServerError("Erro servidor SmartCPF " + response.getStatusCode() + ": " + body);
    }

    private String readBody(org.springframework.http.client.ClientHttpResponse response) {
        try (var is = response.getBody()) {
            return new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
        } catch (Exception e) {
            return "<sem corpo>";
        }
    }

    /* ==================== MODELOS DE RESPOSTA ==================== */

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record SmartCpfResponse(
            Meta meta,
            Result result,
            boolean success,
            List<String> errors
    ) {}

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Meta(
            @JsonProperty("searchType") String searchType,
            @JsonProperty("consultId") UUID consultId,
            String user,
            String message,
            String ip,
            boolean success,
            @JsonProperty("apiVersion") String apiVersion,
            @JsonProperty("date") Instant date,
            @JsonProperty("executionTimeMs") Double executionTimeMs,
            Double balance
    ) {}

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Result(
            String cpf,
            String name,
            @JsonProperty("birthDate") String birthDate,
            Integer age,
            @JsonProperty("motherName") String motherName,
            String gender,
            String status,
            List<String> emails,
            List<Phone> phones,
            List<Address> addresses
    ) {}

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Phone(
            String number,
            @JsonProperty("blockedTelemarketing") boolean blockedTelemarketing,
            String operator,
            String type,
            boolean whatsapp
    ) {}

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Address(
            String zipCode,
            String street,
            String number,
            String complement,
            String neighborhood,
            String city,
            String state,
            String country
    ) {}

    /* ==================== HIERARQUIA DE EXCEÇÕES ==================== */

    public static class SmartCpfException extends RuntimeException {
        public SmartCpfException(String message) { super(message); }

        public static class InvalidCpfFormat extends SmartCpfException {
            public InvalidCpfFormat(String msg) { super(msg); }
        }
        public static class Unauthorized extends SmartCpfException {
            public Unauthorized(String msg) { super(msg); }
        }
        public static class Forbidden extends SmartCpfException {
            public Forbidden(String msg) { super(msg); }
        }
        public static class NotFound extends SmartCpfException {
            public NotFound(String msg) { super(msg); }
        }
        public static class UnprocessableEntity extends SmartCpfException {
            public UnprocessableEntity(String msg) { super(msg); }
        }
        public static class ClientError extends SmartCpfException {
            public ClientError(String msg) { super(msg); }
        }
        public static class ServerError extends SmartCpfException {
            public ServerError(String msg) { super(msg); }
        }
    }
}

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:

  • Autenticação transparente: header X-API-KEY injetado uma única vez no RestClient, sem repetição em chamadas.
  • Rastreabilidade: meta.consultId (UUID) permite correlacionar logs da sua aplicação com logs da SmartCPF em suporte.
  • Controle de custos: meta.balance expõe créditos restantes; monitore e alerte antes de zerar.
  • Validação precoce: formatação do CPF (apenas dígitos) e verificação de 11 caracteres evitam 400 desnecessários.
  • Tratamento semântico de erros: 401 → Unauthorized (chave inválida), 403 → Forbidden (saldo/permissão), 404 → NotFound (CPF inexistente), 422 → UnprocessableEntity (CPF malformado), 5xx → ServerError (retry com backoff).
  • Dados estruturados: records imutáveis mapeiam result (nome, idade, status, e-mails, telefones com operadora/tipo/WhatsApp, endereços) prontos para uso em regras de negócio.

Por que usar a SmartCPF

  • Latência baixa consistente: executionTimeMs típico abaixo de 100 ms em produção, com SLA publicado.
  • Integração sem SDK proprietário: REST padrão + JSON, funciona em qualquer stack (Java, Kotlin, Node, Go, Python).
  • Modelo de créditos previsível: pague por consulta, sem mensalidade mínima; balance em tempo real na resposta.
  • Escalabilidade horizontal: arquitetura stateless suporta picos de Black Friday, onboarding em massa, batch noturno.
  • Conformidade LGPD nativa: dados sensíveis retornados sob demanda, sem armazenamento persistente no lado da API além do log de auditoria (consultId, IP, timestamp).

Conclusão

Integrar a SmartCPF em Spring Boot exige poucas linhas de código quando se usa RestClient com configuração centralizada, records para mapeamento e hierarquia de exceções para erros HTTP. O resultado é um cliente tipado, testável, observável e pronto para produção — sem dependências extras além do próprio Spring Web. 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.