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

1

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.

bashBasic cURL POST with JSON
# 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}"
2

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.

bashSend JSON from file
# 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"
3

Nested JSON and Arrays

bashComplex nested JSON POST
# 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.json
4

Authentication Patterns

bashCommon authentication methods
# 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"}'
5

Common Mistakes and Fixes

Missing Content-Type causes 400 Bad Request

Missing header — API rejects or misparses

❌ Bad
# ❌ 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 incorrectly

Correct — server knows to parse body as JSON

✅ Good
# ✅ 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 Created

Missing 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.

6

Debugging Techniques

bashDebugging tools for cURL POST requests
# 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 debugging

Use httpbin.org to debug what you're actually sending

When unsure if cURL is sending what you think it is, use 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.

Frequently Asked Questions