Learnixo
Back to blog
AI Systemsintermediate

Scalar API Docs — Replacing Swagger With a Modern Developer Experience

How to configure Scalar as the API documentation UI in a .NET Clean Architecture project, why it replaces Swagger/Swashbuckle, and how to annotate endpoints for meaningful API documentation.

Asma Hafeez KhanMay 16, 20264 min read
Clean Architecture.NETScalarAPI DocumentationOpenAPI
Share:𝕏

Why Scalar Over Swagger UI

Scalar is a modern OpenAPI documentation UI that wraps the same OpenAPI spec that Swagger uses but provides a better developer experience:

Scalar vs Swagger UI:
  ✓ Modern, readable layout — collapsible, searchable, syntax-highlighted
  ✓ Built-in API client (send requests without copy-pasting curl)
  ✓ Multiple theming options out of the box
  ✓ First-class support for .NET's built-in OpenAPI (`Microsoft.AspNetCore.OpenApi`)
  ✓ No Swashbuckle dependency — lighter, actively maintained
  ✓ Works with the .NET 9+ `AddOpenApi()` built-in

Installation

XML
<!-- Api.csproj -->
<PackageReference Include="Scalar.AspNetCore" Version="2.*" />

.NET 9+ includes built-in OpenAPI generation — no Swashbuckle required.


Minimal Setup

C#
// Api/Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();   // .NET 9+ built-in OpenAPI
// ... other services

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();               // exposes /openapi/v1.json
    app.MapScalarApiReference();    // exposes /scalar/v1 UI
}

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

With this in place, navigate to /scalar/v1 in development to see the full interactive API reference.


Customizing the Scalar UI

C#
// Api/Program.cs
app.MapScalarApiReference(options =>
{
    options.Title       = "SystemForge API";
    options.Theme       = ScalarTheme.Purple;
    options.DefaultHttpClient = new(ScalarTarget.CSharp, ScalarClient.HttpClient);
    options.Authentication = new ScalarAuthenticationOptions
    {
        PreferredSecurityScheme = "Bearer",
    };
});

OpenAPI Metadata on Controllers

C#
// Api/Controllers/PatientsController.cs
using Microsoft.AspNetCore.Http;

[ApiController]
[Route("api/patients")]
[Authorize]
[Tags("Patients")]   // groups endpoints in Scalar sidebar
public sealed class PatientsController : ControllerBase
{
    [HttpPost]
    [Authorize(Roles = "Clinician,Admin")]
    [ProducesResponseType<object>(StatusCodes.Status201Created)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status409Conflict)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status422UnprocessableEntity)]
    [EndpointSummary("Register a new patient")]
    [EndpointDescription("Creates a patient record. MRN must be unique across the system.")]
    public async Task<IActionResult> Create(
        CreatePatientRequest request, CancellationToken ct)
    { ... }

    [HttpGet("{id:guid}")]
    [ProducesResponseType<PatientResponse>(StatusCodes.Status200OK)]
    [ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
    [EndpointSummary("Get patient by ID")]
    public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
    { ... }

    [HttpGet]
    [ProducesResponseType<PagedResult<PatientListItem>>(StatusCodes.Status200OK)]
    [EndpointSummary("List active patients")]
    [EndpointDescription("Returns paginated list. Supports optional search by name or MRN.")]
    public async Task<IActionResult> List(
        [FromQuery] string? search,
        [FromQuery] int page     = 1,
        [FromQuery] int pageSize = 20,
        CancellationToken ct     = default)
    { ... }
}

Configuring Authentication in the OpenAPI Spec

C#
// Api/OpenApi/AddSecuritySchemeTransformer.cs
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;

public sealed class AddSecuritySchemeTransformer : IOpenApiDocumentTransformer
{
    public Task TransformAsync(
        OpenApiDocument document,
        OpenApiDocumentTransformerContext context,
        CancellationToken ct)
    {
        document.Components ??= new OpenApiComponents();
        document.Components.SecuritySchemes["Bearer"] = new OpenApiSecurityScheme
        {
            Type         = SecuritySchemeType.Http,
            Scheme       = "bearer",
            BearerFormat = "JWT",
            Description  = "Enter your JWT access token.",
        };

        // Apply globally so all endpoints require auth by default
        document.SecurityRequirements.Add(new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id   = "Bearer",
                    }
                },
                []
            }
        });

        return Task.CompletedTask;
    }
}

// Registration
builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer<AddSecuritySchemeTransformer>();
});

Versioning (Optional)

C#
// If you add API versioning:
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1);
    options.ReportApiVersions = true;
    options.AssumeDefaultVersionWhenUnspecified = true;
}).AddApiExplorer(options =>
{
    options.GroupNameFormat           = "'v'V";
    options.SubstituteApiVersionInUrl = true;
});

// Multiple Scalar UIs, one per version
app.MapScalarApiReference("v1", options => options.Title = "SystemForge API v1");
app.MapScalarApiReference("v2", options => options.Title = "SystemForge API v2");

PRO TIP

Scalar respects the [ProducesResponseType] attributes. Document every non-200 response explicitly — including 400, 404, 409, and 422 — so API consumers know what to handle. An API that only documents 200 responses forces the frontend team to discover failures through trial and error in production.


Development vs Production

C#
// In production, Scalar should be disabled
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    app.MapScalarApiReference();
}
// If you want it in staging for QA, use an explicit environment check or feature flag

Production issue I've seen: A team deployed Scalar to production with no authentication. The full API schema — including all endpoint paths, all request/response shapes, and all error codes — was publicly accessible. This is an information disclosure vulnerability. Disable it in production or protect it with authentication.


Key Takeaway

Scalar is a drop-in replacement for Swagger UI with a better developer experience and no Swashbuckle dependency. In .NET 9+, AddOpenApi() is built-in. MapScalarApiReference() adds the UI in one line. Document every response type with [ProducesResponseType] and every endpoint with [EndpointSummary] — the documentation is the first thing a new developer sees when joining the team.

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.