cURLParse cURL Converter

Quoting Hell: Why Your cURL Command Breaks in Bash, zsh, and Windows

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

A curl command fails in four distinct ways, and only one of them is the server's fault. Before blaming the API, blame the shell. If you've ruled out quoting, the next suspect is usually a header or encoding mismatch — see curl works, my code fails.

Single quotes are boring. Use them.

-H 'Authorization: Bearer ***' is inert. Double quotes invite expansion: $VAR interpolates (usually what you want), but backticks and $(...) interpolate too, and a JSON payload containing $(...) becomes a command your shell executes. If the body has no variables, single quotes. Always.

Not hypothetical. This command was sent from bash against a server that prints its request body:

curl -X POST -H 'Content-Type: application/json' \
  -d "{\"note\":\"hi $(echo EXECUTED_1792015198)\"}" https://api.example.com/v1/notes

What the API received:

{"note":"hi EXECUTED_1792015198"}

The $(echo …) never got anywhere near the endpoint. Your shell ran it and spliced the stdout into your payload before curl ever started. Replace echo with aws secrets get or rm -rf and the transcript reads the same way — which is why a body copied out of a tutorial or a ticket deserves single quotes even when it looks inert. Backticks are the same mechanism with the old spelling; the fix is the same one.

# the safe shape: one single-quoted body, variable interpolated in a separate step
BODY=$(printf '{"note":"hi %s"}' "$TOKEN")
curl -X POST -H 'Content-Type: application/json' -d "$BODY" https://api.example.com/v1/notes

Building the JSON with printf/jq and then double-quoting the finished string is the pattern that survives: by the time the shell expands "$BODY" there is nothing left in it to execute.

Windows is three shells and they disagree

  • cmd.exe has no quote stripping worth the name. curl -H 'Content-Type: application/json' sends a header literally named 'Content-Type with a value starting with application/json. Double quotes and escape the inner ones: -H "Content-Type: application/json".
  • PowerShell (5.1) steals curl as an alias for Invoke-WebRequest unless you call curl.exe. Its quote parser then breaks = and @ in ways that look like malware behavior. Windows Terminal's PowerShell 7+ is better; curl.exe is safest.
  • Git Bash behaves like Linux. If your team uses Windows, Git Bash is the least pathological environment for pasting copied commands.

The asymmetry is the trap: single quotes are safer on POSIX shells and fatal on cmd.exe, so the same advice flips on the platform. If your docs serve both audiences, show both — which is what the converter's Windows output mode does.

Here-docs for readable POSTs

curl -X POST https://api.example.com/v1/jobs \
  -H 'Content-Type: application/json' \
  -d @- <<'EOF'
{ "model": "small", "input": "multi-line, no escaping" }
EOF

The <<'EOF' (quoted) blocks interpolation, so the JSON stays verbatim. -d @- reads the body from stdin. The server above received { "model": "small", "input": "multi-line, no escaping" } — byte for byte, quotes intact. This one pattern retires most of the escaping complaints I get asked about.

Two caveats that bite: -d @- still goes through -d's preprocessing, so if your heredoc body contains newlines that matter (NDJSON, GraphQL, PEM), this is the flag that silently deletes them — use --data-binary @-. And an unquoted <<EOF reintroduces everything above: $ and ` `` expand inside it, so the heredoc stops being a safety device.

Continuation lines

A backslash must be the final character on the line. A trailing space after it does not "mostly work": the backslash then escapes the space, not the newline, and you get two failures at once. Verified against bash 5 with curl 8.5.0:

# URL LAST — the next line stops being an argument and becomes its own command:
curl -s -o /dev/null \
   -H "X-Test: 1" \ 
   http://api.example.com/
# bash: http://api.example.com/: No such file or directory     (exit 127)
# URL FIRST — the escaped space becomes a second URL argument:
curl -s -o /dev/null http://api.example.com/ \
   -H "X-Test: 1" \
# curl: (3) URL rejected: Malformed input to a URL function    (exit 3)

The second one is the dangerous variant: the real request still goes out and returns whatever it always returns, and then curl errors on the phantom argument. A CI step that greps the response body for "ok" passes; the exit code is 3; and the person debugging sees a URL complaint in a command whose URL is fine. That is where the folklore "curl thinks -H is the URL" comes from — it isn't -H, it's the one-character argument that a stray space left behind.

So when you get curl: (3) URL rejected on a command that has a perfectly good URL in it, look for trailing whitespace after a backslash before you look anywhere else. Our converter outputs one-line commands by default for exactly this reason; use the "pretty" option if your shell is disciplined.

FAQ

Why does my header arrive as 'Content-Type? Windows cmd.exe. It does not strip single quotes. Switch to double quotes or move to Git Bash.

Is "$TOKEN" in a double-quoted header safe? It interpolates, which is usually the point — but never double-quote a payload you copied from the internet. $(...) inside double quotes executes, and the command's output is what ends up in your request body.

PowerShell keeps mangling my JSON body. Which shell should I use? curl.exe from PowerShell 7+, or Git Bash. Avoid Windows PowerShell 5.1 for pasted curl commands entirely.

Why does curl: (3) URL rejected mention a URL I didn't write? Trailing space after a line-continuation backslash. The backslash escapes the space instead of the newline, curl gets a " " argument, and it parses that as a URL.

Can I just paste devtools' multi-line "Copy as cURL" straight in? Usually yes — devtools puts the backslashes in correctly. The breakage comes from editors that strip trailing whitespace inconsistently, from copy-paste through a chat app that rewraps lines, and from pasting into a shell that isn't the one the command was written for.

Developer Sponsor / Partner
Copied to clipboard!