NV ToolkitFormat · Validate · Convert
Guide

How to POST JSON with curl (Without Quoting Pain)

Every developer's most-retyped command. The shape is simple — method, header, data — and the pain is entirely in shell quoting. Here's the canonical form, the Windows differences, and the @file trick that sidesteps quoting forever.

In short

POST JSON with curl using curl -X POST -H "Content-Type: application/json" -d '<json>' — the Content-Type header is mandatory, not optional, or the server may receive form-encoded data. On Windows, cmd.exe and PowerShell quoting rules differ from Linux/macOS, so the safest cross-platform approach is -d @body.json, reading the payload from a file instead of inlining it.

By NaveenKumar T · Software engineer · builds and maintains NV Toolkit Published 2026-06-04 2 min read

The canonical command

Three parts: -X POST (often optional — -d implies POST), the Content-Type header (NOT optional — without it servers receive form-encoding and many reject or misparse the body), and -d with the JSON. On Linux/macOS, wrap the JSON in single quotes so the double quotes inside survive untouched.

plaintext
curl -X POST https://api.example.com/orders \
  -H "Content-Type: application/json" \
  -d '{"sku": "KB-75", "qty": 1}'

# add auth + capture status:
curl -s -w "\n%{http_code}\n" -X POST https://api.example.com/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"sku": "KB-75", "qty": 1}'

Windows: where the quoting rules flip

cmd.exe has no single-quoted strings — you must use double quotes outside and escape the inner ones as \". PowerShell's native quoting fights curl too (and aliases curl to Invoke-WebRequest on older versions — use curl.exe explicitly). The sanity-preserving move on Windows: don't inline JSON at all; use a file.

plaintext
:: cmd.exe — escape inner quotes
curl.exe -X POST https://api.example.com/orders -H "Content-Type: application/json" -d "{\"sku\": \"KB-75\", \"qty\": 1}"

# PowerShell — single quotes work, but curl.exe (not the alias!)
curl.exe -X POST https://api.example.com/orders -H "Content-Type: application/json" -d '{\"sku\": \"KB-75\"}'

The @file trick: stop escaping entirely

-d @body.json reads the payload from a file — no shell quoting, no escaping, identical on every OS, and the file is reusable and reviewable. This is the professional default for anything beyond a tiny test body. Validate the file first (one click in our validator) so a typo'd comma doesn't masquerade as an API error, and minify large payloads to one line if logs or tooling prefer it.

plaintext
curl -X POST https://api.example.com/orders \
  -H "Content-Type: application/json" \
  -d @body.json

# read from stdin instead:
echo '{"sku":"KB-75"}' | curl -X POST -H "Content-Type: application/json" -d @- https://api.example.com/orders

Reading what comes back

APIs answer in minified JSON. Pipe through a formatter — curl … | python -m json.tool or | jq . — or paste the response into our JSON Formatter for folding, tree view, and JMESPath filtering when the response is large. For debugging the request itself, -v shows exactly what was sent: nine times out of ten, the 'API bug' is a missing Content-Type or a body the shell mangled — both instantly visible there.

How this was tested: The curl and Python examples were run with curl 8.9 and Python 3.12 on 2026-09-10.

Sources

  1. curl — Manual (Everything curl)
  2. MDN — Content-Type header
  3. RFC 8259 — The JSON Data Interchange Format

Frequently asked questions

Why does the server say 'invalid JSON' when my JSON is valid?

Usually the shell ate your quotes before curl ever sent them — the server received {sku: KB-75} or worse. Run with -v and read the actual body sent. The @file method eliminates this class of bug completely.

Do I need -X POST if I'm using -d?

No — -d switches the request to POST automatically. It's harmless to include and common for clarity. (You DO need an explicit -X for PUT/PATCH with the same -d syntax.)

How do I send JSON with special characters or newlines?

From a file (-d @body.json) — files carry any content byte-perfectly. If it must be inline, JSON-escape the content first (newlines as \n, quotes as \") — our JSON Escape tool produces exactly that string.

Validate the body before you curl it — exact error positions beat deciphering a 400 response. Then minify to one line for clean -d usage.

Prep your payload

Keep working

Related tools