Content Negotiation
title: "Content Negotiation — Media Type Versioning with Accept Header" description: "Content negotiation versioning uses the HTTP Accept header with custom media types like application/vnd.api.v1+json to specify the desired API version." date: 2026-06-28 lastmod: 2026-06-28 weight: 16 tags: [apis, versioning] }
Content negotiation versioning leverages the HTTP Accept header with vendor-specific media types (application/vnd.api.v1+json) for version specification.
What You'll Learn
- Vendor media types for versioning
- Accept header parsing
- Content negotiation vs other strategies
Why It Matters
Content negotiation is the most RESTful versioning approach. It keeps URLs clean, works with HTTP caching correctly, and follows web standards.
Code Examples
# Content negotiation with Accept header
@app.route('/api/users')
def get_users():
accept = request.headers.get('Accept', 'application/json')
# Parse vendor media type
version = '1'
if 'vnd.api' in accept:
# application/vnd.api.v2+json
match = re.search(r'vnd\.api\.v(\d+)', accept)
if match:
version = match.group(1)
if version == '1':
data = [{"id": u.id, "name": u.name} for u in users]
content_type = 'application/vnd.api.v1+json'
elif version == '2':
data = [{"id": u.id, "name": u.name, "email": u.email} for u in users]
content_type = 'application/vnd.api.v2+json'
else:
return jsonify({"error": "Unsupported version"}), 400
response = jsonify(data)
response.headers['Content-Type'] = content_type
return response
# Accept header parsing middleware
class ContentNegotiationMiddleware:
VERSIONS = {
'application/vnd.api.v1+json': 1,
'application/vnd.api.v2+json': 2,
}
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
accept = environ.get('HTTP_ACCEPT', '')
for media_type, version in self.VERSIONS.items():
if media_type in accept:
environ['api.version'] = version
break
else:
environ['api.version'] = 1 # Default
return self.app(environ, start_response)
// Express content negotiation
app.get('/api/users', (req, res) => {
const accept = req.headers['accept'] || 'application/json';
let version = 1;
if (accept.includes('vnd.api.v2')) {
version = 2;
} else if (accept.includes('vnd.api.v1')) {
version = 1;
}
const data = version === 2
? users.map(u => ({ id: u.id, name: u.name, email: u.email }))
: users.map(u => ({ id: u.id, name: u.name }));
res.set('Content-Type', `application/vnd.api.v${version}+json`);
res.json(data);
});
# Client usage
curl -H "Accept: application/vnd.api.v1+json" https://api.example.com/users
curl -H "Accept: application/vnd.api.v2+json" https://api.example.com/users
Common Mistakes
1. Non-Standard Media Types
Use proper vendor tree format: application/vnd.COMPANY.VERSION+FORMAT.
2. Ignoring Accept Quality Values
Clients may send Accept: application/vnd.api.v1+json; q=0.9, application/vnd.api.v2+json; q=1.
3. Not Returning Correct Content-Type
Response Content-Type must match the version the client requested.
4. Complex Middleware
Keep Accept parsing simple and centralized in middleware.
5. No Fallback for application/json
Support plain application/json as default version.
Practice Questions
- What header does content negotiation use?
- What is a vendor media type?
- How does this approach compare to URI versioning?
- What is the response Content-Type?
- How do you handle quality values in Accept header?
Answers:
- The HTTP Accept header.
- A custom media type containing a vendor identifier and version.
- More RESTful but more complex for clients to implement.
- Should match the versioned media type the client requested.
- Parse the q parameter and prefer the highest quality version.
Challenge: Implement content negotiation versioning for a REST API. Test with curl requests using different Accept headers.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro