Skip to content

Content Negotiation

DodaTech 2 min read

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

  1. What header does content negotiation use?
  2. What is a vendor media type?
  3. How does this approach compare to URI versioning?
  4. What is the response Content-Type?
  5. How do you handle quality values in Accept header?

Answers:

  1. The HTTP Accept header.
  2. A custom media type containing a vendor identifier and version.
  3. More RESTful but more complex for clients to implement.
  4. Should match the versioned media type the client requested.
  5. 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

What is the format of a vendor media type?

: application/vnd.COMPANY.VERSION+FORMAT e.g., application/vnd.stripe.v2+json.

Is content negotiation the most RESTful?

: Yes, because the resource URL stays the same across versions.

Do I need to support application/json as a fallback?

: Yes, for simpler clients that don't send vendor media types.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro