# Como consumir API de CPF em React Native para apps mobile nativos

> Aprenda a integrar a API de consulta de CPF em React Native com fetch, validação em tempo real e UX otimizada para apps mobile nativos.

**Publicado:** 25/09/2026
**Autor:** Redação CPFHub.io
**URL:** https://www.cpfhub.io/blog/como-consumir-api-cpf-react-native-apps-mobile-nativos

---


Para consumir a API de CPF da CPFHub.io em um app React Native, o caminho correto é nunca chamar a API diretamente do dispositivo — a chave de API ficaria exposta no bundle. O padrão recomendado é criar um backend intermediário (Node.js, por exemplo) que recebe o CPF do app, consulta `https://api.cpfhub.io/cpf/{CPF}` com o header `x-api-key` e devolve apenas os dados necessários. A [documentação oficial do React Native](https://reactnative.dev/docs/network) detalha as opções de rede disponíveis, incluindo `fetch` e `XMLHttpRequest`, que funcionam tanto em iOS quanto em Android.

---

## Arquitetura segura para apps mobile

Em aplicações mobile, a chave de API nunca deve ser incluida no código do app. A arquitetura recomendada e:

1. O app React Native envia o CPF para seu backend.
2. O backend consulta a API da CPFHub.io com a chave de API.
3. O backend retorna o resultado ao app.

Essa camada intermediaria protege a chave de API e permite adicionar rate limiting, cache e logs no servidor.

---

## Backend intermediario (Node.js/Express)

Crie um endpoint simples que recebe o CPF do app e consulta a API:

```javascript
// backend/server.js
const express = require("express");
const app = express();
const PORT = process.env.PORT || 3001;

const CPFHUB_API_KEY = process.env.CPFHUB_API_KEY;

app.get("/api/validar-cpf/:cpf", async (req, res) => {
 const cpf = req.params.cpf.replace(/\D/g, "");

 if (cpf.length !== 11) {
 return res.status(400).json({ erro: "CPF deve ter 11 digitos" });
 }

 const controller = new AbortController();
 const timeoutId = setTimeout(() => controller.abort(), 10000);

 try {
 const response = await fetch(
 `https://api.cpfhub.io/cpf/${cpf}`,
 {
 method: "GET",
 headers: {
 "x-api-key": CPFHUB_API_KEY,
 "Accept": "application/json",
 },
 signal: controller.signal,
 }
 );

 clearTimeout(timeoutId);
 const dados = await response.json();

 if (!dados.success) {
 return res.status(404).json({ erro: "CPF nao encontrado" });
 }

 res.json({
 valido: true,
 dados: {
 nome: dados.data.name,
 genero: dados.data.gender,
 dataNascimento: dados.data.birthDate,
 },
 });
 } catch (error) {
 clearTimeout(timeoutId);
 const mensagem = error.name === "AbortError"
 ? "Timeout na consulta"
 : error.message;
 res.status(500).json({ erro: mensagem });
 }
});

app.listen(PORT, () => console.log(`Backend rodando na porta ${PORT}`));
```

---

## Serviço de API no React Native

Crie um módulo que encapsula as chamadas ao backend:

```typescript
// src/services/cpfService.ts
import { Platform } from "react-native";

const API_BASE_URL = __DEV__
 ? Platform.OS === "android"
 ? "http://10.0.2.2:3001"
 : "http://localhost:3001"
 : "https://api.meuapp.com.br";

const TIMEOUT_MS = 15000;

interface CPFResult {
 valido: boolean;
 dados?: {
 nome: string;
 genero: string;
 dataNascimento: string;
 };
 erro?: string;
}

export async function validarCPF(cpf: string): Promise<CPFResult> {
 const cpfLimpo = cpf.replace(/\D/g, "");

 if (cpfLimpo.length !== 11) {
 return { valido: false, erro: "CPF deve ter 11 digitos" };
 }

 const controller = new AbortController();
 const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);

 try {
 const response = await fetch(
 `${API_BASE_URL}/api/validar-cpf/${cpfLimpo}`,
 {
 method: "GET",
 headers: { "Accept": "application/json" },
 signal: controller.signal,
 }
 );

 clearTimeout(timeoutId);

 const dados = await response.json();

 if (!response.ok) {
 return { valido: false, erro: dados.erro || "Erro na consulta" };
 }

 return dados;
 } catch (error) {
 clearTimeout(timeoutId);

 if ((error as Error).name === "AbortError") {
 return { valido: false, erro: "Tempo limite excedido. Verifique sua conexao." };
 }

 return { valido: false, erro: "Sem conexao com o servidor" };
 }
}
```

Note a diferenciacao de URL para Android (que usa `10.0.2.2` para acessar o localhost do host) e iOS.

---

## Componente de validação de CPF

O componente principal com mascara de input, feedback visual e tratamento de estados:

```tsx
// src/screens/ValidarCPFScreen.tsx
import React, { useState, useCallback } from "react";
import {
 View,
 Text,
 TextInput,
 TouchableOpacity,
 ActivityIndicator,
 StyleSheet,
 Alert,
 KeyboardAvoidingView,
 Platform,
} from "react-native";
import { validarCPF } from "../services/cpfService";

function formatarCPF(texto: string): string {
 const digitos = texto.replace(/\D/g, "").slice(0, 11);
 if (digitos.length <= 3) return digitos;
 if (digitos.length <= 6) return `${digitos.slice(0, 3)}.${digitos.slice(3)}`;
 if (digitos.length <= 9)
 return `${digitos.slice(0, 3)}.${digitos.slice(3, 6)}.${digitos.slice(6)}`;
 return `${digitos.slice(0, 3)}.${digitos.slice(3, 6)}.${digitos.slice(6, 9)}-${digitos.slice(9)}`;
}

export default function ValidarCPFScreen() {
 const [cpf, setCpf] = useState("");
 const [carregando, setCarregando] = useState(false);
 const [resultado, setResultado] = useState<{
 nome?: string;
 genero?: string;
 dataNascimento?: string;
 } | null>(null);
 const [erro, setErro] = useState<string | null>(null);

 const handleChangeCPF = useCallback((texto: string) => {
 setCpf(formatarCPF(texto));
 setResultado(null);
 setErro(null);
 }, []);

 const handleConsultar = useCallback(async () => {
 const cpfLimpo = cpf.replace(/\D/g, "");

 if (cpfLimpo.length !== 11) {
 setErro("Informe um CPF completo com 11 digitos.");
 return;
 }

 setCarregando(true);
 setErro(null);
 setResultado(null);

 try {
 const res = await validarCPF(cpfLimpo);

 if (res.valido && res.dados) {
 setResultado(res.dados);
 } else {
 setErro(res.erro ?? "CPF nao encontrado.");
 }
 } catch {
 Alert.alert("Erro", "Falha ao consultar CPF. Tente novamente.");
 } finally {
 setCarregando(false);
 }
 }, [cpf]);

 return (
 <KeyboardAvoidingView
 style={styles.container}
 behavior={Platform.OS === "ios" ? "padding" : "height"}
 >
 <Text style={styles.titulo}>Validar CPF</Text>

 <TextInput
 style={styles.input}
 value={cpf}
 onChangeText={handleChangeCPF}
 placeholder="000.000.000-00"
 keyboardType="number-pad"
 maxLength={14}
 returnKeyType="done"
 />

 <TouchableOpacity
 style={[styles.botao, carregando && styles.botaoDesabilitado]}
 onPress={handleConsultar}
 disabled={carregando}
 activeOpacity={0.7}
 >
 {carregando ? (
 <ActivityIndicator color="#ffffff" />
 ) : (
 <Text style={styles.botaoTexto}>Consultar</Text>
 )}
 </TouchableOpacity>

 {erro && (
 <View style={styles.erroContainer}>
 <Text style={styles.erroTexto}>{erro}</Text>
 </View>
 )}

 {resultado && (
 <View style={styles.resultadoContainer}>
 <Text style={styles.resultadoLabel}>Nome</Text>
 <Text style={styles.resultadoValor}>{resultado.nome}</Text>

 <Text style={styles.resultadoLabel}>Genero</Text>
 <Text style={styles.resultadoValor}>
 {resultado.genero === "M" ? "Masculino" : "Feminino"}
 </Text>

 <Text style={styles.resultadoLabel}>Data de nascimento</Text>
 <Text style={styles.resultadoValor}>{resultado.dataNascimento}</Text>
 </View>
 )}
 </KeyboardAvoidingView>
 );
}

const styles = StyleSheet.create({
 container: {
 flex: 1,
 padding: 24,
 backgroundColor: "#f8f9fa",
 },
 titulo: {
 fontSize: 24,
 fontWeight: "700",
 marginBottom: 24,
 color: "#1a1a2e",
 },
 input: {
 backgroundColor: "#ffffff",
 borderRadius: 8,
 padding: 16,
 fontSize: 18,
 borderWidth: 1,
 borderColor: "#dee2e6",
 marginBottom: 16,
 },
 botao: {
 backgroundColor: "#4361ee",
 borderRadius: 8,
 padding: 16,
 alignItems: "center",
 },
 botaoDesabilitado: {
 opacity: 0.6,
 },
 botaoTexto: {
 color: "#ffffff",
 fontSize: 16,
 fontWeight: "600",
 },
 erroContainer: {
 backgroundColor: "#fee2e2",
 borderRadius: 8,
 padding: 12,
 marginTop: 16,
 },
 erroTexto: {
 color: "#dc2626",
 fontSize: 14,
 },
 resultadoContainer: {
 backgroundColor: "#ffffff",
 borderRadius: 8,
 padding: 16,
 marginTop: 16,
 borderWidth: 1,
 borderColor: "#d1fae5",
 },
 resultadoLabel: {
 fontSize: 12,
 color: "#6b7280",
 marginTop: 8,
 },
 resultadoValor: {
 fontSize: 16,
 color: "#1a1a2e",
 fontWeight: "500",
 },
});
```

---

## Tratamento de conectividade

Apps mobile enfrentam desafios de conectividade que aplicações web não tem. Use o módulo `@react-native-community/netinfo` para verificar a conexão antes de consultar:

```typescript
import NetInfo from "@react-native-community/netinfo";

async function verificarConexao(): Promise<boolean> {
 const state = await NetInfo.fetch();
 return state.isConnected ?? false;
}

// Antes de consultar
if (!(await verificarConexao())) {
 setErro("Sem conexao com a internet. Verifique sua rede.");
 return;
}
```

---

## Boas práticas para mobile

* **Nunca armazene a chave de API no app** -- Use sempre um backend intermediario.
* **Timeout generoso** -- Conexões mobile são mais lentas; use 15 segundos em vez de 10.
* **Feedback visual** -- Sempre mostre indicadores de carregamento; usuários mobile são impacientes.
* **Teclado numerico** -- Use `keyboardType="number-pad"` para facilitar a digitacao do CPF.
* **Mascara de input** -- Formate o CPF automaticamente para reduzir erros.

---

## Perguntas frequentes

### Por que não posso chamar a API de CPF diretamente do app React Native?

Chamadas diretas do app exporiam a chave de API no bundle JavaScript, que pode ser inspecionado com ferramentas como `react-native-decompiler` ou simplesmente monitorando o tráfego de rede. Com a chave comprometida, qualquer pessoa poderia consumir sua cota ou gerar cobranças. O backend intermediário é a única forma segura de guardar credenciais em aplicações mobile.

### Como o timeout de 15 segundos afeta a experiência do usuário?

Em redes móveis — especialmente 3G ou conexões instáveis — a latência pode ser significativamente maior do que em desktop. O timeout de 15 segundos no lado do app dá margem suficiente para o backend consultar a API (latência típica de ~900ms) e retornar, mesmo com alguma degradação de rede. Se estourar, exiba uma mensagem clara pedindo ao usuário para tentar novamente.

### A API CPFHub.io retorna erro quando o limite de consultas é atingido?

Não. Ao ultrapassar o limite do plano — 50 consultas no gratuito ou 1.000 no Pro (R$149/mês) — a API continua respondendo normalmente e cobra R$0,15 por consulta adicional. Não há interrupção do serviço nem código de erro específico por limite excedido. O controle de custos é feito pelo painel em `app.cpfhub.io/settings/billing`.

### Como adaptar o componente para Expo Go durante o desenvolvimento?

Em projetos Expo, a URL do emulador Android muda para `http://10.0.2.2:PORT`, enquanto dispositivos físicos precisam do IP da máquina na rede local. O serviço já trata isso via `Platform.OS`. Para produção, substitua a URL pelo domínio real do seu backend. Consulte a [documentação de rede do React Native](https://reactnative.dev/docs/network) para mais detalhes.

### Leia também

- [Como validar CPF no frontend com React e API REST](https://cpfhub.io/blog/como-validar-cpf-no-frontend-com-react-e-api-rest)
- [Onboarding digital em fintechs: como validar CPF em menos de 30 segundos](https://cpfhub.io/blog/onboarding-digital-em-fintechs-como-validar-cpf-em-menos-de-30-segundos)
- [Como implementar retry e backoff exponencial em consultas de API de CPF](https://cpfhub.io/blog/como-implementar-retry-backoff-exponencial-consultas-api-cpf)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)

---

## Conclusão

A integração da API de CPF da [**CPFHub.io**](https://www.cpfhub.io/)

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/)

