Where communities thrive

  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
Repo info
  • Oct 16 15:22
    tomasherman opened #1537
  • Oct 15 17:57
    tomasherman commented #1526
  • Oct 15 17:54
    tomasherman synchronize #1526
  • Oct 15 17:47
    tomasherman commented #1526
  • Oct 15 17:42
    tomasherman synchronize #1526
  • Oct 15 14:23

    mergify[bot] on master

    Update akka-stream to 2.6.17 Merge pull request #1536 from s… (compare)

  • Oct 15 14:23
    mergify[bot] closed #1536
  • Oct 15 13:23
    mergify[bot] labeled #1536
  • Oct 15 13:22
    scala-steward opened #1536
  • Oct 15 11:54
    bartekzylinski assigned #1434
  • Oct 15 11:43

    mergify[bot] on head-range-support


  • Oct 15 11:43

    adamw on master

    Implemented accept range support Added test Added handling of HEAD requests… and 16 more (compare)

  • Oct 15 11:43
    adamw closed #1527
  • Oct 15 11:23
    bartekzylinski synchronize #1527
  • Oct 15 11:23

    bartekzylinski on head-range-support

    Fixed setup of double endpoints… (compare)

  • Oct 15 10:48
    adamw commented #1203
  • Oct 15 09:35
    xeppaka commented #1203
  • Oct 15 09:09
    micossow synchronize #1508
  • Oct 15 06:49
    xeppaka commented #1203
  • Oct 14 22:20

    mergify[bot] on master

    Update swagger-ui to 3.52.5 Merge pull request #1535 from s… (compare)

Bill Frasure
Okay I figured it out. Wasn't too hard, but it does rely on internal methods-
import sttp.tapir.EndpointInput.{FixedPath, PathCapture}
import sttp.tapir.internal.RichEndpointInput

serverEndpoint.input.traverseInputs {
        case path: FixedPath[_] => Vector(path.s)
        case pathCapture: PathCapture[_] => Vector (pathCapture.name.map("$" + _) .getOrElse("$unnamedPathVariable") )
1 reply
@adamw I am trying to derive a Schema[Map[User.Id, User]] where User.Id is defined as a newtype (using zio.prelude) with java.util.UUID as the underlying type and I have mapped the Schema[UUID] instance to define the Schema[User.Id] instance. When I use Schema.schemaForMap[User.Id, User] the name of the component in the OpenAPI documentation becomes Map_<refinement>_User. Is there anything I can do to make the macro use a proper name for the newtype? Where would the macro be getting the name <refinement> from in this case?
2 replies

Is it possible to define endpoint that return Seq[T] and response status code different than default?
I would like in case of processing request success return 201 Created together with Seq[MyCustomType] MyCustomType has circe Encoder/Decoder and Schema

Still I'm not able to define it because of type erasure, in docu I see an exaple with Either, but I'm not able to align it for Seq[T] https://tapir.softwaremill.com/en/latest/endpoint/statuscodes.html

6 replies
Thanks in advance for any suggestions :)
basically in case of success I have one scenario: return 201 Created together with Seq[MyCustomType] - is it simple way to express it?

Hello. Is there any way to describe in docs parameters of SeeOther.
I return this:

            .description("Redirect address \n params: `status` - success or fail")

I want to write in docs queryParameter status.

1 reply
Stanislav Bolsun
Hello, I have tapir Schema for some type A and want to generate open-api doc for the type A only. Can I do it? (without generation whole documentation, I need only yaml with single A type description as object).
4 replies

hi everyone!
I've stumbled upon this: I have this in the endpoint definition:


and in the openapi spec it generates it as optional:

"requestBody": {
  "content": {
    "application/json": {
      "schema": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/MyData"
  "required": false

is there a reason for this or is it a bug? can I somehow tell tapir that it's not optional?

3 replies

@ex0ns: hi! :)

I think the reason is that the array is required by default as it's top level, but the array might be empty

that's what I want, but "required": false says it's not even required, and openapi generator for typescript generates the body as optional

and if someone sends a POST request with body=undefined, the backend will try to parse the body as a JSON array and will fail because it will be empty (empty body)

@yurique Iterables are rendered as optional in the documentation because tapir has the opinion that there isn't really any difference between an empty array and an undefined array in JSON. And in most cases that's true, for example with your List[MyData] the codec should generate an empty list if there isn't defined one in the JSON which will fulfill the requirements of the spec.
I agree with Yurique that in some condition, it does cause issue, as for the empty body instead of body with empty list

In the meantime a solution might be to have a fallback when parsing a List in the backend, where you would return an empty list in case of failure, that is far from perfect, but would not be too hard to implement (custom encoder), or you could wrap the list in an object and use it in the json body

final case class RequestBodyData(data: List[MyData])

But the resulting JSON would look like

  "data": [ ] 
well, circe doesn't think an empty string is parseable as an empty list ;)
scala> import io.circe.parser._
import io.circe.parser._

scala> parse("")
val res0: Either[io.circe.ParsingFailure,io.circe.Json] = Left(io.circe.ParsingFailure: exhausted input)
@ex0ns: I was thinking about wrapping that list into a case class, the best I could think of

but this doesn't seem right:

tapir has the opinion that there isn't really any difference between an empty array and an undefined array

to be sure I sent an empty body to that endpoint (with http4s backend), got a Bad Request with a message from circe:
"Invalid value for: body (DecodingFailure at : C[A])"
Patrick Roemer
I'm trying to get a query parameter with a default value. What I have is this:
implicit val xyzSchema: Schema[XYZ] =
    .default(XYZ.X, Some(XYZ.X.tag))

implicit val xyzCodec: PlainCodec[XYZ] =
    .mapDecode(s => DecodeResult.fromOption(XYZ(s)))(_.tag)
5 replies
Works, but if I pass an invalid value (that returns None from XYZ#apply()), it will be accepted and reverted to the default value X. What I'd want is that the default fallback is only applied if the query parameter isn't present at all, but a decode failure if an invalid value is given. Am I doing anything wrong and/or can this be achieved somehow?
Patrick Roemer
Another question: I have an endpoint that generates the same output shape for error responses in the 400s range. I came across this discussion:
The suggested #map() approach seems to work fine in general, but of course the OpenAPI doc only shows the describedStatusCode. Is there any way to get a 4XX range rendered instead? (I guess not, but perhaps I'm missing some trick.)
2 replies
Patrick Roemer
...and a last, rather esoteric question. In the akka-http code base I'm currently trying to switch over to Tapir, we have a couple of endpoints based on a reverse proxy route directive, i.e. these are dispatching the request to another service and return their response as is. For now, I have a Tapir description for these endpoints, but these are only used for OpenAPI generation. Is there any way to tie the Tapir spec to the native akka-http route implementation? Or is there anything else in Tapir to support reverse proxy cases? (I wouldn't even know how either of this should work at the conceptual level, but again, I might be missing something.)
8 replies
Hi! I am trying to transition my http4s routes to tapir, and I am having a little trouble with one route which is translating a x-www-form-urlencoded form into a case class. Right now the case class is very basic, just holding a bunch of values that are strings. I see that I need to create a Codec[String, CaseClass, XWwwFormUrlencoded], but I am not sure how to this (do I need to do it manually? I am using circe and I have an EntityDecoder for the case class). I do not see a similar example in the docs, I am not clear on what exactly I need to implement to solve this?
5 replies
Patrick Roemer
I'd like an endpoint to optionally set a cookie in the response. Is there some high-level functionality that I'm missing (setCookie doesn't seem to cover the optional case) or will I need to drop to header level for this?
4 replies
Hello I have replaced two query inputs with a EndpointInput.QueryParams, it works fine but in the generated swagger it now shows "No parameters", do you know if I am missing something ?
1 reply
Izmar Verhage
We're trying to upgrade tapir from 0.17 to v0.18, used with Akka HTTP. For responding with a dynamic Location header that contains an URI like "/pet/{newPetID}" we used to rely on AkkaHttpServerInterpreter.toDirective, but toDirective was removed in 0.18. Any ideas how to achieve this?
13 replies
Ulyana Morozova

Hi! I use tapir version "0.17.9". I have endpoint with json request model with property of type AggregateFunction. Decoder looks like that:

import io.circe.{Decoder, Encoder, HCursor, Json}
import sttp.tapir.Codec.PlainCodec
import sttp.tapir.{Codec, DecodeResult}

sealed trait AggregateFunction {
  def id: Short
  def name: String
  override def toString: String = name

case object MinFunction     extends AggregateFunction { val name = "min";  val id = 0 }
case object MaxFunction     extends AggregateFunction { val name = "max";  val id = 1 }
case object UnknownFunction extends AggregateFunction { val name = "unknown";  val id = -1 }

object AggregateFunction {
  def apply(name: String): AggregateFunction =
    name match {
      case MinFunction.name => MinFunction
      case MaxFunction.name => MaxFunction
      case _ => UnknownFunction

  def apply(id: Int): AggregateFunction =
    id match {
      case MinFunction.id => MinFunction
      case MaxFunction.id => MaxFunction
      case _ => UnknownFunction

  def encode(valuesAggregator: AggregateFunction): String = valuesAggregator.name

  def decode(s: String): DecodeResult[AggregateFunction] = AggregateFunction(s) match {
    case UnknownFunction => DecodeResult.Error(s, new IllegalArgumentException("Invalid item type."))
    case v => DecodeResult.Value(v)

  implicit val AggregateFunctionCodec: PlainCodec[AggregateFunction] = Codec.string

  implicit val encoder: Encoder[AggregateFunction] = (nt: AggregateFunction) => Json.fromString(nt.name)
  implicit val decoder: Decoder[AggregateFunction] = (hc: HCursor) => hc.as[String].map(apply)


I want to return 400 Bad request error in json format {"message": "Invalid name of aggregate function"} if users try to send wrong function name.
But now the response has string format 400 Bad request "Invalid value for: body (String at 'aggregateFunction')". How I could change error response format from String to Json?

9 replies
Giuseppe Cannella
Sorry for OT there is not a dedicate gitter on quicklens, the question is about Enum see here https://stackoverflow.com/questions/69195290/scala-quicklens-enumeration
1 reply
I'm trying to migrate to sttp client3 / tapir 0.17.0, but my linter fails on Any. I don't want to turn this off, but I'd really rather not add ignore annotations to some 40-odd endpoints. Is there some version of a no-op stream type I can use instead of Any?
Nikita Vilunov
Hi! Is it possible to define a multipart endpoint's input as a coproduct? Or at least as a case class with a defined codec and an optional Part[Array[Byte]] member?
2 replies
Amogh Venkatesh
Hey all,
Is there any example for custom multipartcodec for multipart forms for Tapir?
Below is the case class -
case class Book(name: String, desc : String, file: Part[Path])
type UploadData = Seq[Part[Path]]
My custom codec giving error -
implicit val pathPartCodec: MultipartCodec[Book] = { Codec .multipartCodec( partCodecs = Map("UploadData" -> PartCodec(rawBodyType = RawBodyType.FileBody, codec = Codec.listHead(Codec.path)), "String" -> PartCodec( rawBodyType = RawBodyType.ByteArrayBody, codec = Codec.listHead(Codec.string) )), defaultPartCodec = None ) .asInstanceOf[MultipartCodec[Book]] }
error -
type mismatch; found : sttp.tapir.RawBodyType.ByteArrayBody.type required: sttp.tapir.RawBodyType[java.io.Serializable] Note: Array[Byte] <: java.io.Serializable (and sttp.tapir.RawBodyType.ByteArrayBody.type <: sttp.tapir.RawBodyType.Binary[Array[Byte]]), but trait RawBodyType is invariant in type R. You may wish to define R as +R instead. (SLS 4.5) rawBodyType = RawBodyType.ByteArrayBody,
6 replies
Jack Henahan

I have a piece of common routing logic that I'm trying to make reusable. In essence, the endpoint is endpoint.in(header[MyHeader]). I'd like consumers to be able to write their own standalone endpoints (endpoint.in("whatever").out(whatever)) but be able to layer my endpoint on top of it when building server interpreters. The only functionality I really need to make happen is to make the value read off the header available to the downstream endpoint. Use case: We're building a set of services which receive requests decorated with internal-use headers. These headers are added by an external service, so I'd like to keep the explicit header out of the downstream endpoint definition so that clients generated off the endpoint aren't required to provide header values to make requests.

End of the day, I'd like to be able to write something like

val upstreamHeaders = endpoint.in(header[MyHeader])

val downstreamEndpoint = endpoint.in("test").out(stringBody)

def someLogic(header: MyHeader): Future[String] = ???

// An Akka-HTTP server, for example
val serverInterpreter = AkkaHttpServerInterpreter().toRoute(/* upstreamHeaders composed with downstreamEndpoint */)(someLogic)

val clientInterpreter = SttpClientInterpreter().toClient(downstreamEndpoint, uri, backend)

Is this possible?

I could just insist that downstream consumers write two versions of their endpoints, but I'm hoping there's a nicer way
Jack Henahan
It looks like I can get away with writing a function def myHeader(endpoint: Endpoint[I, E, O, R]): Endpoint[(I, MyHeader), E, O, R] at the cost of having to do some slightly meh tuple munging at the use site. I can live with that if there are no better options
3 replies
Hi @adamw, this module tapir-zio-http_2.13:0.19.0-M10 seems to be missing for M10 release: https://repo1.maven.org/maven2/com/softwaremill/sttp/tapir/tapir-zio-http_2.13/
2 replies
Tom Snee
I would like my API to support the last three versions, where versions are defined through media types as GitHub does. I know how to make one endpoint support multiple media types, but media types aren't passed to the serverLogic method, so I don't know how to differentiate between versions. I tried to make a separate endpoint for each version, but it seems only the first one in the list passed to the server interpreter is considered. What should I do?
8 replies
Amogh Venkatesh

I am using multipart body for file upload. I am getting 400 error when using a client to make the request Invalid value for: body . I have a case class something like
case class UploadData( metadata : FileMetaData, file: Seq[Part[Path]])

metadata consists of just String attributes.
Using Postman to make a call, return 400 - Invalid value for: body. Is there anyway to find out more about the error. I have created custom error handler. But I don't see any error being returned.

2 replies
Tomas Herman
Hello, i see work has been started on netty backend for tapir ... I think that's a great initiative as netty has been proven at my employment as the best-in-class when it comes to performance. You don't always need it but when you do, there is nothing like it. I was wondering - is there something i can help with? I see warning about missing features, i would like to contribute ... perhaps some issues could be created? Thanks!
3 replies
Tom Snee
I wrote up my work on API versioning through content type negotiation at softwaremill/tapir#1502. I would appreciate suggestions for improvement.
Patrick Roemer
Validator.Enumeration will produce a list of the valid target values on #show() (even when specifying #encode(), but the docs already say that this is used for documentation only). For the client side, I'd prefer a list of the valid encoded values in a decode failure message, as they need to be provided in the request. Is there a way to get this behavior? Using a manually assembled Enumeration instance with #show overridden seems to work. But of course I'd rather like to use #derivedEnumeration, extending a case class feels somewhat fishy in general, and I have a feeling that I'm missing something or just holding it wrong. Any hints for improvement appreciated.
5 replies
Jakub Kozłowski
hello, I'd like to share endpoints between a http4s client & server. I wanted to guard against uncaught exceptions and handle them in a common way for all endpoints, i.e. the server would return a 500 with a JSON body like {"message": "an internal server error occurred"}, and the client would decode that to a failed IO with something like new GenericServerError(responseBody.message). Is that possible?
1 reply
So far the limitation for doing this on the client, without exposing the type with message: String, was that the http4s client interpreter gives me a pair like Request[F], Response[F] => F[Either[E, O]], and I can't safely reuse the response if the effect I get in the second value fails.
10 replies
Jakub Kozłowski
on the server side I'm also having issues with it, as I couldn't get a Http4sServerInterpreter exception handler to even be triggered in such a case... ended up adding a handleError on the HttpRoutes that I got from interpreting (or on my server builder - still the same problem). Not a fan of this, as it required me to encode my JSON body myself, without relying on the existing codecs from tapir-json-circe (I used http4s-circe instead)
22 replies
Jakub Kozłowski
anyway here's what I'm trying:
// libraryDependencies ++= Seq(
//   "org.http4s" %% "http4s-ember-server" % "0.23.4",
//   "com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "0.19.0-M10",
//   "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "0.19.0-M10",
// ),
import cats.effect.IO
import cats.effect.IOApp
import com.comcast.ip4s._
import org.http4s.HttpRoutes
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.implicits._
import sttp.tapir.Schema
import sttp.tapir.server.http4s.Http4sServerInterpreter
import sttp.tapir.server.http4s.Http4sServerOptions
import sttp.tapir.server.interceptor.ValuedEndpointOutput
import sttp.tapir.json.circe._
import sttp.tapir.infallibleEndpoint

object Main extends IOApp.Simple {

  def run: IO[Unit] =
      .withHttpApp {

            .customInterceptors[IO, IO]
            .exceptionHandler(ec => Some(ValuedEndpointOutput(jsonBody[String], "failed")))
              .serverLogicInfallible(_ => IO.raiseError[Unit](new Throwable("unexpected error!")))

HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 0
Date: Sun, 03 Oct 2021 23:40:30 GMT
Jakub Kozłowski

similar code in akka-http gives me this

HTTP/1.1 500 Internal Server Error
Content-Length: 35
Content-Type: text/plain; charset=UTF-8
Date: Sun, 03 Oct 2021 23:53:50 GMT
Server: akka-http/10.2.6

There was an internal server error.

so a little better, but it still looks like the custom exception handler isn't being used

Diana Udișteanu
Hello. I have a question if anybody wants to help me: How can I make a multipart codec with an optional file?
3 replies
Mikael Ståldal
I need some help on inlining a schema in generated OpenAPI. The docs says "If you’d like a schema to be inlined, instead of referenced, modify the schema removing the name.", but how do you do that?
8 replies
Tom Snee
I'm using the ZIO http4s interpreter and I would like to log all of my business logic errors. ZHttp4sServerInterpreter only exposes toRoutes, not toRouteRecoverErrors, so it is not clear to me where I can do the logging. Any suggestions?
6 replies
Hollin Wilkins

Hey everyon :wave: , I am trying to setup an endpoint to accept multiple content types:

Is this possible to do with Tapir, support multiple content-types on the input body?

2 replies