curl POST JSON: Complete Guide with Real API Examples (2026)
curl is the universal tool for API testing and scripting. Sending JSON POST requests looks simple but has several common pitfalls - forgetting Content-Type, escaping issues on Windows, quoting problems in shell scripts. This guide covers every pattern correctly, with examples you can copy and run immediately.
Why JSON POSTs Trip People Up
The most common mistake when POSTing JSON with curl is forgetting the Content-Type: application/json header. Without it, many APIs reject the request or misparse the body. The second most common mistake is quoting issues in shell - especially on Windows CMD, where single quotes do not work as string delimiters.
A correctly formed curl JSON POST requires three things:
-X POST(or-dalone, which implies POST)-H 'Content-Type: application/json'-d '{"key": "value"}'(the JSON body)
The Essential Template
curl -X POST https://api.example.com/endpoint \
-H 'Content-Type: application/json' \
-d '{"name": "Alice", "email": "alice@example.com"}'
Note: -d alone without -X POST also works - curl defaults to POST when a request body is provided. Using -X POST explicitly is clearer in scripts.
Step-by-Step: A Real API POST
Here is a complete example posting to a hypothetical user creation endpoint, including authentication and response handling:
Step 1: Prepare your JSON payload
# Inline - fine for simple payloads
curl -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer your-token-here' \
-d '{
"name": "Alice Smith",
"email": "alice@example.com",
"role": "editor",
"active": true
}'
Step 2: Handle the response
# -i includes response headers (shows status code, Content-Type, etc.)
curl -i -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name": "Alice"}'
# -s -o /dev/null -w "%{http_code}" - just print the HTTP status code
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name": "Alice"}')
echo "HTTP status: $STATUS"
Step 3: Parse the JSON response with jq
# Extract a field from the JSON response
USER_ID=$(curl -s -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name": "Alice", "email": "alice@example.com"}' \
| jq -r '.id')
echo "Created user ID: $USER_ID"
Sending JSON from a File
For complex or multi-line payloads, storing the JSON in a file is cleaner and avoids quoting hell:
# payload.json
{
"name": "Alice Smith",
"email": "alice@example.com",
"preferences": {
"newsletter": true,
"theme": "dark"
},
"tags": ["admin", "beta-tester"]
}
# Use @filename to read from a file
curl -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d @payload.json
# The @ prefix tells curl to read the value from a file
# Works for any -d argument
Validate Your JSON Before Sending
Sending malformed JSON is a common source of mysterious 400 errors. Paste your payload into our JSON Formatter to validate and pretty-print it before including it in your curl command.
Open JSON FormatterPUT and PATCH Requests
PUT replaces the entire resource. PATCH applies a partial update. Both work identically to POST in curl, just with different -X values:
# PUT - replace the entire user record
curl -X PUT https://api.example.com/users/123 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer your-token' \
-d '{
"name": "Alice Smith",
"email": "alice.smith@example.com",
"role": "admin",
"active": true
}'
# PATCH - update only specific fields
curl -X PATCH https://api.example.com/users/123 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer your-token' \
-d '{"role": "admin"}'
Sending Arrays and Nested Objects
# Array at the top level
curl -X POST https://api.example.com/batch/users \
-H 'Content-Type: application/json' \
-d '[
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
]'
# Nested objects
curl -X POST https://api.example.com/orders \
-H 'Content-Type: application/json' \
-d '{
"customer": {"id": 42, "name": "Alice"},
"items": [
{"productId": 1, "quantity": 2, "price": 9.99},
{"productId": 7, "quantity": 1, "price": 24.99}
],
"shipping": {
"method": "express",
"address": {"street": "123 Main St", "city": "Springfield"}
}
}'
Using Environment Variables in the Payload
Building dynamic JSON bodies using environment variables requires care with quoting. Use double quotes around the -d argument to allow variable expansion:
USER_NAME="Alice Smith"
USER_EMAIL="alice@example.com"
# Double quotes allow $VAR substitution
curl -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d "{\"name\": \"$USER_NAME\", \"email\": \"$USER_EMAIL\"}"
# Cleaner alternative: use jq to build the JSON safely
# (handles special characters and escaping automatically)
PAYLOAD=$(jq -n \
--arg name "$USER_NAME" \
--arg email "$USER_EMAIL" \
'{"name": $name, "email": $email}')
curl -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d "$PAYLOAD"
The jq approach is strongly recommended when variables may contain special characters like quotes, backslashes, or newlines - it handles JSON encoding correctly and prevents injection issues.
Handling Errors
# -f / --fail: exit with code 22 on HTTP 4xx/5xx (useful in scripts)
curl -f -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name": "Alice"}' || echo "Request failed"
# -w writes output variables after transfer completes
curl -s -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name": "Alice"}' \
-w "\nHTTP %{http_code} in %{time_total}s\n"
# Check for non-2xx in bash scripts
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-d '{"name": "Alice"}')
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | head -n -1)
if [ "$HTTP_CODE" -ge 400 ]; then
echo "Error $HTTP_CODE: $BODY"
exit 1
fi
Windows CMD and PowerShell
Single quotes do not work as string delimiters in Windows CMD. Use double quotes and escape inner double quotes with backslash:
:: Windows CMD
curl -X POST https://api.example.com/users ^
-H "Content-Type: application/json" ^
-d "{\"name\": \"Alice\", \"email\": \"alice@example.com\"}"
# PowerShell - use single quotes for outer, escape inner
curl -X POST https://api.example.com/users `
-H 'Content-Type: application/json' `
-d '{"name": "Alice", "email": "alice@example.com"}'
# PowerShell alternative: use Invoke-RestMethod (native PS cmdlet)
Invoke-RestMethod -Method POST -Uri "https://api.example.com/users" `
-ContentType "application/json" `
-Body '{"name": "Alice"}'
FAQ
Why does my API return 415 Unsupported Media Type?
The 415 status means the server does not accept the Content-Type you sent. Almost always, this means you forgot -H 'Content-Type: application/json'. Without it, curl defaults to application/x-www-form-urlencoded, which most JSON APIs reject. Add the header and the error goes away.
Does -d automatically set Content-Type to application/json?
No. curl sets Content-Type: application/x-www-form-urlencoded by default when you use -d. You must explicitly add -H 'Content-Type: application/json' for every JSON request. Some newer tools like httpie or xh default to JSON, but curl does not.
What is the difference between -d and --data-raw?
-d @filename reads content from a file. --data-raw treats the value literally - the @ character is not treated as a file indicator. Use --data-raw if your JSON string itself contains an @ symbol. For all other cases, -d is fine.
How do I send a JSON null value?
JSON null is a bare keyword without quotes: {"field": null}. Ensure it is not quoted as a string ({"field": "null"} is a different value - the string "null"). Validate your payload in the JSON Formatter to confirm null values are correct before sending.
Can I pretty-print the curl response in the terminal?
Yes. Pipe the output to jq: curl -s ... | jq . - jq formats and colorises the JSON. If jq is not installed, python3 -m json.tool works on most systems: curl -s ... | python3 -m json.tool.
Use our free tool here → JSON Formatter to validate and pretty-print your request payload before sending it with curl.
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.