POST JSON Data with cURL — Complete Examples Guide

Everything you need to POST JSON data with cURL — from simple single-field objects to nested arrays with authentication headers. Each example is tested and ready to use. Includes debugging tips, common errors, and Windows-specific syntax.

-X POST

flag to set HTTP method to POST

-H

"Content-Type: application/json" required

-d

or --data-raw for the request body

@file.json

read body from a file with -d @filename

1

Simple POST Examples

The three required pieces

Every JSON POST needs three things: (1) -X POST to set the method, (2) -H "Content-Type: application/json" to tell the server what format you're sending, and (3) -d with the JSON body. Miss any one and the API will likely return 400 or 415.

bashBasic POST requests
# Minimal JSON POST
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"alice@example.com"}'

# With API key authentication
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key-here" \
  -d '{"name":"Alice"}'

# With Bearer token (JWT) authentication
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." \
  -d '{"name":"Alice"}'

# Show HTTP status code at the end of the response
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}' \
  -w "\nHTTP Status: %{http_code}\n"

# Pretty-print the JSON response
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}' | python3 -m json.tool

# Or with jq (more powerful)
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}' | jq '.'
2

Nested and Complex JSON

bashComplex nested JSON POST
# Nested object with array
curl -X POST https://api.example.com/orders \
  -H "Content-Type: application/json" \
  -d '{
    "customer": {
      "name": "Alice",
      "email": "alice@example.com"
    },
    "items": [
      {"productId": "P001", "qty": 2, "price": 29.99},
      {"productId": "P002", "qty": 1, "price": 79.99}
    ],
    "total": 139.97,
    "paid": true,
    "metadata": null
  }'

# From a JSON file — cleanest approach for complex payloads
cat > order.json << '''EOF'''
{
  "customer": {"name": "Alice"},
  "items": [
    {"productId": "P001", "qty": 2},
    {"productId": "P002", "qty": 1}
  ]
}
EOF

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

Use -d @file.json for complex payloads

For complex JSON payloads, always write to a file first and use -d @file.json. It eliminates shell quoting issues entirely and is much easier to read, version-control, and modify. The @ prefix tells cURL to read the content from the named file.
3

PUT and PATCH Examples

bashPUT (replace) and PATCH (partial update)
# PUT — replace entire resource (all fields required)
curl -X PUT https://api.example.com/users/123 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer token" \
  -d '{"name":"Alice Updated","email":"alice@example.com","status":"active"}'

# PATCH — partial update (only include fields you want to change)
curl -X PATCH https://api.example.com/users/123 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer token" \
  -d '{"status":"inactive"}'

# DELETE with JSON body (some APIs require this)
curl -X DELETE https://api.example.com/users/123 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer token" \
  -d '{"reason":"user_request"}'
4

Real API Examples

bashReal-world API POST calls
# OpenAI Chat Completion API
curl -X POST https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "Hello!"}],
    "max_tokens": 100,
    "temperature": 0.7
  }'

# Anthropic Claude API
curl -X POST https://api.anthropic.com/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-sonnet-4-6",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Hello, Claude"}]
  }'

# GitHub — create an issue
curl -X POST https://api.github.com/repos/owner/repo/issues \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "Accept: application/vnd.github.v3+json" \
  -d '{"title":"Bug: login fails","body":"Steps to reproduce...","labels":["bug"]}'

# Slack — send message via incoming webhook
curl -X POST https://hooks.slack.com/services/T.../B.../xxx \
  -H "Content-Type: application/json" \
  -d '{"text":"Deploy complete! 🚀","username":"DeployBot"}'

# Note: Stripe uses form encoding, NOT JSON
curl -X POST https://api.stripe.com/v1/payment_intents \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "amount=2000&currency=usd&automatic_payment_methods[enabled]=true"
5

Windows-Specific Syntax

bashcURL on Windows CMD and PowerShell
# Windows CMD — use ^ for line continuation, escape inner double quotes with backslash
curl -X POST https://api.example.com/users ^
  -H "Content-Type: application/json" ^
  -d "{\"name\":\"Alice\",\"age\":30}"

# PowerShell — backtick for line continuation, single quotes work here
curl -X POST https://api.example.com/users `
  -H "Content-Type: application/json" `
  -d '{"name":"Alice","age":30}'

# Best approach for Windows: write JSON to a file first, avoid quoting issues
'{"name":"Alice","age":30}' | Out-File -Encoding utf8 body.json
curl -X POST https://api.example.com/users `
  -H "Content-Type: application/json" `
  -d "@body.json"

# PowerShell with Invoke-RestMethod (alternative to curl)
$body = @{ name = "Alice"; age = 30 } | ConvertTo-Json
Invoke-RestMethod -Uri https://api.example.com/users `
  -Method POST `
  -ContentType "application/json" `
  -Body $body
6

Debugging cURL Requests

bashDebug flags for troubleshooting
# -v (verbose): shows request headers, response headers, and SSL handshake
curl -v -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}'
# Output: > REQUEST HEADERS  < RESPONSE HEADERS  BODY

# -i: include response headers in output (less verbose than -v)
curl -i -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}'

# -w: custom output format — show only status code and timing
curl -s -o /dev/null \
  -w "Status: %{http_code}\nTime: %{time_total}s\nSize: %{size_download} bytes\n" \
  -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}'

# -s -S: silent (no progress bar) but still show errors
curl -s -S -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice"}' | jq .

# Validate JSON before sending (save a broken request trip)
echo '{"name":"Alice","age":30}' | python3 -m json.tool
# If invalid, python prints the error. If valid, it pretty-prints the JSON.
7

Common cURL POST Errors and Fixes

ItemErrorCause + Fix
415 Unsupported Media TypeMissing Content-Type headerAdd -H "Content-Type: application/json" to every JSON POST
400 Bad RequestMalformed JSON in the request bodyValidate JSON first: echo '...' | python3 -m json.tool
401 UnauthorizedMissing or expired auth tokenCheck Authorization header value, refresh token if expired
curl: (6) Could not resolve hostDNS failure or wrong hostnameCheck hostname, test with curl -v to see DNS resolution attempt
curl: (35) SSL connect errorTLS/certificate issueAdd -k to skip verification (test only) or specify --cacert
Empty response body (204)API returns 204 No ContentExpected behavior — check status code with -w "%{http_code}"

-d vs --data-raw

-d treats @ as a filename prefix: -d @file.json reads the file. --data-raw sends the exact string as-is, treating @ as a literal character. Use --data-raw when your JSON contains @ symbols in string values (like email addresses in the literal body string, not via file).

Silent mode for scripts

In scripts and CI pipelines, use -s (silence progress bar) with -S (still show errors) and -f (fail on HTTP errors): curl -sSf -X POST url -d @body.json. The -f flag makes curl exit with code 22 on 4xx/5xx responses.

Timing breakdown

Use -w with timing variables: %{time_namelookup} %{time_connect} %{time_starttransfer} %{time_total} to see where time is spent — DNS, TCP connect, TTFB, and total. Useful for diagnosing slow API calls.

Retries with --retry

For transient failures: curl --retry 3 --retry-delay 2 --retry-all-errors -X POST url -d @body.json. Retries on network errors and 5xx responses up to 3 times with 2 second delays between attempts.

Frequently Asked Questions