How to Use Content Negotiation in REST APIs
Content negotiation in REST APIs lets the server choose the best response format based on the client's
Accept header. You use Accept to specify preferred formats like application/json or application/xml, and the server responds accordingly.Syntax
Content negotiation uses the Accept HTTP header sent by the client to tell the server which response formats it prefers. The server reads this header and returns the response in one of the requested formats if supported.
Key parts:
Accept: Header sent by client listing preferred media types.Content-Type: Header sent by server indicating the format of the response.- Server logic to check
Acceptand choose response format.
http
GET /resource HTTP/1.1 Host: example.com Accept: application/json, application/xml;q=0.9 HTTP/1.1 200 OK Content-Type: application/json { "key": "value" }
Example
This example shows a simple REST API endpoint in Python using Flask that returns data in JSON or XML depending on the Accept header sent by the client.
python
from flask import Flask, request, Response import json app = Flask(__name__) data = {'message': 'Hello, world!'} @app.route('/greet') def greet(): accept = request.headers.get('Accept', '') if 'application/xml' in accept: xml_response = f"<response><message>{data['message']}</message></response>" return Response(xml_response, mimetype='application/xml') # Default to JSON return Response(json.dumps(data), mimetype='application/json') if __name__ == '__main__': app.run(debug=True)
Output
Running the server. When client sends Accept: application/json, response is JSON; when Accept: application/xml, response is XML.
Common Pitfalls
Common mistakes when using content negotiation include:
- Ignoring the
Acceptheader and always returning one format. - Not setting the correct
Content-Typeheader in the response. - Failing to handle cases where the client requests unsupported formats.
- Not providing a default format when
Acceptis missing or invalid.
python
from flask import Flask, request, Response import json app = Flask(__name__) data = {'message': 'Hello, world!'} @app.route('/greet') def greet(): accept = request.headers.get('Accept', '') # Wrong: ignoring Accept header # return Response(json.dumps(data), mimetype='application/json') # Right: check Accept and respond accordingly if 'application/xml' in accept: xml_response = f"<response><message>{data['message']}</message></response>" return Response(xml_response, mimetype='application/xml') elif 'application/json' in accept or '*/*' in accept or accept == '': return Response(json.dumps(data), mimetype='application/json') else: # Unsupported media type return Response('Unsupported Media Type', status=415) if __name__ == '__main__': app.run(debug=True)
Quick Reference
| Header/Concept | Purpose | Example Value |
|---|---|---|
| Accept | Client tells server preferred response formats | application/json, application/xml;q=0.9 |
| Content-Type | Server tells client the response format | application/json |
| q-value | Quality factor to rank preferences | application/xml;q=0.8 |
| Default response | Format server uses if no match | application/json |
Key Takeaways
Use the Accept header to let clients specify preferred response formats.
Always set the Content-Type header in your server responses to match the format.
Provide a default response format if the Accept header is missing or unsupported.
Handle unsupported media types by returning a 415 status code.
Test your API with different Accept headers to ensure correct content negotiation.