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
wreqcom lenses para acesso ergonômico aos campos da resposta HTTP. -
Timeout -- Configure timeout nas opções do
wreqpara evitar requisições travadas, alinhado com o tempo de ~900ms da API. -
IO puro -- Mantenha a lógica pura separada do IO. Use
Eitherpara 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.
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.



