Para consumir a API de CPF da CPFHub.io em Scala, você usa o WSClient do Play Framework para fazer um GET https://api.cpfhub.io/cpf/{CPF} com o header x-api-key, deserializa a resposta em case classes com leitores JSON implícitos e modela erros com Either[CpfError, CpfData] para tratamento funcional. O modelo assíncrono do Play e do Akka garante que as chamadas à API — com latência típica de ~900ms — nunca bloqueiem threads de requisição. Se preferir Akka HTTP puro sem o Play, a mesma lógica se aplica usando Http().singleRequest.
1. Pré-requisitos
-
Scala 2.13+ ou Scala 3 com sbt. Consulte o guia de instalação do sbt na documentação oficial do Scala.
-
Play Framework 2.9+ configurado.
-
Uma conta gratuita na CPFHub.io
2. Adicione as dependências
No build.sbt, configure as dependências do projeto:
// build.sbt
name := "cpf-validator"
version := "1.0"
scalaVersion := "2.13.12"
libraryDependencies ++= Seq(
guice,
ws,
"com.typesafe.play" %% "play-json" % "2.10.0",
)
3. Configure as propriedades da API
No conf/application.conf, adicione as configurações:
# conf/application.conf
cpfhub {
base-url = "https://api.cpfhub.io"
api-key = "SUA_CHAVE_DE_API"
api-key = ${?CPFHUB_API_KEY}
timeout = 5 seconds
}
play.ws.timeout.request = 5s
play.ws.timeout.connection = 3s
4. Crie os modelos de dados
Defina case classes com leitores JSON implícitos:
// app/models/CpfModels.scala
package models
import play.api.libs.json._
case class CpfData(
cpf: String,
name: String,
nameUpper: String,
gender: String,
birthDate: String,
day: Int,
month: Int,
year: Int
)
object CpfData {
implicit val reads: Reads[CpfData] = Json.reads[CpfData]
implicit val writes: Writes[CpfData] = Json.writes[CpfData]
}
case class CpfResponse(
success: Boolean,
data: Option[CpfData]
)
object CpfResponse {
implicit val reads: Reads[CpfResponse] = Json.reads[CpfResponse]
}
sealed trait CpfError {
def message: String
def statusCode: Int
}
object CpfError {
case class InvalidCpf(message: String = "CPF deve conter 11 dígitos") extends CpfError { val statusCode = 400 }
case class NotFound(message: String = "CPF não encontrado") extends CpfError { val statusCode = 404 }
case class Unauthorized(message: String = "Chave de API inválida") extends CpfError { val statusCode = 401 }
case class Timeout(message: String = "Timeout na consulta") extends CpfError { val statusCode = 504 }
case class ApiError(message: String, statusCode: Int) extends CpfError
}
5. Implemente o serviço de consulta
Crie o serviço usando WSClient do Play:
// app/services/CpfHubService.scala
package services
import javax.inject._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
import play.api.libs.ws._
import play.api.{Configuration, Logger}
import models._
@Singleton
class CpfHubService @Inject()(
ws: WSClient,
config: Configuration
)(implicit ec: ExecutionContext) {
private val logger = Logger(getClass)
private val baseUrl = config.get[String]("cpfhub.base-url")
private val apiKey = config.get[String]("cpfhub.api-key")
private val timeout = config.get[Duration]("cpfhub.timeout")
def consultarCpf(cpf: String): Future[Either[CpfError, CpfData]] = {
val cpfLimpo = cpf.replaceAll("\\D", "")
if (cpfLimpo.length != 11) {
return Future.successful(Left(CpfError.InvalidCpf()))
}
val url = s"$baseUrl/cpf/$cpfLimpo"
logger.info(s"Consultando CPF: $cpfLimpo")
ws.url(url)
.addHttpHeaders(
"x-api-key" -> apiKey,
"Accept" -> "application/json"
)
.withRequestTimeout(timeout)
.get()
.map { response =>
response.status match {
case 200 =>
response.json.validate[CpfResponse] match {
case JsSuccess(cpfResponse, _) if cpfResponse.success =>
cpfResponse.data match {
case Some(data) =>
logger.info(s"CPF encontrado: ${data.name}")
Right(data)
case None =>
Left(CpfError.ApiError("Resposta sem dados", 500))
}
case _ =>
Left(CpfError.ApiError("Resposta inválida da API", 500))
}
case 400 => Left(CpfError.InvalidCpf("CPF com formato inválido"))
case 401 => Left(CpfError.Unauthorized())
case 404 => Left(CpfError.NotFound())
case other => Left(CpfError.ApiError(s"Erro HTTP $other", other))
}
}
.recover {
case _: scala.concurrent.TimeoutException =>
logger.error("Timeout ao consultar CPF")
Left(CpfError.Timeout())
case ex: Exception =>
logger.error(s"Erro na consulta: ${ex.getMessage}")
Left(CpfError.ApiError(ex.getMessage, 502))
}
}
}
6. Crie o controller
Implemente o controller REST:
// app/controllers/CpfController.scala
package controllers
import javax.inject._
import scala.concurrent.ExecutionContext
import play.api.mvc._
import play.api.libs.json._
import services.CpfHubService
import models.CpfData
@Singleton
class CpfController @Inject()(
cc: ControllerComponents,
cpfService: CpfHubService
)(implicit ec: ExecutionContext) extends AbstractController(cc) {
def consultar(cpf: String): Action[AnyContent] = Action.async {
cpfService.consultarCpf(cpf).map {
case Right(data) =>
Ok(Json.obj(
"success" -> true,
"data" -> Json.toJson(data)
))
case Left(error) =>
Status(error.statusCode)(Json.obj(
"success" -> false,
"error" -> error.message
))
}
}
}
7. Configure as rotas
No conf/routes, adicione a rota:
# conf/routes
GET /api/cpf/:cpf controllers.CpfController.consultar(cpf: String)
8. Versão com Akka HTTP puro
Se preferir usar Akka HTTP sem o Play Framework:
// src/main/scala/CpfHubAkka.scala
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.unmarshalling.Unmarshal
import spray.json._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
object CpfHubAkka {
implicit val system: ActorSystem = ActorSystem("cpfhub")
implicit val ec: ExecutionContext = system.dispatcher
case class CpfData(cpf: String, name: String, gender: String, birthDate: String)
object CpfJsonProtocol extends DefaultJsonProtocol {
implicit val cpfDataFormat: RootJsonFormat[CpfData] = jsonFormat4(CpfData)
}
def consultarCpf(cpf: String, apiKey: String): Future[CpfData] = {
val cpfLimpo = cpf.replaceAll("\\D", "")
val request = HttpRequest(
method = HttpMethods.GET,
uri = s"https://api.cpfhub.io/cpf/$cpfLimpo",
headers = List(
RawHeader("x-api-key", apiKey),
RawHeader("Accept", "application/json")
)
)
Http().singleRequest(request).flatMap { response =>
Unmarshal(response.entity).to[String].map { body =>
import CpfJsonProtocol._
val json = body.parseJson.asJsObject
val data = json.fields("data").convertTo[CpfData]
data
}
}
}
}
9. Boas práticas
-
WSClient -- Use o
WSClientdo Play para requisições HTTP. Ele é assíncrono por padrão e integrado ao ciclo de vida da aplicação. -
Either -- Modele erros com
Either[CpfError, CpfData]para tratamento funcional de erros sem exceções. -
Sealed traits -- Use sealed traits para enumeração de erros, garantindo exaustividade nos pattern matches.
-
Timeout -- Configure timeout tanto no
application.confquanto noWSClientpara evitar requisições travadas. A latência da API CPFHub.io é de ~900ms; um timeout de 5 segundos é adequado para absorver variações. -
Variáveis de ambiente -- Use a substituição
${?VAR}do HOCON para sobrescrever valores sensíveis via variáveis de ambiente. -
LGPD -- A API da CPFHub.io é 100% compatível com a LGPD. Garanta conformidade no tratamento de dados pessoais em sua aplicação Scala.
Perguntas frequentes
Por que usar Either[CpfError, CpfData] em vez de lançar exceções no serviço Scala?
O Either torna o contrato do método explícito: quem chama sabe que pode receber um erro e é obrigado a tratá-lo no pattern match. Exceções são invisíveis na assinatura do método e podem ser esquecidas em algum ponto da cadeia de chamadas. Com sealed traits para CpfError, o compilador garante exaustividade — se você adicionar um novo caso de erro, o compilador aponta todos os lugares que precisam ser atualizados.
Como o modelo assíncrono do Play e Akka lida com a latência da API CPFHub.io?
O WSClient retorna um Future[WSResponse], que não bloqueia nenhuma thread enquanto aguarda a resposta. A latência típica da API é de ~900ms — o Play processa outras requisições durante esse tempo. Configure play.ws.timeout.request = 5s no application.conf como margem de segurança. O .recover no Future captura TimeoutException e retorna um Left(CpfError.Timeout()) sem deixar a requisição travar indefinidamente.
O que acontece quando o volume de consultas ultrapassa o plano contratado?
A API CPFHub.io não retorna erro nem bloqueia a requisição ao ultrapassar o limite. Cada consulta acima da cota é cobrada automaticamente a R$0,15, independentemente do plano. O plano gratuito cobre 50 consultas por mês; o Pro, 1.000 consultas por R$149. Para controlar custos em produção, implemente um contador de consultas no lado do cliente e exponha métricas via Akka Metrics ou Kamon.
Posso usar a mesma abordagem com Scala 3 e o novo sistema de tipos?
Sim. O código funciona com Scala 3 com ajustes mínimos: os implicit val viram given, e os implicit nos parâmetros viram using. O Play Framework 2.9+ tem suporte oficial ao Scala 3. Para projetos novos, consulte o guia de migração para Scala 3 antes de atualizar dependências transitivas do Akka.
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.



