.NET OpenAPI and Swagger Documentation

Learn .NET OpenAPI and Swagger Documentation with practical examples, explanations, best practices, and real-world coding techniques.

Imagine building an ASP.NET Core Web API that provides course information, user accounts, authentication, and coding challenges. Your Angular frontend needs to know which endpoints are available, what data each endpoint accepts, which responses it returns, and whether authentication is required.

Without API documentation, frontend developers may need to read backend source code or repeatedly ask the backend team how an endpoint behaves. As the API grows, undocumented changes can break integrations and make testing more difficult.

OpenAPI provides a machine-readable description of an HTTP API. It documents paths, HTTP methods, parameters, request bodies, response formats, security schemes, and other contract information. Tools can use this description to display documentation, generate client code, and support automated testing.

Swagger is the name associated with an ecosystem of tools for working with API descriptions. Swagger UI is a widely used interactive interface that presents API documentation and allows developers to try eligible requests directly from their browsers.

In modern .NET applications, you can generate OpenAPI documents using built-in ASP.NET Core support or packages such as Swashbuckle. Understanding the difference between the OpenAPI specification, document generation, and interactive documentation helps you choose the right tooling for your project.

1. What Is OpenAPI?

OpenAPI is a specification for describing HTTP APIs in a standardized format. An OpenAPI document can describe the endpoints an API exposes, the parameters those endpoints accept, the request bodies they consume, the responses they return, and the security requirements clients must satisfy.

An OpenAPI document is commonly represented as JSON or YAML. It acts as a contract between an API provider and its consumers, including frontend applications, mobile applications, automated tests, and other backend services.

For example, a course-management API might expose these operations:

  • GET /api/courses retrieves a collection of courses.
  • GET /api/courses/{id} retrieves a specific course.
  • POST /api/courses creates a course.
  • PUT /api/courses/{id} replaces a course representation.
  • DELETE /api/courses/{id} requests deletion of a course.

OpenAPI can describe these operations alongside their parameters, request schemas, success responses, error responses, and authentication requirements. This gives consumers a consistent description of the API without requiring them to inspect its implementation.

OpenAPI is a description of the API contract, not the API implementation itself. Generating a document does not automatically implement an endpoint, validate every business rule, or guarantee that the documentation accurately describes runtime behavior. The document must reflect the API that your application actually exposes.

2. OpenAPI vs. Swagger

OpenAPI and Swagger are related, but they are not interchangeable terms for exactly the same thing.

  • OpenAPI Specification: Defines the standard format used to describe HTTP APIs.
  • OpenAPI document: The JSON or YAML file that describes a particular API according to that specification.
  • OpenAPI generation: The process of producing the document from API metadata, endpoint declarations, and schema information.
  • Swagger UI: A browser-based interface for displaying API documentation and trying supported operations.
  • Swashbuckle.AspNetCore: A .NET package commonly used to generate OpenAPI-compatible Swagger documents and integrate Swagger UI into ASP.NET Core applications.

ASP.NET Core provides built-in OpenAPI document-generation support in modern framework versions. You can use that support to generate the document and add a separate interactive UI when required. Alternatively, Swashbuckle can provide document generation and Swagger UI through its own integration.

An important distinction is that built-in document generation does not automatically mean a Swagger UI page is available. The document endpoint and the interactive interface are separate components that must be configured as appropriate.

3. Enabling Built-In OpenAPI in ASP.NET Core

In modern ASP.NET Core projects, the built-in OpenAPI integration allows you to register document-generation services and expose an OpenAPI endpoint. This approach is available in ASP.NET Core 9 and later, subject to the packages and configuration used by the project.

First, ensure your project has the appropriate ASP.NET Core OpenAPI package for its target framework. Then configure OpenAPI in Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/api/courses", () =>
{
    return new[]
    {
        new { Id = 1, Title = "C# Fundamentals" },
        new { Id = 2, Title = "ASP.NET Core" }
    };
});

app.Run();

The important configuration methods are:

  • AddOpenApi() registers the services used to generate the OpenAPI document.
  • MapOpenApi() maps an endpoint that serves the generated document.
  • IsDevelopment() ensures that the example exposes the document endpoint only in the Development environment.

With the default document configuration, the generated document is commonly available at /openapi/v1.json. The exact endpoint can be changed through the OpenAPI mapping configuration.

After starting the application, open the configured OpenAPI URL in a browser or retrieve it using an HTTP client. You should see a structured JSON document describing the routes and supported operations that the generator discovers.

Exposing documentation only in Development is a common default, but it is not a universal security requirement. Some production APIs publish documentation intentionally for clients. If you expose documentation in production, review whether it reveals internal endpoints, schemas, or operational details that should remain private.

4. Generating Documentation for Minimal APIs

Minimal APIs define endpoints through route-mapping methods such as MapGet(), MapPost(), and MapDelete(). The OpenAPI generator can use endpoint metadata and supported request and response types to describe those operations.

Consider an endpoint that retrieves a course by its identifier:

using Microsoft.AspNetCore.Http.HttpResults;

public sealed record CourseDto(
    int Id,
    string Title);

app.MapGet(
    "/api/courses/{id:int}",
    Results<Ok<CourseDto>, NotFound> (
        int id) =>
    {
        if (id != 1)
        {
            return TypedResults.NotFound();
        }

        return TypedResults.Ok(
            new CourseDto(1, "C# Fundamentals"));
    })
    .WithName("GetCourseById")
    .WithSummary("Get a course by ID")
    .WithDescription(
        "Returns the requested course when it exists.");

This example uses typed results to express the possible success and not-found outcomes. The endpoint name, summary, and description add useful metadata to the API contract.

Descriptive metadata is helpful because a route such as /api/courses/{id} does not fully explain the endpoint's purpose, behavior, or expected results. Meaningful documentation improves discoverability for developers using generated API documentation.

The example uses in-memory sample data to focus on documentation. A real application would normally retrieve the course through an application service or data-access component.

To learn more about route handlers and typed results, see Minimal APIs in .NET.

5. Generating Documentation for Controller-Based APIs

OpenAPI is not limited to Minimal APIs. Controller-based ASP.NET Core applications can also generate API documentation from controller routes, HTTP method attributes, request models, response metadata, and other supported annotations.

For example, a controller can document its purpose and expected response types:

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/courses")]
[Produces("application/json")]
public sealed class CoursesController : ControllerBase
{
    [HttpGet("{id:int}", Name = "GetCourseById")]
    [ProducesResponseType(typeof(CourseDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public IActionResult GetById(int id)
    {
        if (id != 1)
        {
            return NotFound();
        }

        return Ok(new CourseDto(
            1,
            "C# Fundamentals"));
    }
}

public sealed record CourseDto(int Id, string Title);

The response metadata tells API documentation tools which status codes and response types the action is expected to produce. This is especially helpful when an endpoint has several outcomes that are not obvious from its method signature alone.

Attributes such as ProducesResponseType are metadata, not a replacement for correct runtime behavior. If the implementation starts returning a different response, update the contract accordingly and test the endpoint.

6. Adding Swagger UI

An OpenAPI JSON document is machine-readable, but developers may prefer an interactive browser interface that displays operations and provides controls for sending requests. Swagger UI is commonly used for this purpose.

One option is to use Swashbuckle.AspNetCore. Install the package in your ASP.NET Core project:

dotnet add package Swashbuckle.AspNetCore

A traditional Swashbuckle setup registers the Swagger generator and its middleware:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapGet("/api/courses", () =>
{
    return new[]
    {
        new { Id = 1, Title = "C# Fundamentals" },
        new { Id = 2, Title = "ASP.NET Core" }
    };
});

app.Run();

In this setup, AddSwaggerGen() registers document-generation services, UseSwagger() exposes the generated document, and UseSwaggerUI() enables the interactive interface.

The traditional Swashbuckle configuration commonly serves a document at /swagger/v1/swagger.json and the UI at /swagger. Exact routes can be configured. This is a separate configuration approach from the built-in AddOpenApi() and MapOpenApi() approach.

Choose either the built-in OpenAPI generation or a Swashbuckle document-generation setup according to your application's needs. Avoid registering multiple generators without a clear purpose, because it can create duplicate documents or confusing route configuration.

Swagger UI can send requests to your API, but that does not bypass its authentication, authorization, validation, or network-access rules. Requests still follow the behavior configured by your application.

7. Documenting Request Bodies and Response Types

Useful API documentation explains more than route names. It should tell consumers what they need to send, what they can expect in return, and which failure responses may occur.

For a course-creation operation, define an explicit request model:

using System.ComponentModel.DataAnnotations;

public sealed class CreateCourseRequest
{
    [Required]
    [StringLength(150, MinimumLength = 2)]
    public string Title { get; init; } = string.Empty;

    [Range(0, 100000)]
    public decimal Price { get; init; }
}

You can use that request model in a Minimal API endpoint:

app.MapPost(
    "/api/courses",
    (CreateCourseRequest request) =>
    {
        var response = new CourseDto(
            1,
            request.Title);

        return Results.Created(
            "/api/courses/1",
            response);
    })
    .WithName("CreateCourse")
    .WithSummary("Create a course")
    .Produces<CourseDto>(StatusCodes.Status201Created)
    .ProducesValidationProblem()
    .ProducesProblem(StatusCodes.Status500InternalServerError);

The endpoint illustrates how response metadata can describe a successful creation and possible error responses. Its implementation is intentionally simplified: it does not actually save a course to a database, and its validation behavior must be configured appropriately for the target framework and application.

Document important status codes deliberately. For example, a POST endpoint may return 201 Created after successfully creating a resource, while a request with invalid values may return 400 Bad Request. A request to a protected endpoint may produce 401 or 403 when authentication or authorization requirements are not satisfied.

Keep the documentation aligned with the actual validation and exception-handling behavior. Adding a metadata declaration does not automatically implement the declared response.

8. Documenting Authentication and Authorization

Many APIs expose public operations alongside endpoints that require authentication. The OpenAPI contract should communicate the security scheme and the operations that require credentials, so consumers understand how to authenticate requests.

For JWT bearer authentication, a common scheme uses the HTTP authorization header:

Authorization: Bearer YOUR_ACCESS_TOKEN

When using Swagger tooling, you can describe the HTTP bearer scheme in the OpenAPI document. For a traditional Swashbuckle setup, the configuration may look like this:

using Microsoft.OpenApi.Models;

builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition(
        "Bearer",
        new OpenApiSecurityScheme
        {
            Name = "Authorization",
            Type = SecuritySchemeType.Http,
            Scheme = "bearer",
            BearerFormat = "JWT",
            In = ParameterLocation.Header,
            Description =
                "Enter a valid access token."
        });

    options.AddSecurityRequirement(
        new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                    }
                },
                Array.Empty<string>()
            }
        });
});

This example uses Swashbuckle's conventional configuration APIs. Package and OpenAPI model API versions can differ, so match the example to the installed packages.

A security definition describes how authentication is represented in the document. A security requirement indicates where that scheme applies. Do not mark every operation as secured if your API contains public endpoints, and do not assume that documenting bearer authentication configures or validates JWTs at runtime.

Your ASP.NET Core application must independently configure authentication, validate tokens, and enforce authorization policies. Swagger UI is a client for exercising endpoints, not a replacement for application security.

9. Adding Descriptions, Tags, and Operation IDs

Clear operation names and descriptions make documentation easier to navigate, especially when an API contains dozens or hundreds of endpoints. Avoid generic descriptions that simply repeat the HTTP method or route.

Minimal APIs support endpoint metadata methods such as WithSummary(), WithDescription(), WithName(), and, where supported, tag-related methods:

var courses = app.MapGroup("/api/courses")
    .WithTags("Courses");

courses.MapGet("/", () =>
    Results.Ok("List published courses"))
    .WithName("GetCourses")
    .WithSummary("List published courses")
    .WithDescription(
        "Returns the courses available for browsing.");

courses.MapGet("/{id:int}", (int id) =>
    Results.Ok(new { Id = id }))
    .WithName("GetCourseById")
    .WithSummary("Get a course by ID");

Tags help group related operations in documentation tools. Stable, unique operation identifiers are important when using tooling that generates clients or references individual operations.

Descriptions should explain behavior and constraints that may not be evident from the route itself. For example, specify whether results are paginated, which filters are supported, whether the operation requires authentication, and what conditions can produce an error response.

10. API Versioning and OpenAPI Documents

APIs can evolve as application requirements change. A new response format, renamed property, removed operation, or changed parameter requirement may affect existing clients. API versioning helps manage such changes when backward compatibility cannot be maintained.

OpenAPI tooling can produce separate documents for supported API versions when versioning is configured appropriately. The exact setup depends on the versioning library, routing approach, and documentation generator.

When maintaining versioned API documentation:

  • Make the supported API versions clear to consumers.
  • Describe deprecated operations and planned replacement endpoints.
  • Preserve backward compatibility when older clients must continue to work.
  • Ensure each generated document describes the intended version rather than mixing incompatible contracts.
  • Test version-specific routes and response schemas.

Versioning is not simply changing a label in Swagger UI. Your routing, API behavior, compatibility strategy, and documentation must all agree about which version a client is using.

11. OpenAPI in Continuous Integration

API documentation is most useful when it remains synchronized with the application. If the generated document changes unexpectedly or an endpoint loses an important response definition, the difference may reveal a breaking change or an incomplete API contract.

Consider generating an OpenAPI document during CI and reviewing changes alongside code changes. Depending on the tooling and workflow, you can save the generated document as an artifact, validate its structure, compare contract versions, or use it to generate client code.

A practical workflow can include:

  1. Restore dependencies and build the API project.
  2. Run unit and integration tests.
  3. Generate the OpenAPI document using your chosen tooling.
  4. Check that the document is valid and contains the expected operations.
  5. Review incompatible contract changes before release.
  6. Publish the approved document alongside the corresponding API release.

Treating OpenAPI as a release artifact makes integration more predictable for frontend developers and external consumers. It also helps teams identify accidental API changes before they reach production.

12. Protecting API Documentation in Production

Development environments commonly expose documentation pages for convenience. Production environments require an explicit decision about what documentation should be publicly available.

Public documentation can be useful for APIs intended for external developers. Internal APIs may need documentation available only through authenticated access, a private network, or an internal developer portal.

Consider the following precautions:

  • Do not publish secrets, access tokens, connection strings, or private credentials in descriptions or examples.
  • Avoid exposing internal administrative endpoints to audiences that should not know about them.
  • Keep the document's security metadata aligned with the actual authorization configuration.
  • Do not rely on hidden Swagger UI as a security mechanism; enforce authorization in the API itself.
  • Use safe example data and avoid including real customer information.
  • Decide deliberately whether the OpenAPI document and interactive interface should be publicly accessible.

A publicly available OpenAPI document does not inherently make an API insecure. Security depends on the information it reveals, the audience that needs access, and the enforcement of authentication and authorization on the actual endpoints.

13. Common OpenAPI and Swagger Mistakes

Assuming OpenAPI automatically provides Swagger UI

OpenAPI generation produces a machine-readable contract. An interactive UI is a separate tool that must be installed and configured when required.

Documenting only successful responses

Consumers also need to understand validation failures, missing resources, authorization errors, and other expected outcomes. Document the important response types and status codes.

Publishing outdated or inaccurate contracts

Documentation can become misleading when endpoint behavior changes without a corresponding contract update. Generate documents from the current application and review changes during the release process.

Confusing security metadata with runtime security

Declaring a bearer security scheme does not configure JWT validation or enforce authorization. Configure those protections separately in ASP.NET Core.

Mixing several generators without a plan

Multiple OpenAPI generators or UI configurations can produce conflicting endpoints and confusing documents. Choose a clear generation strategy and configure the intended document routes.

Treating example responses as implemented behavior

Response metadata describes a contract; it does not implement persistence, validation, or business rules. Test that each documented outcome matches actual runtime behavior.

14. Best Practices for OpenAPI Documentation

  • Choose built-in ASP.NET Core OpenAPI support or a compatible Swagger-generation tool according to project requirements.
  • Document endpoint names, summaries, parameters, request schemas, response schemas, and expected status codes.
  • Use explicit request and response DTOs where they clarify the API contract.
  • Document authentication schemes accurately and enforce security independently at runtime.
  • Keep descriptions concise, specific, and useful to API consumers.
  • Use stable operation IDs when required by client-generation tooling.
  • Generate and validate documentation during CI or release preparation.
  • Review contract changes that may break existing clients.
  • Protect sensitive documentation and example data when publishing the specification.
  • Verify that generated documentation matches the real behavior of the deployed API.

Conclusion

OpenAPI gives .NET teams a standardized way to describe their HTTP APIs, while Swagger UI provides an interactive way to explore and test supported operations. Modern ASP.NET Core applications can generate OpenAPI documents through built-in support or compatible third-party tools.

Good API documentation describes actual endpoint behavior, including parameters, request bodies, possible responses, and security requirements. Generating a document is only the beginning: keep it synchronized with code, review changes, and test the contract as part of your development workflow.

Continue learning with Minimal APIs in .NET to learn how to define route handlers, REST API Design Best Practices to design consistent endpoints, AppSettings and Configuration to manage environment-specific settings, and .NET Development Best Practices to build maintainable applications.

Code Examples

# Add Swashbuckle to an ASP.NET Core project
dotnet add package Swashbuckle.AspNetCore

# Restore project dependencies
dotnet restore

# Build the API
dotnet build -c Release
    
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/api/courses", () =>
    new[]
    {
        new { Id = 1, Title = "C# Fundamentals" },
        new { Id = 2, Title = "ASP.NET Core" }
    })
    .WithName("GetCourses")
    .WithSummary("List available courses")
    .WithDescription(
        "Returns the available courses.");

app.Run();

// With the default document configuration,
// the OpenAPI document is commonly served at
// /openapi/v1.json.
    
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapGet("/api/courses", () =>
    Results.Ok(new[]
    {
        new { Id = 1, Title = "C# Fundamentals" },
        new { Id = 2, Title = "ASP.NET Core" }
    }));

app.Run();

// The conventional Swagger UI route is /swagger.
// This setup uses Swashbuckle's document generation.
// It is an alternative to the built-in AddOpenApi setup.
    
app.MapGet(
    "/api/courses/{id:int}",
    (int id) =>
    {
        if (id != 1)
        {
            return Results.NotFound();
        }

        return Results.Ok(
            new CourseDto(1, "C# Fundamentals"));
    })
    .WithName("GetCourseById")
    .WithSummary("Get a course by ID")
    .WithDescription(
        "Returns a course or a not-found response.")
    .Produces<CourseDto>(StatusCodes.Status200OK)
    .Produces(StatusCodes.Status404NotFound);

public sealed record CourseDto(int Id, string Title);
    
{
  "openapi": "3.0.3",
  "info": {
    "title": "Courses API",
    "version": "v1",
    "description": "API for retrieving course information."
  },
  "paths": {
    "/api/courses/{id}": {
      "get": {
        "operationId": "GetCourseById",
        "summary": "Get a course by ID",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Course found"
          },
          "404": {
            "description": "Course not found"
          }
        }
      }
    }
  }
}

// This is a simplified illustrative fragment,
// not a complete generated OpenAPI document.
    

Pro Tip

Treat the OpenAPI document as part of your public or internal API contract. Document success and error responses, keep authentication metadata accurate, generate and validate the document during CI, and review changes that might break existing clients. Remember that generating OpenAPI does not automatically add Swagger UI or secure your endpoints.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.