# Como consumir API de CPF em Haskell com Servant e wreq

> Aprenda a consumir a API de CPF da CPFHub.io em Haskell usando Servant para definir APIs tipadas e wreq para requisições HTTP.

**Publicado:** 19/07/2026
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-consumir-api-de-cpf-em-haskell-com-servant-e-wreq

---


O **Haskell** é uma linguagem puramente funcional com sistema de tipos extremamente expressivo. Para consumir a API de CPF da CPFHub.io em Haskell, a combinação de **Servant** — que define rotas como tipos verificados em tempo de compilação — e **wreq** — que oferece uma interface HTTP ergonômica inspirada no `requests` do Python — resulta em código seguro, conciso e confiável. A API responde em ~900ms via `GET https://api.cpfhub.io/cpf/{CPF}` com autenticação pelo header `x-api-key`. Consulte a [documentação oficial do Haskell](https://www.haskell.org/documentation) para configurar GHC e Cabal antes de começar.

---

## 1. Pré-requisitos

* **GHC 9.4+** e **Cabal** ou **Stack** instalados.

* Uma conta gratuita na [**CPFHub.io**](https://www.cpfhub.io/)

---

## 2. Configure o projeto

No `package.yaml` (Stack) ou `cpf-validator.cabal`, adicione as dependências:

```yaml
# package.yaml
name: cpf-validator
version: 0.1.0.0

dependencies:
 - base >= 4.7 && < 5
 - servant-server
 - warp
 - wreq
 - aeson
 - text
 - bytestring
 - lens
 - lens-aeson
 - http-client
 - http-types
 - mtl
 - time
```

---

## 3. Defina os tipos de dados

Crie os tipos para representar a resposta da API:

```haskell
-- src/CpfHub/Types.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

module CpfHub.Types where

import Data.Aeson
import Data.Text (Text)
import GHC.Generics (Generic)

data CpfData = CpfData
 { cpf :: Text
 , name :: Text
 , nameUpper :: Text
 , gender :: Text
 , birthDate :: Text
 , day :: Int
 , month :: Int
 , year :: Int
 } deriving (Show, Generic)

instance FromJSON CpfData
instance ToJSON CpfData

data CpfResponse = CpfResponse
 { success :: Bool
 , cpfData :: Maybe CpfData
 } deriving (Show, Generic)

instance FromJSON CpfResponse where
 parseJSON = withObject "CpfResponse" $ \v ->
 CpfResponse
 <$> v .: "success"
 <*> v .:? "data"

data CpfError
 = InvalidCpf Text
 | NotFound Text
 | Unauthorized Text
 | RateLimited Text
 | TimeoutError Text
 | ApiError Text Int
 deriving (Show)

instance ToJSON CpfError where
 toJSON err = object
 [ "success" .= False
 , "error" .= errorMessage err
 ]

errorMessage :: CpfError -> Text
errorMessage (InvalidCpf msg) = msg
errorMessage (NotFound msg) = msg
errorMessage (Unauthorized msg) = msg
errorMessage (RateLimited msg) = msg
errorMessage (TimeoutError msg) = msg
errorMessage (ApiError msg _) = msg
```

---

## 4. Implemente o cliente da API

Use `wreq` para consumir a API da CPFHub:

```haskell
-- src/CpfHub/Client.hs
{-# LANGUAGE OverloadedStrings #-}

module CpfHub.Client
 ( consultarCpf
 , CpfHubConfig(..)
 ) where

import Control.Exception (try, SomeException)
import Control.Lens ((^.), (&), (.~))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Aeson (eitherDecode)
import Network.Wreq as Wreq
import Network.HTTP.Client (HttpException(..))
import qualified Data.ByteString.Lazy as BL

import CpfHub.Types

data CpfHubConfig = CpfHubConfig
 { configApiKey :: Text
 , configBaseUrl :: String
 , configTimeout :: Int
 }

defaultConfig :: CpfHubConfig
defaultConfig = CpfHubConfig
 { configApiKey = "SUA_CHAVE_DE_API"
 , configBaseUrl = "https://api.cpfhub.io"
 , configTimeout = 5
 }

limparCpf :: Text -> Text
limparCpf = T.filter (`elem` ['0'..'9'])

consultarCpf :: CpfHubConfig -> Text -> IO (Either CpfError CpfData)
consultarCpf config cpfRaw = do
 let cpfLimpo = limparCpf cpfRaw

 if T.length cpfLimpo /= 11
 then return $ Left (InvalidCpf "CPF deve conter exatamente 11 dígitos")
 else do
 let url = configBaseUrl config ++ "/cpf/" ++ T.unpack cpfLimpo
 opts = Wreq.defaults
 & Wreq.header "x-api-key" .~ [encodeUtf8 (configApiKey config)]
 & Wreq.header "Accept" .~ ["application/json"]
 & Wreq.manager .~ Nothing

 resultado <- try (Wreq.getWith opts url) :: IO (Either SomeException (Wreq.Response BL.ByteString))

 case resultado of
 Left ex -> return $ Left (ApiError (T.pack $ show ex) 502)
 Right resp -> do
 let statusCode = resp ^. Wreq.responseStatus . Wreq.statusCode
 body = resp ^. Wreq.responseBody

 case statusCode of
 200 -> case eitherDecode body of
 Right cpfResp ->
 if success cpfResp
 then case cpfData cpfResp of
 Just d -> return $ Right d
 Nothing -> return $ Left (ApiError "Resposta sem dados" 500)
 else return $ Left (ApiError "Consulta sem sucesso" 500)
 Left err -> return $ Left (ApiError (T.pack err) 500)
 400 -> return $ Left (InvalidCpf "CPF com formato inválido")
 401 -> return $ Left (Unauthorized "Chave de API inválida ou ausente")
 404 -> return $ Left (NotFound "CPF não encontrado na base de dados")
 -- Nota: a CPFHub.io não retorna 429; ao exceder o plano gratuito,
 -- cobra R$0,15 por consulta adicional sem bloquear requisições.
 _ -> return $ Left (ApiError (T.pack $ "Erro HTTP " ++ show statusCode) statusCode)
 where
 encodeUtf8 = BL.toStrict . BL.fromStrict . T.encodeUtf8
 T.encodeUtf8 = Data.Text.Encoding.encodeUtf8
```

---

## 5. Defina a API com Servant

Crie a definição da API como um tipo Haskell:

```haskell
-- src/CpfHub/Api.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}

module CpfHub.Api where

import Data.Text (Text)
import Servant
import CpfHub.Types (CpfData, CpfError)

type CpfAPI =
 "api" :> "cpf" :> Capture "cpf" Text :> Get '[JSON] CpfData
```

---

## 6. Implemente o servidor

Conecte a definição da API à implementação:

```haskell
-- src/CpfHub/Server.hs
{-# LANGUAGE OverloadedStrings #-}

module CpfHub.Server where

import Data.Text (Text)
import Servant
import Network.Wai.Handler.Warp (run)
import CpfHub.Api (CpfAPI)
import CpfHub.Client (consultarCpf, CpfHubConfig(..))
import CpfHub.Types

cpfServer :: CpfHubConfig -> Server CpfAPI
cpfServer config = consultarCpfHandler
 where
 consultarCpfHandler :: Text -> Handler CpfData
 consultarCpfHandler cpf = do
 resultado <- liftIO $ consultarCpf config cpf
 case resultado of
 Right dados -> return dados
 Left (InvalidCpf msg) -> throwError $ err400 { errBody = encode msg }
 Left (NotFound msg) -> throwError $ err404 { errBody = encode msg }
 Left (Unauthorized msg) -> throwError $ err401 { errBody = encode msg }
 Left (RateLimited msg) -> throwError $ err429 { errBody = encode msg }
 Left (TimeoutError msg) -> throwError $ err504 { errBody = encode msg }
 Left (ApiError msg _) -> throwError $ err500 { errBody = encode msg }

 encode = Data.ByteString.Lazy.fromStrict . Data.Text.Encoding.encodeUtf8

 err429 = ServerError 429 "Too Many Requests" "" []
 err504 = ServerError 504 "Gateway Timeout" "" []

app :: CpfHubConfig -> Application
app config = serve (Proxy :: Proxy CpfAPI) (cpfServer config)

startServer :: IO ()
startServer = do
 let config = CpfHubConfig
 { configApiKey = "SUA_CHAVE_DE_API"
 , configBaseUrl = "https://api.cpfhub.io"
 , configTimeout = 5
 }
 putStrLn "Servidor iniciado na porta 3000"
 run 3000 (app config)
```

---

## 7. Ponto de entrada

```haskell
-- app/Main.hs
module Main where

import CpfHub.Server (startServer)

main :: IO ()
main = startServer
```

---

## 8. Teste a integração

Compile e execute o projeto:

```bash
stack build && stack exec cpf-validator-exe
```

```bash
curl -X GET http://localhost:3000/api/cpf/12345678900
```

Resposta esperada:

```json
{
 "cpf": "12345678900",
 "name": "João da Silva",
 "nameUpper": "JOÃO DA SILVA",
 "gender": "M",
 "birthDate": "15/06/1990",
 "day": 15,
 "month": 6,
 "year": 1990
}
```

---

## 9. Boas práticas

* **Tipos** -- Aproveite o sistema de tipos do Haskell para garantir correção em tempo de compilação. O Servant verifica rotas e tipos de resposta automaticamente.

* **ADTs** -- Use Algebraic Data Types (como `CpfError`) para modelar todos os estados de erro possíveis.

* **wreq** -- Use `wreq` com lenses para acesso ergonômico aos campos da resposta HTTP.

* **Timeout** -- Configure timeout nas opções do `wreq` para evitar requisições travadas, alinhado com o tempo de ~900ms da API.

* **IO puro** -- Mantenha a lógica pura separada do IO. Use `Either` para modelar falhas de forma funcional.

* **LGPD** -- A API da CPFHub.io é 100% compatível com a LGPD. Garanta tratamento adequado de dados pessoais em sua aplicação Haskell.

---

## Perguntas frequentes

### Como autenticar requisições à API de CPF em Haskell com wreq?

A autenticação é feita pelo header `x-api-key` em cada requisição. Com `wreq`, configure o header via lenses antes de chamar `getWith`: `opts = Wreq.defaults & Wreq.header "x-api-key" .~ [encodeUtf8 apiKey]`. A chave é gerada no painel da CPFHub.io e deve ser mantida em variável de ambiente, nunca no código-fonte.

### Qual a latência esperada ao consultar a API CPFHub.io a partir de um serviço Haskell?

A API responde em ~900ms em condições normais de rede. Configure o timeout do `wreq` com folga suficiente — 5 segundos é um valor seguro para produção. O Servant propaga o resultado assíncrono sem bloquear o runtime do GHC, desde que a lógica de IO esteja corretamente separada.

### O que acontece quando o limite de consultas do plano gratuito é ultrapassado?

A API não bloqueia nem retorna erro de limite. O plano gratuito inclui 50 consultas mensais; ao exceder esse número, cada consulta adicional é cobrada a R$0,15 automaticamente. Para volumes previsíveis, o plano Pro oferece 1.000 consultas por R$149/mês com o mesmo modelo de excedente.

### Como o Servant garante segurança de tipos na integração com a API de CPF?

O Servant define a rota como um tipo Haskell (`type CpfAPI = "api" :> "cpf" :> Capture "cpf" Text :> Get '[JSON] CpfData`), forçando em tempo de compilação que qualquer handler aceite exatamente os parâmetros e retorne exatamente o tipo declarado. Inconsistências entre rota e implementação geram erro de compilação, não falha em produção.

---

### 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)
- [Boas práticas para consumir APIs de CPF de forma segura](https://cpfhub.io/blog/boas-praticas-consumir-apis-cpf-segura)
- [Como consumir API de CPF em Rust com reqwest e serde](https://cpfhub.io/blog/como-consumir-api-de-cpf-em-rust-com-reqwest-e-serde)
- [Como criar um SDK interno para padronizar consultas de CPF na empresa](https://cpfhub.io/blog/como-criar-sdk-interno-padronizar-consultas-cpf-empresa)

---

## Conclusão

Consumir a API da [**CPFHub.io**](https://www.cpfhub.io/)

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

