< HTTP/1.1 415 Unsupported Media Type415 Unsupported Media Type
415 Unsupported Media Type: the server won't accept your Content-Type. Real Spring, DRF and ASP.NET errors, curl reproduction, fixes for fetch and FormData.
- Cacheable
- Only with explicit freshness
- Retry?
- No: fix Content-Type
- Usually sent by
- Origin app
- Often confused with
- 406
On this page
TL;DR: 415 Unsupported Media Type means the server rejects the format of your request body. Match
Content-Typeto the endpoint, serialize JSON withJSON.stringify, and let the client generate the multipart boundary for uploads. CheckContent-Encodingif the body is compressed.
What you see in your client
The messages and response bodies here are illustrative. Client messages use https://api.example.com/resource; reason phrases and URLs can vary.
- Axios:
AxiosError: Request failed with status code 415with the default status handling. Inspecterror.response.datafor the server’s explanation. Axios source. - fetch: the promise resolves with
response.status === 415andresponse.ok === false. Check the status before reading the body; HTTP errors do not entercatchautomatically. MDN fetch. - Python requests:
requests.exceptions.HTTPError: 415 Client Error: Unsupported Media Type for url: https://api.example.com/resourcewhen you callresponse.raise_for_status(). Requests source. - curl -f:
curl: (22) The requested URL returned error: 415. Usecurl -iwithout-fwhile inspecting the error body. curl source. - .NET:
HttpRequestExceptionwith the English messageResponse status code does not indicate success: 415 (Unsupported Media Type).afterEnsureSuccessStatusCode(). Runtime message template. - Spring WebClient:
WebClientResponseException$UnsupportedMediaType: 415 Unsupported Media Type from POST https://api.example.com/resourcewithretrieve(). ReadgetResponseBodyAsString(). Spring 6.2 source. - Spring RestTemplate (6.2):
HttpClientErrorException$UnsupportedMediaType: 415 Unsupported Media Type on POST request for "https://api.example.com/resource": [no body]with the default error handler and an empty response body. Read the exception body when present. Message builder, exception subclasses.
What it means
RFC 9110 section 15.5.16: the origin server refuses the request because the content is in a format the target resource does not support for this method. It is a check on the request body format. It has nothing to do with the size (see 413) or the content being invalid inside a supported format (see 422).
HTTP/1.1 415 Unsupported Media Type
Content-Type: application/json
Accept-Post: application/json, application/ld+json
{"error":"Unsupported Media Type","detail":"Content-Type 'text/plain' is not supported"}
Accept-Post (or Accept-Patch) on the response tells you what to send instead.
Reproduce it
curl -d defaults to application/x-www-form-urlencoded; a JSON-only endpoint can reject that type:
# Triggers 415 on a JSON-only endpoint
curl -sv -X POST https://api.example.com/orders -d '{"sku":"A1","qty":2}'
# > Content-Type: application/x-www-form-urlencoded
# Fixed
curl -sv -X POST https://api.example.com/orders \
-H 'Content-Type: application/json' \
-d '{"sku":"A1","qty":2}'
Real error strings by framework
Spring builds the Content-Type message in its exception constructor, and DRF defines the unsupported-media message in its exception class. Spring source, DRF source.
| Stack | What you see |
|---|---|
| Spring Boot | HttpMediaTypeNotSupportedException: Content-Type 'text/plain;charset=UTF-8' is not supported (the route’s consumes or the missing HttpMessageConverter) |
| Django REST framework | {"detail":"Unsupported media type \"text/plain\" in request."} |
| ASP.NET Core | 415 when [Consumes] or the input formatters do not match; the error body depends on API configuration |
| Express | express.json() skips unmatched media types, but unsupported encodings or charsets can produce 415 |
| FastAPI | Request body validation errors return 422; inspect the detail array (FastAPI docs) |
Common causes by stack
- Spring Boot / Spring MVC: a route’s
consumescondition or the available message converters reject the body type, raisingHttpMediaTypeNotSupportedExceptionand returning 415; compare@PostMapping(consumes=...)with the actual request header. Spring exception mappings. - Django REST framework: accessing
request.dataraisesUnsupportedMediaTypewhen no configured parser accepts the request type; add the intended parser toparser_classesor send a supported type. DRF exceptions. - ASP.NET Core controllers:
[Consumes("application/json")]rejects another request type with 415; compare[FromBody]with[FromForm]and the configured input formatters. Microsoft API documentation. - Express / body-parser:
inflate: falserejects compressed bodies; unsupported encodings and charsets set status 415 withencoding.unsupportedorcharset.unsupported. Logerr.typeand send an accepted encoding or charset. Parser errors. - AWS API Gateway REST APIs:
passthroughBehavior: NEVERrejects a content type without a matching mapping template;WHEN_NO_TEMPLATESalso rejects unmatched types when a template exists. Add the matching template or deliberately change passthrough behavior. AWS integration docs.
Fix it
Clients
// JSON: set the header and serialize the body
await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sku: 'A1', qty: 2 })
})
// Multipart: leave Content-Type unset; the browser adds the boundary
const form = new FormData()
form.append('file', fileInput.files[0])
await fetch('/api/upload', { method: 'POST', body: form })
If you set Content-Type: multipart/form-data yourself the request goes out without boundary=..., and servers reject it or fail to parse it.
Other client-side causes: PATCH with application/json when the API wants application/merge-patch+json or application/json-patch+json; a charset or vendor type (application/vnd.api+json) the server does not list; a request body compressed with Content-Encoding: gzip to an endpoint that does not decompress it.
Servers
// Spring: accept the types you intend, or you get 415 for everything else
@PostMapping(path = "/orders", consumes = { "application/json", "application/*+json" })
public Order create(@RequestBody OrderRequest body) { ... }
// Express: parse each type you accept, and answer 415 yourself for the rest
app.use(express.json())
app.post('/orders', (req, res) => {
if (!req.is('application/json')) {
res.set('Accept-Post', 'application/json')
return res.status(415).json({ error: 'Content-Type must be application/json' })
}
res.status(201).json(createOrder(req.body))
})
For DRF, check both DEFAULT_PARSER_CLASSES in settings and the view’s parser_classes; the view can override the global list. JSONParser handles application/json, FormParser handles application/x-www-form-urlencoded, and MultiPartParser handles multipart/form-data uploads. A view limited to [JSONParser] rejects a browser form when it reads request.data. Add form parsers only to endpoints intended to accept those formats. For a raw file upload, FileUploadParser matches */* and should generally stand alone; use MultiPartParser for browser multipart uploads. The distinction determines whether the body is read as one file or as named form fields. DRF parser configuration.
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.response import Response
from rest_framework.views import APIView
class UploadView(APIView):
parser_classes = [FormParser, MultiPartParser]
def post(self, request):
return Response({'fields': list(request.data.keys())})
Compare the client’s Content-Type and Content-Encoding with the headers logged at the application. If they differ, inspect the proxy or gateway transformation. API Gateway can itself return 415 before calling the backend when its mapping-template passthrough rules reject the incoming media type. AWS passthrough behavior.
Debug the headers and the bytes together
Accept: application/json asks for a JSON response; it does not label the request body. A POST can have that header and still send Content-Type: text/plain. Inspect both directions with curl -v, then set Content-Type: application/json on the outgoing body. With Python requests, requests.post(url, json=data) serializes JSON and sets its type, while data=data with a dictionary form-encodes it. Requests serialization source.
For multipart uploads, inspect the boundary in the outgoing header and the body delimiters. Browser FormData generates both; setting the header yourself prevents the browser from adding its boundary parameter. Let curl do the same work with curl -v -F 'file=@report.txt' https://api.example.com/upload. The endpoint must accept multipart data as well as the particular file or part types it validates. MDN FormData guidance, curl manual.
For compressed requests, Content-Encoding: gzip describes compression applied to the body; Content-Type still describes the decoded format. Removing the encoding header while sending compressed bytes leaves the parser reading gzip as JSON. Send an uncompressed JSON body or enable the server’s supported decompression path. RFC 9110 allows a 415 response to advertise accepted request codings with Accept-Encoding; inspect that response header before changing compression. RFC 9110 section 15.5.16.
415 versus neighbors
- 400 Bad Request: the request is malformed (for example JSON that does not parse). The type was acceptable, the content was not.
- 422 Unprocessable Content: JSON parsed fine but failed validation.
- 406 Not Acceptable: the problem is your
Acceptheader, not yourContent-Type. - 413 Content Too Large: the right type, too many bytes.
Frequently asked questions
Does setting Accept: application/json fix 415?
Accept selects the desired response format. For a JSON request, set Content-Type: application/json and serialize the body as JSON. For browser FormData, leave Content-Type unset so the browser generates the multipart boundary.
What does 415 Unsupported Media Type mean?
The server refused the request because the format of the body, usually named by the Content-Type header, is not one the target endpoint accepts. It can also be triggered by an unsupported Content-Encoding such as a gzip-compressed request body.
How do I fix a 415 error when calling a JSON API?
Send Content-Type: application/json together with a body that is actually serialized JSON. With curl use -H "Content-Type: application/json" -d @body.json; with fetch set the header and pass JSON.stringify(data) as the body.
Why do I get 415 with FormData or file uploads?
The endpoint may require another format, or the multipart header may be missing its boundary. When using browser FormData, leave Content-Type unset so the browser generates a boundary matching the body. A malformed multipart request can also produce 400, depending on the parser.
What is the difference between 415 and 406?
415 is about the format of what you sent (Content-Type, Content-Encoding). 406 Not Acceptable is about the format you asked to receive (the Accept header).
How can I tell which media types an endpoint accepts?
Look for an Accept-Post or Accept-Patch response header if the endpoint supplies one. Check the API documentation or framework route definition when those headers are absent.
Sources
Related
400 Bad Request
400 Bad Request means the server could not parse your request. Find which layer sent it, fix bad JSON and oversized cookies or headers, and reproduce with curl.
405 Method Not Allowed
405 Method Not Allowed means the URL exists but not for that method. Read the Allow header, then fix redirects turning POST into GET, static hosting and WAFs.
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.
408 Request Timeout
408 Request Timeout means the server gave up waiting for the client to finish sending. Tune nginx, Apache, Node and ALB timeouts and tell it apart from 504.