
curl POST JSON: Body, Headers and File Examples
To POST JSON with curl, use --json followed by the request body and the endpoint URL. curl selects POST and sets both Content-Type: application/json and Accept: application/json:
curl --json '{"name":"Ada","role":"developer"}' \
https://api.example.com/users
Replace https://api.example.com/users with your endpoint; it is a placeholder, not a working API. These shell examples use Bash or Zsh. --json requires curl 7.82.0 or newer; check your installed version with curl --version. The curl option history records that version requirement.
What --json sends
The --json shortcut combines binary-preserving request data with two JSON headers. You do not need -X POST for this request. curl explains the equivalence in its JSON POST guide.
| Request part | Value in the example |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Accept | application/json |
| Body | {"name":"Ada","role":"developer"} |
Content-Type describes the body you send. Accept tells the server which response format you prefer; the server can still return an error page or another content type.
curl does not validate JSON. It can transmit malformed JSON and let the server reject it. Keep JSON property names and string values in double quotes, even when the shell wraps the whole body in single quotes.
Send JSON without --json
For an older curl installation, write out the headers and use --data-binary:
curl --header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data-binary '{"name":"Ada","role":"developer"}' \
https://api.example.com/users
The data option selects POST here too. Plain -d defaults to a form content type unless you override it. A server expecting JSON may reject that mismatch with 415 Unsupported Media Type, or return an API-specific validation error.
POST JSON from a file
Save this content as payload.json:
{
"name": "Ada",
"role": "developer"
}
Then send the file:
curl --json @payload.json https://api.example.com/users
The @ tells curl to read the file. Without it, --json payload.json sends the literal text payload.json as the body. Keep a reusable request file separate from credentials so you can review or version the payload without exposing a key.
For the manual-header form, use --data-binary @payload.json. The curl manual's data options distinguish this from -d @payload.json: --data strips carriage returns, newlines and null bytes when reading a file; --data-binary preserves the bytes. Removing formatting newlines from valid pretty-printed JSON does not by itself make that JSON invalid, but exact bytes matter for signatures and debugging.
Read JSON from stdin
Use @- to read the request body from standard input. A quoted here-document keeps the shell from expanding $variables or command substitutions inside your JSON:
curl --json @- https://api.example.com/users <<'JSON'
{
"name": "Ada",
"role": "developer"
}
JSON
For generated data, use a JSON serializer rather than concatenating user input into a string. An apostrophe in a name can break a shell-quoted body; an unescaped double quote can break JSON even if the shell accepts the command.
Windows and PowerShell
Use a UTF-8 payload.json file to avoid inline-quote differences between shells. Call the executable as curl.exe on Windows. Microsoft's curl on Windows guide notes that Windows PowerShell 5.1 maps the curl alias to Invoke-WebRequest; PowerShell 7+ does not:
curl.exe --json "@payload.json" "https://api.example.com/users"
The PowerShell line is a file-based variant. The request checks for this article ran on Bash and Zsh, not on Windows. Check curl.exe --version before using --json there.
Add authentication to a JSON request
A bearer token belongs in the Authorization header. Supply WEBCLAW_API_KEY through your environment before running this Webclaw example:
curl --fail-with-body --silent --show-error \
https://api.webclaw.io/v1/scrape \
--header "Authorization: Bearer ${WEBCLAW_API_KEY:?Set WEBCLAW_API_KEY}" \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
-d '{"url":"https://example.com","formats":["markdown"],"only_main_content":true}'
This endpoint returns extracted page content. The scrape API reference documents its fields; the link-to-text guide shows how to use plain text and Markdown in an application.
Environment variables keep literal credentials out of a reusable command, but they do not make every execution context private. An expanded header may still appear in process inspection or diagnostic logs. Avoid sharing verbose output from authenticated requests without removing credentials.
Handle HTTP errors in scripts
Without a fail option, curl can exit with code zero after receiving an HTTP error response. --fail-with-body returns a failure status for HTTP 400 and above while retaining the response body. It requires curl 7.76.0 or newer.
Save the body and print the HTTP status as a separate line:
curl --fail-with-body --silent --show-error \
--json @payload.json \
--output response.json \
--write-out 'HTTP %{http_code}\n' \
https://api.example.com/users
Check the command's exit status before consuming response.json. The filename does not guarantee JSON: a gateway or application can return HTML. The curl fail-with-body reference documents the option and its limits, including authentication-related cases.
During debugging, -i includes response headers in the output. -v also exposes request details, so treat its output as sensitive when authentication is involved. Avoid adding automatic POST retries unless the API documents how to prevent duplicate side effects.
Common curl JSON errors
| Symptom | Likely check |
|---|---|
option --json: is unknown | curl is older than 7.82.0; use the manual headers or update curl |
415 Unsupported Media Type | Confirm the endpoint accepts JSON and the request's Content-Type is correct |
400 Bad Request | Validate JSON syntax, then check required fields and types against the API contract |
401 or 403 | Inspect authentication, permissions and the response body; JSON syntax may be fine |
Server receives payload.json | Add @ before the filename |
| Body works from a file but not inline | Check shell quoting and variable expansion |
| HTML returned instead of JSON | Inspect status, Content-Type and the destination; Accept does not force a JSON response |
Frequently asked questions
Do I need -X POST with --json or -d?
No, those data options select POST for the examples above. Use an explicit method only when the endpoint requires a different method, and check redirect behavior before adding --location to authenticated requests.
Can I use several --json options to merge objects?
curl concatenates their text. It does not merge JSON objects. Two complete objects sent in separate --json options can produce an invalid body. Build one valid JSON document before sending it.
Does --json replace the Authorization header?
No. It handles the body and JSON content negotiation headers. Authentication remains a separate API requirement.
Can I POST JSON with Linux curl?
Yes. The Bash examples work on a Linux installation with a compatible curl version. Check the installed curl version rather than assuming the distribution ships the newest release.
Why does a valid JSON body still fail?
Valid JSON syntax does not guarantee valid API input. An endpoint can reject an unknown field, a missing value or the wrong type. Read the response body and the endpoint's request schema before changing the transport flags.