What Is HTTP 415 Unsupported Media Type?
A 415 Unsupported Media Type response means the server refuses your request because it does not support the Content-Type you declared for the request body. It is not a problem with your JSON being malformed — it is that the server does not know what format it is receiving.
This almost always happens when sending a JSON body without the Content-Type: application/json header.
Why 415 Happens: All the Causes
Cause 1 (Most common): Missing Content-Type header
// WRONG — browser defaults to text/plain;charset=UTF-8
fetch("/api/users", {
method: "POST",
body: JSON.stringify({ name: "Ravi" }),
});Without a Content-Type header, the browser sends text/plain;charset=UTF-8 (for string bodies) or application/x-www-form-urlencoded (for form data). The server's JSON parser middleware never runs.
Cause 2: Wrong Content-Type value
// WRONG — application/x-www-form-urlencoded cannot carry JSON
fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: JSON.stringify({ name: "Ravi" }),
});Cause 3: charset suffix causing mismatch
Some servers only accept application/json exactly and reject application/json;charset=utf-8. This is technically wrong server behavior but you may encounter it with strict SOAP or enterprise APIs.
Cause 4: Sending a pre-stringified body to axios
// WRONG — axios cannot detect Content-Type from a string body
await axios.post("/api/users", JSON.stringify({ name: "Ravi" }));
// sends Content-Type: text/plain — triggers 415Cause 5: Server-side JSON middleware not configured
The server does not have a JSON body parser registered — Express without express.json(), Spring without @RequestBody, etc.
Fix: JavaScript fetch (The Complete Version)
const response = await fetch("/api/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": `Bearer ${token}`,
},
body: JSON.stringify(payload),
});
if (response.status === 415) {
throw new Error("Server rejected our Content-Type. Check that the API accepts JSON.");
}
if (!response.ok) {
const err = await response.json().catch(() => ({}));
throw new Error(err.message || `HTTP ${response.status}`);
}
const data = await response.json();Fix: axios
Pass the plain object — never stringify manually before passing to axios:
// CORRECT — axios auto-sets Content-Type: application/json
await axios.post("/api/orders", payload);
// Also correct — explicit headers
await axios.post("/api/orders", payload, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
},
});
// WRONG — triggers 415 because axios sees a string, not an object
await axios.post("/api/orders", JSON.stringify(payload));Fix: Python requests Library
import requests
# WRONG — sends application/x-www-form-urlencoded
requests.post("/api/orders", data={"name": "Ravi"})
# CORRECT — json= param serializes and sets Content-Type automatically
requests.post("/api/orders", json={"name": "Ravi"})
# Also correct — fully manual approach
import json
requests.post(
"/api/orders",
data=json.dumps({"name": "Ravi"}),
headers={"Content-Type": "application/json"},
)Fix: curl
# WRONG — no Content-Type
curl -X POST https://api.example.com/users \
-d '{"name":"Ravi"}'
# CORRECT
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name":"Ravi"}'
# With a file
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d @payload.jsonServer-Side Fixes: Configure JSON Parsing
Express (Node.js):
const express = require("express");
const app = express();
app.use(express.json()); // parses application/json
app.use(express.urlencoded({ extended: true })); // parses form-encoded (optional)Without express.json(), req.body is undefined for all JSON requests.
FastAPI (Python):
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class UserCreate(BaseModel):
name: str
email: str
@app.post("/users")
async def create_user(user: UserCreate):
# FastAPI automatically expects application/json for Pydantic body params
return {"created": user.name}Spring Boot (Java):
@RestController
@RequestMapping("/api")
public class UserController {
@PostMapping(value = "/users",
consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> createUser(@RequestBody UserRequest req) {
// Spring automatically parses application/json bodies via Jackson
return ResponseEntity.ok(userService.create(req));
}
}Go (net/http):
func createUser(w http.ResponseWriter, r *http.Request) {
ct := r.Header.Get("Content-Type")
if !strings.HasPrefix(ct, "application/json") {
http.Error(w, "Content-Type must be application/json", 415)
return
}
var req UserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 400)
return
}
// process...
}Django REST Framework (Python):
# settings.py — ensure JSONParser is in the list
REST_FRAMEWORK = {
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
"rest_framework.parsers.FormParser",
"rest_framework.parsers.MultiPartParser",
]
}Debugging Checklist
| Check | Where to look | What you expect |
|---|---|---|
| Request Content-Type | Browser Network tab → Headers | application/json |
| Request body format | Browser Network tab → Payload | JSON string {"key":"value"} |
| Server middleware | Server code | express.json() or equivalent |
| Axios payload type | Your code | Plain object, not JSON.stringify'd string |
| Python requests call | Your code | json= param, not data= |
Use JSONKit's JSON Validator to confirm your JSON payload is syntactically correct before sending it, and use the curl examples above to test the endpoint directly.