cURL POST Requests: A Practical Guide for API Testing


Sarah Whitmore
Tool Guides
If you've ever needed to check whether an API accepts your JSON, confirm that authentication works, or reproduce a bug in a support ticket, cURL is usually the fastest way to do it. It ships with macOS and Windows 10+, runs everywhere, and gives you exact control over what leaves your machine. A visual client like Postman is convenient, but nothing beats a one-line cURL command you can drop into a script, a CI pipeline, or a README.
This guide focuses on POST requests specifically: how to structure them, how to send headers and JSON, how to authenticate, and how to route them through a proxy when you need to test an endpoint from a different region. If you want the broader picture of what cURL can do, our overview of cURL as a data-transfer tool is a good companion read.
Confirming cURL Is Installed
Most systems already have it. On Debian or Ubuntu, if it's missing, install it with:
sudo apt update && sudo apt install curlOlder Windows builds may need a package from the official cURL site. Once it's in place, verify the version and supported protocols:
curl --versionThe output lists the version plus the features and protocols compiled in (look for HTTPS, HTTP2, and proxy support). One note for Windows: run the commands below in the classic Command Prompt (cmd.exe). PowerShell aliases curl to its own Invoke-WebRequest and handles quoting differently, which trips people up constantly.
Anatomy of a cURL POST Request
Every cURL invocation follows the same shape: the command, some options, and a URL.
curlA POST request submits data to a server — a form, an API call, a webhook. The HTTP specification (RFC 9110) defines POST as the method for sending a payload to be processed by the target resource. In cURL terms you need three things:
The method. Add
-X POST. cURL will infer POST as soon as you attach data, but being explicit keeps your intent obvious to anyone reading the command later.The data. Use
-dfollowed by the payload. The exact format is dictated by the API you're calling.The URL. The endpoint that receives the request.
A minimal example against httpbin.org, which echoes back whatever you send it and is perfect for testing:
curl \
-X POST \
https://httpbin.org/post \
-d "message=HelloFromEvomi"Most modern APIs expect JSON rather than form-encoded data. For that you set a Content-Type header (covered below) and pass the JSON string:
curl \
-X POST \
https://httpbin.org/post \
-H "Content-Type: application/json" \
-d '{"user":"test_account", "status":"active", "id":123}'Wrap the JSON in single quotes so the shell doesn't try to interpret the double quotes inside it. On Windows Command Prompt you'll usually need to escape the inner quotes instead, since cmd.exe treats single quotes literally.
Headers, Authentication, and Files
Real API calls carry metadata in headers. Add each one with a separate -H flag in "Header-Name: Value" form:
curl -X POST https://httpbin.org/post \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "User-Agent: MyAwesomeApp/1.0" \
-d '{"item":"widget", "quantity":5}'Two authentication patterns cover the majority of APIs. The first is HTTP Basic auth — a username and password — handled by the -u flag:
curl \
-u "your_username:your_password" \
-X POST \
https://api.example.com/data \
-d '{"info":"some_data"}'If you work with proxies regularly, that same -u versus embedded-credentials distinction matters — our cURL Basic Auth guide breaks down when to use each.
The second is token-based auth, where an API key or OAuth token travels in the Authorization header:
curl -X POST \
https://api.example.com/action \
-H "Authorization: Bearer YOUR_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"perform_task"}'When the payload lives in a file, point cURL at it with -d @ (or --data-binary @ to send the bytes untouched, which is what you want for anything other than trivial form data):
curl \
-X POST \
https://httpbin.org/post \
-H "Content-Type: text/plain" \
--data-binary
The difference between -d and --data-binary is real: plain -d strips newlines and carriage returns, which will quietly corrupt a JSON file or a signed payload. Use --data-binary whenever the exact bytes matter.
Routing POST Requests Through a Proxy
A proxy sits between your machine and the target server. For API and QA work that's genuinely useful: you can confirm that a public endpoint returns the right localized content in another country, verify geo-routing rules, or test that your own service behaves correctly for users on different networks. cURL supports proxies through the -x (or --proxy) flag.
Pass the proxy address and port, and if the proxy needs credentials, include them as protocol://user:password@host:port. Testing an endpoint through one of Evomi's residential proxies looks like this (placeholder credentials shown):
curl \
-x http://your_evomi_user:your_evomi_pass@rp.evomi.com:1000 \
-X POST \
https://api.targetservice.com/submit \
-H "Content-Type: application/json" \
-d '{"location_test":"ch", "value":42}'That sends the POST through the specified residential endpoint. Evomi's proxies are ethically sourced and Swiss-based, with residential, mobile, datacenter, and static ISP options so you can match the connection type to your test. Residential starts at $0.49/GB and datacenter at $0.30/GB, and there are free trials if you want to see how cURL behaves through the network before committing. If you want to confirm which IP and location the proxy actually presents, run a quick check against geo.evomi.com before firing your real request.
Reading Responses and Diagnosing Errors
By default cURL prints the response body to your terminal. That's fine for a quick sanity check, but debugging needs more. Two flags earn their keep:
-ishows the response headers alongside the body.-vprints the full exchange — the request headers you sent, the TLS handshake, and the response headers. This is where you catch a missingContent-Type, a mangled token, or a redirect you didn't expect.
Every response carries an HTTP status code, and learning to read them saves hours:
2xx(200 OK,201 Created): the request succeeded.4xx(400,401,403,404): the problem is on your side — malformed data, missing or invalid credentials, wrong URL.5xx(500,503): the server is failing; retry logic or a support ticket, not a fix to your command.
Send plain text to an endpoint that wants JSON and you'll typically get a 400 Bad Request with a body like:
{
"error": "Invalid JSON payload received"
}Hit a protected route without credentials and you'll see 401 Unauthorized. Some APIs return descriptive messages; others stick to bare status codes. If a proxy-related status is throwing you, our reference on common proxy error codes covers the usual suspects.
A Complete, Reusable Template
cURL has a huge option surface — the official documentation is worth bookmarking — but most POST calls come down to combining method, headers, auth, and data. Here's a template that covers a typical authenticated JSON request with verbose output for testing:
curl -X POST https://api.yourservice.com/v1/process \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"input_data": "value1", "parameter": "config_a"}' \
-vIt sets POST explicitly, sends a bearer token and content type, requests JSON back via Accept, and turns on -v so you can see exactly what happened on the wire. Once a request works from the command line, translating it into Python, Node, or a CI job is trivial.
From here, the natural next step is the read side of the equation — see our walkthrough on performing cURL GET requests to round out your command-line toolkit.
If you've ever needed to check whether an API accepts your JSON, confirm that authentication works, or reproduce a bug in a support ticket, cURL is usually the fastest way to do it. It ships with macOS and Windows 10+, runs everywhere, and gives you exact control over what leaves your machine. A visual client like Postman is convenient, but nothing beats a one-line cURL command you can drop into a script, a CI pipeline, or a README.
This guide focuses on POST requests specifically: how to structure them, how to send headers and JSON, how to authenticate, and how to route them through a proxy when you need to test an endpoint from a different region. If you want the broader picture of what cURL can do, our overview of cURL as a data-transfer tool is a good companion read.
Confirming cURL Is Installed
Most systems already have it. On Debian or Ubuntu, if it's missing, install it with:
sudo apt update && sudo apt install curlOlder Windows builds may need a package from the official cURL site. Once it's in place, verify the version and supported protocols:
curl --versionThe output lists the version plus the features and protocols compiled in (look for HTTPS, HTTP2, and proxy support). One note for Windows: run the commands below in the classic Command Prompt (cmd.exe). PowerShell aliases curl to its own Invoke-WebRequest and handles quoting differently, which trips people up constantly.
Anatomy of a cURL POST Request
Every cURL invocation follows the same shape: the command, some options, and a URL.
curlA POST request submits data to a server — a form, an API call, a webhook. The HTTP specification (RFC 9110) defines POST as the method for sending a payload to be processed by the target resource. In cURL terms you need three things:
The method. Add
-X POST. cURL will infer POST as soon as you attach data, but being explicit keeps your intent obvious to anyone reading the command later.The data. Use
-dfollowed by the payload. The exact format is dictated by the API you're calling.The URL. The endpoint that receives the request.
A minimal example against httpbin.org, which echoes back whatever you send it and is perfect for testing:
curl \
-X POST \
https://httpbin.org/post \
-d "message=HelloFromEvomi"Most modern APIs expect JSON rather than form-encoded data. For that you set a Content-Type header (covered below) and pass the JSON string:
curl \
-X POST \
https://httpbin.org/post \
-H "Content-Type: application/json" \
-d '{"user":"test_account", "status":"active", "id":123}'Wrap the JSON in single quotes so the shell doesn't try to interpret the double quotes inside it. On Windows Command Prompt you'll usually need to escape the inner quotes instead, since cmd.exe treats single quotes literally.
Headers, Authentication, and Files
Real API calls carry metadata in headers. Add each one with a separate -H flag in "Header-Name: Value" form:
curl -X POST https://httpbin.org/post \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "User-Agent: MyAwesomeApp/1.0" \
-d '{"item":"widget", "quantity":5}'Two authentication patterns cover the majority of APIs. The first is HTTP Basic auth — a username and password — handled by the -u flag:
curl \
-u "your_username:your_password" \
-X POST \
https://api.example.com/data \
-d '{"info":"some_data"}'If you work with proxies regularly, that same -u versus embedded-credentials distinction matters — our cURL Basic Auth guide breaks down when to use each.
The second is token-based auth, where an API key or OAuth token travels in the Authorization header:
curl -X POST \
https://api.example.com/action \
-H "Authorization: Bearer YOUR_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"perform_task"}'When the payload lives in a file, point cURL at it with -d @ (or --data-binary @ to send the bytes untouched, which is what you want for anything other than trivial form data):
curl \
-X POST \
https://httpbin.org/post \
-H "Content-Type: text/plain" \
--data-binary
The difference between -d and --data-binary is real: plain -d strips newlines and carriage returns, which will quietly corrupt a JSON file or a signed payload. Use --data-binary whenever the exact bytes matter.
Routing POST Requests Through a Proxy
A proxy sits between your machine and the target server. For API and QA work that's genuinely useful: you can confirm that a public endpoint returns the right localized content in another country, verify geo-routing rules, or test that your own service behaves correctly for users on different networks. cURL supports proxies through the -x (or --proxy) flag.
Pass the proxy address and port, and if the proxy needs credentials, include them as protocol://user:password@host:port. Testing an endpoint through one of Evomi's residential proxies looks like this (placeholder credentials shown):
curl \
-x http://your_evomi_user:your_evomi_pass@rp.evomi.com:1000 \
-X POST \
https://api.targetservice.com/submit \
-H "Content-Type: application/json" \
-d '{"location_test":"ch", "value":42}'That sends the POST through the specified residential endpoint. Evomi's proxies are ethically sourced and Swiss-based, with residential, mobile, datacenter, and static ISP options so you can match the connection type to your test. Residential starts at $0.49/GB and datacenter at $0.30/GB, and there are free trials if you want to see how cURL behaves through the network before committing. If you want to confirm which IP and location the proxy actually presents, run a quick check against geo.evomi.com before firing your real request.
Reading Responses and Diagnosing Errors
By default cURL prints the response body to your terminal. That's fine for a quick sanity check, but debugging needs more. Two flags earn their keep:
-ishows the response headers alongside the body.-vprints the full exchange — the request headers you sent, the TLS handshake, and the response headers. This is where you catch a missingContent-Type, a mangled token, or a redirect you didn't expect.
Every response carries an HTTP status code, and learning to read them saves hours:
2xx(200 OK,201 Created): the request succeeded.4xx(400,401,403,404): the problem is on your side — malformed data, missing or invalid credentials, wrong URL.5xx(500,503): the server is failing; retry logic or a support ticket, not a fix to your command.
Send plain text to an endpoint that wants JSON and you'll typically get a 400 Bad Request with a body like:
{
"error": "Invalid JSON payload received"
}Hit a protected route without credentials and you'll see 401 Unauthorized. Some APIs return descriptive messages; others stick to bare status codes. If a proxy-related status is throwing you, our reference on common proxy error codes covers the usual suspects.
A Complete, Reusable Template
cURL has a huge option surface — the official documentation is worth bookmarking — but most POST calls come down to combining method, headers, auth, and data. Here's a template that covers a typical authenticated JSON request with verbose output for testing:
curl -X POST https://api.yourservice.com/v1/process \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"input_data": "value1", "parameter": "config_a"}' \
-vIt sets POST explicitly, sends a bearer token and content type, requests JSON back via Accept, and turns on -v so you can see exactly what happened on the wire. Once a request works from the command line, translating it into Python, Node, or a CI job is trivial.
From here, the natural next step is the read side of the equation — see our walkthrough on performing cURL GET requests to round out your command-line toolkit.

Author
Sarah Whitmore
Digital Privacy & Cybersecurity Consultant
About Author
Sarah is a cybersecurity strategist with a passion for online privacy and digital security. She explores how proxies, VPNs, and encryption tools protect users from tracking, cyber threats, and data breaches. With years of experience in cybersecurity consulting, she provides practical insights into safeguarding sensitive data in an increasingly digital world.



