Content Negotiation in REST APIs — Complete Guide
In this tutorial, you will learn about Content Negotiation in REST APIs. We cover key concepts, practical examples, and best practices to help you master this topic.
Content negotiation is an HTTP mechanism that allows clients and servers to agree on the format for data exchange, using Accept and Content-Type headers to serve JSON, XML, or other representations from the same endpoint.
flowchart LR A[Client] -->|Accept: application/json| B[Server] A -->|Accept: application/xml| C[Server] A -->|Accept: */*| D[Server defaults to JSON] B -->|Content-Type: application/json| A C -->|Content-Type: application/xml| A style A fill:#e1f5fe style B fill:#c8e6c9 style C fill:#fff9c4
The client sends an Accept header listing the media types it can understand. The server picks the best match from what it supports and responds with the chosen format in the Content-Type header. If the server cannot satisfy any Accept value, it returns 406 Not Acceptable.
Think of content negotiation like ordering food in a restaurant. The Accept header is your order: "I would like JSON please." The Content-Type header is what the kitchen confirms: "Here is your JSON." If you ask for XML and the kitchen only serves JSON, they say 406 Not Acceptable.
Example: Requesting JSON from the Same Endpoint
import requests
# Request JSON
headers = {"Accept": "application/json"}
response = requests.get("https://api.example.com/users/42", headers=headers)
print(f"Content-Type: {response.headers['Content-Type']}")
print(f"Data: {response.json()}")
Expected output:
Content-Type: application/json
Data: {"id": 42, "name": "Alice", "email": "alice@example.com"}
Example: Requesting XML
import requests
headers = {"Accept": "application/xml"}
response = requests.get("https://api.example.com/users/42", headers=headers)
print(f"Content-Type: {response.headers['Content-Type']}")
print(f"Data: {response.text}")
Expected output:
Content-Type: application/xml
Data: <user><id>42</id><name>Alice</name><email>alice@example.com</email></user>
Example: Quality Values and Wildcards
import requests
# Prefer JSON, accept XML as fallback, accept anything as last resort
headers = {"Accept": "application/json;q=0.9, application/xml;q=0.5, */*;q=0.1"}
response = requests.get("https://api.example.com/users/42", headers=headers)
print(f"Chosen format: {response.headers['Content-Type']}")
Expected output:
Chosen format: application/json
Common Mistakes
- Not supporting the Accept header — Returning JSON for every request regardless of the Accept header means XML or CSV clients cannot work with your API.
- Returning 406 unnecessarily — If you support multiple formats, try to match the client preference. Only return 406 when truly incompatible.
- Misusing the Content-Type header — Content-Type describes the response body format. Content-Encoding describes compression. Do not confuse them.
- Ignoring Accept header for error responses — Error responses should also respect the Accept header and return errors in the requested format.
- Not documenting supported formats — Clients should know what formats your API supports without trial and error.
Practice Questions
- What is the purpose of the Accept header in HTTP?
- What status code indicates the server cannot satisfy the Accept header?
- How do quality values (q=0.9) work in content negotiation?
- What is the difference between Content-Type and Content-Encoding?
- Challenge: Write a Python function that caches the response from an API endpoint and serves it in JSON or XML based on the Accept header.
FAQ
Mini Project
Build a Flask or FastAPI endpoint that supports content negotiation. The endpoint should return data in JSON format when the client sends Accept: application/json and XML format when the client sends Accept: application/xml. Test both scenarios and verify the Content-Type header.
What's Next
Now learn about HTTP headers in REST API Design.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro