# Como Consumir a API de CPF em Dart Usando http Package

> Aprenda a consumir a API de consulta de CPF em Dart usando o pacote http. Guia completo com serialização, tratamento de erros e testes.

**Publicado:** 27/12/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/consumir-api-cpf-dart-http

---


Consumir a API de CPF do CPFHub.io em Dart com o pacote `http` exige apenas três passos: modelar a resposta com classes e factory constructors, criar um serviço com tratamento de erros tipado e injetar um `http.Client` mockável para testes — o resultado é um serviço completo e reutilizável em qualquer projeto Dart ou Flutter.

## Introdução

Dart é a linguagem por trás do Flutter e vem ganhando espaço também em aplicações server-side. O pacote **http** é a solução oficial e mais utilizada para requisições HTTP em Dart, oferecendo uma API simples e eficiente. Este guia mostra como integrar a API do CPFHub.io em Dart puro, construindo um serviço completo com serialização JSON, tratamento de erros e validação local, pronto para ser reutilizado em qualquer projeto Dart ou Flutter.

---

## Modelando a resposta com classes Dart

Dart não possui um equivalente direto ao Codable do Swift ou ao @Serializable do Kotlin, mas você pode criar factories para deserialização de JSON.

```dart
class CPFResponse {
 final bool success;
 final CPFData data;

 CPFResponse({required this.success, required this.data});

 factory CPFResponse.fromJson(Map<String, dynamic> json) {
 return CPFResponse(
 success: json['success'] as bool,
 data: CPFData.fromJson(json['data'] as Map<String, dynamic>),
 );
 }
}

class CPFData {
 final String cpf;
 final String name;
 final String nameUpper;
 final String gender;
 final String birthDate;
 final String day;
 final String month;
 final String year;

 CPFData({
 required this.cpf,
 required this.name,
 required this.nameUpper,
 required this.gender,
 required this.birthDate,
 required this.day,
 required this.month,
 required this.year,
 });

 factory CPFData.fromJson(Map<String, dynamic> json) {
 return CPFData(
 cpf: json['cpf'] as String,
 name: json['name'] as String,
 nameUpper: json['nameUpper'] as String,
 gender: json['gender'] as String,
 birthDate: json['birthDate'] as String,
 day: json['day'] as String,
 month: json['month'] as String,
 year: json['year'] as String,
 );
 }

 Map<String, dynamic> toJson() => {
 'cpf': cpf, 'name': name, 'nameUpper': nameUpper,
 'gender': gender, 'birthDate': birthDate,
 'day': day, 'month': month, 'year': year,
 };
}
```

| Padrão | Descrição | Benefício |
|--------|-----------|-----------|
| **factory constructor** | Cria instância a partir de Map | Deserialização limpa e type-safe |
| **toJson()** | Converte para Map | Serialização para cache ou envio |
| **required named params** | Parâmetros nomeados obrigatórios | Código auto-documentado |

- **factory** -- construtor que pode retornar instâncias existentes ou de subtipos
- **Map<String, dynamic>** -- tipo padrão do Dart para representar objetos JSON
- **final** -- campos imutáveis garantem que os dados não sejam alterados após criação

---

## Criando o serviço de consulta com http

O pacote http oferece uma API direta para requisições GET com headers customizados.

```dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class CPFServiceException implements Exception {
 final String message;
 final int? statusCode;

 CPFServiceException(this.message, {this.statusCode});

 @override
 String toString() => 'CPFServiceException: $message (status: $statusCode)';
}

class CPFService {
 final String _apiKey;
 final http.Client _client;
 final String _baseUrl = 'https://api.cpfhub.io/cpf';

 CPFService({
 required String apiKey,
 http.Client? client,
 }) : _apiKey = apiKey,
 _client = client ?? http.Client();

 Future<CPFData> consultarCPF(String cpf) async {
 final cpfLimpo = cpf.replaceAll(RegExp(r'[^0-9]'), '');

 if (cpfLimpo.length != 11) {
 throw CPFServiceException('CPF deve conter 11 dígitos');
 }

 try {
 final response = await _client
 .get(
 Uri.parse('$_baseUrl/$cpfLimpo'),
 headers: {'x-api-key': _apiKey},
 )
 .timeout(const Duration(seconds: 15));

 if (response.statusCode == 200) {
 final json = jsonDecode(response.body) as Map<String, dynamic>;
 final cpfResponse = CPFResponse.fromJson(json);

 if (!cpfResponse.success) {
 throw CPFServiceException('CPF não encontrado');
 }

 return cpfResponse.data;
 } else if (response.statusCode == 401) {
 throw CPFServiceException(
 'Chave de API inválida',
 statusCode: 401,
 );
 } else {
 // A API do CPFHub.io não retorna 429 nem bloqueia requisições;
 // consultas acima do plano são cobradas a R$0,15 cada.
 throw CPFServiceException(
 'Erro na API',
 statusCode: response.statusCode,
 );
 }
 } on CPFServiceException {
 rethrow;
 } catch (e) {
 throw CPFServiceException('Erro de conexão: $e');
 }
 }

 void dispose() {
 _client.close();
 }
}
```

- **http.Client** -- injetável no construtor para facilitar testes com mock
- **timeout** -- evita que a requisição fique pendurada indefinidamente
- **rethrow** -- relança exceções do tipo correto sem perder o stack trace

---

## Validação local do CPF

Validar o CPF localmente antes de chamar a API economiza requisições e dá feedback mais rápido.

```dart
class CPFValidator {
 static bool validar(String cpf) {
 final numeros = cpf.replaceAll(RegExp(r'[^0-9]'), '');

 if (numeros.length != 11) return false;

 // Rejeitar sequências repetidas
 if (RegExp(r'^(\d)\1{10}$').hasMatch(numeros)) return false;

 final digits = numeros.split('').map(int.parse).toList();

 // Primeiro dígito verificador
 int soma1 = 0;
 for (int i = 0; i < 9; i++) {
 soma1 += digits[i] * (10 - i);
 }
 final check1 = (soma1 % 11 < 2) ? 0 : 11 - (soma1 % 11);
 if (digits[9] != check1) return false;

 // Segundo dígito verificador
 int soma2 = 0;
 for (int i = 0; i < 10; i++) {
 soma2 += digits[i] * (11 - i);
 }
 final check2 = (soma2 % 11 < 2) ? 0 : 11 - (soma2 % 11);
 if (digits[10] != check2) return false;

 return true;
 }

 static String formatar(String cpf) {
 final n = cpf.replaceAll(RegExp(r'[^0-9]'), '');
 if (n.length != 11) return cpf;
 return '${n.substring(0, 3)}.${n.substring(3, 6)}'
 '.${n.substring(6, 9)}-${n.substring(9, 11)}';
 }
}

// Uso combinado
Future<void> validarEConsultar(String cpf) async {
 if (!CPFValidator.validar(cpf)) {
 print('CPF inválido localmente');
 return;
 }

 final service = CPFService(apiKey: 'sua-chave-aqui');
 try {
 final dados = await service.consultarCPF(cpf);
 print('Nome: ${dados.name}');
 print('CPF: ${CPFValidator.formatar(dados.cpf)}');
 print('Nascimento: ${dados.birthDate}');
 } on CPFServiceException catch (e) {
 print('Erro: ${e.message}');
 } finally {
 service.dispose();
 }
}
```

- **RegExp** -- expressões regulares nativas do Dart para validação de padrões
- **static** -- métodos estáticos que não precisam de instância para serem chamados
- **on catch** -- captura apenas exceções de tipo específico para tratamento diferenciado

---

## Testando o serviço com MockClient

O pacote http fornece MockClient para testes sem chamadas reais à rede.

```dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:test/test.dart';

void main() {
 group('CPFService', () {
 test('deve retornar dados quando CPF é válido', () async {
 final mockClient = MockClient((request) async {
 expect(request.url.path, contains('/cpf/12345678909'));
 expect(request.headers['x-api-key'], equals('test-key'));

 return http.Response(
 jsonEncode({
 'success': true,
 'data': {
 'cpf': '12345678909',
 'name': 'João da Silva',
 'nameUpper': 'JOÃO DA SILVA',
 'gender': 'M',
 'birthDate': '01/01/1990',
 'day': '01',
 'month': '01',
 'year': '1990',
 }
 }),
 200,
 );
 });

 final service = CPFService(apiKey: 'test-key', client: mockClient);
 final result = await service.consultarCPF('12345678909');

 expect(result.name, equals('João da Silva'));
 expect(result.gender, equals('M'));
 });

 test('deve lançar exceção para CPF inválido', () async {
 final service = CPFService(apiKey: 'test-key');
 expect(
 () => service.consultarCPF('123'),
 throwsA(isA<CPFServiceException>()),
 );
 });
 });
}
```

| Cenário de teste | Método | Resultado esperado |
|-----------------|--------|-------------------|
| CPF válido | consultarCPF('12345678909') | CPFData com nome |
| CPF curto | consultarCPF('123') | CPFServiceException |
| API retorna 401 | Mock com status 401 | Exceção de autenticação |
| Timeout | Mock com delay | Exceção de conexão |

- **MockClient** -- substitui chamadas HTTP reais por respostas controladas
- **Injeção de dependência** -- o client é injetado no construtor, facilitando a troca por mock
- **group/test** -- estrutura padrão de testes do Dart com descrições em português

---

## Perguntas frequentes

### Por que usar o pacote `http` em vez do `dio` para consumir a API de CPF em Dart?

O pacote `http` é a biblioteca oficial do time do Dart, mantida pela equipe do [dart.dev](https://pub.dev/packages/http), e cobre a maioria dos casos de uso com uma API mínima e bem testada. Para projetos Flutter ou Dart que não precisam de interceptors complexos, retries automáticos ou cancelamento avançado, `http` é a escolha mais simples e com menos dependências transitivas. O `dio` faz sentido quando você precisa desses recursos nativos sem implementar do zero.

### Como adicionar o pacote `http` ao projeto Dart ou Flutter?

Adicione `http: ^1.2.0` (ou versão mais recente) ao bloco `dependencies` do seu `pubspec.yaml` e execute `dart pub get` ou `flutter pub get`. Importe com `import 'package:http/http.dart' as http;` para evitar conflitos de namespace com as classes nativas do Dart.

### A API do CPFHub.io retorna HTTP 429 quando o limite de consultas é atingido?

Não. A API do CPFHub.io não bloqueia requisições ao atingir o limite do plano — ela cobra R$0,15 por consulta adicional. O plano gratuito oferece 50 consultas/mês sem cartão de crédito, e o plano Pro inclui 1.000 consultas mensais por R$149. Não é necessário tratar HTTP 429 no seu código; erros de autenticação (401) e CPF não encontrado são os casos relevantes a cobrir.

### Como reutilizar o `CPFService` em um app Flutter com gerenciamento de estado?

O `CPFService` foi projetado com injeção de dependência via construtor, o que facilita o registro em qualquer container de DI. Com o pacote `provider`, registre-o como `ProxyProvider` para que receba a chave de API de outro provider. Com `riverpod`, crie um `Provider<CPFService>` e injete-o em `AsyncNotifier` ou `FutureProvider`. O método `dispose()` deve ser chamado no `dispose()` do ViewModel ou do Notifier.

### Leia também

- [Como consumir API de CPF em Flutter com Dart e pacote http](https://cpfhub.io/blog/como-consumir-api-de-cpf-em-flutter-com-dart-e-pacote-http)
- [Como Criar um App Flutter com Validação de CPF em Tempo Real](https://cpfhub.io/blog/app-flutter-validacao-cpf-tempo-real)
- [Como Consumir a API de CPF em Kotlin Usando Ktor Client e Retrofit](https://cpfhub.io/blog/consumir-api-cpf-kotlin-ktor-retrofit)
- [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

Consumir a API de CPF em Dart com o pacote http é direto e eficiente. A combinação de classes com factory constructors para deserialização, tratamento robusto de erros com exceções tipadas e validação local do CPF resulta em um serviço completo e testável. O design com injeção de dependência permite trocar facilmente o cliente HTTP por um mock nos testes, garantindo cobertura sem dependência de rede.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e integre a consulta de CPF ao seu app Flutter ou projeto Dart server-side em minutos.

