- Home
- HTTP Status Codes
- 406 Not Acceptable
Status Code
406 Not Acceptable
The server cannot produce a response matching the client's Accept headers. Learn about content negotiation and how to handle format mismatches.
What is 406 Not Acceptable?
TL;DR: Server can’t return content in the format you requested via Accept headers. Check supported formats and adjust your request.
A 406 Not Acceptable status code means the server cannot produce a response that matches the client’s Accept headers. Think of it like ordering food at a restaurant and specifying you only want vegan options, but the chef can only prepare meat dishes—they can’t fulfill your specific requirements.
This happens during content negotiation when the server understands the request but can’t deliver the content in the format, language, or encoding the client requested.
When Does This Happen?
You’ll see a 406 Not Acceptable response in these common situations:
1. Unsupported Media Type
Client requests JSON, server only has XML
Accept: application/json → 406 (only text/xml available)
2. Unsupported Language
Client requests Spanish, server only has English
Accept-Language: es → 406 (only en available)
3. Unsupported Encoding
Client only accepts gzip, server doesn't compress
Accept-Encoding: gzip → 406 (no compression available)
4. Unsupported Charset
Client requests UTF-16, server only has UTF-8
Accept-Charset: utf-16 → 406 (only utf-8 available)
5. API Version Mismatch
Client requests v2 format, server deprecated it
Accept: application/vnd.api.v2+json → 406
Example Responses
Media Type Mismatch:
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
Vary: Accept
{
"error": "Not Acceptable",
"message": "Cannot produce response in requested format",
"requested": "application/pdf",
"available": [
"application/json",
"text/html",
"application/xml"
]
}
```text
**Language Not Available:**
```http
HTTP/1.1 406 Not Acceptable
Content-Type: text/html; charset=utf-8
Content-Language: en
Vary: Accept-Language
<!DOCTYPE html>
<html lang="en">
<head><title>Language Not Available</title></head>
<body>
<h1>Requested Language Not Available</h1>
<p>The requested language (es) is not available.</p>
<p>Available languages: en, fr, de</p>
</body>
</html>
Encoding Not Supported:
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
Vary: Accept-Encoding
{
"error": "Encoding not acceptable",
"message": "Server cannot produce response with requested encoding",
"requested_encoding": "compress",
"supported_encodings": ["gzip", "deflate", "br", "identity"]
}
```text
## Real-World Example
Imagine you're building an API client that only accepts JSON responses:
**Client Request (JSON Only):**
```http
GET /api/users/12345 HTTP/1.1
Host: api.example.com
Accept: application/json
Accept-Language: en-US
Accept-Encoding: gzip, deflate
Server Only Has XML:
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
Vary: Accept
Link: <https://api.example.com/docs/formats>; rel="help"
{
"status": 406,
"error": "Not Acceptable",
"message": "Cannot produce response in application/json format",
"details": {
"requested_format": "application/json",
"available_formats": [
{
"media_type": "application/xml",
"example_url": "/api/users/12345?format=xml"
},
{
"media_type": "text/html",
"example_url": "/api/users/12345.html"
}
]
},
"help_url": "https://api.example.com/docs/formats"
}
```text
**Alternative: Server Ignores Accept and Returns Default:**
```http
HTTP/1.1 200 OK
Content-Type: application/xml
Vary: Accept
<?xml version="1.0" encoding="UTF-8"?>
<user>
<id>12345</id>
<name>John Doe</name>
</user>
406 vs Other Client Error Codes
| Code | Meaning | Issue | Solution |
|---|---|---|---|
| 406 | Not acceptable | Response format unavailable | Request different format |
| 415 | Unsupported media type | Request format unacceptable | Send different format |
| 400 | Bad request | Malformed request | Fix request syntax |
| 404 | Not found | Resource doesn’t exist | Check URL |
Important Characteristics
Content Negotiation Failure:
Client: Accept: application/pdf
Server: Can only produce: application/json, text/html
Result: 406 Not Acceptable
```text
**Proactive vs Reactive:**
```text
Proactive negotiation:
- Client specifies preferences in Accept headers
- Server selects best match OR returns 406
Reactive negotiation:
- Server returns 300 Multiple Choices
- Client selects from available options
```text
**Vary Header Important:**
```http
HTTP/1.1 406 Not Acceptable
Vary: Accept, Accept-Language ← Indicates what caused 406
Content-Type: application/json
Common Mistakes
❌ Not providing list of available formats
HTTP/1.1 406 Not Acceptable
Content-Type: text/plain
Not acceptable. ← Unhelpful, doesn't say what IS available
```text
**❌ Using 406 for wrong content type in request body**
```http
POST /api/users
Content-Type: text/csv ← Request body format issue
HTTP/1.1 406 Not Acceptable ← Wrong! Should be 415
❌ Returning 406 when you could serve default format
Accept: application/vnd.custom.v99+json
HTTP/1.1 406 Not Acceptable ← Could return default JSON instead
```text
**✅ Correct usage with helpful information**
```http
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
Vary: Accept
{
"error": "Not Acceptable",
"available_formats": ["application/json", "text/html"],
"documentation": "/docs/api-formats"
}
Best Practices
Provide List of Available Formats:
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
Vary: Accept
Link: <https://api.example.com/users/123?format=json>; rel="alternate"; type="application/json"
Link: <https://api.example.com/users/123?format=xml>; rel="alternate"; type="application/xml"
{
"error": "Format not available",
"requested": "application/pdf",
"available": [
{
"format": "application/json",
"url": "/users/123?format=json"
},
{
"format": "application/xml",
"url": "/users/123?format=xml"
},
{
"format": "text/html",
"url": "/users/123.html"
}
]
}
```javascript
**Consider Serving Default Format:**
```javascript
// Instead of strict 406, serve default if possible
app.get('/api/resource', (req, res) => {
const accept = req.accepts(['json', 'xml', 'html'])
if (!accept) {
// Could return 406, but serving default is more user-friendly
console.warn('No acceptable format, serving JSON')
return res.json({ data: resource })
}
switch (accept) {
case 'json':
return res.json({ data: resource })
case 'xml':
return res.type('xml').send(toXML(resource))
case 'html':
return res.render('resource', { data: resource })
}
})
Include Documentation Links:
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
Link: <https://docs.example.com/api/content-negotiation>; rel="help"
{
"error": "Not Acceptable",
"message": "Requested format not available",
"documentation": "https://docs.example.com/api/content-negotiation"
}
```text
**Use Vary Header Correctly:**
```http
HTTP/1.1 406 Not Acceptable
Vary: Accept, Accept-Language, Accept-Encoding
Content-Type: application/json
{
"error": "Content negotiation failed",
"details": "Cannot provide es-MX in gzip encoding"
}
Content Negotiation Best Practices
Quality Values:
Accept: application/json;q=1.0, application/xml;q=0.8, text/html;q=0.5
```text
**Wildcard Handling:**
```http
Accept: application/json, */*;q=0.1 ← Accept anything with low priority
Server Response Strategy:
function selectFormat(acceptHeader) {
const supported = ['application/json', 'application/xml']
const preferred = parseAcceptHeader(acceptHeader)
for (let format of preferred) {
if (supported.includes(format.type)) {
return format.type
}
}
// Return 406 or default?
if (acceptHeader.includes('*/*')) {
return 'application/json' // Default
}
throw new NotAcceptableError(supported)
}
```javascript
## Implementation Examples
**Express.js:**
```javascript
app.get('/api/users/:id', (req, res) => {
const user = getUser(req.params.id)
// Content negotiation
res.format({
'application/json': () => {
res.json(user)
},
'application/xml': () => {
res.type('xml').send(userToXML(user))
},
'text/html': () => {
res.render('user', { user })
},
default: () => {
// 406 Not Acceptable
res.status(406).json({
error: 'Not Acceptable',
available: ['application/json', 'application/xml', 'text/html']
})
}
})
})
Django:
from django.http import JsonResponse, HttpResponse
from django.views import View
class UserDetailView(View):
def get(self, request, user_id):
user = get_user(user_id)
accept = request.META.get('HTTP_ACCEPT', '')
if 'application/json' in accept:
return JsonResponse({'user': user})
elif 'application/xml' in accept:
return HttpResponse(user_to_xml(user),
content_type='application/xml')
elif 'text/html' in accept:
return render(request, 'user.html', {'user': user})
else:
return JsonResponse({
'error': 'Not Acceptable',
'available': ['application/json', 'application/xml', 'text/html']
}, status=406)
```javascript
**ASP.NET Core:**
```csharp
[HttpGet("api/users/{id}")]
public IActionResult GetUser(int id)
{
var user = _userService.GetUser(id);
var accept = Request.Headers["Accept"].ToString();
if (accept.Contains("application/json"))
{
return Ok(user);
}
else if (accept.Contains("application/xml"))
{
return new ObjectResult(user)
{
ContentTypes = { "application/xml" }
};
}
else
{
return StatusCode(406, new
{
error = "Not Acceptable",
available = new[] { "application/json", "application/xml" }
});
}
}
Go:
func userHandler(w http.ResponseWriter, r *http.Request) {
user := getUser(r.URL.Query().Get("id"))
accept := r.Header.Get("Accept")
switch {
case strings.Contains(accept, "application/json"):
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
case strings.Contains(accept, "application/xml"):
w.Header().Set("Content-Type", "application/xml")
xml.NewEncoder(w).Encode(user)
default:
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotAcceptable)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": "Not Acceptable",
"available": []string{"application/json", "application/xml"},
})
}
}
Try It Yourself
Visit our request builder and trigger a 406 response:
- Set method to GET
- Set path to /api/users/1
- Add header
Accept: application/pdf - Click Send request
- See 406 with list of available formats
Related Status Codes
- 415 Unsupported Media Type - Request body format not supported
- 300 Multiple Choices - Multiple representation options available
- 400 Bad Request - Malformed request
- 200 OK - Successful content negotiation
Frequently Asked Questions
What does 406 Not Acceptable mean?
A 406 error means the server cannot produce a response matching the Accept headers sent by the client. The server has the resource but cannot deliver it in the requested format.
How do I fix a 406 error?
Check your Accept, Accept-Language, or Accept-Encoding headers. Broaden them to accept more formats, or verify the server supports the format you are requesting.
What is the difference between 406 and 415?
406 means the server cannot return content in the format you accept. 415 means the server cannot process the format you sent. 406 is about response format; 415 is about request format.
What is content negotiation?
Content negotiation is the process where client and server agree on the best format for a response using Accept headers. The server picks the best match from available representations.