Why Your JSON API Returns 415 Unsupported Media Type

API Debugging Guide

Why Your JSON API Returns 415 Unsupported Media Type

A 415 Unsupported Media Type error usually appears when the API understands your route and method, but refuses the request body format you sent. For beginners, this error feels confusing because the JSON may look correct in Postman, but the same request fails from fetch, Axios, WordPress, PHP, Laravel, Express, Django, Spring Boot, or another client. This guide shows you how to debug the exact mismatch.

Quick answer: A 415 Unsupported Media Type error usually means the request body and the Content-Type header do not match what the server endpoint accepts. The most common fix is to send the correct body format, set the correct Content-Type, and confirm that the backend has the correct parser or middleware enabled.

415 Unsupported Media Type Content-Type Accept JSON body fetch Axios Postman Backend parsers

What 415 Unsupported Media Type actually means

A 415 Unsupported Media Type response is not the same as a broken URL, missing route, invalid token, or server crash. It usually means the server reached the part of the request where it looked at the body format and decided, “I do not accept this format here.”

Think of the API endpoint as a door with a label. The label might say:

This endpoint accepts:
Content-Type: application/json

Expected body:
{
  "email": "student@example.com",
  "password": "example-password"
}

If your client sends text/plain, application/x-www-form-urlencoded, multipart/form-data, an empty body, or a JavaScript object that was not converted to JSON, the server may reject it with 415.

Status Plain-English meaning Usually check
400 Bad Request The server thinks the request is malformed or invalid. Syntax, missing fields, invalid JSON, route validation.
401 Unauthorized The request is missing valid authentication. Token, cookie, API key, login session.
403 Forbidden The server understood the request but refuses the action. Role, permission, plan, resource ownership, policy.
406 Not Acceptable The server cannot provide the response format the client requested. Accept header and response format negotiation.
415 Unsupported Media Type The server does not accept the request body format you sent. Content-Type header, body format, parser, endpoint expectation.
422 Unprocessable Content The server understood the body format but the data failed validation. Required fields, data types, rules, business validation.

The biggest beginner mistake is treating 415 like a general API failure. It is more specific than that. It points to the packaging of the request body. The server may be saying, “I expected JSON, but you sent something else,” or “You said this is JSON, but the body is not valid JSON.”

Content-Type vs Accept: the header mix-up behind many 415 errors

Two headers often get mixed up: Content-Type and Accept. They sound similar, but they point in opposite directions.

Content-Type

This tells the server what you are sending in the request body. For a JSON request body, it is commonly application/json.

Accept

This tells the server what response formats the client can understand. For a JSON API response, it is commonly application/json.

For most beginner JSON API calls, this is the safe starting point:

Content-Type: application/json
Accept: application/json

But they do not mean the same thing. If you send JSON, use Content-Type: application/json. If you want JSON back, use Accept: application/json. A 415 usually points to the sending side, not the response side. A 406 usually points to the response format the client asked for.

Simple memory trick: Content-Type describes your request body. Accept describes what response body you can accept.

Common reasons your JSON API returns 415

Most 415 errors come from one of these practical mistakes. The exact framework may be different, but the pattern is usually the same.

Cause What it looks like Fix
Missing Content-Type The body is JSON, but the request does not tell the server it is JSON. Add Content-Type: application/json.
Wrong Content-Type The endpoint expects JSON, but the request says text/plain or application/x-www-form-urlencoded. Send the media type the endpoint expects.
Body not stringified in JavaScript You pass a JavaScript object directly to fetch. Use JSON.stringify(data).
Manually setting multipart boundary incorrectly You upload FormData but manually set multipart/form-data. Let the browser set the multipart boundary.
Backend parser missing The server route exists, but the framework is not configured to parse JSON. Enable JSON body parsing middleware or framework configuration.
Endpoint expects form data, not JSON You send JSON to a login or upload endpoint that expects form encoding. Use application/x-www-form-urlencoded or multipart/form-data if documentation requires it.
Vendor media type required The API expects something like application/vnd.api+json. Use the exact documented media type.
Content encoding mismatch The request says it is compressed or encoded in a way the server does not support. Check Content-Encoding and gateway/proxy behavior.

The fastest way to narrow it down is to compare the exact working request from Postman or cURL with the failing browser request in DevTools. Do not compare what you think you sent. Compare the actual headers and actual payload.

The safest debugging order for a 415 error

When 415 appears, beginners often change random pieces of the request. They switch fetch to Axios, remove headers, add CORS settings, change the URL, or rewrite the backend route. That makes the problem harder. Use this order instead.

1

Confirm the method and endpoint

Make sure the route actually expects a request body. A GET request usually should not send a JSON body. A POST, PUT, or PATCH route commonly does.

2

Check what body format the API documentation expects

Do not assume every endpoint accepts JSON. Some token endpoints, file upload endpoints, legacy PHP forms, and OAuth-style endpoints expect form encoding or multipart data.

3

Check the actual Content-Type header

In browser DevTools, Postman Console, or curl -v, inspect the request headers. Confirm that Content-Type is present and exactly matches the body format you are sending.

4

Check the actual request payload

If the body says [object Object], the JavaScript object was not converted to JSON. If the body is empty, your client may not be attaching it. If the payload is form encoded, do not label it as JSON.

5

Check the backend parser or middleware

If the client request looks correct, the server may not be configured to parse that media type. Express, Laravel, Django, Spring Boot, PHP, and WordPress each handle request bodies differently.

6

Check CORS only after separating the layers

CORS can appear around the same time because Content-Type: application/json may trigger preflight on cross-origin browser requests. If OPTIONS fails, fix preflight first. If the real request returns 415, debug the body format.

7

Make the error response more specific

If you control the API, return a useful message such as “Expected Content-Type application/json” instead of only “Unsupported Media Type.” That helps frontend developers fix the request faster.

Correct examples for JSON API requests

Correct fetch request with JSON

This is the basic browser fetch pattern for sending JSON:

const response = await fetch("https://api.example.com/v1/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Accept": "application/json"
  },
  body: JSON.stringify({
    name: "Sagar",
    email: "sagar@example.com"
  })
});

const data = await response.json();

if (!response.ok) {
  console.error("API error:", response.status, data);
}

The important pair is Content-Type: application/json plus JSON.stringify(). One without the other often creates confusing behavior.

Wrong fetch request that can cause 415

This request looks innocent, but it is wrong because the body is a JavaScript object, not a JSON string:

const response = await fetch("https://api.example.com/v1/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: {
    name: "Sagar",
    email: "sagar@example.com"
  }
});

Depending on the environment, the server may receive an invalid body, an empty body, or a body that does not match the declared media type.

Correct Axios request with JSON

Axios usually serializes plain objects as JSON when used normally, but it is still helpful to be explicit when debugging:

const response = await axios.post(
  "https://api.example.com/v1/users",
  {
    name: "Sagar",
    email: "sagar@example.com"
  },
  {
    headers: {
      "Content-Type": "application/json",
      "Accept": "application/json"
    }
  }
);

Correct cURL request with JSON

A clear cURL request is useful because it removes browser and frontend framework confusion:

curl -i -X POST "https://api.example.com/v1/users" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  --data '{"name":"Sagar","email":"sagar@example.com"}'

Postman settings to check

In Postman, a JSON request should usually use:

  • Method: POST, PUT, or PATCH depending on the endpoint.
  • Body tab: raw.
  • Format dropdown: JSON.
  • Header: Content-Type: application/json.
  • Optional response preference: Accept: application/json.

Postman trap: Postman may add or preserve headers from previous attempts. Always check the actual Headers tab and the Postman Console if your request works in one place but fails somewhere else.

When the API does not want JSON

Not every request body should be JSON. A file upload endpoint, OAuth token endpoint, legacy PHP endpoint, or regular HTML form endpoint may expect a different format. If you force JSON into an endpoint that expects form data, a 415 response may be correct.

Form URL encoded request

Some endpoints expect application/x-www-form-urlencoded:

const params = new URLSearchParams();
params.append("email", "sagar@example.com");
params.append("password", "example-password");

const response = await fetch("https://api.example.com/login", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    "Accept": "application/json"
  },
  body: params.toString()
});

Multipart FormData request

For file uploads, use FormData. Do not manually set the multipart boundary:

const formData = new FormData();
formData.append("avatar", fileInput.files[0]);
formData.append("name", "Sagar");

const response = await fetch("https://api.example.com/profile/avatar", {
  method: "POST",
  body: formData
});

When using browser FormData, the browser sets the correct multipart Content-Type with a boundary. If you manually set only multipart/form-data, the server may not be able to parse the upload correctly.

Practical rule: JSON endpoints need JSON body plus JSON Content-Type. Upload endpoints usually need FormData. Form-style endpoints may need URLSearchParams. Match the endpoint, not your preference.

Backend parser mistakes that create 415 errors

Sometimes the frontend is correct, but the backend is not ready to accept the media type. The route exists, the method is correct, and the body is valid, but the framework parser is missing or too strict.

Express and Node.js

In Express, JSON body parsing must be enabled before routes that need req.body:

import express from "express";

const app = express();

app.use(express.json());

app.post("/users", (req, res) => {
  res.json({
    received: req.body
  });
});

If the client sends JSON but the server does not parse JSON, you may see empty bodies, validation failures, or media type errors depending on the middleware and route logic.

Laravel

Laravel can handle JSON requests, but your route, validation, middleware, and client headers still need to line up. A typical client request should include:

Content-Type: application/json
Accept: application/json

If Laravel returns HTML error pages instead of JSON during API debugging, check your Accept header and route group. API clients usually expect JSON responses, not browser-style HTML error pages.

Spring Boot

Spring Boot endpoints can reject a body if the consumes media type does not match the request:

@PostMapping(
  value = "/users",
  consumes = "application/json",
  produces = "application/json"
)
public User createUser(@RequestBody User user) {
  return userService.create(user);
}

If the endpoint consumes JSON and the client sends form data or text, 415 is a reasonable response.

Django REST Framework

In Django REST Framework, parser configuration controls which request body types the API accepts. If a view only allows JSON parsing, form or multipart requests may be rejected. If a view expects multipart uploads, JSON may be the wrong format.

Plain PHP

In plain PHP, JSON request bodies do not automatically appear in $_POST. You usually need to read the raw input and decode JSON:

$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true);

if (json_last_error() !== JSON_ERROR_NONE) {
  http_response_code(400);
  echo json_encode(array(
    'error' => 'invalid_json',
    'message' => 'Request body must be valid JSON.'
  ));
  exit;
}

If a PHP project expects $_POST, it may actually be expecting form data, not JSON. That is a common issue in student projects and older downloaded PHP/MySQL projects.

Why 415 sometimes appears with CORS errors

A JSON request from a browser can trigger CORS preflight because of the method, headers, or body type. That means you may see both CORS and 415 while debugging the same feature.

Example browser preflight:

OPTIONS /v1/users HTTP/1.1
Origin: http://localhost:5173
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

The server should allow the requested header and method:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

If OPTIONS fails, your real request may not run. If OPTIONS succeeds and the real POST returns 415, then the CORS layer is probably okay and the body format layer is the next issue.

Layer check: Failed OPTIONS means debug CORS first. Successful OPTIONS plus real POST 415 means debug Content-Type, body format, and backend parser.

WordPress and PHP API notes

WordPress users run into 415 errors in two common ways: calling an external API from a Custom HTML block or calling an API from PHP using the WordPress HTTP API. The fix depends on where the request is being made.

Browser JavaScript inside WordPress

If a Custom HTML block contains JavaScript that calls an API, it runs in the visitor’s browser. That means CORS, browser headers, and frontend request formatting matter:

fetch("https://api.example.com/v1/subscribe", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Accept": "application/json"
  },
  body: JSON.stringify({
    email: "reader@example.com"
  })
});

Server-side WordPress request

If you call the API from PHP, use the correct headers and encode the body:

$response = wp_remote_post('https://api.example.com/v1/subscribe', array(
  'headers' => array(
    'Content-Type' => 'application/json',
    'Accept' => 'application/json',
  ),
  'body' => wp_json_encode(array(
    'email' => 'reader@example.com',
  )),
  'timeout' => 20,
));

Do not pass a PHP array as the body while also saying it is JSON unless your code actually JSON-encodes it. The body format and the header must agree.

WordPress security note: If an API key is private, do not expose it in public JavaScript. Call the API from server-side PHP, a protected endpoint, or another backend layer.

Bad fixes that make 415 harder to debug

Removing Content-Type randomly

This may change the error, but it does not prove the request is correct. The server still needs to know what body format you are sending.

Forcing every endpoint to JSON

Some endpoints legitimately expect form data or multipart uploads. Read the endpoint requirement instead of assuming JSON everywhere.

Using mode: "no-cors"

This can hide browser errors and give your frontend an opaque response it cannot read. It is usually not a real API fix.

Changing 415 to 500 on the backend

A 500 makes the error less clear. If the client sent an unsupported media type, a 415 with a useful message is better.

Copying Postman code without checking headers

Generated code can be helpful, but always inspect the real request headers and body. A small media type difference can break the API.

Debugging validation first

If the server rejects the media type, validation may not even run. Fix the body format before debugging field-level errors.

Copyable 415 debugging checklist

Use this note when asking a teammate, instructor, API provider, or support team for help. It forces you to collect the details that usually reveal the problem.

415 Unsupported Media Type debugging note

Endpoint:
Example: https://api.example.com/v1/users

Method:
POST / PUT / PATCH

Expected request body format from docs:
JSON / form-urlencoded / multipart / text / vendor media type

Actual Content-Type sent:
Example: application/json

Actual Accept sent:
Example: application/json

Actual request body:
Paste sanitized body here.

Client used:
Postman / cURL / fetch / Axios / PHP / WordPress / mobile app

Does it work in Postman?
Yes / No

Does it work in cURL?
Yes / No

Does it fail in browser?
Yes / No

Browser Network tab:
OPTIONS status:
Real request status:
Console error:

Backend/framework:
Express / Laravel / Django / Spring Boot / WordPress / PHP / other

Parser/middleware enabled:
JSON parser:
Form parser:
Multipart parser:

What changed recently:
New endpoint / new frontend / new hosting / proxy / CORS change / auth change / API version change

Most likely issue:
Missing Content-Type / wrong Content-Type / body not stringified / endpoint expects form data / backend parser missing / CORS preflight failing

Related CodeZips tools for this problem

Use these CodeZips tools when you want to turn the error into a clean debugging checklist, request example, or support note.

API Content-Type and Accept Header Explainer and Fix Checklist Generator

Use this first when you are debugging 415, 406, JSON body format, form body format, or a mismatch between what you send and what the API expects.

HTTP Status Code Debugging Helper

Use this when you need a plain-English explanation of 415 and related status codes like 400, 401, 403, 406, 422, 500, 502, or 503.

API Request Troubleshooting Checklist Generator

Use this when the 415 error may be mixed with method, URL, authentication, CORS, body, timeout, or backend log issues.

cURL to JavaScript, PHP and WordPress Converter

Use this when the API documentation provides a cURL example and you need to convert it into fetch, Axios-style JavaScript, PHP, or WordPress request code.

API Error Response Explainer and Debug Checklist

Use this when the API returns a JSON error body and you want to understand whether the problem is body format, validation, auth, permission, or server logic.

CORS Preflight Request Explainer and Fix Checklist Generator

Use this when adding Content-Type: application/json causes a browser preflight request or a CORS error before the real request reaches your API.

JSON Error Response Builder for API Projects

Use this if you are building your own API and want clearer 415, 400, 406, 422, and validation error responses for frontend developers.

Technical references used for this guide

This article is written as a practical beginner debugging workflow, with status-code and header behavior checked against web platform documentation.

FAQ

What does 415 Unsupported Media Type mean in a JSON API?

It usually means the server does not accept the request body format you sent. For JSON APIs, check whether you are sending Content-Type: application/json, a valid JSON body, and a backend route that is configured to parse JSON.

Is 415 the same as invalid JSON?

Not always. Invalid JSON may produce 400, 415, or another error depending on the server. A 415 is more specifically about the request media type or content format being unsupported by that endpoint.

What is the difference between Content-Type and Accept?

Content-Type describes what you are sending in the request body. Accept describes what response format your client can understand. A 415 usually relates to Content-Type. A 406 usually relates to Accept.

Why does my request work in Postman but fail with 415 in fetch?

Postman may be sending a different body format or automatically setting a correct Content-Type. In fetch, you may need to manually set the header and use JSON.stringify() for JSON request bodies.

Should I always set Content-Type to application/json?

No. Set it only when you are actually sending JSON and the endpoint expects JSON. For FormData uploads, the browser should usually set the multipart header automatically. For form-encoded endpoints, use application/x-www-form-urlencoded.

Can CORS cause a 415 error?

CORS does not directly mean 415, but both can appear during the same browser API call. A JSON request may trigger preflight. If preflight fails, debug CORS first. If preflight succeeds and the real request returns 415, debug the body format and Content-Type.

How do I fix 415 in Express?

Check that the client sends valid JSON with Content-Type: application/json, and make sure Express has JSON parsing middleware such as app.use(express.json()) before the route that needs the body.

How do I fix 415 in PHP or WordPress?

Make sure the request body is actually JSON-encoded when you set Content-Type: application/json. In plain PHP, read JSON from php://input. In WordPress, use wp_json_encode() when sending JSON through wp_remote_post().

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top