DEV Community

Giannis Georgopoulos
Giannis Georgopoulos

Posted on • Originally published at eventuallyconsistent.xyz

A Professional Looking API

Your OpenAPI spec is the contract that defines how consumers interact with your API. It powers visual tools that help developers understand, test, and integrate with your endpoints.

This spec powers visual tools that help developers understand, test, and integrate with your API. While Swagger UI has been the go-to for years, modern alternatives like Scalar are raising the bar with cleaner UX, better search, and more intuitive navigation. For this post, we’ll be using Scalar to showcase how a well-designed OpenAPI spec gives your API a professional, developer-friendly front.

💡 Hint: Starting in .NET 9, there’s a built-in way to generate the OpenAPI spec using builder.Services.AddOpenApi(). However, as of now, it does not support XML comments, which are essential for rich and descriptive documentation. This makes it a no-go for production APIs that prioritize clarity. It is supported however in .NET 10 and you can see an example here.

In Part 1, we cleaned up a flawed UpdateOrder endpoint and replaced it with clear, task-based actions like SetShippingInfo. Now in Part 2, we’ll visualize the impact of those changes in our OpenAPI docs. You’ll see how thoughtful design decisions translate into:

  • Cleaner, more understandable docs

  • Easier client integration

  • Fewer support questions

  • And a stronger impression of your API as production-grade

Let’s make your OpenAPI spec do more than just validate — let’s make it teach, guide, and inspire confidence.

Visualizing the Difference: v1 vs v2

Let’s bring our changes to life by exposing two versions of the same endpoint:

  • Version 1 (v1) shows the original, flawed UpdateOrder design

  • Version 2 (v2) presents the refactored, task-based SetShippingInfo approach

This not only helps us compare the before/after in Swagger/Scalar — it also gives us a chance to introduce API versioning, a crucial concept for long-term maintainability.

Setting Up Versioning and Swagger

We’ll use URL-based versioning (/api/v1/..., /api/v2/...) since it’s the most straightforward to demonstrate in OpenAPI docs and widely supported by tools like Scalar.

First, install the necessary NuGet packages:

# API versioning for controllers
dotnet add package Asp.Versioning.Mvc 

# Adds support for exposing versioned API docs
dotnet add package Asp.Versioning.Mvc.ApiExplorer 

# Swagger/OpenAPI generation
dotnet add package Swashbuckle.AspNetCore

# Adds support for example responses in Swagger, we will use it to provide examples for `ProblemDetails` responses
dotnet add package Swashbuckle.AspNetCore.Filters

# Optional: Use Scalar instead of Swagger UI for a better experience
dotnet add package Scalar.AspNetCore

Enter fullscreen mode Exit fullscreen mode

In your Program.cs:

using Asp.Versioning;  
using BloggingExamples;  
using Scalar.AspNetCore;  
using Swashbuckle.AspNetCore.Filters;  

var builder = WebApplication.CreateBuilder(args);  

builder.Services  
    .AddSwaggerGen()  
    .ConfigureOptions<ConfigureSwaggerOptions>();  

builder.Services.AddSwaggerExamplesFromAssemblyOf<NotFoundProblemDetailsExample>();  

builder.Services.AddApiVersioning(options => // adds api versioning and configures the strategy  
    {  
        options.DefaultApiVersion = new ApiVersion(1, 0);  
        options.ReportApiVersions = true;  
        options.ApiVersionReader = new UrlSegmentApiVersionReader();  
    })    .AddMvc() // add support for versioning controllers   
.AddApiExplorer(options =>  
    {  
        options.GroupNameFormat = "'v'VVV"; // e.g., v1  
        options.SubstituteApiVersionInUrl = true;  
    });  
builder.Services.AddControllers();  

var app = builder.Build();  

app.UseSwagger();  

app.MapControllers();  
app.MapScalarApiReference(x =>  
{  
        x.OpenApiRoutePattern = "/swagger/{documentName}/swagger.json";  
    x.AddDocument("v1");  
    x.AddDocument("v2");  
});  

await app.RunAsync();
Enter fullscreen mode Exit fullscreen mode

In OrderController.cs:

[ApiVersion("1")]  
[ApiVersion("2")]  
[ApiController]  
[Route("api/v{v:apiVersion}/[controller]")]  
public class OrderController(OrderService orderService) : ControllerBase  
{  
    [MapToApiVersion("1")]  
    [HttpPost("updateOrder")]  
    public async Task<IActionResult> UpdateOrder([FromBody] OrderDto order)  
    {        var updatedOrder = await orderService.Update(order);  

        return Ok(updatedOrder);  
    }


    [MapToApiVersion("2")]  
    [HttpPatch("{orderId}/shipping")]  
    [ProducesResponseType(StatusCodes.Status204NoContent)]  
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]  
    [ProducesResponseType(StatusCodes.Status404NotFound)]  
    public async Task<IActionResult> SetShippingInfo(Guid orderId, SetShippingInfoRequest request)  
    {        await orderService.SetShippingInfo(orderId, request);  
        return NoContent();  
    }}
Enter fullscreen mode Exit fullscreen mode

Now, if you launch your API and visit /swagger, you’ll see both v1 and v2 grouped cleanly — and instantly appreciate the differences:

Let's take a look at v1 first.

scalar-v1

This is functional, but far from intuitive:

  • No example values provided, everything is nullable abd you’re left guessing how to structure your request

  • Errors like 400 Bad Request aren’t documented, leaving you blind to what might go wrong

  • No clear separation between request and response — just a generic 200 OK

  • It works, but it feels unpolished and leaves the developer with more questions than answers

Now lets take a look at v2 and compare them:

scalar-v2

In v2 we can see:

  • Ready-to-run example values make it easy to try out the endpoint without guesswork

  • All possible status codes are clearly documented, along with explanations

  • A structured ProblemDetails schema shows exactly how errors are returned

  • Clear separation between request and response models

  • The UI communicates confidence — this feels like a production-grade API

Wrapping Up

Improving your OpenAPI spec isn’t just about aesthetics — it’s about creating a developer experience that feels reliable, guided, and easy to integrate with.

By versioning your API and refining your endpoints, you instantly elevate the quality of your documentation. It becomes easier to test, easier to onboard new consumers, and easier to maintain over time.

In this post, we saw how small changes — like switching from a vague UpdateOrder to a specific SetShippingInfo — dramatically improve how your API looks and feels in tools like Scalar.

If you enjoyed this article visit my blog for more.

Top comments (0)