cURLParse cURL Converter

The cURL Flags That Actually Matter

cURLParse Guides · Updated 2026-10-14 · All guides

Of curl's roughly 230 options, about fifteen carry the entire internet. Learn these and you can read any "copy as cURL" from devtools, and our converter will handle the mapping to your language.

Everything below was checked against curl 8.5.0 against a local echo server that prints the raw request. Where the flag's reputation and the transcript disagree, the transcript wins.

The request shape

  • -X POST — method. Suspicious when present: if it's missing, curl inferred POST from -d.
  • -H 'Key: value' — headers, repeatable. -H 'Content-Type: application/json' is the one that trips everyone; with -d, curl defaults to application/x-www-form-urlencoded, so the API sees a form POST you meant to be JSON.
  • -d / --data-raw / --data-urlencode — body. -d mangles characters (@ reads a file, % is untouched); --data-raw sends your text byte-for-byte.
  • -G — pairs with -d to turn the data into a query string on a GET. This is the flag that makes -d on a GET legitimate. See GET with a body for why that shape exists.

The Content-Type default is not folklore. This is what a server sees for curl -d 'name=spark' http://host/ with no -X and no -H at all:

POST / HTTP/1.1
Host: 127.0.0.1:8992
User-Agent: curl/8.5.0
Accept: */*
Content-Length: 10
Content-Type: application/x-www-form-urlencoded

POST appeared out of nowhere and so did the content type. That single transcript explains most "my JSON endpoint returns 400 invalid JSON" reports — see curl works, my code fails for the same trap from the client side.

Three flags that lie about your body

-d treats a leading @ as "read this file", and -d also deletes every newline it finds. Both are silent. Sent to the same endpoint, three nearly identical commands produced three different bodies:

# 1. NDJSON file, three records, 24 bytes on disk
printf '{"a":1}\n{"a":2}\n{"a":3}\n' > recs.ndjson

curl -H 'Content-Type: application/x-ndjson' -d @recs.ndjson        http://host/
# server received: {"a":1}{"a":2}{"a":3}      <- one 21-byte record, unparseable

curl -H 'Content-Type: application/x-ndjson' --data-binary @recs.ndjson http://host/
# server received: {"a":1}\n{"a":2}{"a":3}\n  <- intact

The -d @file form is the one every blog post shows, and for NDJSON, GraphQL queries, PEM keys, or anything where a newline is data, it corrupts the payload without an error, a warning, or a non-zero exit code. The parser on the other end gets one giant malformed record.

And the third variant inverts the meaning entirely:

curl --data-raw @recs.ndjson http://host/
# server received: @recs.ndjson    <- 12 bytes, the literal filename

--data-raw does exactly what it says and skips the @ preprocessing, which means it will happily send you the string @recs.ndjson. If you reached for --data-raw specifically to be safe about escaping, that safety is what dropped your file.

Two more from the same transcript, both worth memorizing:

curl -d @missing.txt http://host/     # exit 26, "error encountered when reading a file"
curl -d 'note=100%25 discount' http://host/   # %25 sent as-is; -d does NOT url-decode

Spaces are not collapsed — a b c arrives as a b c. It is only the line breaks that vanish, which is precisely the class of damage a diff tool reports as "no changes" and a reviewer never sees.

The transport details

  • --compressed — asks for gzip/brotli. Without it you can time a slow API, conclude the server is slow, and be timing an uncompressed 4MB response.
  • -L — follow redirects. Without it, a 301 from an HTTP endpoint gives you the redirect page, and your parse silently succeeds on the wrong document.
  • --connect-timeout 5 --max-time 30 — CI scripts need both. Without them a hung TCP handshake waits 130+ seconds, and your pipeline's "timeout" fires at the wrong layer.
  • -k / --insecure — skips TLS verification. Useful for a lab, indefensible in production; the fix for a self-signed cert is the CA bundle, --cacert.

The output details

  • -i / -D - — response headers. Half of "it worked in curl" complaints are people not looking at the status line; -i forces you to see a 400 before the body pretends to be success.
  • -s — silence. Combine with -S to keep errors visible while hiding the progress meter: the two flags together are the correct answer to 90% of "curl is noisy" advice.
  • -w '%{http_code}\n' — status code alone, for when you just want to assert.
  • --fail-with-body — non-zero exit on HTTP errors, and keeps the body printed. Old-timers say -f; -f hides the error body, which is the only interesting part.

The one habit that pays for itself is asserting on the status without reading the body, because a 400 with -s and no -f looks exactly like success to a shell script:

code=$(curl -s -o /tmp/out.json -w '%{http_code}' https://api.example.com/v1/thing)
[ "$code" = 200 ] || { echo "upstream returned $code:"; cat /tmp/out.json; exit 1; }

Devtools "Copy as cURL" also emits --compressed and -H 'Accept: */*' in places you wouldn't have typed them. They are noise and you can delete them.

One boundary worth stating plainly: this site's converter reads the URL, -H headers, -X, and -d. It does not read -G, -u, -b/-c, -F, --data-binary, --data-urlencode, or --compressed — verified by feeding each to the shipped parser and inspecting the parsed result. So an empty headers={} in generated Python after a -u user:pass command is the parser being narrow, not your credentials vanishing. Read the flags you typed against the code you got back before you trust either.

FAQ

What's the difference between -d, --data-raw, and --data-binary? -d strips newlines and interprets a leading @ as a filename. --data-binary keeps newlines but still honors @. --data-raw honors neither — it sends your argument byte-for-byte, including a literal @. Only --data-binary @file means "this file, unmodified".

Why did my multi-line POST body arrive as one line? Because you used -d @file, which deletes newlines as designed. Switch to --data-binary @file. Nothing logged this; check the receiving end.

Is -f enough to make a script fail on a bad request? No. -f exits non-zero but discards the response body, which is where the error message lives. Use --fail-with-body, or capture the code with -w '%{http_code}' and branch on it.

Do I need --compressed if the server supports gzip anyway? No server will compress because you asked nicely — it compresses because your Accept-Encoding said that was allowed. -d's cousin devtools adds the flag automatically; your hand-written command will not, and you will benchmark the uncompressed transfer.

Developer Sponsor / Partner
Copied to clipboard!