SiteError.comYour friendly guide to HTTP status codes
Status CodesBlog
  1. Home
  2. Blog
  3. Understanding HTTP 415 Unsupported Media Type: Content-Type, Not Content Correctness

Understanding HTTP 415 Unsupported Media Type: Content-Type, Not Content Correctness

September 10, 20268 min read
4xxClient Error

415 Unsupported Media Type is a code about format, not content — and that distinction gets lost more often than it should. This post covers what 415 actually promises, how it differs from 400 (malformed content) and 406 (the client rejecting the server's chosen format, rather than the reverse), and a detail that trips people up in practice: express.json() doesn't automatically return 415 for a mismatched Content-Type the way many developers assume.

What Is a 415?

A 415 tells the client that the server understood what it was asking for, but can't process the format the request body was sent in.

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 format problem might be due to the request's indicated Content-Type or Content-Encoding, or as a result of inspecting the data directly. — RFC 9110, Section 15.5.16

In plain English: "I don't understand the format of the data you sent me." Sending XML to an endpoint that only accepts JSON, uploading a .bmp to an endpoint that only handles JPEG and PNG, or sending a Content-Encoding the server can't decompress, are all textbook 415s. Note the RFC's own phrasing: the problem can come from the declared Content-Type/Content-Encoding header, or from the server actually inspecting the bytes and finding they don't match what was claimed — either is a valid basis for a 415.

Don't confuse 415 with 400. Use 415 specifically when the content format or encoding is wrong. Use 400 when the content is in the right format but has validation errors. A malformed JSON payload can go either way — many APIs use 400 if the body can't even be parsed as JSON, reserving 415 for when the Content-Type itself declares a format the endpoint doesn't accept at all.

Format vs Content: Where the Line Actually Sits

The recurring confusion with 415 is treating it as a general-purpose "something's wrong with your request body" code. It isn't — it's specifically about the shape of the data, not what's inside it:

  • 415 asks: can I even parse or interpret data in this format, on this endpoint? A Content-Type: application/xml on a JSON-only endpoint fails here regardless of whether the XML itself is well-formed.
  • 400 asks: is the request even parseable, given the format it claims to be in? Content-Type: application/json with a body that isn't valid JSON — a trailing comma, an unclosed brace — is a 400: the format is right, the bytes don't honor it.
  • 422 asks: once parsed, does the data satisfy the endpoint's business rules? Valid JSON with a startDate after an endDate is a 422, not a 415 or 400 — the format and syntax are both fine.

A practical test: if changing the request's Content-Type header to something the server does support would make the request at least parseable (even if it later fails validation), the original response should have been 415. If no Content-Type would have helped because the bytes themselves are broken, that's 400.

The Other Direction: 415 vs 406

415 and 406 Not Acceptable are mirror images of each other, and mixing them up is a common mistake:

  • 415 is about the request: the client sent a body in a format the server can't consume.
  • 406 is about the response: the client's Accept header specified formats the server can't produce, so the server can't satisfy the client's request for output format.

Put another way: 415 fires on the way in, 406 fires on the way out. An API that only accepts and returns JSON would use 415 if a client POSTs XML, and 406 if a client sends Accept: application/xml on a GET and the server has nothing but JSON to offer.

415 vs 400 vs 406

CodeMeaningUse when
400 Bad RequestMalformed requestThe declared format is fine, but the body doesn't actually parse as that format
415 Unsupported Media TypeUnsupported request formatThe Content-Type (or the inspected data) declares a format this endpoint doesn't accept at all
406 Not AcceptableUnsatisfiable response formatThe client's Accept header requests a response format the server can't produce

Common Causes

The recurring shapes of a genuine 415:

  1. Sending XML when JSON is expected — or any format mismatch where the endpoint has a fixed, narrow set of formats it understands.
  2. Wrong Content-Type header — the header doesn't match what the client actually sent, or doesn't match any format the endpoint supports (text/plain against a strictly application/json API).
  3. Unsupported file format — an upload endpoint that only accepts specific image or document types receiving something outside that set.

Returning 415 Correctly

Express / Node.js

This is the detail worth calling out explicitly: express.json() does not return 415 when the Content-Type doesn't match application/json. By default, if the incoming Content-Type doesn't match the parser's configured type option, body-parser (which express.json() wraps) just skips parsing entirely and calls next() — req.body ends up undefined rather than the middleware throwing an error. The 415s that body-parser does throw on its own are narrower: unsupported Content-Encoding values (like an unrecognized compression scheme), not a mismatched Content-Type. If you want a strict 415 for the wrong Content-Type, check it yourself:

import express from "express";
 
const app = express();
app.use(express.json());
 
app.post("/api/orders", (req, res, next) => {
  // req.is() returns the matching type, false, or null (no Content-Type header)
  if (!req.is("application/json")) {
    return res.status(415).json({
      error: "unsupported_media_type",
      message: "This endpoint only accepts application/json.",
      accepted: ["application/json"],
    });
  }
  next();
}, (req, res) => {
  res.status(201).json(req.body);
});

Next.js App Router

Route Handlers get the same freedom (and the same responsibility) — nothing rejects an unexpected Content-Type unless you check for it:

// app/api/orders/route.ts
export async function POST(request: Request) {
  const contentType = request.headers.get("content-type") ?? "";
 
  if (!contentType.includes("application/json")) {
    return Response.json(
      {
        error: "unsupported_media_type",
        message: "This endpoint only accepts application/json.",
        accepted: ["application/json"],
      },
      { status: 415 },
    );
  }
 
  const body = await request.json();
  return Response.json(body, { status: 201 });
}

NGINX

NGINX doesn't validate Content-Type against application semantics — that's inherently an application-layer decision, since only your code knows which formats a given endpoint supports. Where NGINX does participate is enforcing what it will accept for uploads at the proxy layer, if you use a module like ngx_http_dav_module with restrictions, or simply passing the request (and any 415 the backend returns) straight through:

location /api/ {
    proxy_pass http://backend;
    # Content-Type validation happens in the application;
    # NGINX forwards the request and the resulting 415 unchanged.
}

REST API JSON body

HTTP/1.1 415 Unsupported Media Type
Content-Type: application/json
Accept: application/json
 
{"error": "unsupported_media_type", "message": "This endpoint only accepts application/json.", "accepted": ["application/json"]}

Including an Accept header in the response (or listing accepted types in the body) gives the client something actionable, mirroring how a well-formed 406 response should list what the server can produce.

Common Pitfalls

  1. Assuming express.json() rejects the wrong Content-Type automatically. By default it silently skips parsing instead of erroring — if you need a hard 415 for a bad Content-Type, check req.is() (or the header directly) yourself.
  2. Using 415 for a body that's the right format but broken. Invalid JSON syntax under a correct Content-Type: application/json is a 400 — the format declaration was right, the bytes weren't.
  3. Confusing request-format problems (415) with response-format problems (406). They sit on opposite sides of the exchange — double check which direction the mismatch is actually in before picking a code.
  4. Returning 415 without saying what formats are accepted. A bare "unsupported media type" leaves the client guessing — list the accepted Content-Type values in the response body.
  5. Trusting the declared Content-Type without ever inspecting the body. The RFC explicitly allows basing a 415 on inspecting the actual data, not just the header — useful for upload endpoints where a client might mislabel a file's real format.

Wrapping Up

415 is about the shape of what arrived, not whether its contents make sense. The rules of thumb:

  • 415 means the request body's format isn't supported by this endpoint — a Content-Type/Content-Encoding problem, not a data-validation one
  • It's distinct from 400 (right format, broken bytes) and 406 (the response format the client wants isn't available, the mirror-image problem)
  • express.json() silently skips parsing on a mismatched Content-Type by default — it doesn't throw 415 for you; check req.is() if you want that behavior
  • A useful 415 response tells the client which formats the endpoint actually accepts

For more, see our page on 415 Unsupported Media Type, and 400 Bad Request and 406 Not Acceptable for the codes it's most often confused with.

Full Reference

415 Unsupported Media Type

The media format of the requested data is not supported by the server.

Related Status Codes

🤨400Bad Request🔐401Unauthorized💳402Payment Required🚫403Forbidden🔍404Not Found🙅405Method Not Allowed🍽️406Not Acceptable🎫407Proxy Authentication Required⏰408Request Timeout⚔️409Conflict👻410Gone📏411Length Required❌412Precondition Failed📦413Payload Too Large📜414URI Too Long📼415Unsupported Media Type📖416Range Not Satisfiable😞417Expectation Failed🫖418I'm a Teapot🚪421Misdirected Request🤔422Unprocessable Entity🔒423Locked🎯424Failed Dependency⏰425Too Early⬆️426Upgrade Required🔑428Precondition Required🚦429Too Many Requests📋431Request Header Fields Too Large⚖️451Unavailable For Legal Reasons
Back to Blog

Popular Status Codes

  • 200 OK
  • 301 Moved Permanently
  • 302 Found
  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable

Compare Codes

  • 401 vs 403
  • 301 vs 302
  • 404 vs 410
  • 500 vs 502
  • Compare any codes →

Categories

  • Informational
  • Success
  • Redirection
  • Client Error
  • Server Error
  • NGINX
  • Cloudflare
  • AWS ELB
  • Microsoft IIS

Tools

  • Cheat Sheet
  • Status Code Quiz
  • URL Checker
  • API Playground
  • Blog

© 2026 SiteError.com. All rights reserved.