An API is only as useful as its documentation is accurate, current, and discoverable. Documentation that lags behind the implementation, has no examples, or omits error cases forces Angular developers to probe the API empirically — slowing integration and creating mismatches. Treating documentation as a first-class concern alongside code (reviewed in PRs, updated with every feature, versioned alongside the API) is what separates APIs that developers love from those they dread.
Documentation Standards
// ── Complete controller documentation template ─────────────────────────────
/// <summary>
/// Manages blog posts — CRUD operations, publishing, and search.
/// Authentication: JWT Bearer token required for write operations.
/// </summary>
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[Produces("application/json")]
[Consumes("application/json")]
public class PostsController : ControllerBase
{
/// <summary>
/// Returns a paginated list of published posts.
/// </summary>
/// <param name="page">Page number (1-based). Default: 1.</param>
/// <param name="size">Items per page (1-100). Default: 10.</param>
/// <param name="search">Optional full-text search query.</param>
/// <param name="category">Optional category slug filter.</param>
/// <returns>Paginated list of post summaries.</returns>
/// <response code="200">Successfully returned the post list.</response>
/// <response code="400">Invalid pagination parameters.</response>
[HttpGet]
[AllowAnonymous]
[ProducesResponseType(typeof(PagedResult<PostSummaryDto>), 200)]
[ProducesResponseType(typeof(ValidationProblemDetails), 400)]
public async Task<ActionResult<PagedResult<PostSummaryDto>>> GetAll(
[FromQuery, Range(1, int.MaxValue)] int page = 1,
[FromQuery, Range(1, 100)] int size = 10,
[FromQuery, StringLength(100)] string search = "",
[FromQuery, RegularExpression(@"^[a-z0-9-]*$")] string category = "",
CancellationToken ct = default)
=> Ok(await _service.GetPublishedAsync(page, size, search, category, ct));
}
// ── API Changelog format (CHANGELOG.md) ──────────────────────────────────
// ## [2.0.0] - 2025-06-01
// ### Breaking Changes
// - `GET /api/posts/{id}`: `authorName` (string) replaced by `author` (object)
// - `POST /api/posts`: `tags` now required (was optional)
//
// ## [1.1.0] - 2025-03-15
// ### Added
// - `GET /api/posts/{id}/analytics` — view count and engagement metrics
// - `viewCount` field added to PostSummaryDto
//
// ## [1.0.0] - 2025-01-01
// ### Initial Release
Sunset header advertises when a deprecated version will be removed. Once you publish a sunset date, commit to it — removing an API version before the announced sunset date violates the trust of clients who planned their migration timeline around your announcement. Announce deprecation at least 6 months before sunset for production APIs. Keep the sunset date realistic: a v1 with thousands of active clients cannot be sunset in 30 days regardless of how eager you are to remove it.Generating the OpenAPI Spec File
// ── Generate spec file from CLI (for CI pipeline) ─────────────────────────
// dotnet tool install --global NSwag.ConsoleCore
// nswag aspnetcore2openapi /project:src/BlogApp.Api/BlogApp.Api.csproj /output:swagger.json
// ── Or use the swashbuckle CLI ─────────────────────────────────────────────
// dotnet tool install --global Swashbuckle.AspNetCore.Cli
// swagger tofile --output swagger.json src/BlogApp.Api/bin/Release/net8.0/BlogApp.Api.dll v1
// ── CI step (GitHub Actions) ───────────────────────────────────────────────
// - name: Generate OpenAPI spec
// run: |
// swagger tofile --output swagger.json ${{ env.API_DLL }} v1
// # Upload spec as CI artifact
// # Trigger NSwag client regeneration in Angular project
//
// ── Validate spec has not broken existing contracts ────────────────────────
// dotnet tool install --global openapi-diff
// openapi-diff --fail-on-incompatible old-swagger.json new-swagger.json
// # Returns exit code 1 if breaking changes detected — fails the PR build
Common Mistakes
Mistake 1 — Not updating docs when API changes (stale documentation loses developer trust)
❌ Wrong — new field added to response but ProducesResponseType still shows old schema; Swagger shows wrong type.
✅ Correct — treat documentation attributes as part of the change that must be reviewed in the PR.
Mistake 2 — No breaking change detection in CI (accidental breaking changes reach production)
❌ Wrong — field renamed in refactoring; no check detects the breaking change; Angular clients break after deployment.
✅ Correct — add openapi-diff or similar tool to CI that compares the new spec against the previous version and fails on breaking changes.