Skip to content
Shiny.Net.HttpServer v1 - A lightweight feature rich HTTP Server - Tunnels, Websockets, AOT, ASPNET Featureset, & Works EVERYWHERE!Let me see!

OpenAPI

app.MapOpenApi(configure: o =>
{
o.Title = "Widgets";
o.Version = "1.0.0";
o.AddBearerAuthentication();
});

/openapi.json now serves an OpenAPI 3.0.3 document. It is built on the first request and cached — the route table is frozen by the time anything can ask for it — and the endpoint excludes itself from what it publishes.

To write it out at build or test time instead of serving it:

var json = OpenApiDocumentBuilder.BuildJson(app);
File.WriteAllText("openapi.json", json);

Two sources, both compile-time.

The route table. Generated endpoints carry an ApiOperation the generator emitted from the analysis it had already done to write the binder: parameter names and sources, the body type, the return type, and the <summary> from the method’s doc comment. Raw routes get path parameters inferred from their template, including the constraint’s type.

Your JsonSerializerContext. Schemas are read off JsonTypeInfo rather than Type.GetProperties(), so property names go through the same naming policy the serializer uses — the document cannot describe a payload the server would not actually produce. It also means schema generation is reflection-free and survives AOT.

The whole thing is written with Utf8JsonWriter straight to bytes: no document object model to keep in sync with the spec, and nothing to register in order to serialize it.

/// <summary>Fetches a single widget by id.</summary>
[Get("/{id:int}")]
[Produces(200, typeof(Widget))]
[Produces(404, Description = "No widget has that id")]
public async Task<IActionResult> GetWidget(int id, CancellationToken ct) => …;
Attribute Effect
[Produces(status, type?)] Declares a response. Also takes Description and ContentType
[ApiTags("…")] Groups operations. On a class it covers every endpoint; on a method it replaces the class’s. Without it the class name is the tag
[ApiExclude] Omits a class or method from the document — the route still works

[Produces] matters most for a method returning IActionResult, which has deliberately hidden its status codes; that is the point of the abstraction. When the generator can see the return type, it infers a 200 from it.

The <summary> doc comment becomes the operation summary, so the documentation you already write for your own team is the documentation the API publishes.

Describe applies to the most recently mapped route:

app.OnGet("/ping", ctx => ctx.Response.WriteAsync("pong"))
.Describe(o =>
{
o.Summary = "Liveness probe";
o.Tags.Add("ops");
o.Responses.Add(new ApiResponse { StatusCode = 200, Type = typeof(string), ContentType = "text/plain" });
});

There are shorthands on the route-builder form used by modules and groups: WithSummary, WithTags and ExcludeFromDescription.

ApiOperation carries Summary, Description, OperationId, Deprecated, Exclude, RequiresAuthorization, Tags, Parameters, RequestBody and Responses.

Property Default Notes
Title / Version / Description API, 1.0.0 The info block
Servers empty Left empty, no servers block is written — tooling reads that as “relative to wherever you fetched this from”, which is usually right for a server whose address is a tunnel URL decided at runtime
Indented true Documents are read by people at least as often as by tools
IncludeUndescribedRoutes true Set false to publish only what was described deliberately
SecuritySchemes empty Keyed by the name operations reference
DefaultSecurityScheme null Applied to operations that require authorization
ConfigureOperation null A convention applied to every endpoint before it is written
o.AddBearerAuthentication();

Adds a Bearer/JWT scheme and makes it the default, so every endpoint carrying [Authorize] is documented as needing a token — and the “Authorize” button in whatever UI reads this actually does something. ApiSecurityScheme covers the http, apiKey and openIdConnect shapes if you need a different one.

o.ConfigureOperation = (operation, endpoint) =>
{
if (operation.RequiresAuthorization)
operation.Responses.Add(new ApiResponse { StatusCode = 401 });
};
  • OpenAPI 3.0.3 only.
  • A type only gets a schema if it is in a registered JsonSerializerContext — the same requirement as returning it from an endpoint, and the generator warns (SWS006) when it is not met.
  • A catch-all token is documented as an ordinary path parameter, since OpenAPI cannot express a multi-segment one.
  • A trailing optional route parameter becomes two paths, because OpenAPI has no way to say a path segment is optional and the route genuinely matches two URLs.