tapir is a free, open source monitoring & observability project written in Scala and released under Apache-2.0. It has 1,475 GitHub stars, 472 forks and 173 open issues, and was last pushed 11 hours ago. On this registry it ranks #200 of 270 tracked projects in Monitoring & Observability, with 5 head-to-head comparisons available.

What is tapir?

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 tapir@softwaremill.com 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

tapir

Welcome!

Ideas, suggestions, problems, questions CI Maven Central

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:

  1. There's a number of tutorials, which provide a gentle introduction to the library
  2. Nothing compares to tinkering with working code, that's why we've prepared runnable examples, covering solutions to many "everyday" problems
  3. 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 tapir@softwaremill.com 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.

Adobe Swisscom Swissborg
Kaizo Process Street Tranzzo
Kelkoo group SoftwareMill Carvana
Moneyfarm Ocado Technology Wegtam
Broad Kensu Colisweb
iceo dpg hunters
moia pits hootsuite
ematiq fugo budgetbakers
flo xing

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.akkahttp.AkkaHttpServerInterpreter
import akka.http.scaladsl.server.Route
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

def bookListingLogic(bfy: BooksQuery,
                     limit: Limit,
                     at: AuthToken): Future[Either[String, List[Book]]] =
  Future.successful(Right(List(Book("The Sorrows of Young Werther"))))
  
val booksListingRoute: Route = AkkaHttpServerInterpreter()
  .toRoute(booksListing.serverLogic((bookListingLogic _).tupled))


// Convert to sttp Request

import sttp.tapir.client.sttp4.SttpClientInterpreter
import sttp.client4.*

val booksListingRequest: Request[DecodeResult[Either[String, List[Book]]]] = 
  SttpClientInterpreter()
    .toRequest(booksListing, Some(uri"http://localhost:8080"))
    .apply((BooksQuery("SF", 2016), 20, "xyz-abc-123"))

Quickstart with sbt

Add the following dependency:

"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.13.31"

Then, import:

import sttp.tapir._

And finally, type endpoint. and see where auto-complete gets you!

Scala 2.12

Partial unification is now enabled by default from Scala 2.13. However, if you're using Scala 2.12 or older, then you'll need partial unification enabled in the compiler (alternatively, you'll need to manually provide type arguments in some cases):

scalacOptions += "-Ypartial-unification"

Sidenote for scala 2.12.4 and higher: if you encounter an issue with compiling your project because of a StackOverflowException related to this scala bug, please increase your stack memory. Example:

sbt -J-Xss4M clean compile

Other sttp projects

sttp is a family of Scala HTTP-related projects, and currently includes:

  • sttp client: the Scala HTTP client you always wanted!
  • sttp tapir: this project
  • sttp model: simple HTTP model classes (used by client & tapir)
  • sttp shared: shared web socket, FP abstractions, capabilities and streaming code.
  • sttp apispec: OpenAPI, AsyncAPI and JSON Schema models.

Contributing

All suggestions welcome :)

See the list of issues and pick one! Or report your own.

If you are having doubts on the why or how something works, don't hesitate to ask a question on discourse or via github. This probably means that the documentation, scaladocs or code is unclear and be improved for the benefit of all. In order to develop the documentation, you can use the doc/watch.sh script, which runs Sphinx using Python. Use doc/requirements.txt to set up your Python environment with pip. If you're using Nix, you can just run nix develop in the doc directory to set up a working shell with all the dependencies.

The core module needs to remain binary-compatible with earlier versions. To check if your changes meet this requirement, you can run core/mimaReportBinaryIssues from the sbt console. However, be aware that tags from the repository aren’t automatically fetched during forking; hence, the command will not operate without first fetching the tags.

After forking and cloning the repository, add the original repository as a remote:

git remote add upstream git@github.com:softwaremill/tapir.git

Fetch the tags from the upstream:

git fetch --tags upstream

When you have a PR ready, take a look at our "How to prepare a good PR" guide. Thanks! :)

Scoping which projects are included by sbt

  • when STTP_NATIVE is set, Scala native projects are included in the build (when running sbt)
  • when ALSO_LOOM is set, projects using virtual threads and requiring Java 21 are included in the build
  • when ONLY_LOOM is set, only projects using virtual threads are included in the build

Testing locally

The JS tests use Gecko instead of Chrome, although this causes another problem: out of memory when running JS tests for multiple modules. Work-arounds:

  • run only tests for a specific Scala version and platform using testScoped 2.13 JS (supported versions: 2.12, 2.13, 3; supported platforms: JVM, JS, Native)
  • test single JS projects
  • use CI (GitHub Actions) to test all projects - the .github/workflows/ci.yml enumerates them one by one

You can test only server/client/doc/other projects using testServers, testClients, testDocs and testOther.

To verify that the code snippet in docs compile, run compileDocumentation. A full mdoc run is done during a release (when the documentation is generated).

Importing into IntelliJ

By default, when importing to IntelliJ, only the Scala 3/JVM subprojects will be imported. This is controlled by the ideSkipProject setting in build.sbt (inside commonSettings).

If you'd like to work on a different platform or Scala version, simply change this setting temporarily so that the correct subprojects are imported. For example:

// import only Scala 2.13, JS projects
ideSkipProject := (scalaVersion.value != scala2_13) || !thisProjectRef.value.project.contains("JS")

// import only Scala 3, JVM projects
ideSkipProject := (scalaVersion.value != scala3) || thisProjectRef.value.project.contains("JS") || thisProjectRef.value.project.contains("Native"),

// import only Scala 2.13, Native projects
ideSkipProject := (scalaVersion.value != scala2_13) || !thisProjectRef.value.project.contains("Native")

Commercial Support

We offer commercial support for tapir and related technologies, as well as development services. Contact us to learn more about our offer!

Copyright

Copyright (C) 2018-2025 SoftwareMill https://softwaremill.com.

Frequently asked questions

Is tapir free to use?

tapir is open source under the Apache-2.0 licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does tapir do?

Rapid development of self-documenting APIs

What is tapir written in?

tapir is primarily written in Scala. Its source is publicly available at https://github.com/softwaremill/tapir, and it has 1,475 GitHub stars.