Tapir is a Scala library that lets developers describe an HTTP API once in a type-safe, declarative form and then expose that description as a server, consume it as a client, and generate OpenAPI, AsyncAPI, and JSON Schema documentation from it, and it is aimed at Scala teams building HTTP services that want their API documentation to stay in step with their code.
What it is
Tapir is an open-source library for the Scala ecosystem, licensed under Apache-2.0 and distributed through Maven Central under the com.softwaremill.sttp.tapir group, with tapir-core_3 published for Scala 3. It is a library rather than a framework: an endpoint is described as a value, with the shape of the inputs and outputs declared separately from the server logic that handles them. That single description is then interpreted by whichever integration the developer chooses, whether that is a running HTTP server, an HTTP client, or a documentation generator. Integrations cover all the major Scala HTTP server implementations, with the project's topics naming akka-http, http4s, Play, and ZIO, and further integrations exist for common custom types, JSON libraries, and observability tooling.
The concrete problem it solves is drift between an API and its description. Maintaining a hand-written OpenAPI document beside a codebase means two artefacts describing one interface, and they fall out of step as the code changes. Tapir derives the documentation from the endpoint description itself, so the OpenAPI, AsyncAPI, and JSON Schema output follows the declaration rather than trailing behind it. The declarative style also moves errors earlier, giving compile-time guarantees, development-time completion, and read-time information about an endpoint's inputs and outputs. Because the shape of an endpoint is separated from its logic, common endpoint definitions and individual inputs and outputs can be reused across an application.
Key capabilities
- Declarative endpoint definitions built from readable combinators, for example
endpoint.get.in("hello").in(query[String]("name")).out(stringBody).handleSuccess(name => s"Hello, $name!").
- Automatic documentation generation from endpoint descriptions in OpenAPI, AsyncAPI, and JSON Schema formats.
- A Netty-based Scala HTTP server, described as one of the best-performing available, for exposing endpoints directly.
- Integrations with all popular Scala HTTP server implementations, allowing an entire API to be defined in Tapir or Tapir-managed routes to be exposed alongside native ones.
- HTTP client consumption, so the same endpoint description can be used to call a service as well as serve it.
- The use of endpoint metadata to report rich metrics and tracing information, supporting observability.
- Reusable abstractions for whole endpoints and for individual inputs and outputs.
Who uses it and how
- Teams adopting the library gradually can expose Tapir-managed routes next to existing native routes, which the README gives as a use case for partial adoption or selected endpoints.
- Teams working in functional programming styles can keep their preferred stack, since the integrations span ZIO, http4s, akka-http, and Play, and the endpoint API does not force one approach.
- Teams that need API documentation produced as part of the build use the OpenAPI, AsyncAPI, and JSON Schema generators rather than maintaining specification files by hand.
- Teams that need tracing and metrics from their HTTP layer use the endpoint metadata to feed observability libraries.
- The documentation maintains an adopters section listing companies using Tapir, and organisations wishing to be featured email
[email protected] with permission to display a logo.
Getting started
Add the Tapir core dependency from Maven Central, published as com.softwaremill.sttp.tapir:tapir-core, and follow the tutorials and runnable examples linked from tapir.softwaremill.com.
How it compares
Tapir does not compete with the HTTP servers it targets; http4s, akka-http, Play, and ZIO are integration points rather than rivals, and Tapir-managed routes can sit alongside routes written natively for those stacks. On the client side it relies on the sttp ecosystem, which appears among its topics alongside its own server. Within this registry it stands on its own as a description-and-documentation layer for Scala HTTP APIs rather than as a replacement for any of the servers it works with.
When to use it — and when not to
A self-hoster adopting Tapir operates no database, object storage, or mail service, because it is a library rather than a hosted platform; what is required is a Scala and JVM build and a decision about which server integration to run. It is a poor fit for teams outside the Scala and JVM ecosystem, and for anyone wanting a framework with opinions supplied rather than a library that adapts to an existing stack. The README is an entry point rather than a full reference, with the substantive documentation kept externally at tapir.softwaremill.com, and the repository carries 173 open issues, so prospective users should expect to read the external documentation and to work through the issue backlog themselves.
project readme (upstream, from github) — read inline

Welcome!

Intro
Tapir is a library to describe HTTP APIs, expose them as a server, consume as a client, and automatically document
using open standards.
Tapir is fast and developer-friendly. The endpoint definition APIs are crafted with readability and discoverability in
mind. Our Netty-based server is one of the best-performing Scala HTTP servers available.
endpoint
.get.in("hello").in(query[String]("name"))
.out(stringBody)
.handleSuccess(name => s"Hello, $name!")
Tapir integrates with all major Scala stacks, so you can use your favorite approach to Functional Programming, while
leveraging all the benefits that Tapir brings!
Seamless integration with the Scala and HTTP ecosystems is one of Tapir's major strengths:
- all popular Scala HTTP server implementations are supported. You can define your entire API using Tapir, or expose
Tapir-managed routes alongside "native" ones. This is especially useful when gradually adopting Tapir, or using it for
selected use-cases.
- the Scala ecosystem is rich with libraries leveraging its type-safety and enhancing the developer's toolbox,
that's why Tapir provides integrations with many of such custom type, JSON and observability libraries
- documentation can be generated in the OpenAPI, AsyncAPI and JSON Schema formats
Depending on how you'd prefer to explore Tapir, this documentation has three main sections:
- There's a number of tutorials, which provide a gentle introduction to the library
- Nothing compares to tinkering with working code, that's why we've prepared runnable examples,
covering solutions to many "everyday" problems
- Finally, the reference documentation describes all of Tapir's aspects in depth - take a look at the menu on
the left, starting with the "Endpoints" section
Documentation
Tapir documentation is available at tapir.softwaremill.com.
Why tapir?
- type-safety: compile-time guarantees, develop-time completions, read-time information
- declarative: separate the shape of the endpoint (the "what"), from the server logic (the "how")
- OpenAPI / Swagger integration: generate documentation from endpoint descriptions
- observability: leverage the metadata to report rich metrics and tracing information
- abstraction: re-use common endpoint definitions, as well as individual inputs/outputs
- library, not a framework: integrates with your stack
Adopters
Is your company already using tapir? We're continually expanding the "adopters" section in the documentation; the more the merrier! It would be great to feature your company's logo, but in order to do that, we'll need written permission to avoid any legal misunderstandings.
Please email us at [email protected] from your company's email with a link to your logo (if we can use it, of course!) or with details who to kindly ask for permission to feature the logo in tapir's documentation. We'll handle the rest.
Teaser
import sttp.tapir.*
import sttp.tapir.generic.auto.*
import sttp.tapir.json.circe.*
import io.circe.generic.auto.*
type Limit = Int
type AuthToken = String
case class BooksQuery(genre: String, year: Int)
case class Book(title: String)
// Define an endpoint
val booksListing: PublicEndpoint[(BooksQuery, Limit, AuthToken), String, List[Book], Any] =
endpoint
.get
.in(("books" / path[String]("genre") / path[Int]("year")).mapTo[BooksQuery])
.in(query[Limit]("limit").description("Maximum number of books to retrieve"))
.in(header[AuthToken]("X-Auth-Token"))
.errorOut(stringBody)
.out(jsonBody[List[Book]])
// Generate OpenAPI documentation
import sttp.apispec.openapi.circe.yaml.*
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
val docs = OpenAPIDocsInterpreter().toOpenAPI(booksListing, "My Bookshop", "1.0")
println(docs.toYaml)
// Convert to akka-http Route
import sttp.tapir.server.akkaht