This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

Download Microsoft Edge More info about Internet Explorer and Microsoft Edge

In this article

The OpenAPI specification is a programming language-agnostic standard for documenting HTTP APIs. This standard is supported in minimal APIs through a combination of built-in APIs and open-source libraries. There are three key aspects to OpenAPI integration in an application:

  • Generating information about the endpoints in the app.
  • Gathering the information into a format that matches the OpenAPI schema.
  • Exposing the generated OpenAPI schema via a visual UI or a serialized file.
  • Minimal APIs provide built-in support for generating information about endpoints in an app via the Microsoft.AspNetCore.OpenApi package.

    The following code is generated by the ASP.NET Core minimal web API template and uses OpenAPI:

    using Microsoft.AspNetCore.OpenApi;
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddOpenApi();
    var app = builder.Build();
    if (app.Environment.IsDevelopment())
        app.MapOpenApi();
    app.UseHttpsRedirection();
    var summaries = new[]
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    app.MapGet("/weatherforecast", () =>
        var forecast = Enumerable.Range(1, 5).Select(index =>
            new WeatherForecast
                DateTime.Now.AddDays(index),
                Random.Shared.Next(-20, 55),
                summaries[Random.Shared.Next(summaries.Length)]
            .ToArray();
        return forecast;
    .WithName("GetWeatherForecast");
    app.Run();
    internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    

    In the preceding highlighted code:

  • AddOpenApi registers services required for OpenAPI document generation into the application's DI container.
  • MapOpenApi adds an endpoint into the application for viewing the OpenAPI document serialized into JSON.
  • Microsoft.AspNetCore.OpenApi NuGet package

    The Microsoft.AspNetCore.OpenApi package provides functionality that encompasses the following features:

  • Support for generating OpenAPI documents at runtime and accessing them via an endpoint on the application
  • Support for "transformer" APIs that allow modifying the generated document
  • Support for generating OpenAPI documents at buildtime and serializing them to disk
  • Microsoft.AspNetCore.OpenApi is added as a PackageReference to a project file:

    <Project Sdk="Microsoft.NET.Sdk.Web">
      <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
      <ItemGroup>    
        <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.*-*" />
      </ItemGroup>
    </Project>
    

    To learn more about the Microsoft.AspNetCore.OpenApi package, see Get started with Microsoft.AspNetCore.OpenApi.

    Describe endpoints

    OpenAPI supports providing summaries and descriptions of routes that are registered in an app. Minimal APIs support two strategies for setting these properties on a given endpoint, using the following extension methods and attributes:

  • Summaries: WithSummary
  • Descriptions: WithDescription
  • Summaries: EndpointSummaryAttribute
  • Descriptions: EndpointDescriptionAttribute
  • The following sample demonstrates the different strategies for setting summaries and descriptions.

    app.MapGet("/extension-methods", () => "Hello world!")
      .WithSummary("This is a summary.")
      .WithDescription("This is a description.");
    app.MapGet("/attributes",
      [EndpointSummary("This is a summary.")]
      [EndpointDescription("This is a description.")]
      () => "Hello world!");
    

    Set OpenAPI tags

    OpenAPI supports specifying tags on each endpoint as a form of categorization. Minimal APIs supports two strategies for setting OpenAPI tags on a given endpoint, using:

  • WithTags
  • TagsAttribute
  • The follow sample demonstrates the different strategies for setting tags.

    app.MapGet("/extension-methods", () => "Hello world!")
      .WithTags("todos", "projects");
    app.MapGet("/attributes",
      [Tags("todos", "projects")]
      () => "Hello world!");
    

    Excluding endpoints from the generated document

    By default, all endpoints that are defined in an app are documented in the generated OpenAPI file. Minimal APIs supports two strategies for excluding a given endpoint from the OpenAPI document, using:

  • ExcludeFromDescription
  • ExcludeFromDescriptionAttribute
  • The following sample demonstrates the different strategies for excluding a given endpoint from the generated OpenAPI document.

    app.MapGet("/extension-method", () => "Hello world!")
      .ExcludeFromDescription();
    app.MapGet("/attributes",
      [ExcludeFromDescription]
      () => "Hello world!");
    

    Describe response types

    OpenAPI supports providing a description of the responses returned from an API. Minimal APIs support three strategies for setting the response type of an endpoint:

  • Via the Produces extension method on the endpoint.
  • Via the ProducesResponseType attribute on the route handler.
  • By returningTypedResults from the route handler.
  • The Produces extension method can be used to add Produces metadata to an endpoint. When no parameters are provided, the extension method populates metadata for the targeted type under a 200 status code and an application/json content type.

    app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
      .Produces<IList<Todo>>();
    

    Using TypedResults in the implementation of an endpoint's route handler automatically includes the response type metadata for the endpoint. For example, the following code automatically annotates the endpoint with a response under the 200 status code with an application/json content type.

    app.MapGet("/todos", async (TodoDb db) =>
        var todos = await db.Todos.ToListAsync());
        return TypedResults.Ok(todos);
    

    Set responses for ProblemDetails

    When setting the response type for endpoints that may return a ProblemDetails response, the ProducesProblem extension method or TypedResults.Problem can be used to add the appropriate annotation to the endpoint's metadata.

    When there are no explicit annotations provided by one of these strategies, the framework attempts to determine a default response type by examining the signature of the response. This default response is populated under the 200 status code in the OpenAPI definition.

    Multiple response types

    If an endpoint can return different response types in different scenarios, you can provide metadata in the following ways:

  • Call the Produces extension method multiple times, as shown in the following example:

    app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
             await db.Todos.FindAsync(id) 
             is Todo todo
             ? Results.Ok(todo) 
             : Results.NotFound())
       .Produces<Todo>(StatusCodes.Status200OK)
       .Produces(StatusCodes.Status404NotFound);
    
  • Use Results<TResult1,TResult2,TResultN> in the signature and TypedResults in the body of the handler, as shown in the following example:

    app.MapGet("/book{id}", Results<Ok<Book>, NotFound> (int id, List<Book> bookList) =>
        return bookList.FirstOrDefault((i) => i.Id == id) is Book book
         ? TypedResults.Ok(book)
         : TypedResults.NotFound();
    

    The Results<TResult1,TResult2,TResultN> union types declare that a route handler returns multiple IResult-implementing concrete types, and any of those types that implement IEndpointMetadataProvider will contribute to the endpoint’s metadata.

    The union types implement implicit cast operators. These operators enable the compiler to automatically convert the types specified in the generic arguments to an instance of the union type. This capability has the added benefit of providing compile-time checking that a route handler only returns the results that it declares it does. Attempting to return a type that isn't declared as one of the generic arguments to Results<TResult1,TResult2,TResultN> results in a compilation error.

    Describe request body and parameters

    In addition to describing the types that are returned by an endpoint, OpenAPI also supports annotating the inputs that are consumed by an API. These inputs fall into two categories:

  • Parameters that appear in the path, query string, headers, or cookies.
  • Data transmitted as part of the request body.
  • The framework infers the types for request parameters in the path, query, and header string automatically based on the signature of the route handler.

    To define the type of inputs transmitted as the request body, configure the properties by using the Accepts extension method to define the object type and content type that are expected by the request handler. In the following example, the endpoint accepts a Todo object in the request body with an expected content-type of application/xml.

    app.MapPost("/todos/{id}", (int id, Todo todo) => ...)
      .Accepts<Todo>("application/xml");
    

    In addition to the Acceptsextension method, a parameter type can describe its own annotation by implementing the IEndpointParameterMetadataProvider interface. For example, the following Todo type adds an annotation that requires a request body with an application/xml content-type.

    public class Todo : IEndpointParameterMetadataProvider
        public static void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder)
            builder.Metadata.Add(new ConsumesAttribute(typeof(Todo), isOptional: false, "application/xml"));
    

    When no explicit annotation is provided, the framework attempts to determine the default request type if there's a request body parameter in the endpoint handler. The inference uses the following heuristics to produce the annotation:

  • Request body parameters that are read from a form via the [FromForm] attribute are described with the multipart/form-data content-type.
  • All other request body parameters are described with the application/json content-type.
  • The request body is treated as optional if it's nullable or if the AllowEmpty property is set on the FromBody attribute.
  • ASP.NET Core OpenAPI source code on GitHub

  • AddOpenApi
  • OpenApiDocumentService
  • OpenApiOptions
  • Additional Resources

  • Authentication and authorization in minimal APIs
  • The OpenAPI specification is a programming language-agnostic standard for documenting HTTP APIs. This standard is supported in minimal APIs through a combination of built-in APIs and open-source libraries. There are three key aspects to OpenAPI integration in an application:

  • Generating information about the endpoints in the app.
  • Gathering the information into a format that matches the OpenAPI schema.
  • Exposing the generated OpenAPI schema via a visual UI or a serialized file.
  • Minimal APIs provide built-in support for generating information about endpoints in an app via the Microsoft.AspNetCore.OpenApi package. Exposing the generated OpenAPI definition via a visual UI requires a third-party package.

    The following code is generated by the ASP.NET Core minimal web API template and uses OpenAPI:

    using Microsoft.AspNetCore.OpenApi;
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    var app = builder.Build();
    if (app.Environment.IsDevelopment())
        app.UseSwagger();
        app.UseSwaggerUI();
    app.UseHttpsRedirection();
    var summaries = new[]
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    app.MapGet("/weatherforecast", () =>
        var forecast = Enumerable.Range(1, 5).Select(index =>
            new WeatherForecast
                DateTime.Now.AddDays(index),
                Random.Shared.Next(-20, 55),
                summaries[Random.Shared.Next(summaries.Length)]
            .ToArray();
        return forecast;
    .WithName("GetWeatherForecast")
    .WithOpenApi();
    app.Run();
    internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    

    In the preceding highlighted code:

  • Microsoft.AspNetCore.OpenApi is explained in the next section.
  • AddEndpointsApiExplorer : Configures the app to use the API Explorer to discover and describe endpoints with default annotations. WithOpenApi overrides matching, default annotations generated by the API Explorer with those produced from the Microsoft.AspNetCore.OpenApi package.
  • UseSwaggeradds the Swagger middleware.
  • `UseSwaggerUI` enables an embedded version of the Swagger UI tool.
  • WithName: The IEndpointNameMetadata on the endpoint is used for link generation and is treated as the operation ID in the given endpoint's OpenAPI specification.
  • WithOpenApi is explained later in this article.
  • Microsoft.AspNetCore.OpenApi NuGet package

    ASP.NET Core provides the Microsoft.AspNetCore.OpenApi package to interact with OpenAPI specifications for endpoints. The package acts as a link between the OpenAPI models that are defined in the Microsoft.AspNetCore.OpenApi package and the endpoints that are defined in Minimal APIs. The package provides an API that examines an endpoint's parameters, responses, and metadata to construct an OpenAPI annotation type that is used to describe an endpoint.

    Microsoft.AspNetCore.OpenApi is added as a PackageReference to a project file:

    <Project Sdk="Microsoft.NET.Sdk.Web">
      <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
      <ItemGroup>    
        <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.*-*" />
        <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
      </ItemGroup>
    </Project>
    

    When using Swashbuckle.AspNetCore with Microsoft.AspNetCore.OpenApi, Swashbuckle.AspNetCore 6.4.0 or later must be used. Microsoft.OpenApi 1.4.3 or later must be used to leverage copy constructors in WithOpenApi invocations.

    Add OpenAPI annotations to endpoints via WithOpenApi

    Calling WithOpenApi on the endpoint adds to the endpoint's metadata. This metadata can be:

  • Consumed in third-party packages like Swashbuckle.AspNetCore.
  • Displayed in the Swagger user interface or in YAML or JSON generated to define the API.
  • app.MapPost("/todoitems/{id}", async (int id, Todo todo, TodoDb db) =>
        todo.Id = id;
        db.Todos.Add(todo);
        await db.SaveChangesAsync();
        return Results.Created($"/todoitems/{todo.Id}", todo);
    .WithOpenApi();
    

    Modify the OpenAPI annotation in WithOpenApi

    The WithOpenApi method accepts a function that can be used to modify the OpenAPI annotation. For example, in the following code, a description is added to the first parameter of the endpoint:

    app.MapPost("/todo2/{id}", async (int id, Todo todo, TodoDb db) =>
        todo.Id = id;
        db.Todos.Add(todo);
        await db.SaveChangesAsync();
        return Results.Created($"/todoitems/{todo.Id}", todo);
    .WithOpenApi(generatedOperation =>
        var parameter = generatedOperation.Parameters[0];
        parameter.Description = "The ID associated with the created Todo";
        return generatedOperation;
    

    Add operation IDs to OpenAPI

    Operation IDs are used to uniquely identify a given endpoint in OpenAPI. The WithName extension method can be used to set the operation ID used for a method.

    app.MapGet("/todoitems2", async (TodoDb db) =>
        await db.Todos.ToListAsync())
        .WithName("GetToDoItems");
    

    Alternatively, the OperationId property can be set directly on the OpenAPI annotation.

    app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
        .WithOpenApi(operation => new(operation)
            OperationId = "GetTodos"
    

    Add tags to the OpenAPI description

    OpenAPI supports using tag objects to categorize operations. These tags are typically used to group operations in the Swagger UI. These tags can be added to an operation by invoking the WithTags extension method on the endpoint with the desired tags.

    app.MapGet("/todoitems", async (TodoDb db) =>
        await db.Todos.ToListAsync())
        .WithTags("TodoGroup");
    

    Alternatively, the list of OpenApiTags can be set on the OpenAPI annotation via the WithOpenApi extension method.

    app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
        .WithOpenApi(operation => new(operation)
            Tags = new List<OpenApiTag> { new() { Name = "Todos" } }
    

    Add endpoint summary or description

    The endpoint summary and description can be added by invoking the WithOpenApi extension method. In the following code, the summaries are set directly on the OpenAPI annotation.

    app.MapGet("/todoitems2", async (TodoDb db) => await db.Todos.ToListAsync())
        .WithOpenApi(operation => new(operation)
            Summary = "This is a summary",
            Description = "This is a description"
    

    Exclude OpenAPI description

    In the following sample, the /skipme endpoint is excluded from generating an OpenAPI description:

    using Microsoft.AspNetCore.OpenApi;
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    var app = builder.Build();
    if (app.Environment.IsDevelopment())
        app.UseSwagger();
        app.UseSwaggerUI();
    app.UseHttpsRedirection();
    app.MapGet("/swag", () => "Hello Swagger!")
        .WithOpenApi();
    app.MapGet("/skipme", () => "Skipping Swagger.")
                        .ExcludeFromDescription();
    app.Run();
    

    Mark an API as obsolete

    To mark an endpoint as obsolete, set the Deprecated property on the OpenAPI annotation.

    app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
        .WithOpenApi(operation => new(operation)
            Deprecated = true
    

    Describe response types

    OpenAPI supports providing a description of the responses returned from an API. Minimal APIs support three strategies for setting the response type of an endpoint:

  • Via the Produces extension method on the endpoint
  • Via the ProducesResponseType attribute on the route handler
  • By returning TypedResults from the route handler
  • The Produces extension method can be used to add Produces metadata to an endpoint. When no parameters are provided, the extension method populates metadata for the targeted type under a 200 status code and an application/json content type.

    .MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync()) .Produces<IList<Todo>>();

    Using TypedResults in the implementation of an endpoint's route handler automatically includes the response type metadata for the endpoint. For example, the following code automatically annotates the endpoint with a response under the 200 status code with an application/json content type.

    app.MapGet("/todos", async (TodoDb db) =>
        var todos = await db.Todos.ToListAsync());
        return TypedResults.Ok(todos);
    

    Set responses for ProblemDetails

    When setting the response type for endpoints that may return a ProblemDetails response, the ProducesProblem extension method or TypedResults.Problem can be used to add the appropriate annotation to the endpoint's metadata.

    When there are no explicit annotations provided by one of the strategies above, the framework attempts to determine a default response type by examining the signature of the response. This default response is populated under the 200 status code in the OpenAPI definition.

    Multiple response types

    If an endpoint can return different response types in different scenarios, you can provide metadata in the following ways:

  • Call the Produces extension method multiple times, as shown in the following example:

    app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
             await db.Todos.FindAsync(id) 
             is Todo todo
             ? Results.Ok(todo) 
             : Results.NotFound())
       .Produces<Todo>(StatusCodes.Status200OK)
       .Produces(StatusCodes.Status404NotFound);
    
  • Use Results<TResult1,TResult2,TResultN> in the signature and TypedResults in the body of the handler, as shown in the following example:

    app.MapGet("/book{id}", Results<Ok<Book>, NotFound> (int id, List<Book> bookList) =>
        return bookList.FirstOrDefault((i) => i.Id == id) is Book book
         ? TypedResults.Ok(book)
         : TypedResults.NotFound();
    

    The Results<TResult1,TResult2,TResultN> union types declare that a route handler returns multiple IResult-implementing concrete types, and any of those types that implement IEndpointMetadataProvider will contribute to the endpoint’s metadata.

    The union types implement implicit cast operators. These operators enable the compiler to automatically convert the types specified in the generic arguments to an instance of the union type. This capability has the added benefit of providing compile-time checking that a route handler only returns the results that it declares it does. Attempting to return a type that isn't declared as one of the generic arguments to Results<TResult1,TResult2,TResultN> results in a compilation error.

    Describe request body and parameters

    In addition to describing the types that are returned by an endpoint, OpenAPI also supports annotating the inputs that are consumed by an API. These inputs fall into two categories:

  • Parameters that appear in the path, query string, headers, or cookies
  • Data transmitted as part of the request body
  • The framework infers the types for request parameters in the path, query, and header string automatically based on the signature of the route handler.

    To define the type of inputs transmitted as the request body, configure the properties by using the Accepts extension method to define the object type and content type that are expected by the request handler. In the following example, the endpoint accepts a Todo object in the request body with an expected content-type of application/xml.

    app.MapPost("/todos/{id}", (int id, Todo todo) => ...)
      .Accepts<Todo>("application/xml");
    

    In addition to the Accepts extension method, A parameter type can describe its own annotation by implementing the IEndpointParameterMetadataProvider interface. For example, the following Todo type adds an annotation that requires a request body with an application/xml content-type.

    public class Todo : IEndpointParameterMetadataProvider
        public static void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder)
            builder.Metadata.Add(new ConsumesAttribute(typeof(Todo), isOptional: false, "application/xml"));
    

    When no explicit annotation is provided, the framework attempts to determine the default request type if there's a request body parameter in the endpoint handler. The inference uses the following heuristics to produce the annotation:

  • Request body parameters that are read from a form via the [FromForm] attribute are described with the multipart/form-data content-type.
  • All other request body parameters are described with the application/json content-type.
  • The request body is treated as optional if it's nullable or if the AllowEmpty property is set on the FromBody attribute.
  • Support API versioning

    Minimal APIs support API versioning via the Asp.Versioning.Http package. Examples of configuring versioning with minimal APIs can be found in the API versioning repo.

    ASP.NET Core OpenAPI source code on GitHub

  • WithOpenApi
  • OpenApiGenerator
  • Additional Resources

  • Authentication and authorization in minimal APIs
  • An app can describe the OpenAPI specification for route handlers using Swashbuckle.

    The following code is a typical ASP.NET Core app with OpenAPI support:

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen(c =>
        c.SwaggerDoc("v1", new() { Title = builder.Environment.ApplicationName,
                                   Version = "v1" });
    var app = builder.Build();
    if (app.Environment.IsDevelopment())
        app.UseSwagger(); // UseSwaggerUI Protected by if (env.IsDevelopment())
        app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
                                        $"{builder.Environment.ApplicationName} v1"));
    app.MapGet("/swag", () => "Hello Swagger!");
    app.Run();
    

    Exclude OpenAPI description

    In the following sample, the /skipme endpoint is excluded from generating an OpenAPI description:

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    var app = builder.Build();
    if (app.Environment.IsDevelopment())
        app.UseSwagger();
        app.UseSwaggerUI(); // UseSwaggerUI Protected by if (env.IsDevelopment())
    app.MapGet("/swag", () => "Hello Swagger!");
    app.MapGet("/skipme", () => "Skipping Swagger.")
                        .ExcludeFromDescription();
    app.Run();
    

    Describe response types

    The following example uses the built-in result types to customize the response:

    app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
             await db.Todos.FindAsync(id) 
             is Todo todo
             ? Results.Ok(todo) 
             : Results.NotFound())
       .Produces<Todo>(StatusCodes.Status200OK)
       .Produces(StatusCodes.Status404NotFound);
    

    Add operation ids to OpenAPI

    app.MapGet("/todoitems2", async (TodoDb db) =>
        await db.Todos.ToListAsync())
        .WithName("GetToDoItems");
    

    Add tags to the OpenAPI description

    The following code uses an OpenAPI grouping tag:

    app.MapGet("/todoitems", async (TodoDb db) =>
        await db.Todos.ToListAsync())
        .WithTags("TodoGroup");
    			Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see: https://aka.ms/ContentUserFeedback.

    Submit and view feedback for

    This product
  •