Watson generates OpenAPI documents for the routes you register, and serves Swagger UI, with no extra package and no external dependency. As of 7.2.0 you choose which specification version it emits — 3.0, 3.1, or 3.2 — from a single setting. The default stays 3.0 so existing consumers see the exact document they saw before; the newer versions are opt-in.
Webserver server = new Webserver(new WebserverSettings("127.0.0.1", 8080, false), DefaultRoute);
server.UseOpenApi(openApi => openApi.Info.Title = "Example API");That registers /openapi.json and /swagger. Every route you document with openApiMetadata shows up in
the document; undocumented routes still appear with a minimal stub so the surface is complete.
server.UseOpenApi(openApi =>
{
openApi.Info.Title = "Example API";
openApi.Version = OpenApiVersionEnum.V3_1; // V3_0 (default), V3_1, or V3_2
});The version you pick changes how the document is encoded, not just the string in the openapi field.
| Setting | Emitted openapi |
What changes |
|---|---|---|
V3_0 (default) |
3.0.3 |
The original Watson encoding. Byte-compatible with pre-7.2 output. |
V3_1 |
3.1.1 |
Schema Object follows JSON Schema 2020-12. |
V3_2 |
3.2.0 |
Everything in 3.1, plus the 3.2 additions below. |
If you need to pin a specific patch release, set VersionString (for example "3.1.0"); it overrides the
string without changing the encoding rules, which follow Version.
The jump from 3.0 to 3.1 is the one that actually rewrites schemas, because 3.1 aligns the Schema Object with JSON Schema 2020-12. If you maintain a client generated against a 3.0 document, these are the shapes that will look different after you switch.
Nullability is the big one. Under 3.0 a nullable string is {"type":"string","nullable":true}. Under 3.1 the
nullable keyword is gone; the null is folded into the type:
A nullable $ref cannot carry a sibling nullable, so 3.1 wraps it:
// 3.1
{ "anyOf": [ { "$ref": "#/components/schemas/Cat" }, { "type": "null" } ] }Binary payloads move off format. What was {"type":"string","format":"binary"} becomes
{"type":"string","contentMediaType":"application/octet-stream"}, and format: byte becomes
contentEncoding: base64. Schema-level examples move from the singular example to an examples array.
Watson performs all of these rewrites for you from the same OpenApiSchemaMetadata you already write — you do
not maintain two schema trees.
The 3.1 document object also gains fields Watson populates when you set them: info.summary,
license.identifier (an SPDX expression, mutually exclusive with license.url), a top-level webhooks object,
jsonSchemaDialect, mutualTLS security schemes, and OAuth2 flows. When webhooks are present the paths
object may be omitted, which Watson does automatically.
3.2 is additive on top of 3.1. Watson emits the following when you target V3_2 and set the corresponding
values:
$selfon the document (OpenApiSettings.Self)- the
queryHTTP method as a first-class path operation, plusadditionalOperationsfor any other non-standard method, both driven byOpenApiSettings.AdditionalOperations - streaming media via
itemSchemaon a media type (OpenApiMediaTypeMetadata.ItemSchema), for formats likeapplication/jsonlandtext/event-stream - hierarchical tags:
summary,parent, andkindonOpenApiTag - a
nameon servers - the OAuth2 device authorization flow and
oauth2MetadataUrl
Requesting any 3.2-only construct while Version is lower than V3_2 is a hard error rather than a silently
invalid document — see the validation section.
By default /openapi.json and /swagger are registered ahead of authentication, so anyone who can reach the
server can read them. Set RequireAuthentication to move both endpoints behind whatever authentication you
have configured:
server.Routes.AuthenticateApiRequest = async ctx =>
{
string key = ctx.Request.RetrieveHeaderValue("X-API-Key");
return key == "secret"
? new AuthResult { AuthenticationResult = AuthenticationResultEnum.Success, AuthorizationResult = AuthorizationResultEnum.Permitted }
: new AuthResult { AuthenticationResult = AuthenticationResultEnum.NotFound, AuthorizationResult = AuthorizationResultEnum.DeniedImplicit };
};
server.UseOpenApi(openApi =>
{
openApi.Info.Title = "Example API";
openApi.RequireAuthentication = true; // /openapi.json and /swagger now require auth
});With RequireAuthentication = true an unauthenticated request to /openapi.json gets the server's standard
authentication-failure response and never the document body. The flag only decides where the two endpoints
live; IncludePreAuthRoutes and IncludePostAuthRoutes still control which of your other routes appear inside
the document.
Watson refuses to emit a document it knows is invalid for the selected version, throwing
OpenApiValidationException from Generate. The checks that fire:
- a license with both
urlandidentifierset identifier,webhooks,jsonSchemaDialect,mutualTLS, or anulltype in atypearray under a version that does not support it- additional operations,
$self, servername, tag hierarchy, or the device flow under a version below 3.2 - an
oauth2scheme with no flows - a nullable schema under 3.1+ that has no base type to attach
nullto - a duplicate
operationIdanywhere in the document
Catching these at generation time keeps a broken document from reaching a client generator, where the failure would be far less obvious.
Route metadata is the same regardless of version; the generator adapts the output.
server.Routes.PreAuthentication.Parameter.Add(
HttpMethod.GET,
"/users/{id}",
GetUserHandler,
openApiMetadata: OpenApiRouteMetadata.Create("Get a user", "Users")
.WithParameter(OpenApiParameterMetadata.Path("id", "User ID", OpenApiSchemaMetadata.Integer()))
.WithResponse(200, OpenApiResponseMetadata.Json("User found", OpenApiSchemaMetadata.CreateRef("User")))
.WithResponse(404, OpenApiResponseMetadata.NotFound()));Reusable schemas live under OpenApiSettings.Schemas and are referenced with
OpenApiSchemaMetadata.CreateRef("User"), exactly as before.
OpenAPI path templates have no catch-all syntax, so a catch-all route such as /files/{*path} is documented as
/files/{path} with a required path path parameter. Describe it with OpenApiParameterMetadata.Path("path", ...)
if you want to note that the value can contain slashes.
The /swagger page loads swagger-ui-dist assets from the unpkg CDN, so it needs internet access at page load;
a browser with no route to unpkg.com renders a blank page. The pinned version is configurable through
OpenApiSettings.SwaggerUiVersion (default 5.17.14) if you need to move it. Bundling the assets for fully
offline operation is not something Watson does today.
Point any OpenAPI validator at the generated document to confirm it is well formed under the version you chose. From the sample project:
dotnet run --project src/Test.OpenApi/Test.OpenApi.csproj -- 3.1
curl http://localhost:8080/openapi.json | jq .openapi # "3.1.1"
Pass 3.2 to see the newer surface, and add secure as a second argument to watch the document move behind an
X-API-Key check. The shared test suite exercises each version's encoding and every validation rule in
src/Test.Shared/SharedOpenApiCompositionTests.cs, and the endpoint behavior in
src/Test.Shared/SharedOpenApiEndpointTests.cs.