Fixing the 415 Status Code: A Developer's Guide

Web data extraction guides, proxy tutorials, automation best practices, and developer documentation for Scrappey — a reliable API for collecting publicly available web data at scale.

Fixing the 415 Status Code: A Developer's Guide

Fixing the 415 Status Code: A Developer's Guide

Created time
May 22, 2026 10:27 AM
Date
Status
You send a request that looks right. The endpoint is correct. The auth token is valid. Your payload even looks clean in the debugger. Then the server answers with 415 Unsupported Media Type.
That response is annoying because it sounds narrower than the failure usually feels. You weren't asking the server to do something exotic. You were posting JSON, uploading a file, or replaying a browser call from DevTools. Yet the server still refused to process it.
A 415 error usually means one thing: the server doesn't accept the representation you sent for that endpoint. In plain terms, your client and the server disagree about the format of the request body. But in production systems, that disagreement isn't always limited to a bad Content-Type header. The body might be malformed. The multipart boundary might be broken. The Content-Encoding might not match the actual bytes. In scraping workflows, the 415 status code can even show up as a misleading block signal.
The fastest way to fix it is to stop treating 415 like a generic request failure. It's a parsing and protocol problem. You solve it by comparing what you sent against what the endpoint consumes, then validating the body and the transport details with the same rigor you'd use for auth or schema debugging.

Introduction When Your Request Gets Rejected

Most developers hit the 415 status code in a familiar moment. You make a POST request, expect a clean success response, and get rejected before your application logic even runs. Nothing is obviously down. You're not unauthorized. The server is refusing to handle the payload you've sent.
That distinction matters. A 415 isn't the server telling you "try again later." It's the server saying "I don't accept this representation." If you keep retrying the exact same request, you'll keep getting the same result because the problem is structural, not transient.
The quickest mental model is a language mismatch. The endpoint expects one request format, and your client speaks another. Sometimes that's as simple as JSON sent with text/plain. Sometimes it's more subtle, like sending a compressed body without the right encoding header, or sending multipart data with a malformed boundary.
That mindset saves time. Developers often burn an hour checking network health, auth middleware, or timeout settings when the issue is sitting in the headers and body. In API work, the fix is usually to align your request with the endpoint contract. In scraping, you also need to consider whether the response is telling the truth about why the request was rejected.

What Is the 415 Unsupported Media Type Error

The formal meaning is straightforward. HTTP 415 is defined as a server refusal to process a request because the payload is in a format not supported by the target resource, and in practice it's usually a negotiation failure between the request representation and what the endpoint can parse, as explained in Baeldung's overview of HTTP 415.
notion image

How the mismatch happens

The key header is Content-Type. It tells the server how to interpret the request body.
A few common examples:
Request body
Correct Content-Type
JSON object
application/json
HTML form body
application/x-www-form-urlencoded
File upload
multipart/form-data
XML document
application/xml
If the endpoint only consumes JSON and you send XML, the server can reject the request with 415. If you send JSON bytes but label them as text/plain, same outcome. The body and the header need to agree, and both need to match what the endpoint accepts.
Developers also confuse Content-Type with Accept. They solve different problems:
  • Content-Type describes the body you're sending.
  • Accept tells the server what response formats you'd like back.
A bad Accept header can cause other negotiation issues. It usually isn't the first place to look for a 415 status code.

A concrete failure example

This is the classic broken request:
POST /api/users HTTP/1.1 Host: example-api.test Content-Type: text/plain {"name":"Ada","role":"admin"}
The body is JSON, but the header says plain text. A strict server can reject it:
HTTP/1.1 415 Unsupported Media Type Content-Type: application/json {"error":"Unsupported Media Type"}
The fixed request declares the body correctly:
POST /api/users HTTP/1.1 Host: example-api.test Content-Type: application/json {"name":"Ada","role":"admin"}
If you're tracing unfamiliar failures in automation, it's useful to compare your request against a compact reference for HTTP error code behavior in scraping environments.

Diagnosing the Common Causes of a 415 Error

A wrong Content-Type is common, but it isn't the whole story. MDN's reference on 415 notes that the problem may come from the request's indicated Content-Type or Content-Encoding, or arise while processing the body itself. It also points out that a 415 can happen when the server recognizes the media type but can't parse the content, including cases like missing multipart boundaries or mismatched compression encodings.
notion image

Start with the contract, not the code

Before changing client code, verify what the endpoint accepts. If the docs say the route consumes application/json, don't improvise with form encoding because it "usually works" elsewhere.
Use a short checklist:
  • Check the endpoint docs: Look for accepted request media types, not just response examples.
  • Inspect a known-good request: Browser DevTools, a working SDK call, or a maintained Postman collection is often more trustworthy than stale docs.
  • Confirm the method and route: Some APIs accept JSON on one route and multipart on another route that looks almost identical.
A 415 often disappears once the body format and route line up. That's the easy case.

Validate the body against the declared type

Developers often stop after fixing the header. That's a mistake.
If your header says JSON, the bytes need to be valid JSON. If your body is multipart, the boundary has to be present and well-formed. If the server recognizes the media type but fails while parsing the actual content, you can still get a 415.
Common patterns I look for:
  • JSON declared, non-JSON sent: String interpolation errors, trailing commas, comments, or accidental raw text.
  • Form declared, JSON sent: The body was serialized one way while the header was set manually another way.
  • Multipart declared manually: The client didn't generate the boundary correctly, or you overrode the header and lost the autogenerated boundary.
  • Empty body on a route that expects payload data: Some frameworks treat this as unsupported input rather than a validation problem.

Check Content-Encoding and body transformations

Many "mystery 415s" frequently emerge here.
If a proxy, scraper, or HTTP client compresses a body, chunks it, or rewrites it, the final wire format may not match the headers you think you're sending. A body marked as gzip-compressed must be gzip-compressed. If the bytes are plain and the header says otherwise, some servers reject the payload immediately.
Use this quick diagnostic table:
Symptom
What to test
Request looks correct in app logs but still fails
Capture the raw outgoing request with a proxy or replay tool
Multipart upload fails only in code, not in Postman
Remove manual Content-Type and let the client generate the boundary
JSON works in curl but fails in production app
Compare middleware, interceptors, and serializer behavior
Failure appears after adding compression
Disable compression and resend the same logical payload

Look for middleware and framework interference

The last layer is often self-inflicted. Axios interceptors, reverse proxies, API gateways, browser polyfills, or custom wrappers may overwrite headers or transform payloads after your code builds the request.
A common pattern is: your application code sets JSON, then some helper serializes to form data, or a fetch wrapper injects a default Content-Type that doesn't match the body. Another one is retry middleware resending a consumed stream or mutated request body.
When debugging, strip the request path down to one reproducible call in curl or Postman. If the simple replay works, your problem isn't the endpoint. It's your client stack.

Practical Fixes with Code Examples

Most 415 fixes come down to one principle: make the body, headers, and serializer agree.
notion image

Fixing 415 with curl

curl is still the fastest way to isolate whether the problem is your application or the request shape.
Broken request:
curl -X POST https://api.example.test/users \ -H "Content-Type: text/plain" \ -d '{"name":"Ada"}'
The body is JSON, but the header says plain text.
Fixed request:
curl -X POST https://api.example.test/users \ -H "Content-Type: application/json" \ -d '{"name":"Ada"}'
For form-encoded data, don't keep the JSON body and only swap the header. Change both:
curl -X POST https://api.example.test/login \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=ada&password=secret"
For multipart uploads, let curl build the multipart body:
curl -X POST https://api.example.test/upload \ -F "[email protected]"
Don't manually add Content-Type: multipart/form-data unless you know exactly what boundary is being generated.

Fixing 415 with fetch and Axios

A lot of frontend 415 errors happen because the code serializes data incorrectly or adds a header that doesn't match the body.
Broken fetch example:
fetch("https://api.example.test/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: { name: "Ada" } // broken: this is a JS object, not JSON text });
Fixed version:
fetch("https://api.example.test/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Ada" }) // fix: actual JSON payload });
Another broken pattern is file uploads with a forced multipart header:
const form = new FormData(); form.append("file", file); fetch("https://api.example.test/upload", { method: "POST", headers: { "Content-Type": "multipart/form-data" // broken: browser won't add boundary correctly }, body: form });
Fixed version:
const form = new FormData(); form.append("file", file); fetch("https://api.example.test/upload", { method: "POST", body: form // fix: let the browser set Content-Type with boundary });
Axios has the same trap. For FormData, don't force the header in the browser unless you have a specific reason and know the boundary handling path.
If you're working with JSON request payloads in automation, a concise JSON POST request guide for scraper workflows is useful when comparing client behavior across tools.

Fixing 415 with Python requests

Python's requests library gets one thing very right: using the json= parameter serializes the body and sends the matching header.
Broken version:
import requests payload = {"name": "Ada"} response = requests.post( "https://api.example.test/users", data=payload, headers={"Content-Type": "application/json"} )
That code is suspicious because data= doesn't serialize to JSON in the same way json= does.
Fixed version:
import requests payload = {"name": "Ada"} response = requests.post( "https://api.example.test/users", json=payload )
For form data, use data= and don't label it as JSON:
import requests response = requests.post( "https://api.example.test/login", data={"username": "ada", "password": "secret"} )
For file uploads:
import requests with open("report.csv", "rb") as f: response = requests.post( "https://api.example.test/upload", files={"file": f} )
Again, let the library generate multipart details.
A short walkthrough can help if you want to see these patterns applied in a debugging flow:

What usually doesn't work

Some fixes are placebo.
  • Blind retries: A 415 status code isn't a flaky network timeout.
  • Changing only Accept: That affects the response format, not the request body format.
  • Copying browser headers without matching the body serializer: The request still fails if the bytes are wrong.
  • Setting every header manually: This often breaks multipart and encoded requests that the client library would build correctly on its own.

The 415 Status Code in Web Scraping Workflows

In scraping systems, the 415 status code isn't always a plain media-type problem. Scrapfly's analysis of 415 in scraping warns that 415 errors can occur on GET requests or inconsistently, which may indicate blocking rather than an invalid body. It recommends comparing browser traffic with scraper traffic to separate a genuine semantic error from a request that was flagged by anti-bot controls.
notion image

When 415 doesn't make semantic sense

A GET request with no body returning 415 should make you pause. So should a request that succeeds in the browser but fails from your scraper with the same visible endpoint and apparent payload. Those are signs that the server may be using 415 as a surface-level rejection while a different system upstream decides your request doesn't look legitimate.
That can happen behind CDNs, WAFs, bot mitigation layers, or custom middleware. The application doesn't necessarily parse your body and reject it. The request may be classified earlier and mapped to a misleading status code.

How to test whether it's a real format issue

Use a compare-and-diff approach instead of guessing.
  1. Capture a successful browser requestExport the full request from DevTools, including headers, cookies, request order, and any preflight or token-fetch step.
  1. Replay the same call outside your scraperUse curl or Postman. If the replay works, your scraper runtime is likely altering the request.
  1. Compare request context, not just headersCheck whether the browser established a session first, loaded CSRF tokens, or sent the request after a page script generated hidden values.
  1. Inspect anti-bot signalsA bodyless GET returning 415, or a 415 that appears only from certain IPs or automation stacks, points away from media-type negotiation and toward traffic classification.
A direct request debugging workflow is easier when you can inspect the full request path, especially in tools built for raw HTTP request reproduction in scraping setups.

What actually works in scraping environments

Once you've ruled out a real payload mismatch, the fix changes completely.
A few practical adjustments often matter more than toggling Content-Type:
  • Session continuity: Some targets expect cookies or a request sequence before accepting API-style calls.
  • Browser parity: Header order, fetch metadata, and browser-generated values can matter.
  • Request timing: Sending the right call too early can look unlike a real user flow.
  • IP and reputation controls: The same request can be accepted from one network path and challenged from another.
That's why "just set application/json" sometimes feels useless in scraping work. The protocol may be correct. The server may still reject the request because the client fingerprint, session state, or request sequence doesn't match what the target expects.

API Design Best Practices to Prevent 415 Errors

If you're building the API instead of consuming it, you can prevent most 415 confusion before users ever hit it.

Publish the exact contract

Don't document an endpoint with a vague "POST user data" description. State the accepted media types explicitly for each route and method. If the endpoint only accepts JSON, say that in the contract and examples. If uploads require multipart, show a working request sample instead of a prose description.
Developers copy examples more often than they read paragraphs. If the examples are correct, support tickets drop.

Return useful error bodies

A bare 415 status code is technically valid and operationally weak. A better response tells the caller what failed and what the endpoint accepts.
For example:
{ "error": "unsupported_media_type", "message": "This endpoint accepts application/json only." }
That message turns a generic rejection into a quick fix. It also reduces the urge to retry or misdiagnose the problem as auth, transport, or infrastructure.

Be selective about flexibility

Supporting multiple media types sounds friendly, but it increases parser paths, test coverage, and ambiguity. If clients don't need XML, don't accept XML. If a route is designed for JSON, keep it JSON-only and say so clearly.
The best developer experience isn't maximal flexibility. It's a precise contract, consistent examples, and error responses that point directly to the mistake.

Conclusion Moving Beyond Media Type Mismatches

The 415 status code is strict, but it's usually fair. The server is rejecting how the request is represented, not randomly failing.
The fastest troubleshooting order is simple. Check the declared media type. Validate the actual body against that declaration. Then inspect encoding, multipart construction, and anything in your client stack that may be mutating the request before it leaves the process.
In scraping systems, add one more branch to that decision tree. If the 415 response appears on GET requests, shows up inconsistently, or only affects automation, treat it as a possible block signal and compare your scraper traffic with real browser traffic.
Once you start reading 415 as a parsing clue instead of a generic error, debugging gets much faster. It stops being a wall and becomes a concrete puzzle with a finite set of moving parts.
If you're building scraping or data extraction pipelines and need more control over request behavior, Scrappey gives developers tools for browser rendering, direct HTTP requests, session handling, and anti-bot aware collection flows, which makes it easier to debug tricky responses like the 415 status code in real-world targets.