How HTTP Works
4xx · Client error
< HTTP/1.1 415 Unsupported Media Type

415 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.

Reviewed 6 min readbeginner4 sourcesTry itMarkdown
Cacheable
Only with explicit freshness
Retry?
No: fix Content-Type
Usually sent by
Origin app
Spec
RFC 9110 §15.5.16
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-Type to the endpoint, serialize JSON with JSON.stringify, and let the client generate the multipart boundary for uploads. Check Content-Encoding if 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 415 with the default status handling. Inspect error.response.data for the server’s explanation. Axios source.
  • fetch: the promise resolves with response.status === 415 and response.ok === false. Check the status before reading the body; HTTP errors do not enter catch automatically. MDN fetch.
  • Python requests: requests.exceptions.HTTPError: 415 Client Error: Unsupported Media Type for url: https://api.example.com/resource when you call response.raise_for_status(). Requests source.
  • curl -f: curl: (22) The requested URL returned error: 415. Use curl -i without -f while inspecting the error body. curl source.
  • .NET: HttpRequestException with the English message Response status code does not indicate success: 415 (Unsupported Media Type). after EnsureSuccessStatusCode(). Runtime message template.
  • Spring WebClient: WebClientResponseException$UnsupportedMediaType: 415 Unsupported Media Type from POST https://api.example.com/resource with retrieve(). Read getResponseBodyAsString(). 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.

StackWhat you see
Spring BootHttpMediaTypeNotSupportedException: 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 Core415 when [Consumes] or the input formatters do not match; the error body depends on API configuration
Expressexpress.json() skips unmatched media types, but unsupported encodings or charsets can produce 415
FastAPIRequest body validation errors return 422; inspect the detail array (FastAPI docs)

Common causes by stack

  • Spring Boot / Spring MVC: a route’s consumes condition or the available message converters reject the body type, raising HttpMediaTypeNotSupportedException and returning 415; compare @PostMapping(consumes=...) with the actual request header. Spring exception mappings.
  • Django REST framework: accessing request.data raises UnsupportedMediaType when no configured parser accepts the request type; add the intended parser to parser_classes or 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: false rejects compressed bodies; unsupported encodings and charsets set status 415 with encoding.unsupported or charset.unsupported. Log err.type and send an accepted encoding or charset. Parser errors.
  • AWS API Gateway REST APIs: passthroughBehavior: NEVER rejects a content type without a matching mapping template; WHEN_NO_TEMPLATES also 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

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

  1. MDN Web Docs: 415 Unsupported Media Typedeveloper.mozilla.org
  2. RFC 9110 Section 15.5.16: 415 Unsupported Media Typerfc-editor.org
  3. RFC 9110 Section 8.3: Content-Typerfc-editor.org
  4. MDN Web Docs: Accept-Postdeveloper.mozilla.org

Keep going

Browse /search