Troubleshooting

415 Unsupported Media Type: what error code 415 means and how to fix it

Error code 415 Unsupported Media Type means the server rejects the request body's format. Fix the Content-Type in curl, fetch, axios or on the server.

Published ~8 min read
On this page

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.

curl · the most common case and its fix
# 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 later

What 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:

Example output · curl -si, form body to a JSON-only endpoint, other headers left out
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

CodeWhat the server saysTypical triggerFix on the client
400 Bad RequestThe request is malformed or could not be parsedBroken JSON, a header too large, a missing field in some frameworksFix the syntax; read the response body
406 Not AcceptableIt cannot answer in a format your Accept header allowsAccept: application/xml to a JSON-only APISend Accept: application/json or */*
413 Content Too LargeThe body is bigger than it acceptsAn upload over the limitSmaller body, or raise the limit on the server
415 Unsupported Media TypeIt will not accept the body's format or encodingWrong or missing Content-Type, a charset, a Content-EncodingSend the type, charset and encoding the API expects
422 Unprocessable ContentFormat and syntax are fine, the values are notValidation errorsFix 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=latin1 got 415 with unsupported charset "LATIN1" in our test, while charset=utf-8 worked.
  • 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: compress got 415 with unsupported 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-data gets JSON, or a SOAP 1.2 client sends application/soap+xml to a SOAP 1.1 service that expects text/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:

Example output · curl -v, request headers, other lines left out
# 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: 17

Note 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 bodyContent-Type sentSource
fetch() with a string body, such as JSON.stringify(…)text/plain;charset=UTF-8Our test, Node.js 25.7.0
fetch() with URLSearchParamsapplication/x-www-form-urlencoded;charset=UTF-8Our test
fetch() with FormDatamultipart/form-data; boundary=…Our test
requests.post(url, json=data)application/jsonOur test, requests 2.28.1
requests.post(url, data=a_dict)application/x-www-form-urlencodedOur test
requests.post(url, data=a_string)No Content-Type header at allOur test
Invoke-RestMethod -Method Post without -ContentTypeapplication/x-www-form-urlencodedMicrosoft's PowerShell 7 reference (PUT gets application/json)
axios with a plain objectapplication/jsonThe axios README

The fixes, one per client:

JavaScript · fetch, in the browser or Node.js 18 and later
await fetch("https://api.example.com/servers", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "web-1" }),
});
JavaScript · axios
// 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" },
});
Python · requests
import requests

r = requests.post("https://api.example.com/servers", json={"name": "web-1"})
print(r.status_code, r.headers.get("Accept"))
PowerShell
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:

Express 5 · route that answers 415 itself
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.

Spring MVC
@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.

ASP.NET Core · controller action
[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 -v prints the request headers with >. In the browser, DevTools, Network, the failed request, Headers, Request Headers: read Content-Type and Content-Encoding.
  • See what the server says. curl -si shows the Accept or Accept-Encoding header if the server sends one, and the body often names the expected type.
  • Compare with the API’s documentation, character for character: application/json and application/vnd.api+json are 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.

Related articles

All troubleshooting