Skip to content
Back to skills

Dotnet Api Versioning

ASecurity

Reviews ASP.NET Core API versioning setup. Flags missing Asp.Versioning wiring (endpoints versioned only by folder/route convention), a version reader limited to the URL segment with no header/query fallback, breaking changes made in-place to an existing version's contract instead of introducing a new version, missing deprecation/sunset signaling on superseded versions, and Swagger/OpenAPI docs not grouped per version. Outputs findings with pilot-dotnet api-versioning standard IDs.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 6, 2026
developmentgoapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 6, 2026

npx -y skills add AgenticPawan/FullStack-Pilot --skill dotnet-api-versioning --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dotnet Api Versioning?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dotnet Api Versioning
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/agenticpawan-dotnet-api-versioning/badge)](https://www.skillsdirectory.com/skills/agenticpawan-dotnet-api-versioning)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dotnet-api-versioning
description: Reviews ASP.NET Core API versioning setup. Flags missing Asp.Versioning wiring (endpoints versioned only by folder/route convention), a version reader limited to the URL segment with no header/query fallback, breaking changes made in-place to an existing version's contract instead of introducing a new version, missing deprecation/sunset signaling on superseded versions, and Swagger/OpenAPI docs not grouped per version. Outputs findings with pilot-dotnet api-versioning standard IDs.
when_to_use: API versioning, Asp.Versioning, AddApiVersioning, ApiVersionReader, MapToApiVersion, breaking change, deprecated API version, sunset header, versioned Swagger, versioned OpenAPI, v1 v2 endpoint, ReportApiVersions
---

## Standard IDs

| ID | Severity | What it checks |
|----|----------|-----------------|
| AV-001 | P1 | No `Asp.Versioning.*` package / `AddApiVersioning()` wired — versioning is convention-only |
| AV-002 | P2 | Version read only from the URL segment with no header/query-string fallback |
| AV-003 | P1 | Breaking change applied in-place to an existing version's DTO/contract |
| AV-004 | P2 | No deprecation policy — superseded version has no `Deprecated = true` / sunset header |
| AV-005 | P3 | Swagger/OpenAPI generation not grouped per API version (advisory) |

---

## Check A — No enforced versioning (AV-001)

### Detection

1. Check the API project's `.csproj` for `Asp.Versioning.Mvc` / `Asp.Versioning.Http` (minimal APIs), and `Program.cs` for `AddApiVersioning()`.
2. If versioning exists only as a route-string convention (`api/v1/orders` hardcoded per controller) with no `IApiVersionReader`/negotiation, a client can't discover supported versions and the server can't enforce a default or reject unsupported ones.

### BAD — versioning by folder/route-string convention only

```csharp
[ApiController]
[Route("api/v1/orders")]
public class OrdersV1Controller : ControllerBase { ... }

[ApiController]
[Route("api/v2/orders")]
public class OrdersV2Controller : ControllerBase { ... }
// No AddApiVersioning() — "v1"/"v2" are just route text, not negotiated versions.
```

### GOOD — Asp.Versioning wired with a version set

```csharp
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
}).AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV";
    options.SubstituteApiVersionInUrl = true;
});

[ApiController]
[Route("api/v{version:apiVersion}/orders")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class OrdersController : ControllerBase
{
    [MapToApiVersion("2.0")]
    [HttpGet]
    public async Task<ActionResult<OrderDtoV2>> GetV2() => ...
}
```

---

## Check B — Version reader limited to URL segment (AV-002)

### Detection

Check the `AddApiVersioning` options for `ApiVersionReader`. If unset (URL-segment-only default) or explicitly `new UrlSegmentApiVersionReader()` with no combination, a caller that can't rewrite its base path (some API gateways, generated SDKs pinned to headers) has no alternative negotiation path.

### BAD — URL segment only

```csharp
builder.Services.AddApiVersioning(); // defaults to URL-segment reader only
```

### GOOD — combined header/query/URL negotiation

```csharp
builder.Services.AddApiVersioning(options =>
{
    options.ApiVersionReader = ApiVersionReader.Combine(
        new UrlSegmentApiVersionReader(),
        new HeaderApiVersionReader("Api-Version"),
        new QueryStringApiVersionReader("api-version"));
});
```

---

## Check C — Breaking change made in-place (AV-003)

### Detection

1. Diff a DTO/contract used by an already-shipped API version for a removed field, renamed field, changed type, or changed validation that a consuming client already depends on.
2. Additive changes (new optional field) are not a finding. Removing/renaming/retyping a field on a version already in use by external/mobile/partner clients is a breaking change and must land as a new `[ApiVersion]`, not an edit to the existing one.

### BAD — v1 contract changed in place

```csharp
// v1.0 OrderDto originally had `decimal Total`
public class OrderDto
{
    public decimal TotalAmount { get; set; } // renamed in place — breaks every v1.0 client
}
```

### GOOD — new version introduced, old version untouched

```csharp
[MapToApiVersion("1.0")]
[HttpGet]
public async Task<ActionResult<OrderDtoV1>> GetV1() => ...; // unchanged contract

[MapToApiVersion("2.0")]
[HttpGet]
public async Task<ActionResult<OrderDtoV2>> GetV2() => ...; // TotalAmount lives only here
```

---

## Check D — No deprecation signaling (AV-004)

### Detection

Once a newer version ships, check whether the superseded `[ApiVersion("1.0")]` declares `Deprecated = true` and whether responses carry a `Sunset`/`Deprecation` header so clients get advance notice before removal.

### BAD — old version silently kept with no deprecation signal

```csharp
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class OrdersController : ControllerBase { ... }
```

### GOOD — deprecated version flagged with a sunset header

```csharp
[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
public class OrdersController : ControllerBase { ... }

// middleware adds: Sunset: Sat, 31 Jan 2026 00:00:00 GMT  and  Deprecation: true
```

---

## Check E — Swagger/OpenAPI not grouped per version (AV-005, advisory)

### Detection

Confirm `IConfigureOptions<SwaggerGenOptions>` (or the OpenAPI equivalent) generates one document per discovered `ApiVersionDescription` instead of a single flattened doc that mixes all versions' operations together, which makes it impossible for consumers to browse "just v2."

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…