How to POST JSON Data Using cURL — Complete Guide with Examples
cURL is the standard tool for making HTTP requests from the command line. Posting JSON requires the right headers and body format — two elements that trip up most beginners. This guide covers every cURL POST scenario: simple JSON, nested data, authentication, posting from a file, Windows quoting issues, debugging responses, and the most common mistakes that cause 400 Bad Request errors when the JSON looks correct.
-d
flag to send request body data
-H
flag to add headers (Content-Type required)
-X POST
explicitly set HTTP method
--data-raw
send exact string without processing
Basic JSON POST — The Essential Pattern
Every JSON POST with cURL requires two things: the Content-Type: application/json header to tell the server what format you're sending, and the JSON body itself passed with the -d flag. Without the Content-Type header, many APIs reject the request or fail to parse the body correctly.
The two required elements
The two required elements for a JSON POST: (1) -H "Content-Type: application/json"to tell the server you're sending JSON, and (2) -d '{"key":"value"}'for the request body. Without the Content-Type header, many APIs reject or misparse the body. The -X POST flag is optional when using -d — cURL defaults to POST when a body is provided.
# Simple JSON POST — minimal required flags
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice","email":"alice@example.com","age":30}'
# With authorization header (Bearer token)
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -H "Authorization: Bearer your-token-here" -d '{"name":"Alice","email":"alice@example.com"}'
# POST and see response headers too (-i includes response headers)
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice"}' -i
# POST with verbose output — shows full request AND response headers
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice"}' -v
# POST and save response to file
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice"}' -o response.json
# Just print the HTTP status code
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice"}' -o /dev/null -w "%{http_code}"POST JSON from a File
For complex or multiline JSON, posting from a file is cleaner and avoids shell quoting issues. Use the @filename syntax with the -d flag. This also makes it easy to version control your test payloads.
# Create a JSON file
cat > payload.json << 'EOF'
{
"name": "Alice Johnson",
"email": "alice@example.com",
"preferences": {
"newsletter": true,
"notifications": ["email", "sms"]
}
}
EOF
# POST from file (@ prefix means "read from file")
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d @payload.json
# POST with --data-binary to preserve exact bytes (including newlines)
# Use this if your JSON contains special characters or binary data
curl -X POST https://api.example.com/users -H "Content-Type: application/json" --data-binary @payload.json
# POST from a generated/computed JSON file
python3 -c "import json; print(json.dumps({'name': 'Alice', 'ts': __import__('time').time()}))" | curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d @- # @- means "read from stdin"Nested JSON and Arrays
# Nested objects — use single quotes on Unix/Mac
curl -X POST https://api.example.com/orders -H "Content-Type: application/json" -d '{
"customerId": 123,
"items": [
{"productId": "A1", "qty": 2, "price": 29.99},
{"productId": "B3", "qty": 1, "price": 49.99}
],
"shipping": {
"method": "express",
"address": {
"street": "123 Main St",
"city": "Boston"
}
}
}'
# Windows cmd.exe — use double quotes and escape inner quotes with backslash
curl -X POST https://api.example.com/users ^
-H "Content-Type: application/json" ^
-d "{"name":"Alice","age":30}"
# Windows PowerShell — single quotes work, or use a file
curl -X POST https://api.example.com/users `
-H "Content-Type: application/json" `
-d '{"name":"Alice","age":30}'
# Best practice for Windows: always use a file to avoid quote escaping
echo {"name":"Alice","age":30} > payload.json
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d @payload.jsonAuthentication Patterns
# Bearer token (most common for REST APIs)
curl -X POST https://api.example.com/data -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." -d '{"query": "example"}'
# API key in header
curl -X POST https://api.example.com/data -H "Content-Type: application/json" -H "X-API-Key: your-api-key-here" -d '{"query": "example"}'
# Basic auth (username:password)
curl -X POST https://api.example.com/data -H "Content-Type: application/json" -u "username:password" -d '{"query": "example"}'
# API key as query parameter (less secure — shows in server logs)
curl -X POST "https://api.example.com/data?api_key=your-key" -H "Content-Type: application/json" -d '{"query": "example"}'
# Store credentials in .netrc to avoid typing them
# ~/.netrc: machine api.example.com login username password mypassword
curl -X POST https://api.example.com/data -H "Content-Type: application/json" --netrc -d '{"query": "example"}'Common Mistakes and Fixes
Missing Content-Type causes 400 Bad Request
Missing header — API rejects or misparses
# ❌ Missing Content-Type header — server can't parse body
curl -X POST https://api.example.com/users -d '{"name":"Alice","email":"alice@example.com"}'
# Result: 400 Bad Request or body parsed incorrectlyCorrect — server knows to parse body as JSON
# ✅ Include Content-Type: application/json always
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice","email":"alice@example.com"}'
# Result: 200 OK or 201 CreatedMissing Content-Type header
Without -H "Content-Type: application/json", the server may not know how to parse the body. It defaults to application/x-www-form-urlencoded, which produces a completely different parse result. Always include it for JSON payloads.
Quote escaping on Windows
Windows cmd requires escaping inner quotes with backslash: {"name":"Alice"} becomes {\"name\":\"Alice\"}. Best practice: use -d @file.json with a JSON file to avoid escaping issues entirely on all platforms.
-d vs --data-raw
-d interprets @ as a file reference and strips newlines. --data-raw sends the string exactly as-is without processing. Use --data-raw when your JSON string contains @ characters that should not be treated as file paths.
Checking the response properly
Add -i to see response headers. Add -v for full verbose output showing both request and response headers. Add -w "%{http_code}" -o /dev/null to print only the status code. Pipe to jq for formatted JSON output.
Debugging Techniques
# 1. Validate your JSON before sending
echo '{"name":"Alice","age":30}' | python3 -m json.tool
# Valid: prints formatted JSON
# Invalid: json.decoder.JSONDecodeError: ...
# 2. See exactly what cURL sends (dry run)
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice"}' --trace-ascii /dev/stdout 2>&1 | head -50
# 3. Pretty-print JSON response with jq
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice"}' | jq .
# 4. Save response to file AND see it in terminal simultaneously
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice"}' | tee response.json | jq .
# 5. Test against httpbin.org (returns exactly what you sent)
curl -X POST https://httpbin.org/post -H "Content-Type: application/json" -d '{"name":"Alice","age":30}' | jq .json
# Shows your JSON as the server received it — useful for debuggingUse httpbin.org to debug what you're actually sending
https://httpbin.org/postas the URL. httpbin returns a JSON response that shows exactly what headers and body it received. The .json field in the response shows your parsed JSON body. This is the fastest way to debug cURL request formatting issues.