Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
ArchitectureJune 21, 20264 min read

REST API Design: Mastering Header-Based Versioning for Clean Evolution

REST API design is often cluttered by versioned URLs. Learn how to use content negotiation to manage API versioning effectively and keep your code clean.

REST APIAPI DesignVersioningContent NegotiationArchitectureMicroservicesAPIBackendSystem Design
A close-up view of PHP code displayed on a computer screen, highlighting programming and development concepts.

When I was refactoring our core order-processing service last year, I spent an entire afternoon staring at a URL structure that looked like /v1/orders/v2/items. It was a mess. We had accumulated so much technical debt from rapid iterations that our endpoints were becoming unreadable, and our documentation was falling out of sync with reality.

If you’re building a REST API design that needs to survive more than a single release cycle, you’ve likely debated where to put the version number. Putting it in the URL is the most common approach, but it’s not the only one. In fact, it often forces you into a corner where you’re maintaining parallel code paths forever.

The Problem with URL Versioning

Early in my career, I was a massive fan of /v1/, /v2/ prefixes. They’re easy to cache, easy to route, and dead simple to debug with curl. However, they violate the principle that a URI should identify a resource, not a specific implementation of that resource.

When you ship a change, you’re not necessarily creating a new resource; you’re just representing the existing one differently. By forcing the version into the URI, you treat the resource as a different entity entirely. This makes it harder to apply shared API versioning strategies because your load balancers and API gateways have to be aware of every single version prefix you’ve ever launched.

Moving Logic to the Header

Instead of polluting the URI, we started using content negotiation. It’s a standard HTTP feature that allows the client and server to agree on the representation format. By using the Accept header, we can request specific versions of a resource while keeping the URI stable.

Here is how a request looks when you use media type negotiation:

HTTP
GET /orders/123 HTTP/1.1
Host: api.example.com
Accept: application/vnd.mycompany.v2+json

The server inspects the Accept header. If it finds v2, it executes the logic for the second version. If the header is missing, it defaults to a stable v1 implementation. This pattern is far more elegant than URL versioning because the resource identity remains constant regardless of the structural changes underneath.

Why This Matters for Microservices

In a microservices architecture, you don't want your upstream services to know about your internal implementation details. If you change a field name in your JSON response, you don't want to force every consuming service to update their base URL.

We first tried to route requests based on a custom X-API-Version header. It worked for about two months. The problem? It wasn't standard. When we added a caching layer at the edge, the cache keys were inconsistent because the header wasn't part of the standard Vary field. We ended up serving cached v1 responses to v2 clients, which caused a production incident that took us roughly 4 hours to trace back to the CDN configuration.

Switching to Accept headers fixed this. Because Accept is a standard HTTP header, well-behaved caches automatically include it in the Vary response header. This ensures that the cache correctly distinguishes between different representations of the same URL.

Implementation Considerations

When you implement content negotiation for versioning, keep these three rules in mind:

  1. Define a Default: Always provide a sensible default if the client doesn't send a version header. Don't break clients that don't know how to negotiate.
  2. Use Custom Media Types: Don't just use application/json. Use vendor-specific types like application/vnd.mycompany.v2+json. This clearly signals the intent and version.
  3. Handle Errors Gracefully: If a client requests a version that doesn't exist, return a 406 Not Acceptable status code. This is the correct HTTP way to tell the client you can't fulfill their specific representation request.

The Trade-offs

I won't pretend this is perfect. Debugging is slightly harder because you can't just copy-paste a URL into a browser to see the result; you have to set the header. Also, some older proxy servers or restrictive firewalls might strip custom headers or struggle with complex Accept values.

However, the payoff is a significantly cleaner codebase. You stop thinking about "v1 endpoints" and "v2 endpoints" and start thinking about "v1 representations" and "v2 representations." It’s a subtle shift, but it forces you to design your REST API design around the resource, not the deployment timeline.

I’m still not 100% convinced this is the best approach for every team. If your clients are primarily non-technical users or simple web forms, the added complexity of setting headers might be a dealbreaker. But for internal service-to-service communication, it’s been a game changer for us. We’ve managed to deprecate two older versions of our order schema in the last year without touching a single URL path, which is a win in my book.

Back to Blog

Similar Posts

Real estate investment concept with money and house models on table.
ArchitectureJune 20, 20264 min read

REST API design choices that scale without technical debt

REST API design choices dictate your system's longevity. Learn the patterns that prevent breaking changes, simplify client integration, and scale reliably.

Read more
A woman writes 'Use APIs' on a whiteboard, focusing on software planning and strategy.
Architecture
June 21, 2026
4 min read

API Versioning Strategies: Maintaining Backward Compatibility at Scale

Master API versioning and maintain backward compatibility in your distributed systems. Learn pragmatic strategies to evolve your services without breaking clients.

Read more
Close-up view of a symmetrical concrete structure showcasing texture and geometry.
ArchitectureJune 20, 20264 min read

When to split a monolith: A pragmatic guide for engineers

When to split a monolith is a question of team scaling, not just tech. Learn how to weigh the operational overhead against the benefits of decoupling.

Read more