# Como consumir API de CPF em Scala com Play Framework e Akka HTTP

> Aprenda a consumir a API de CPF da CPFHub.io em Scala usando Play Framework e Akka HTTP com exemplos assíncronos e tipados.

**Publicado:** 16/07/2026
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-consumir-api-de-cpf-em-scala-com-play-framework-e-akka-http

---


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](https://docs.scala-lang.org/getting-started/index.html) na documentação oficial do Scala.

* **Play Framework 2.9+** configurado.

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

---

## 2. Adicione as dependências

No `build.sbt`, configure as dependências do projeto:

```scala
// 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:

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

```scala
// 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:

```scala
// 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:

```scala
// 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:

```scala
// 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 `WSClient` do 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.conf` quanto no `WSClient` para 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](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) antes de atualizar dependências transitivas do Akka.

---

### 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 consumir API de CPF em TypeScript com tipagem segura](https://cpfhub.io/blog/como-consumir-api-de-cpf-em-typescript-com-tipagem-segura)

---

## Conclusão

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

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

