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.

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


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

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