Validação de CPF em Rust com Axum: exemplo prático com SmartCPF
Aprenda a validar CPF em Rust usando Axum e a API SmartCPF v2. Código completo com autenticação X-API-KEY, tratamento de erros e parsing da resposta.

Validar CPFs em aplicações backend é um requisito comum em sistemas brasileiros — seja para onboarding de usuários, prevenção de fraude ou conformidade regulatória. Embora existam bibliotecas locais para verificação de dígito verificador, elas não confirmam se o CPF existe na base da Receita Federal, se está regular, ou retornam dados cadastrais associados. A API SmartCPF resolve isso consultando fontes oficiais em tempo real, e este artigo mostra como integrá-la a um serviço Rust com Axum.
O exemplo abaixo implementa um endpoint /validar-cpf que recebe um CPF, chama a SmartCPF v2 com o header X-API-KEY obrigatório, trata erros de rede e parsing, e devolve apenas os campos relevantes para sua regra de negócio. O foco é código de produção: timeouts, retry, logging estruturado e separação de responsabilidades.
Como a SmartCPF valida CPFs na prática
A API expõe GET /v2/individuals/{cpf}?package=X onde package define o conjunto de dados retornado (básico, completo, com score, etc.). A autenticação é exclusivamente via header X-API-KEY — não há suporte a Bearer token, query string ou cookie. A resposta traz meta (metadados da consulta: consultId, balance, executionTimeMs) e result (dados do CPF: nome, status, e-mails, telefones, endereços). Códigos HTTP: 200 sucesso, 401 chave inválida, 404 CPF não encontrado, 422 formato inválido, 429 rate limit, 5xx erro interno.
Vantagens de validar com API oficial
1. Fonte autoritativa
Consulta bases da Receita Federal e bureaus de crédito, não apenas algoritmo de dígito verificador.
2. Dados enriquecidos
Retorna nome, data de nascimento, nome da mãe, sexo, status (Regular, Suspenso, Cancelado, Nulo), e-mails, telefones com operadora/tipo/WhatsApp e endereços.
3. Rastreabilidade
Cada consulta gera consultId único para auditoria e suporte.
4. Controle de custo
Campo balance no retorno mostra créditos restantes em tempo real — sem surpresas na fatura.
5. Latência previsívelexecutionTimeMs na resposta permite monitorar SLA e configurar timeouts adequados no cliente.
6. Pacotes flexíveis
Parâmetro package permite pagar apenas pelos dados que sua regra precisa.
Desafios ao integrar validação externa
1. Dependência de rede
Falhas de DNS, timeout ou indisponibilidade da API exigem retry com backoff e circuit breaker.
2. Gestão de segredos
A X-API-KEY deve vir de cofre (Vault, AWS Secrets Manager, variável de ambiente injetada no deploy), nunca hardcoded.
3. Rate limiting
Planos têm limite de requisições/segundo; implemente token bucket no cliente ou respeite 429 com Retry-After.
4. LGPD e minimização
Solicite apenas o package necessário; descarte campos sensíveis após uso (ex.: não logue result completo).
5. Versionamento
A URL contém /v2/ — mudanças breaking exigem atualização controlada do cliente.
Como implementar com a SmartCPF no Axum
Abaixo, um módulo cpf_validator isolado, testável e pronto para injeção no router Axum. Use reqwest com Client reutilizável, serde para desserialização seletiva e thiserror para erros tipados.
use reqwest::{Client, StatusCode, header::{HeaderMap, HeaderValue, AUTHORIZATION}};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use std::time::Duration;
use tracing::{info, warn, error, instrument};
/// Erros tipados para o validador de CPF
#[derive(Debug, Error)]
pub enum CpfValidationError {
#[error("CPF inválido: {0}")]
InvalidFormat(String),
#[error("Erro de rede ao consultar SmartCPF: {0}")]
Network(#[from] reqwest::Error),
#[error("Autenticação falhou: API key inválida ou ausente")]
Unauthorized,
#[error("CPF não encontrado na base oficial")]
NotFound,
#[error("Rate limit excedido: tente novamente em {retry_after}s")]
RateLimited { retry_after: u64 },
#[error("Erro interno da SmartCPF: {status}")]
UpstreamError { status: StatusCode },
#[error("Resposta inválida da API: {0}")]
InvalidResponse(String),
}
/// Configuração do cliente SmartCPF
#[derive(Clone, Debug)]
pub struct SmartCpfConfig {
pub api_key: String,
pub base_url: String,
pub timeout: Duration,
pub package: String, // ex: "a", "b", "c", ...
}
impl Default for SmartCpfConfig {
fn default() -> Self {
Self {
api_key: String::new(),
base_url: "https://api.smartcpf.com.br/v2".to_string(),
timeout: Duration::from_secs(30),
package: "g".to_string(),
}
}
}
/// Subconjunto dos campos que a aplicação consome
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct CpfValidationResult {
pub cpf: String,
pub name: String,
pub status: String,
pub birth_date: Option<String>,
pub age: Option<u8>,
pub mother_name: Option<String>,
pub gender: Option<String>,
pub emails: Vec<String>,
pub phones: Vec<PhoneInfo>,
pub addresses: Vec<AddressInfo>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PhoneInfo {
pub number: String,
pub operator: Option<String>,
pub type_: Option<String>,
pub whatsapp: Option<bool>,
#[serde(rename = "blockedTelemarketing")]
pub blocked_telemarketing: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AddressInfo {
pub street: Option<String>,
pub number: Option<String>,
pub neighborhood: Option<String>,
pub city: Option<String>,
pub state: Option<String>,
pub zip_code: Option<String>,
}
/// Metadados da consulta para auditoria e monitoramento
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ConsultMeta {
#[serde(rename = "consultId")]
pub consult_id: String,
#[serde(rename = "executionTimeMs")]
pub execution_time_ms: f64,
pub balance: f64,
pub success: bool,
pub message: String,
}
/// Resposta completa da API (campos não usados são ignorados)
#[derive(Debug, Deserialize)]
struct ApiResponse {
meta: ConsultMeta,
result: CpfValidationResult,
success: bool,
errors: Vec<String>,
}
/// Cliente assíncrono para validação de CPF
pub struct CpfValidator {
client: Client,
config: SmartCpfConfig,
}
impl CpfValidator {
/// Cria novo validador com configuração customizada
pub fn new(config: SmartCpfConfig) -> Result<Self, CpfValidationError> {
if config.api_key.trim().is_empty() {
return Err(CpfValidationError::InvalidFormat(
"API key não pode ser vazia".into(),
));
}
let client = Client::builder()
.timeout(config.timeout)
.build()?;
Ok(Self { client, config })
}
/// Valida CPF e retorna dados enriquecidos + metadados
#[instrument(skip(self), fields(cpf = %cpf))]
pub async fn validate(&self, cpf: &str) -> Result<(CpfValidationResult, ConsultMeta), CpfValidationError> {
// 1. Normaliza CPF (remove pontuação)
let clean_cpf = cpf.chars().filter(|c| c.is_ascii_digit()).collect::<String>();
if clean_cpf.len() != 11 {
return Err(CpfValidationError::InvalidFormat(
"CPF deve conter 11 dígitos numéricos".into(),
));
}
// 2. Monta URL com package
let url = format!(
"{}/individuals/{}?package={}",
self.config.base_url.trim_end_matches('/'),
clean_cpf,
self.config.package
);
// 3. Prepara headers — AUTENTICAÇÃO OBRIGATÓRIA VIA X-API-KEY
let mut headers = HeaderMap::new();
headers.insert(
"X-API-KEY",
HeaderValue::from_str(&self.config.api_key)
.map_err(|_| CpfValidationError::InvalidResponse("API key inválida no header".into()))?,
);
headers.insert("Accept", HeaderValue::from_static("application/json"));
headers.insert("User-Agent", HeaderValue::from_static("smartcpf-rust-axum/1.0"));
// 4. Executa requisição com retry simples (até 2 tentativas em 5xx/timeout)
let mut last_err = None;
for attempt in 1..=3 {
let req = self.client.get(&url).headers(headers.clone());
match req.send().await {
Ok(resp) => {
let status = resp.status();
match status {
StatusCode::OK => {
let api_resp: ApiResponse = resp.json().await
.map_err(|e| CpfValidationError::InvalidResponse(e.to_string()))?;
if !api_resp.success || !api_resp.meta.success {
return Err(CpfValidationError::InvalidResponse(
api_resp.errors.join("; ")
));
}
info!(
consult_id = %api_resp.meta.consult_id,
execution_ms = api_resp.meta.execution_time_ms,
balance = api_resp.meta.balance,
"CPF validado com sucesso"
);
return Ok((api_resp.result, api_resp.meta));
}
StatusCode::UNAUTHORIZED => {
error!("API key rejeitada pela SmartCPF");
return Err(CpfValidationError::Unauthorized);
}
StatusCode::NOT_FOUND => {
warn!("CPF não encontrado: {}", clean_cpf);
return Err(CpfValidationError::NotFound);
}
StatusCode::UNPROCESSABLE_ENTITY => {
return Err(CpfValidationError::InvalidFormat(
"Formato de CPF inválido para a API".into(),
));
}
StatusCode::TOO_MANY_REQUESTS => {
let retry_after = resp
.headers()
.get("Retry-After")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse().ok())
.unwrap_or(60);
return Err(CpfValidationError::RateLimited { retry_after });
}
s if s.is_server_error() => {
warn!(attempt, status = %s, "Erro 5xx da API, retentando...");
last_err = Some(CpfValidationError::UpstreamError { status: s });
tokio::time::sleep(Duration::from_millis(200 * attempt)).await;
continue;
}
other => {
return Err(CpfValidationError::UpstreamError { status: other });
}
}
}
Err(e) if attempt < 3 && (e.is_timeout() || e.is_connect()) => {
warn!(attempt, error = %e, "Falha de rede, retentando...");
last_err = Some(CpfValidationError::Network(e));
tokio::time::sleep(Duration::from_millis(200 * attempt)).await;
}
Err(e) => return Err(CpfValidationError::Network(e)),
}
}
Err(last_err.unwrap_or(CpfValidationError::Network(
reqwest::Error::new(reqwest::ErrorKind::Request, "max retries exceeded"),
)))
}
}
/// Exemplo de uso: handler Axum que expõe POST /validar-cpf
#[cfg(feature = "axum-handler")]
pub mod axum_handler {
use super::*;
use axum::{
extract::{Json, State},
http::StatusCode,
response::{IntoResponse, Response},
Json as JsonResp,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Deserialize)]
pub struct ValidateCpfRequest {
pub cpf: String,
}
#[derive(Serialize)]
pub struct ValidateCpfResponse {
pub cpf: String,
pub name: String,
pub status: String,
pub birth_date: Option<String>,
pub age: Option<u8>,
pub consult_id: String,
pub balance: f64,
pub execution_time_ms: f64,
}
impl From<(CpfValidationResult, ConsultMeta)> for ValidateCpfResponse {
fn from((result, meta): (CpfValidationResult, ConsultMeta)) -> Self {
Self {
cpf: result.cpf,
name: result.name,
status: result.status,
birth_date: result.birth_date,
age: result.age,
consult_id: meta.consult_id,
balance: meta.balance,
execution_time_ms: meta.execution_time_ms,
}
}
}
/// Estado compartilhado: validador + config
#[derive(Clone)]
pub struct AppState {
pub validator: Arc<CpfValidator>,
}
/// POST /validar-cpf
pub async fn validate_cpf_handler(
State(state): State<AppState>,
Json(payload): Json<ValidateCpfRequest>,
) -> Response {
match state.validator.validate(&payload.cpf).await {
Ok((result, meta)) => {
let resp: ValidateCpfResponse = (result, meta).into();
(StatusCode::OK, JsonResp(resp)).into_response()
}
Err(CpfValidationError::InvalidFormat(msg)) => {
(StatusCode::BAD_REQUEST, JsonResp(serde_json::json!({ "error": msg }))).into_response()
}
Err(CpfValidationError::Unauthorized) => {
(StatusCode::UNAUTHORIZED, JsonResp(serde_json::json!({ "error": "API key inválida" }))).into_response()
}
Err(CpfValidationError::NotFound) => {
(StatusCode::NOT_FOUND, JsonResp(serde_json::json!({ "error": "CPF não encontrado" }))).into_response()
}
Err(CpfValidationError::RateLimited { retry_after }) => {
let mut resp = (StatusCode::TOO_MANY_REQUESTS, JsonResp(serde_json::json!({
"error": "Rate limit excedido",
"retry_after_seconds": retry_after
}))).into_response();
resp.headers_mut().insert("Retry-After", HeaderValue::from(retry_after));
resp
}
Err(e) => {
error!(error = %e, "Erro interno ao validar CPF");
(StatusCode::INTERNAL_SERVER_ERROR, JsonResp(serde_json::json!({
"error": "Erro interno do servidor"
}))).into_response()
}
}
}
}
Resposta da API
{
"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:
- Rastreabilidade total:
consultIdúnico por consulta permite auditoria e suporte preciso - Controle financeiro em tempo real:
balance(9.77 no exemplo) mostra créditos restantes antes de estourar a cota - Observabilidade nativa:
executionTimeMs(74.1ms) alimenta dashboards de latência e alertas de SLA - Decisão de negócio imediata:
status("Regular") +age(33) +phones[].operator("VIVO") +phones[].whatsapp(false) dão subsídio para fluxos de KYC, risco e comunicação - Resiliência: retry automático em 5xx/timeout +
Retry-Afterno 429 evita falhas em cascata
Por que usar a SmartCPF em produção
- Latência média sub-100ms com
executionTimeMsexposto para monitoramento contínuo - Integração simples: um header (
X-API-KEY), um endpoint REST, JSON padronizado — sem SDK obrigatório - Modelo de créditos transparente:
balanceno retorno elimina planilhas paralelas de controle de uso - Escalabilidade horizontal: arquitetura stateless permite múltiplas instâncias do validador atrás de load balancer
- Cobertura nacional: consulta bases oficiais (Receita, bureaus) com atualização constante, não apenas validação matemática
Conclusão
Validar CPF com a SmartCPF em Rust/Axum exige poucas linhas de código quando a responsabilidade é bem separada: configuração tipada, cliente HTTP reutilizável, erros modelados e handler fino. O exemplo acima cobre autenticação correta (X-API-KEY), normalização de entrada, retry com backoff, parsing seletivo e resposta enxuta para o frontend. Crie sua conta na SmartCPF e faça sua primeira consulta em minutos.
Sobre a redação
Redação Smart CPF
Autor do blog
Mais artigos

Validação de CPF em plugin WordPress: passo a passo com a API SmartCPF
Tutorial completo de como validar CPF em plugins WordPress usando a API SmartCPF v2, com exemplos PHP práticos e tratamento de erros.

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.

Locadoras de veículos: validando CPF de locatários antes da retirada
Como integrar validação de CPF no fluxo de locação para reduzir fraudes, agilizar check-in e garantir conformidade legal.

Validação de CPF em Go: exemplo prático com net/http
Aprenda a validar e consultar CPF em Go usando a API SmartCPF v2 com net/http padrão, autenticação X-API-KEY e tratamento de erros robusto.
Pronto para validar CPFs em segundos?
Consultas gratuitas para testar agora. Acesso imediato à documentação.