Add API Versioning using URL Versioning in .NET

Today in this article, we shall learn how to enable or add API Versioning using URL Versioning in .NET. In this type of versioning technique, you add a version number to the URI for each resource.
Updated October 2026: a troubleshooting section on readers and defaults, a Minimal API, deprecation and OpenAPI section, an FAQ, and .NET 10 package updates.
Existing URIs continue to operate as per contract, returning resources that conform to the original schema.
Using the version specified in the API route the REST API allows accepting the request in any format like JSON or XML. The version specified in each URL URL gives the resource of different types as required by the client.
Currently, the parameters used in media types for content negotiation can be content in detail to derive the API versioning on the server-side.
As we understood in our last article Change in API is inevitable and API versioning comes to the rescue to protect your API from any breaking changes or changes in resources for any new requirements more importantly without breaking any existing clients.
However, as I understood, ASP.NET Core has no extensive built-in API versioning, so you add the Asp.Versioning packages.
Today in this article, we will cover below aspects,
For more detail on other API versioning techniques, please please visit the below article,
API Versioning using URL Versioning
Getting started
Let’s create ASP.NET Core API using ASP.NET Core on .NET 10 (the current LTS release),

Please install below NuGet package,
PM> Install-Package Microsoft.AspNetCore.Mvc.Versioning -Version 4.1.1
Or
Install from NuGet Package Manager,

Please update the ConfigureServices method to add the MediaTypeApiVersionReader service to the service collection as below,
Step1
In this type of versioning technique, you add a version number to the URI for each resource.
Existing URIs continue to operate as per contract, returning resources that conform to the original schema.
services.AddApiVersioning(option =>
{
option.DefaultApiVersion = new ApiVersion(1, 0);
option.AssumeDefaultVersionWhenUnspecified = true;
option.ApiVersionReader = new UrlSegmentApiVersionReader();
});
Step2
Register API URL versioning by explicit opt-in as below,

Assign annotation [ApiVersion] at the Controller/Method level and then use any of the versioning techniques as enabled in the ConfigureServices method.
Here is the c Please see below the complete code,
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[ApiVersion("3.0")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
[MapToApiVersion("1.0")]
public IEnumerable<WeatherForecast> GetWatherUSA()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
[HttpGet]
[MapToApiVersion("2.0")]
public IEnumerable<WeatherForecastNew> GetWeatherUK()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecastNew
{
DateOfForcast = DateTime.Now.AddDays(index),
Temperature = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
[HttpGet]
[MapToApiVersion("3.0")]
public IEnumerable<WeatherForecastIndia> GetWeatherIndia()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecastIndia
{
ForcastDay = DateTime.Now.AddDays(index),
Temperature = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
Release API as 1.0

Release API as 2.0

Above we are able to add a version number to the URL itself and able to fetch the required resources.
Let’s see the v1 version execution,
So here V1.0 produces the below JSON result,
{
"dateOfForcast": "2020-05-08T18:10:03.1268402-04:00",
"temperature": 22,
"temperatureFerehnite": 71,
"summary": "Bracing"
}
Let’s try executing V2.0 version,
{
"date": "2020-05-08T18:11:00.1929376-04:00",
"temperatureC": 32,
"temperatureF": 89,
"summary": "Bracing"
}
Why Your Versioned Route Returns 404 or UnsupportedApiVersion
The Asp.Versioning library reads the version from the query string (?api-version=1) by default. A route like api/v{version:apiVersion}/[controller] only works once you set UrlSegmentApiVersionReader inside AddApiVersioning, so the library looks in the path instead.
In plain English: A version reader is the piece that decides where the server looks for the version number. Think of a mail room clerk who checks only the envelope. If the number is written on the letter inside, they’ll never see it.
How do the route, [ApiVersion] and [MapToApiVersion] fit together?
The route template supplies the version value, [ApiVersion] on the class declares which versions the controller supports, and [MapToApiVersion] pins one action to a single version. If the three disagree, the request fails.
Setting DefaultApiVersion to 1.0 with AssumeDefaultVersionWhenUnspecified = true maps unversioned requests to the default. That’s useful when you add versioning to an established API, because existing clients keep working. It can also hide client mistakes, since a forgotten version silently returns v1. Skip it when the version is mandatory in the path, because the route template wouldn’t match without a version anyway.
What should you check when you get a 404 or UnsupportedApiVersion?
Work through this list in order. Most failures come from the first two items.
- Wrong reader: The route has a version segment but the reader is still the default query string reader.
- Missing or mismatched [ApiVersion]: The attribute is absent, or it doesn’t include the version in the URL. A request to
v3fails if you only declared 1.0 and 2.0. - Version not declared on the controller or group: An action has
[MapToApiVersion], but the class doesn’t list that version. In Minimal APIs, the group is missingHasApiVersion. - Format mismatch: The template already contains the literal
v, so the URL value is1or1.0, notv1. Also remember that 1.1 is a different version from 1.0.
Which NuGet package do you need?
Install Asp.Versioning.Mvc for controllers and Asp.Versioning.Http for Minimal APIs. Add Asp.Versioning.Mvc.ApiExplorer when you want versioned API descriptions for documentation. These packages replace the older Microsoft.AspNetCore.Mvc.Versioning package used earlier in this article, so use the new names on current .NET.
How do you avoid copy-pasting controllers per version?
Keep v1 and v2 thin and let them share services and domain logic. Give each version its own DTOs, so a response change in v2 never leaks into v1. Duplicated controllers drift apart, and a bug fixed in one version stays alive in the other.
Add QueryString API Versioning
For more details visit- Add QueryString API Versioning
Header/Media API versioning in .NET
For more details visit: Header/Media API versioning in .NET
References :
Do you have any comments or ideas or any better suggestions to share?
Please sound off your comments below.
Happy Coding !!
Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published (2026) best practices and guidelines for software design and development.
Frequently asked questions
Which NuGet package do I need for API versioning in .NET: Asp.Versioning.Http or Asp.Versioning.Mvc?
Use Asp.Versioning.Mvc for controller-based APIs and Asp.Versioning.Http for Minimal APIs. Add Asp.Versioning.Mvc.ApiExplorer for versioned API descriptions and Asp.Versioning.OpenApi to connect versioning to the built-in OpenAPI generator. These packages were formerly published as Microsoft.AspNetCore.Mvc.Versioning, which is the package name the steps above use, so new projects should install the newer names instead.
Why does my /api/v1 route return 404 or UnsupportedApiVersion?
The default reader looks in the query string, so set options.ApiVersionReader = new UrlSegmentApiVersionReader() inside AddApiVersioning. Then check that the route template uses {version:apiVersion} and that [ApiVersion] or HasApiVersion declares the same version as the URL. Also make sure you haven’t mixed v1 in the URL with 1.0 in the declaration, or the other way round.
Should I set AssumeDefaultVersionWhenUnspecified to true with URL versioning?
Set it to true with a DefaultApiVersion of 1.0 only when you add versioning to an existing API whose unversioned URLs must keep working. If the version is mandatory in the path, leave it off. Then a missing or mistyped version fails visibly instead of silently hitting v1.
How do I mark an API version as deprecated and tell clients?
Use HasDeprecatedApiVersion on a Minimal API group, or Deprecated = true on [ApiVersion] for controllers, and set ReportApiVersions = true. The api-supported-versions and api-deprecated-versions response headers then tell clients which versions are live and which are being retired.