To send a POST request with curl, add -d with your data: curl -d 'name=alice' URL sends a form, the way a browser does. For JSON, use --json '{"name":"alice"}', which also sets the JSON content type. To upload a file, use -F 'file=@report.csv'. All three make the request a POST on their own, so you do not need -X POST.
Every command here ran on 10 October 2026 on our Ubuntu 24.04 server, with its curl 8.5.0. The target was httpbin.org, which answers with what it received, so each output shows how the server read the body. We also tested the same JSON from three Windows shells with the curl.exe 8.21.0 that Windows 11 ships.
Which curl option fits which body?
curl has six ways to send a body. They differ in the content type, in what @ means and in what happens to newlines. The manual describes each one:
| Option | Content type curl sends | A leading @ means | Newlines from a file | Use it for |
|---|---|---|---|---|
-d | application/x-www-form-urlencoded | read a file | removed | Simple form fields. |
--data-urlencode | application/x-www-form-urlencoded | read a file | kept, then encoded | Form values with spaces or &. |
--data-raw | application/x-www-form-urlencoded | a literal @ | (no file) | Text that starts with @. |
--data-binary | application/x-www-form-urlencoded | read a file | kept | Exact bytes, with your own -H. |
--json | application/json | read a file | kept | JSON, from curl 7.82.0. |
-F | multipart/form-data | attach a file | kept | File uploads and mixed forms. |
You need curl in a terminal. What is cURL? shows how to check your version, which matters for --json.
How do you send form fields?

Step 1: send fields with -d
curl -s -d 'name=alice' -d 'tool=curl' https://httpbin.org/post
httpbin read {"name":"alice","tool":"curl"} as form fields, sent as application/x-www-form-urlencoded. curl joins several -d options with &, so this is the same as -d 'name=alice&tool=curl'.
Step 2: encode values with spaces or &
-d sends the text exactly as written. A value with a space and an & breaks into pieces:
$ curl -s -d 'q=fish & chips' https://httpbin.org/post | jq -c .form
{" chips":"","q":"fish "}
$ curl -s --data-urlencode 'q=fish & chips' https://httpbin.org/post | jq -c .form
{"q":"fish & chips"}
The & ended the first field, so the server saw q as fish and a second, empty field. --data-urlencode encodes the part after =, and the value arrived whole.
Step 3: watch out for a leading @
When -d data starts with @, curl reads the rest as a file name. We sent -d '@alice', meaning the text, and curl 8.5.0 stopped with curl: option -d: error encountered when reading a file. --data-raw '@alice' sent the text as written.
Step 4: send the same data as a GET
-G moves -d data into the query string of a GET request. curl -s -G -d 'q=curl' https://httpbin.org/anything reached httpbin as a GET with q=curl in the address. That is useful for search forms and APIs that take parameters in the URL.
How do you POST JSON?
Use --json, from curl 7.82.0:
curl -s --json '{"name":"alice","admin":false}' https://httpbin.org/post
httpbin parsed the body as JSON and saw both Content-Type and Accept set to application/json. The manual describes --json as a shortcut for --data-binary plus those two headers. It does not check your JSON, so a typo still goes out.
The common mistake is JSON with -d alone. curl then labels the body as a form, and httpbin found no JSON at all:
$ curl -s -d '{"name":"alice"}' https://httpbin.org/post | jq -c '{json, form, type: .headers["Content-Type"]}'
{"json":null,"form":{"{\"name\":\"alice\"}":""},"type":"application/x-www-form-urlencoded"}
The whole object became the name of one empty form field. On a curl older than 7.82.0, send JSON with --data-binary and -H 'Content-Type: application/json'.
JSON from a file: -d removes the newlines
We saved a small JSON file of 22 bytes with line breaks and sent it twice:
$ curl -s -H 'Content-Type: application/json' -d @body.json https://httpbin.org/post | jq '.data'
"{ \"name\": \"alice\"}"
$ curl -s -H 'Content-Type: application/json' --data-binary @body.json https://httpbin.org/post | jq '.data'
"{\n \"name\": \"alice\"\n}\n"
-d @file removed every newline, as the manual says it does. For JSON that is still valid, but for a signed payload, a CSV or anything binary it changes the data. Use --json @body.json or --data-binary @body.json to send the file as it is.
PUT and PATCH
-X changes the method word when you need one curl has no shortcut for. curl -s -X PUT --json '{"name":"bob"}' https://httpbin.org/anything arrived as a PUT with the JSON body. Use -X for PUT, PATCH and DELETE, not for POST. With -L, -X POST forces POST on every redirect, and in our redirect test that produced a POST with an empty body.
How do you upload a file?
-F sends multipart/form-data, the format of a browser form with a file field. RFC 7578 defines it. An @ before the name attaches the file, and ;type= sets its content type:

curl -s -F 'file=@report.csv;type=text/csv' -F 'note=October' https://httpbin.org/post
httpbin received report.csv under files and note under form. curl set the content type to multipart/form-data with a boundary, a separator line it chooses between the parts. Repeat -F for more files or fields. Two more forms are worth knowing:
-F 'note=<notes.txt'puts the text of a file into a normal field instead of attaching it.--form-string 'note=@alice'sends a value that starts with@or<as plain text. Our run delivered"note":"@alice".
You do not need -H 'Content-Type: multipart/form-data'. When we added it anyway, curl 8.5.0 still appended its boundary, and httpbin read the form.
Why does JSON break on Windows?
Quoting. We sent the same JSON body from three Windows shells with curl.exe 8.21.0 and read what httpbin received:
| Shell and command | What httpbin received | Valid JSON? |
|---|---|---|
Windows PowerShell 5.1: curl.exe --json '{"name":"alice"}' URL | {name:alice} | No |
| PowerShell 7.6.6: the same command | {"name":"alice"} | Yes |
cmd.exe: curl.exe --json "{\"name\":\"alice\"}" URL | {"name":"alice"} | Yes |
cmd.exe: curl.exe --json '{"name":"alice"}' URL | '{name:alice}' | No |
Windows PowerShell 5.1 drops double quotes inside an argument when it starts a program like curl.exe. Microsoft's documentation says PowerShell 7.3 changed this and now passes the quotes on. JSON requires quotation marks around every name, as RFC 8259 defines it, so {name:alice} is not JSON. cmd.exe does not treat single quotes as quotes at all. A Stack Overflow question titled "Unexpected character (''' (code 39))" shows what an API answers to that.
On Windows, use PowerShell 7, escape the quotes in cmd.exe, or keep the JSON in a file and send it with --json @body.json. In Windows PowerShell 5.1, also type curl.exe, because curl there means Invoke-WebRequest.
What does a proxy see of a POST?
We sent the same JSON through a stub proxy on our server. It asked for the login USER:PASS and logged each request:
| Target | What the proxy logged |
|---|---|
https://httpbin.org/post | CONNECT httpbin.org:443 with the login. Nothing of the POST. |
http://httpbin.org/post | POST http://httpbin.org/post with the login and Content-Type: application/json. |
Both posts arrived intact. Over HTTPS the proxy only opens a tunnel, and the POST travels encrypted inside it. Over plain HTTP the proxy handles the request itself and can read the body. Send anything private to https:// addresses. The flags are the usual -x and -U; how to use proxies with curl covers them. With a real gateway that is -x http://premium.hproxy.com:10000 -U USER:PASS and your own login, as our residential proxies use.
What goes wrong most often?
| What you see | Why | What to do |
|---|---|---|
| The API says the body is not JSON | -d labelled it as a form | Use --json, or add -H 'Content-Type: application/json'. |
A field arrives cut at & or a space | -d sends the text as written | Use --data-urlencode. |
option -d: error encountered when reading a file | The data starts with @ | Use --data-raw. |
| A JSON or CSV file lost its line breaks | -d @file removes newlines | Use --data-binary @file or --json @file. |
{name:alice} arrives from Windows | Windows PowerShell 5.1 dropped the quotes | Use PowerShell 7, escaped quotes in cmd, or a file. |
Unexpected character (''' (code 39)) | Single quotes in cmd.exe | Use double quotes with \" inside. |
| An empty POST after a redirect | -X POST together with -L | Drop -X POST. |
What this page could not check
Every body went to httpbin.org, which echoes what it parses; a real API may read a body differently or reject it. The Linux runs used curl 8.5.0 and the Windows quoting test used curl.exe 8.21.0, one run per shell. We did not send bodies large enough to bring in Expect: 100-continue, and no chunked uploads. The proxy was our own stub. The wording of the @ error comes from curl 8.5.0 and may differ in other versions. curl and PowerShell both change between releases, so we will check this page again by 10 January 2027.
Sources
- The curl man page for version 8.23.0, read on 10 October 2026: curl.se/docs/manpage.html.
- RFC 7578, Returning Values from Forms: multipart/form-data, July 2015: rfc-editor.org/rfc/rfc7578.
- RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format, December 2017: rfc-editor.org/rfc/rfc8259.
- Microsoft Learn, about_Parsing (arguments with quote characters), updated 29 June 2026: learn.microsoft.com.
- Microsoft Learn, Invoke-WebRequest for Windows PowerShell 5.1, with its curl alias, updated 20 January 2026: learn.microsoft.com.
- Stack Overflow, "Unexpected character (''' (code 39))", asked 19 July 2015, read through the Stack Exchange API on 10 October 2026.
- Our own POST tests of 10 October 2026: curl 8.5.0 on Ubuntu 24.04 against httpbin.org and through a stub proxy, and curl.exe 8.21.0 from three Windows shells. This page's research folder keeps every transcript, capture and script from those runs.


