# Como consumir API de CPF em TypeScript com tipagem segura

> Aprenda a consumir a API de consulta de CPF do CPFHub.io em TypeScript com interfaces tipadas, validação Zod e client reutilizável.

**Publicado:** 19/07/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-consumir-api-de-cpf-em-typescript-com-tipagem-segura

---


Para consumir a API do CPFHub.io em TypeScript com tipagem segura, defina interfaces que espelhem o contrato da resposta, use Zod para validação em runtime e encapsule as chamadas em um client reutilizável com tratamento de erros tipado. Essa abordagem elimina bugs de acesso a campos inexistentes, oferece autocompletar completo no editor e torna o código mais fácil de manter.

## Introdução

TypeScript se consolidou como a linguagem padrão para desenvolvimento web profissional, oferecendo tipagem estática que detecta erros em tempo de compilação e melhora a experiência do desenvolvedor com autocompletar e documentação inline. Quando se trata de consumir APIs externas, a tipagem segura garante que a aplicação trata corretamente todos os campos da resposta, evitando bugs sutis que só apareceriam em produção.

---

## Pré-requisitos

* **Node.js 18+** -- Para suporte nativo a fetch.

* **TypeScript 5+** -- Para recursos modernos de tipagem.

* **Conta no CPFHub.io** -- Crie uma conta gratuita em [cpfhub.io](https://www.cpfhub.io/)

### Instalação

```bash
npm init -y
npm install typescript zod
npm install -D @types/node tsx
npx tsc --init
```

---

## Definindo as interfaces

O primeiro passo é definir interfaces TypeScript que representam a estrutura da resposta da API.

### Tipos da API (types.ts)

```typescript
// types.ts

export interface CPFData {
 cpf: string;
 name: string;
 nameUpper: string;
 gender: 'M' | 'F';
 birthDate: string;
 day: number;
 month: number;
 year: number;
}

export interface CPFHubSuccessResponse {
 success: true;
 data: CPFData;
}

export interface CPFHubErrorResponse {
 success: false;
 error?: string;
}

export type CPFHubResponse = CPFHubSuccessResponse | CPFHubErrorResponse;

export interface CPFHubClientConfig {
 apiKey: string;
 baseUrl?: string;
 timeout?: number;
}

export type CPFHubErrorCode =
 | 'INVALID_FORMAT'
 | 'NOT_FOUND'
 | 'RATE_LIMITED'
 | 'UNAUTHORIZED'
 | 'TIMEOUT'
 | 'NETWORK_ERROR'
 | 'UNKNOWN';

export class CPFHubError extends Error {
 constructor(
 message: string,
 public readonly code: CPFHubErrorCode,
 public readonly statusCode?: number
 ) {
 super(message);
 this.name = 'CPFHubError';
 }
}
```

---

## Validação com Zod

Interfaces TypeScript existem apenas em tempo de compilação. Para garantir que a resposta da API realmente corresponde à interface esperada, usamos Zod para validação em runtime.

### Schemas Zod (schemas.ts)

```typescript
// schemas.ts

import { z } from 'zod';

export const CPFDataSchema = z.object({
 cpf: z.string().length(11),
 name: z.string().min(1),
 nameUpper: z.string().min(1),
 gender: z.enum(['M', 'F']),
 birthDate: z.string().regex(/^\d{2}\/\d{2}\/\d{4}$/),
 day: z.number().int().min(1).max(31),
 month: z.number().int().min(1).max(12),
 year: z.number().int().min(1900).max(2100),
});

export const CPFHubSuccessResponseSchema = z.object({
 success: z.literal(true),
 data: CPFDataSchema,
});

export const CPFHubErrorResponseSchema = z.object({
 success: z.literal(false),
 error: z.string().optional(),
});

export const CPFHubResponseSchema = z.discriminatedUnion('success', [
 CPFHubSuccessResponseSchema,
 CPFHubErrorResponseSchema,
]);

// Validação de CPF (formato e dígitos verificadores)
export const CPFInputSchema = z
 .string()
 .transform((val) => val.replace(/\D/g, ''))
 .refine((val) => val.length === 11, {
 message: 'CPF deve ter 11 dígitos',
 })
 .refine((val) => !/^(\d)\1{10}$/.test(val), {
 message: 'CPF com todos os dígitos iguais é inválido',
 })
 .refine(
 (val) => {
 // Primeiro dígito verificador
 let soma = 0;
 for (let i = 0; i < 9; i++) {
 soma += parseInt(val[i]) * (10 - i);
 }
 let resto = (soma * 10) % 11;
 if (resto === 10) resto = 0;
 if (resto !== parseInt(val[9])) return false;

 // Segundo dígito verificador
 soma = 0;
 for (let i = 0; i < 10; i++) {
 soma += parseInt(val[i]) * (11 - i);
 }
 resto = (soma * 10) % 11;
 if (resto === 10) resto = 0;
 return resto === parseInt(val[10]);
 },
 { message: 'Dígitos verificadores inválidos' }
 );
```

---

## Client reutilizável (client.ts)

O client encapsula toda a lógica de comunicação com a API, com tipagem completa e tratamento de erros.

```typescript
// client.ts

import {
 CPFHubClientConfig,
 CPFHubResponse,
 CPFHubSuccessResponse,
 CPFData,
 CPFHubError,
} from './types';
import { CPFHubResponseSchema, CPFInputSchema } from './schemas';

const DEFAULT_BASE_URL = 'https://api.cpfhub.io/cpf';
const DEFAULT_TIMEOUT = 10000;

export class CPFHubClient {
 private readonly apiKey: string;
 private readonly baseUrl: string;
 private readonly timeout: number;

 constructor(config: CPFHubClientConfig) {
 this.apiKey = config.apiKey;
 this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
 this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
 }

 async consultarCPF(cpfInput: string): Promise<CPFData> {
 // Validar e limpar CPF
 const parseResult = CPFInputSchema.safeParse(cpfInput);
 if (!parseResult.success) {
 throw new CPFHubError(
 parseResult.error.errors[0].message,
 'INVALID_FORMAT'
 );
 }
 const cpf = parseResult.data;

 // Fazer requisição
 let response: Response;
 try {
 response = await fetch(`${this.baseUrl}/${cpf}`, {
 method: 'GET',
 headers: {
 'x-api-key': this.apiKey,
 Accept: 'application/json',
 },
 signal: AbortSignal.timeout(this.timeout),
 });
 } catch (error) {
 if (error instanceof DOMException && error.name === 'TimeoutError') {
 throw new CPFHubError(
 'Timeout na consulta ao CPFHub.io',
 'TIMEOUT'
 );
 }
 throw new CPFHubError(
 `Erro de conexão: ${(error as Error).message}`,
 'NETWORK_ERROR'
 );
 }

 // Tratar códigos de erro HTTP
 if (response.status === 401) {
 throw new CPFHubError('Chave de API inválida ou ausente', 'UNAUTHORIZED', 401);
 }
 if (response.status === 429) {
 throw new CPFHubError('Rate limit excedido', 'RATE_LIMITED', 429);
 }

 // Parsear resposta
 const json: unknown = await response.json();

 // Validar com Zod
 const validationResult = CPFHubResponseSchema.safeParse(json);
 if (!validationResult.success) {
 throw new CPFHubError(
 'Resposta da API com formato inesperado',
 'UNKNOWN',
 response.status
 );
 }

 const resultado: CPFHubResponse = validationResult.data;

 if (!resultado.success) {
 throw new CPFHubError('CPF não encontrado', 'NOT_FOUND', 404);
 }

 return resultado.data;
 }

 async verificarCPF(
 cpfInput: string,
 nomeEsperado?: string
 ): Promise<{
 valido: boolean;
 dados: CPFData;
 nomeConfere: boolean;
 }> {
 const dados = await this.consultarCPF(cpfInput);

 let nomeConfere = true;
 if (nomeEsperado) {
 const nomeUpper = nomeEsperado.toUpperCase().trim();
 nomeConfere =
 dados.nameUpper.includes(nomeUpper) ||
 nomeUpper.includes(dados.nameUpper);
 }

 return {
 valido: true,
 dados,
 nomeConfere,
 };
 }
}
```

---

## Usando o client

### Exemplo básico (main.ts)

```typescript
// main.ts

import { CPFHubClient, CPFHubError } from './client';

const client = new CPFHubClient({
 apiKey: process.env.CPFHUB_API_KEY!,
});

async function main() {
 try {
 // Consulta simples
 const dados = await client.consultarCPF('123.456.789-00');
 console.log(`Nome: ${dados.name}`);
 console.log(`Nascimento: ${dados.birthDate}`);
 console.log(`Gênero: ${dados.gender}`);

 // Verificação com match de nome
 const verificacao = await client.verificarCPF(
 '12345678900',
 'João da Silva'
 );
 console.log(`CPF válido: ${verificacao.valido}`);
 console.log(`Nome confere: ${verificacao.nomeConfere}`);

 } catch (error) {
 if (error instanceof CPFHubError) {
 console.error(`Erro [${error.code}]: ${error.message}`);

 switch (error.code) {
 case 'INVALID_FORMAT':
 console.error('Verifique o formato do CPF informado.');
 break;
 case 'NOT_FOUND':
 console.error('CPF não encontrado na base.');
 break;
 case 'RATE_LIMITED':
 console.error('Aguarde antes de fazer nova consulta.');
 break;
 case 'UNAUTHORIZED':
 console.error('Verifique sua chave de API.');
 break;
 case 'TIMEOUT':
 console.error('A API demorou para responder.');
 break;
 default:
 console.error('Erro inesperado.');
 }
 }
 }
}

main();
```

### Executando

```bash
CPFHUB_API_KEY=SUA_CHAVE_DE_API npx tsx main.ts
```

---

## Usando em projetos Express com TypeScript

```typescript
import express from 'express';
import { CPFHubClient, CPFHubError } from './client';

const app = express();
app.use(express.json());

const cpfClient = new CPFHubClient({
 apiKey: process.env.CPFHUB_API_KEY!,
});

app.get('/cpf/:cpf', async (req, res) => {
 try {
 const dados = await cpfClient.consultarCPF(req.params.cpf);
 res.json({ success: true, data: dados });
 } catch (error) {
 if (error instanceof CPFHubError) {
 const statusMap: Record<string, number> = {
 INVALID_FORMAT: 400,
 NOT_FOUND: 404,
 RATE_LIMITED: 429,
 UNAUTHORIZED: 500,
 TIMEOUT: 504,
 NETWORK_ERROR: 502,
 UNKNOWN: 500,
 };
 res.status(statusMap[error.code] ?? 500).json({
 success: false,
 error: error.message,
 code: error.code,
 });
 }
 }
});

app.listen(3000);
```

---

## Benefícios da tipagem segura

| Aspecto | Sem tipagem | Com tipagem TypeScript |
| --- | --- | --- |
| Erros em runtime | Frequentes | Detectados na compilação |
| Autocompletar no editor | Limitado | Completo |
| Refatoração | Arriscada | Segura |
| Documentação | Manual | Inline via tipos |
| Validação de resposta | Inexistente | Zod em runtime |

A combinação de TypeScript (compilação) e Zod (runtime) garante que tanto o código quanto os dados estejam corretos em todas as etapas.

---

## Boas práticas

* **Nunca use `any`** -- Tipar todos os retornos e parâmetros.

* **Valide em runtime** -- Use Zod ou similar para validar respostas de APIs externas.

* **Centralize o client** -- Um único módulo de client para toda a aplicação.

* **Trate todos os erros** -- O discriminated union de erros permite tratamento exaustivo.

* **Configure o timeout** -- O CPFHub.io responde em ~900ms; um timeout de 10 segundos é adequado.

---

## Perguntas frequentes

### Por que usar Zod além das interfaces TypeScript para validar a resposta da API?
Interfaces TypeScript são apagadas em tempo de execução — elas existem apenas durante a compilação. Se a API retornar um campo com tipo diferente do esperado (por exemplo, `birthDate` como `null` em vez de `string`), o TypeScript não detectará o problema em produção. O Zod valida a estrutura dos dados em runtime, garantindo que qualquer desvio do contrato da API seja capturado e tratado antes de propagar um bug.

### Como lidar com o código de erro `RATE_LIMITED` no CPFHub.io?
O CPFHub.io não bloqueia chamadas ao atingir o limite do plano gratuito — cobra R$0,15 por consulta adicional. O status 429 pode ocorrer em caso de excesso de requisições simultâneas em janelas de tempo muito curtas. Ao receber esse código, implemente backoff exponencial antes de tentar novamente, ou revise se há chamadas duplicadas desnecessárias no fluxo da aplicação.

### É possível usar este client em projetos NestJS?
Sim. A classe `CPFHubClient` pode ser encapsulada em um `@Injectable()` do NestJS, com a chave de API injetada via `ConfigService`. Registre-a como provider no módulo correspondente e injete-a nos services que precisam validar CPF, mantendo a mesma lógica de tratamento de erros tipados.

### Como garantir conformidade com a LGPD ao processar CPF em TypeScript?
Não persista o CPF em logs de aplicação — use a [ANPD](https://www.gov.br/anpd) como referência para determinar bases legais de tratamento. No código, evite serializar o objeto `CPFData` completo em logs de debug; prefira registrar apenas o resultado da validação (sucesso/falha) e um identificador interno que não seja o CPF em si.

### 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)
- [SLA de API de CPF: níveis de disponibilidade e o que exigir do seu provedor](https://cpfhub.io/blog/sla-api-cpf-niveis-disponibilidade)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)
- [10 erros mais comuns ao integrar uma API de CPF e como evitá-los](https://cpfhub.io/blog/10-erros-mais-comuns-ao-integrar-uma-api-de-cpf)

---

## Conclusão

Consumir a API do CPFHub.io em TypeScript com tipagem completa e validação Zod resulta em um código mais robusto, fácil de manter e menos sujeito a surpresas em produção. A combinação de interfaces em tempo de compilação com schemas Zod em runtime cobre todos os pontos de falha possíveis ao integrar uma API externa, desde formatos inesperados de resposta até erros de rede e timeout. O client reutilizável apresentado aqui pode ser adaptado para qualquer framework Node.js ou ambiente de backend TypeScript.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e integre validação de CPF com tipagem segura no seu projeto TypeScript.

