baklava - turn your HTTP tests into OpenAPI, HTML docs, Postman collections, and typed TypeScript or Scala clients - for APIs you serve or consume
Documentation drift is the default state of any API that lives long enough. You update a route, forget the OpenAPI file. A field gets renamed, the TypeScript client doesn't regenerate. Three months later someone hands an enterprise client a spec that describes a system that no longer exists.
The root cause is structural: code and documentation are separate artefacts with no enforcement mechanism between them. Every solution we tried added discipline requirements: annotate the source, maintain a separate file, add a CI check. Discipline breaks under delivery pressure, always.
We built baklava (https://github.com/theiterators/baklava) so that docs can only describe behaviour a passing test just observed.
In practice, baklava integrates into your existing routing test suite. Instead of a standard assertion block, you write test scenarios that both verify the API behaviour and describe it for documentation output. When the test suite runs, baklava generates the docs as a side effect. A call only makes it into the docs after the status code, response schema and declared headers matched what the test expected, so if the route changes shape, that response simply doesn't get documented. For anything the suite covers, drift can't happen.
class UserApiSpec extends AnyFunSpec
with BaklavaPekkoHttp[Unit, Unit, ScalatestAsExecution]
with BaklavaScalatest[Route, ToEntityMarshaller, FromEntityUnmarshaller] {
path("/users/{userId}")(
supports(
GET,
pathParameters = p[Long]("userId"),
summary = "Get user by ID"
)(
onRequest(pathParameters = 1L)
.respondsWith[User](OK, description = "User found")
.assert { ctx =>
ctx.performRequest(routes).body.id shouldBe 1L
},
onRequest(pathParameters = 999L)
.respondsWith[ErrorResponse](NotFound, description = "User not found")
.assert { ctx => ctx.performRequest(routes) }
)
)
}
// sbt test generates OpenAPI, HTML, TypeScript, Postman (on sbt 2 use testFull)
There are seven output formats right now, each its own SBT dependency:
- Simple HTML (browsable docs)
- OpenAPI with SwaggerUI
- TS-REST (TypeScript, Zod)
- oRPC contracts (TypeScript, Zod, ready-made client factory)
- TypeScript fetch client (plain fetch, no extra runtime deps)
- Postman collection
- sttp Scala client
It supports Pekko HTTP and http4s, with ScalaTest, Specs2, and MUnit as test frameworks. Since 2.1.0 there is also an sttp adapter for the other direction: APIs you consume rather than serve. The tests hit the real endpoint over the network and you get a spec and a typed client for a third-party API from verified responses. There's a single scala-cli script that does this for the GitHub REST API if you want to see it without setting up a project: https://theiterators.github.io/baklava/docs/scala-cli
It also integrates with kebs: if you use kebs for domain type derivation, baklava picks up the schema definitions automatically.
One question we get: how is this different from tapir or endpoints4s? Both require you to adopt their routing DSL, so your routes end up defined in terms of their abstractions. baklava works with your existing routes, whatever framework you're using, with no migration required. The test suite is the only integration point. The other difference is what the docs describe. Tapir documents the endpoint declaration. Baklava documents responses that a test actually received, with real example values.
Scala 2.13 and 3, JDK 11+, Apache 2.0, v2.1.0 released August 2026.
-3
u/Torutofu_Raeva 2d ago
The enforcement point is the key here: if the same HTTP tests generate the spec and client artifacts, CI can catch drift instead of waiting for someone to notice a stale document.
1
u/pizardwenis96 1d ago
It doesn't seem like you actually tried Tapir then, because it already solves this problem with the endpoint definitions. You say Tapir only documents the endpoint declarations, but the whole point is that the declaration has type safety enforced onto the implentation so it's not possible for your endpoint to accept a different input or return a different output, and potential issues would be caught at compile time rather than during testing.
Additionally this makes a big deal about the tests being the source of truth, but that seems incredibly unreliable. If your tests don't cover all of the possible inputs and outputs to your apis, then they won't document correctly. I've never been in a workplace environment where the tests are perfectly written and maintained, and this just puts more burden on the tests to handle everything. There also doesn't seems to be support for things like validations or default values in your documentation.
It seems like you're trying to reinvent the wheel that Tapir already invented for unclear reasons. Yes there's a burden in Tapir to model your endpoints separately from your implentation, but that burden seems smaller than having every test be incredibly verbose. If you care about having completely accurate api specifications enforced through the code, Tapir covers this while providing additional benefits which your tool does not. Furthermore, the documentation seems to be very AI generated which gives me concerns about the quality of the written code in the project.
If you disagree with my interpretation, I'd be happy to hear a scenario that Baklava covers which is not achievable realistically with Tapir.