Why curl -d Doesn't Send application/json

You wrote curl -d '{"name":"Ada"}' https://api.example.com/users, the body is obviously JSON, and the API still rejects it. Nothing about the body is wrong — -d silently attaches Content-Type: application/x-www-form-urlencoded unless you tell it otherwise, and that header, not the body, is what your server actually trusts.

reference guide curl HTTP

// what -d actually sends

-d/--data is older than JSON being a common API format — it was built for submitting HTML forms, and curl has never changed its default to match how the flag gets used today. curl doesn't look at your body at all to decide the Content-Type; it just applies the same default every time you use -d without an explicit header.

what you wrote vs. what actually goes over the wire
the command
curl -d '{"name":"Ada"}' https://api.example.com/users
the request headers curl actually sends
POST /users HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 16

{"name":"Ada"}
the fix
curl -H 'Content-Type: application/json' -d '{"name":"Ada"}' https://api.example.com/users
The body bytes are identical in both versions — real, valid JSON, byte for byte. Only the label changes. Your server picks which parser to run based on that label, not by inspecting the body, so a correct body with the wrong label gets handled as if it were the wrong body.

// what your API actually does with it

What happens next depends entirely on the framework, and it's rarely an error message that points anywhere near the real cause:

framework-by-framework
ASP.NET Core, with [ApiController]an unbindable [FromBody] parameter is treated as a validation failure — you get an automatic 400 Bad Request / ProblemDetails response before your action method ever runs, with no mention of Content-Type anywhere in it
ASP.NET Core, without [ApiController]no automatic validation exists, so the parameter can bind to null silently — your method runs, and the first thing that touches the parameter throws a NullReferenceException that looks unrelated to the actual request
Express + express.json()the middleware only parses a body when Content-Type matches application/json; anything else leaves req.body undefined, with no error raised at all
None of these produce a message that says "wrong Content-Type." A 400 with no obvious cause, or a null/undefined body that shouldn't be null, is the actual symptom — the header is the thing to check first, before assuming the body itself is malformed.

// why Postman and fetch don't have this problem

Postman's body editor sets Content-Type for you based on which mode you picked (raw + JSON sets application/json automatically), and fetch/axios do the same when you pass an object instead of a raw string. If you tested an endpoint in Postman first and then hand-wrote the equivalent curl command, this is exactly the gap — Postman was quietly doing something curl never does on its own.

// the cURL Converter reproduces this faithfully, not silently

The cURL Converter defaults generated request code to application/x-www-form-urlencoded under exactly the same condition curl does — no explicit Content-Type header in the command you pasted. It doesn't try to detect that your body "looks like JSON" and quietly fix it, because guessing wrong in the other direction (assuming JSON when a real form submission was intended) would be its own silent bug. If the generated code's Content-Type looks wrong, the fix is the same one-line addition to the original curl command: add -H 'Content-Type: application/json', then reconvert.

// frequently asked questions

Why doesn't curl -d send Content-Type: application/json automatically?

Because -d predates JSON as a web convention — it was built for HTML form submission, and curl still defaults its Content-Type to application/x-www-form-urlencoded regardless of what the body actually contains. curl never inspects the body to guess its format; it sends exactly the header you gave it, or that one default if you gave it none.

What does my API actually receive when the Content-Type is wrong?

The real JSON text, byte for byte, labeled as a different format. Nothing about the body itself is corrupted — the server just never hands it to a JSON parser, because it trusts the Content-Type header to decide which parser to use, and that header says form data.

Why does ASP.NET Core return 400 instead of a JSON parse error?

With [ApiController], a [FromBody] parameter that can't be bound is treated as a validation failure, not a parse failure — the framework never gets far enough to attempt parsing, so it returns its automatic 400 Bad Request / ProblemDetails response before your action method ever runs.

Why did my Express req.body come back undefined instead of an error?

express.json() only parses the body when Content-Type matches application/json (or a type you explicitly configured it to accept). Any other Content-Type means the middleware skips the body entirely and leaves req.body undefined — silently, with no error — rather than rejecting the request.

Does the cURL Converter fix this automatically?

No, and deliberately not — it translates your curl command faithfully, defaulting to application/x-www-form-urlencoded exactly when curl itself would, unless your command already includes an explicit Content-Type header. If the header's missing, the generated code reproduces the same bug rather than silently guessing what you meant.