A Guide to API Versioning: Comparing URI, Header, and Query Parameters


When you build an API, you'll eventually encounter this question: How do I handle versioning?
Versioning allows you to update your API without disrupting existing clients. Think of it this way: if your API is a contract, versioning is your way of saying, "I'm making changes, but here's how you can keep using the old version if needed."
There are several ways to version an API, but the three most common methods are:
Using the URI (or URL path)
Using a Header
Using Query Parameters
Let's go through each one.
๐น 1. URI Versioning (a.k.a Path Versioning)
Request:
GET /api/v1/users
Example Controller:
csharpCopyEdit[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetV1() => Ok(new { version = "v1", users = new[] { "Alice", "Bob" } });
}
โ Pros:
Very clear: You can immediately see the version from the URL.
Easy to manage in routing: Frameworks often make it easy to handle versioned routes.
Simple for clients: No need to mess with headers or query strings.
โ Cons:
Breaks RESTful purity a bit โ the resource path (
/users
) technically hasnโt changed, only the version.Might lead to duplicate code if you donโt manage your controllers or handlers well.
๐น 2. Header Versioning
Request:
GET /api/users
Header: X-API-Version: 2.0
Example Controller:
csharpCopyEdit[ApiController]
[Route("api/[controller]")]
[ApiVersion("2.0")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetV2() => Ok(new { version = "v2", users = new[] { "Alice", "Bob", "Charlie" } });
}
The route remains the same (/api/users
), but the version is selected from the header.
โ Pros:
Clean URL: Keeps the path nice and tidy.
More flexible: You can version by resource or operation without changing the URL.
โ Cons:
Hidden from users: Not as obvious unless you inspect the headers.
More complex for clients: Especially for front-end devs using tools like
fetch
oraxios
, they need to manually set headers.Might be harder to cache at CDN or proxy level (depending on your setup).
๐น 3. Query Parameter Versioning
Example:
GET /api/users?version=3.0
Example Controller:
csharpCopyEdit[ApiController]
[Route("api/[controller]")]
[ApiVersion("3.0")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetV3() => Ok(new { version = "v3", users = new[] { "Alice", "Bob", "Charlie", "Diana" } });
}
You can access the correct version by sending ?version=3.0
in the query string.
โ Pros:
Easy to try out: You can test versions directly in the browser or with simple tools.
Flexible: Can quickly switch versions during testing.
โ Cons:
Less RESTful: Versioning is a concern of API behavior, not data filtering โ query params are usually for filtering/sorting/searching.
Also less visible in API docs and routing logic.
โ Tip: Multiple Versions in One Controller
You can even have all versions in the same controller using [MapToApiVersion]
:
csharpCopyEdit[ApiController]
[Route("api/users")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class UsersController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public IActionResult GetV1() => Ok(new { version = "v1", users = new[] { "Alice", "Bob" } });
[HttpGet]
[MapToApiVersion("2.0")]
public IActionResult GetV2() => Ok(new { version = "v2", users = new[] { "Alice", "Bob", "Charlie" } });
}
๐ง So... Which One Should You Use?
Thereโs no one-size-fits-all answer. But hereโs a quick rule of thumb:
Use Case | Recommended Strategy |
Public APIs | URI versioning |
Internal APIs (microservices) | Header versioning |
Rapid prototyping/testing | Query params (but donโt keep them long-term) |
If you want the simplest approach to start, URI versioning is a good bet. Itโs easy to understand, easy to implement, and widely accepted.
๐ Visual Comparison
Hereโs a quick visual summary to compare the strategies:
Feature | URI Versioning | Header Versioning | Query Param Versioning |
๐ Visibility | High (easy to see) | Low (hidden in headers) | Medium (in URL) |
๐ Simplicity | Easy to implement | Slightly complex | Easy |
๐งผ Clean URLs | No | Yes | No |
๐ CDN Caching | Easy | Can be tricky | Depends |
๐งช Testing Friendly | Yes | No (needs custom headers) | Yes |
๐ง RESTful Compliance | Medium | High | Low |
๐ Final Thoughts
API versioning is about planning for the future. Even if your API only has one version today, thinking ahead will save you from breaking your clients tomorrow.
Keep it simple. Communicate changes clearly. And choose the versioning style that matches your audience and infrastructure.
Let your API grow โ but grow gracefully.
Subscribe to my newsletter
Read articles from Dewa Mahendra directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Dewa Mahendra
Dewa Mahendra
I'm a highly motivated and experienced developer expertise in leveraging the power of .NET Core Technology. Currently collaborating with an Australian company based in Nusa Dua, Bali, Indonesia, to deliver innovative application development services that push the boundaries of what technology can achieve, and also contribute to the ever-evolving landscape of the global IT industry