spyderproxy

cURL POST Request: Send JSON, Form Data & Files (2026)

A

Alex R.

|
Published date

Sun May 10 2026

Quick verdict: Use -d '{"key":"value"}' -H "Content-Type: application/json" for JSON. Use -F "field=value" -F "[email protected]" for multipart file uploads. Use -d "name=alice&age=30" for form-encoded data. cURL adds -X POST automatically when you use -d or -F, so you rarely need it explicitly.

POST JSON

The most common pattern in 2026:

curl -X POST https://api.example.com/users      -H "Content-Type: application/json"      -d '{"name":"Alice","email":"[email protected]"}'

Three things to know:

  • -X POST is technically optional — -d implies POST. But explicit is clearer in scripts.
  • -d by default sets Content-Type: application/x-www-form-urlencoded. Override with -H for JSON.
  • Wrap JSON in single quotes (Bash) so dollars and exclamations are not expanded. Inside JSON, double quotes around keys/values.

For complex JSON, read from a file:

curl -X POST https://api.example.com/users      -H "Content-Type: application/json"      -d @user.json

The @ prefix tells cURL "read body from this file." Useful when JSON has special chars or is generated by another tool (jq, etc.).

POST Form-Encoded Data

Old-school HTML form submission:

curl -X POST https://example.com/login      -d "username=alice"      -d "password=secret123"

Multiple -d flags concatenate with &. Equivalent to:

curl -X POST https://example.com/login      -d "username=alice&password=secret123"

Content-Type defaults to application/x-www-form-urlencoded. The body is exactly the encoded string (no JSON wrapping).

URL-Encoded Special Characters

If your form values have spaces, ampersands, or equal signs, use --data-urlencode:

curl -X POST https://example.com/search      --data-urlencode "query=hello world & more"      --data-urlencode "page=1"

cURL escapes the space, ampersand, and other unsafe chars before sending. Without --data-urlencode, the unescaped & would split your value mid-string.

Multipart File Upload

Forms with file inputs use multipart/form-data. Use -F:

curl -X POST https://api.example.com/upload      -F "title=Beach photo"      -F "file=@/home/alice/photo.jpg"

The @ in file=@path is the file-upload syntax. cURL reads the file, sets the right MIME type, and builds the multipart body for you.

Override the MIME type:

-F "[email protected];type=image/jpeg"

Set the filename in the form (different from the disk path):

-F "file=@/tmp/upload.bin;filename=document.pdf;type=application/pdf"

Multiple files at once:

curl -X POST https://api.example.com/upload      -F "[email protected]"      -F "[email protected]"

Raw POST Body (no encoding)

Sometimes you want the body sent verbatim, without cURL stripping newlines or interpreting @:

curl -X POST https://api.example.com/raw      -H "Content-Type: text/plain"      --data-raw "$(cat body.txt)"

--data-raw is identical to -d except it does NOT interpret @ as "read from file." Useful when your body genuinely starts with an @ sign (uncommon but exists).

For binary data:

curl -X POST https://api.example.com/binary      -H "Content-Type: application/octet-stream"      --data-binary @image.png

--data-binary @file reads the file byte-for-byte. -d @file strips line endings, which corrupts binary data.

POST with Auth

Bearer token + JSON body:

curl -X POST https://api.example.com/items      -H "Authorization: Bearer eyJhbGc..."      -H "Content-Type: application/json"      -d '{"name":"widget"}'

Basic auth + form data:

curl -X POST https://api.example.com/items      -u user:pass      -d "name=widget"

See cURL basic authentication for all the auth options.

POST Through a Proxy

Useful for testing geo-locked APIs or routing through residential IPs:

curl -X POST https://api.example.com/data      --proxy http://USER:[email protected]:8000      -H "Content-Type: application/json"      -d '{"region":"us-east"}'

For high-volume API calls behind anti-bot protection, route through Premium Residential proxies for natural-looking traffic.

Capturing the Response

By default cURL prints the body to stdout. Capture it:

# Save to file
curl -X POST https://api.example.com/data -d '...' -o response.json

# Variable in bash
RESPONSE=$(curl -X POST https://api.example.com/data -d '...')

# Get HTTP status code
STATUS=$(curl -X POST https://api.example.com/data -d '...'          -o /dev/null -w "%{http_code}")

Pipe through jq for JSON pretty-printing:

curl -s -X POST https://api.example.com/data -d '{}' | jq .

Debugging POSTs

The -v flag shows the full request and response:

curl -v -X POST https://api.example.com/data -d '{"foo":1}'

You will see lines starting with > (request) and < (response). Critical things to check:

  • Content-Type matches what the API expects
  • Content-Length matches your body size
  • Authorization header is present and not truncated
  • The exact body cURL sent (sometimes shell expansion mangles it)

Use --trace-ascii /dev/stdout for even more detail — shows the bytes cURL sent on the wire.

curl vs wget compares the two download tools. cURL timeout settings covers --max-time and --connect-timeout. Mastering cURL is the full reference. For the JavaScript equivalent, see cURL in JavaScript.