Short answer: error code 415 Unsupported Media Type means the server refuses the request because the body is in a format it does not accept for that URL and method. Most often the Content-Type header is missing or wrong, such as JSON sent as form data. Send the type the API expects, for example Content-Type: application/json, and check the charset and Content-Encoding.
# wrong: -d labels the body application/x-www-form-urlencoded
curl -si -d '{"name":"web-1"}' https://api.example.com/servers
# right: say what the body is
curl -si -H 'Content-Type: application/json' --data '{"name":"web-1"}' https://api.example.com/servers
curl -si --json '{"name":"web-1"}' https://api.example.com/servers # curl 7.82.0 and laterWhat does 415 Unsupported Media Type mean?
RFC 9110, section 15.5.16, defines it: “The 415 (Unsupported Media Type) status code indicates that the origin server is refusing to service the request because the content is in a format not supported by this method on the target resource.” The RFC names three ways the server can tell: the request’s Content-Type, its Content-Encoding, or a look at the data itself. It also says what a good server sends back: an Accept header listing the media types it would take, or an Accept-Encoding header when the compression was the problem.
So read the response headers, not only the status line. A test endpoint we built with Express 5.2.1 on Debian 12, following the RFC, answered a form-encoded body like this:
HTTP/1.1 415 Unsupported Media Type
Accept: application/json
{"error":"Content-Type must be application/json"}The important distinction: 415 is about what you send. The header that describes your request body is Content-Type. The Accept header you send is about the response you want, and a mismatch there gives 406, not 415.
415 vs 400 vs 406, and the other 4xx codes for bodies
| Code | What the server says | Typical trigger | Fix on the client |
|---|---|---|---|
| 400 Bad Request | The request is malformed or could not be parsed | Broken JSON, a header too large, a missing field in some frameworks | Fix the syntax; read the response body |
| 406 Not Acceptable | It cannot answer in a format your Accept header allows | Accept: application/xml to a JSON-only API | Send Accept: application/json or */* |
| 413 Content Too Large | The body is bigger than it accepts | An upload over the limit | Smaller body, or raise the limit on the server |
| 415 Unsupported Media Type | It will not accept the body's format or encoding | Wrong or missing Content-Type, a charset, a Content-Encoding | Send the type, charset and encoding the API expects |
| 422 Unprocessable Content | Format and syntax are fine, the values are not | Validation errors | Fix the values the response names |
Frameworks draw the line between 400 and 415 differently, so the same mistake can produce either. In our Express test, a form-encoded body sent to a JSON route was not refused by the parser at all; the route found no body and answered 400. ASP.NET Core API controllers answer the same request with 415, according to Microsoft’s documentation.
Why servers send 415
- No Content-Type, or the wrong one. By far the most common case. The client library chose a default that the API does not parse, as the next section shows for curl, fetch, Python and PowerShell.
- A charset the server will not decode. Express’s JSON parser accepts only Unicode charsets.
Content-Type: application/json; charset=latin1got415withunsupported charset "LATIN1"in our test, whilecharset=utf-8worked. - A Content-Encoding the server does not support. body-parser 2.3.0, Express’s parser, decompresses gzip, deflate and brotli bodies; our request with
Content-Encoding: compressgot415withunsupported content encoding "compress", while the same JSON gzipped was accepted. - The endpoint wants a different kind of body. A file upload route that expects
multipart/form-datagets JSON, or a SOAP 1.2 client sendsapplication/soap+xmlto a SOAP 1.1 service that expectstext/xml. - A webhook sender and receiver disagree. Many webhooks and callbacks post form data: Slack’s slash commands, for example, send
application/x-www-form-urlencoded. A receiver that parses only JSON refuses them.
What your client sends by default
We sent the same small body with each option to a test server on Debian 12 and recorded the request headers. curl 7.88.1 with -v, request lines only:
# curl -d @body.json
> Content-Length: 16
> Content-Type: application/x-www-form-urlencoded
# curl --json @body.json
> Content-Type: application/json
> Accept: application/json
> Content-Length: 17
# curl -F file=@body.json
> Content-Type: multipart/form-data; boundary=------------------------ebdac7f2ef2008d1
# curl --data-binary @body.json -H Content-Type:application/json
> Content-Type:application/json
> Content-Length: 17Note the lengths: -d @file strips newlines from the file, so 17 bytes became 16. For a file, use --data-binary @file or --json @file, which send it unchanged. More request patterns are in our curl examples.
| Client and body | Content-Type sent | Source |
|---|---|---|
fetch() with a string body, such as JSON.stringify(…) | text/plain;charset=UTF-8 | Our test, Node.js 25.7.0 |
fetch() with URLSearchParams | application/x-www-form-urlencoded;charset=UTF-8 | Our test |
fetch() with FormData | multipart/form-data; boundary=… | Our test |
requests.post(url, json=data) | application/json | Our test, requests 2.28.1 |
requests.post(url, data=a_dict) | application/x-www-form-urlencoded | Our test |
requests.post(url, data=a_string) | No Content-Type header at all | Our test |
Invoke-RestMethod -Method Post without -ContentType | application/x-www-form-urlencoded | Microsoft's PowerShell 7 reference (PUT gets application/json) |
| axios with a plain object | application/json | The axios README |
The fixes, one per client:
await fetch("https://api.example.com/servers", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "web-1" }),
});// a plain object: axios serialises it and sets application/json itself
await axios.post("https://api.example.com/servers", { name: "web-1" });
// a string you built yourself: set the header explicitly
await axios.post("https://api.example.com/servers", body, {
headers: { "Content-Type": "application/json" },
});import requests
r = requests.post("https://api.example.com/servers", json={"name": "web-1"})
print(r.status_code, r.headers.get("Accept"))Invoke-RestMethod -Method Post -Uri https://api.example.com/servers `
-ContentType 'application/json' -Body '{"name":"web-1"}'How servers decide on 415
Express
express.json() parses only requests whose Content-Type matches its type option (application/json by default) and skips the rest. In Express 5.2.1, a skipped request reaches your route with req.body undefined, so the 415 never comes from a wrong type; it comes only from an unsupported charset or content encoding, as above. If you want clients to get a clear 415, check the type yourself and say what you accept:
app.post("/servers", (req, res, next) => {
if (!req.is("application/json")) {
res.set("Accept", "application/json"); // RFC 9110: name what you accept
return res.status(415).json({ error: "Content-Type must be application/json" });
}
next();
}, express.json(), saveServer);That route produced the example output at the top of this page. Without an error handler of your own, Express’s default handler with NODE_ENV=production answered the charset case with a bare HTML page reading Unsupported Media Type and printed the stack trace to the server’s console.
Spring
A request whose Content-Type does not match a mapping’s consumes, or that no message converter can read into the @RequestBody parameter, raises HttpMediaTypeNotSupportedException, which Spring’s javadoc describes as thrown “when a client POSTs, PUTs, or PATCHes content of a type not supported by request handler”. Its DefaultHandlerExceptionResolver maps that exception to 415, and HttpMediaTypeNotAcceptableException (the produces side) to 406.
@PostMapping(path = "/servers", consumes = "application/json")
public Server create(@RequestBody Server server) {
// ...
}ASP.NET Core
With controllers, Microsoft’s web API documentation says that requests to an action marked [Consumes("application/xml")] that do not carry that Content-Type get 415. In controllers marked [ApiController], a [FromBody] parameter that no input formatter can read ends the same way, through UnsupportedContentTypeFilter. Minimal APIs bind bodies from JSON only, and their binding failures table lists a wrong content type as 415 and JSON that does not deserialise as 400.
[HttpPost]
[Consumes("application/json")]
public IActionResult Create([FromBody] Server server) => CreatedAtAction(nameof(Get), new { id = server.Id }, server);Behind nginx or another proxy
A plain reverse proxy passes the application’s 415 through unchanged, so the reason is in the application’s response body or log. Look at the proxy only if its configuration overrides request headers, for example a proxy_set_header Content-Type line copied from somewhere, or if a web application firewall in front rejects certain content types.
Debug a 415 in three steps
- See what you send.
curl -vprints the request headers with>. In the browser, DevTools, Network, the failed request, Headers, Request Headers: readContent-TypeandContent-Encoding. - See what the server says.
curl -sishows theAcceptorAccept-Encodingheader if the server sends one, and the body often names the expected type. - Compare with the API’s documentation, character for character:
application/jsonandapplication/vnd.api+jsonare different types to a strict server.
If a plain GET on the same URL looks normal in our HTTP header checker while your POST gets 415, the URL and the proxy in front of it answer normally, and the body of your request is the place to look.
If the API is yours and runs on a Linux VPS, log the Content-Type and Content-Encoding of every request you refuse with 415. That one log line answers most support questions about the error before anyone has to reproduce it.
Frequently asked
What does error code 415 mean?
415 Unsupported Media Type means the server refuses the request because the body is in a format it does not accept for that URL and method, judged by the Content-Type, the Content-Encoding or the data itself. A well-behaved server names the types it accepts in an Accept response header.
How do I fix 415 Unsupported Media Type?
Send the Content-Type the API expects, usually application/json, with a body in that format: curl --json, or curl -H 'Content-Type: application/json' --data, fetch with a Content-Type header and JSON.stringify, or requests.post with json=. Also check that the charset is UTF-8 and that the server accepts any Content-Encoding you use.
What is the difference between 415 and 406?
415 is about the request body you send, described by your Content-Type header. 406 Not Acceptable is about the response: the server cannot answer in any format that your Accept header allows.
Why does curl -d return 415?
curl -d labels the body application/x-www-form-urlencoded, the format of HTML forms, even when the text is JSON. An API that accepts only JSON refuses it with 415, or fails to parse it and answers 400. Use --json, or set the Content-Type header yourself.