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.

Redação CPFHub.io
Redação CPFHub.io
··8 min de leitura
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 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


2. Configure o projeto

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

# 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:

-- 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:

-- 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:

-- 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:

-- 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

-- app/Main.hs
module Main where

import CpfHub.Server (startServer)

main :: IO ()
main = startServer

8. Teste a integração

Compile e execute o projeto:

stack build && stack exec cpf-validator-exe
curl -X GET http://localhost:3000/api/cpf/12345678900

Resposta esperada:

{
    "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.



Conclusão

Consumir a API da CPFHub.io

Cadastre-se em cpfhub.io

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

Sobre a redação

Redação CPFHub.io

Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.

WhatsAppFale conosco via WhatsApp