curl POST JSON With a Bearer Token: The Exact Command

To POST JSON with a Bearer token, run curl -X POST with two headers: Content-Type: application/json and Authorization: Bearer YOUR_TOKEN, then add -d with your JSON body and an https URL. The first header declares the format; the second carries your credential. Always use HTTPS.

The exact command

Here is the canonical form, broken across lines with backslash continuations for readability. This is the command you copy, swap in your token and URL, and run:

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"title":"Hello","published":true}' \
  https://api.example.com/posts

Each flag does exactly one job. -X POST sets the HTTP method. The first -H tells the server the request body is JSON, so it parses it instead of treating it as form data. The second -H attaches your credential in the standard Authorization: Bearer <token> format that OAuth 2.0 and most modern APIs expect. The -d flag carries the request body.

One subtlety worth knowing: -d automatically implies a POST, so -X POST is technically redundant when you supply a body with -d. Keeping -X POST in is harmless and makes the intent obvious to whoever reads the command later, which is why most snippets leave it in.

Why the Content-Type header is not optional

This is the single most common reason a curl request that "looks right" returns a 400 or 415. By default, when you pass data with -d, curl sets Content-Type: application/x-www-form-urlencoded — the form-encoding type, not JSON. A JSON API receiving that header will either reject the request or fail to parse your body, even though the body itself is valid JSON.

Setting -H "Content-Type: application/json" explicitly fixes this. If you ever want to confirm what curl is actually sending, add -v (verbose) and read the lines prefixed with >, which show the exact outgoing headers and method. It is the fastest way to debug a mismatch between what you think you sent and what the server received.

You can also add -H "Accept: application/json" to tell the server you want a JSON response back. Some APIs use the Accept header for content negotiation and will otherwise return XML or HTML.

Reading the body from a file: -d @file.json

Inline JSON gets unwieldy fast, and shell quoting around braces and quotes is error-prone. The cleaner approach is to keep the payload in a file and let curl read it. Prefix the filename with @:

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d @payload.json \
  https://api.example.com/posts

The @ tells curl to read the body from payload.json rather than treating the string literally. Use @- to read from stdin instead, which is handy for piping generated JSON: echo '{"a":1}' | curl -d @- .... Note that curl does not validate the JSON for you — if the file contains a trailing comma or a stray quote, curl sends it as-is and the server rejects it. Run your payload through a JSON formatter first to catch syntax errors before they reach the wire.

The modern shortcut: --json

curl 7.82.0 (released March 2022) added a --json flag that bundles three things into one option: it sends the data as the body, sets Content-Type: application/json, and sets Accept: application/json. You still add the Authorization header yourself:

curl --json @payload.json \
  -H "Authorization: Bearer YOUR_TOKEN" \
  https://api.example.com/posts

Check your version with curl --version. If it reports 7.82.0 or newer, --json is the tidiest form. On older systems, stick with the explicit -d plus -H "Content-Type: application/json" combination, which works everywhere. Keep in mind that --json still does no validation of the data it sends.

Fetch the token, then use it: the TOKEN=$(...) pattern

Most real APIs make you authenticate first to get a token, then send it on subsequent requests. The standard shell idiom is to capture the token into a variable using command substitution and jq to pull the right field out of the JSON login response:

TOKEN=$(curl -s -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"s3cret"}' \
  https://api.example.com/auth/login | jq -r '.access_token')

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${TOKEN}" \
  -d '{"title":"Hello"}' \
  https://api.example.com/posts

The -s flag silences curl's progress meter so only the JSON response is piped to jq. The -r (raw) flag on jq strips the surrounding quotes so TOKEN holds the bare string. Adjust .access_token to whatever your API names the field — common variants are .id_token, .token, or a nested path like .data.accessToken. If a token looks malformed, paste it into a JWT decoder to inspect its claims and expiry without trusting a third-party site.

HTTPS only, and other safety notes

Send tokens over https:// exclusively. A Bearer token is a plaintext credential; over plain HTTP it travels unencrypted and anyone on the network path can read and reuse it. Treat it like a password.

Two more habits worth adopting. First, avoid passing the token inline on the command line in shared or logged shells — the full command (token included) lands in your shell history and process listings. Prefer the TOKEN=$(...) variable approach or an environment variable. Second, when you need to hand this command to a teammate who works in Python, JavaScript, or Go, do not rewrite it by hand. Drop it into a curl-to-code converter and it will produce the equivalent request in the target language, headers and body intact.

Quick reference

GoalFlag
Set method to POST-X POST (implied by -d)
Declare JSON body-H "Content-Type: application/json"
Attach Bearer token-H "Authorization: Bearer TOKEN"
Inline body-d '{"k":"v"}'
Body from file-d @file.json
Modern one-flag JSON--json @file.json (curl 7.82+)
Silence progress for piping-s
Debug outgoing headers-v

With these eight flags you can express almost any authenticated JSON POST. Start from the exact command at the top, swap in your endpoint and token, and add -v the moment anything behaves unexpectedly.

Frequently Asked Questions

No. The -d flag automatically switches curl to a POST request, so -X POST is technically redundant when you supply a body. Many people keep -X POST anyway because it makes the command's intent clear at a glance.

The most common cause is a missing Content-Type header. When you use -d, curl defaults to application/x-www-form-urlencoded, not JSON. Add -H "Content-Type: application/json" so the server parses your body correctly.

With -d you must also set the Content-Type header manually. The --json flag (curl 7.82.0 and newer) sets both Content-Type and Accept to application/json automatically, so it is a cleaner single-flag option on modern curl versions.

No. A Bearer token is a plaintext credential, so over plain HTTP anyone on the network path can capture and reuse it. Always send tokens to https:// URLs and avoid leaving them in shell history or logs.