Getting started
Add the dependency
def mvnDeps = Seq(mvn"com.softinio::verdict4s::0.1.0")
Get an API key
Every request carries a key. Wrap it once, at the edge of your application:
import com.softinio.verdict4s.algebra.ApiKey
val apiKey = ApiKey.fromString(sys.env("TYPESAFE_API_KEY"))
ApiKey.fromString returns a ValidatedNec, so a blank key is caught where
you read it rather than on your first request. The key never appears in a
toString, a Show, or a rendered error — it prints as ApiKey(***), and
http4s redacts the Authorization header too.
On the JVM you can skip the boilerplate:
Verdict4sEnv.default[IO].use { client => ??? }
which reads TYPESAFE_API_KEY, TYPESAFE_BASE_URL and
TYPESAFE_DEFAULT_MODEL, matching the official SDKs. That object is JVM-only,
because Scala.js has no environment to read.
Your first request
import cats.effect.{IO, IOApp}
import com.softinio.verdict4s.*
import com.softinio.verdict4s.algebra.*
object Main extends IOApp.Simple:
enum Dept derives Options:
case Billing, Technical, Sales
def run: IO[Unit] =
val key = ApiKey.fromString(sys.env("TYPESAFE_API_KEY")).toOption.get
Verdict4s.default[IO](key).use { client =>
client
.ask(
Ask(
Question.noul("Does this convey urgency?"),
Question.choice[Dept]("Which team should handle this?")
),
"Help! My payouts have been failing for 3 days."
)
.flatMap { (urgent, team) =>
IO.println(s"urgent=${urgent.isTrue()} team=${team.choice}")
}
}
The Resource owns the connection pool, so allocate it once for the lifetime
of your application rather than once per call.
The same code on Scala.js
There is nothing to change. Verdict4s.default has one signature on both
platforms and picks the transport for you — Ember on the JVM, the Fetch API on
Scala.js, which works in the browser and on Node 18+.
Ember is deliberately not used on Scala.js: it needs raw TCP sockets, which a browser does not expose.
One caveat for browsers specifically: a key shipped to a browser is a key you have given away. Put verdict4s behind your own endpoint rather than calling the service directly from a page.
Batch your questions
Asking three questions in one request is markedly cheaper and faster than three
requests, and it is the shape the service is designed around. Ask takes up to
twelve, and each keeps its own answer type.
Speculative questions are cheap: ask something you might need and let your code decide whether to read it.
Reading confidence
Choice and Score answers carry a confidence, derived from the whole
probability distribution. It is a second axis: the answer tells you what, and
confidence tells you whether to act.
if (team.confidence.value: Double) >= 0.8 then route(team.choice)
else sendToHumanReview()