Content-Type: application/json, what curl, Python and fetch actually send
Most 'my API ignores the JSON body' bugs are a missing or wrong Content-Type. We logged what each common client really sends.
1. What arrived at the server
| Client code | Content-Type sent |
|---|---|
curl -d '{"a":1}' URL | application/x-www-form-urlencoded |
curl -H 'Content-Type: application/json' -d '{"a":1}' URL | application/json |
curl --json '{"a":1}' URL | application/json (and Accept: application/json) |
Python requests.post(url, data={"a": 1}) | application/x-www-form-urlencoded, body a=1 |
Python requests.post(url, data=json.dumps({"a": 1})) | none at all |
Python requests.post(url, json={"a": 1}) | application/json |
fetch(url, {method: "POST", body: JSON.stringify(obj)}) (Chrome and Node 24) | text/plain;charset=UTF-8 |
same, with headers: {"Content-Type": "application/json"} | application/json |
The body was identical JSON in most rows; only the header differed. Servers that pick a parser by Content-Type (Express express.json(), Spring, ASP.NET, PHP's $_POST) see an empty or form body in the bold rows.
2. Copy-paste versions that work
# curl 7.82+
curl --json '{"name":"Ada"}' https://api.example.com/users
# older curl
curl -H 'Content-Type: application/json' -d '{"name":"Ada"}' https://api.example.com/users
# Python
import requests
r = requests.post("https://api.example.com/users", json={"name": "Ada"})
// JavaScript (browser or Node 18+)
const r = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ada" }),
});
Need the same request in another language? Paste the curl command into the free curl converter.
3. Accept vs Content-Type
Content-Type describes the body you send; Accept says what you want back. Only curl --json set Accept: application/json by itself; every other client sent Accept: */*. Add it when an API can answer in several formats.
4. Two pitfalls
-H 'Content-Type: application/json'with--data-urlencodestill sent a URL-encoded body (q=%7B%22a%22…) labelled as JSON. Use-dor--data-binaryfor raw JSON.- In the browser, a cross-origin fetch with
Content-Type: application/jsontriggers a CORS preflight (OPTIONS) first;text/plaindoes not. If a JSON POST works from curl but fails in the browser, check the server's OPTIONS response (our test page was same-origin, so this point is from the Fetch spec).
Frequently asked questions
Does curl -d send application/json?
No. In our test curl -d sent application/x-www-form-urlencoded. Add -H 'Content-Type: application/json' or use --json (curl 7.82+).
What is the difference between data= and json= in Python requests?
json= serialises the object and sets Content-Type: application/json. data=json.dumps(...) sent the same text with no Content-Type at all; data={...} sent a form body.
Does fetch set Content-Type for JSON.stringify bodies?
No. Chrome and Node 24 both sent text/plain;charset=UTF-8. Set the header yourself.
Is it application/json; charset=utf-8?
application/json is enough: JSON is UTF-8 by definition, and a charset parameter is allowed but has no effect for compliant parsers.
Written with AI assistance. Measured on 2026-10-01: a local server logged the Content-Type, Accept and body of POSTs sent by curl 8.14.1, Python requests 2.32.3, Node 24 fetch and Chromium 153 fetch (same-origin page). How Express, Spring, ASP.NET and PHP react is from their documentation, not this test. PlainHeaders is our extension; curl, requests and fetch are free.