cURL for API Testing: Commands Every Tester Should Know
cURL is the universal language of API debugging. Developers paste cURL commands into bug tickets, browsers export failing requests as cURL, Postman imports and exports it, and every Linux server and Windows 10+ machine already has it installed. If you can read and write cURL, you can reproduce any API problem in seconds, on any machine, without opening a tool. This lesson gives you every cURL command a tester needs, each runnable against the live practice API.
-X (method), -H (header), -d / --data (body), -i (show response headers), -v (verbose: full request and TLS details), -u (Basic auth), -L (follow redirects), -o (save output), -s (silent), -w (print status code and timing) and --max-time (timeout). Example: curl -i -X POST URL -H "Content-Type: application/json" -d '{"name":"A"}'.Why Testers Should Learn cURL
quantity as a string. Ten minutes instead of two days of back-and-forth.- Exact, shareable reproduction steps in one line.
- Available on servers and CI machines where there’s no GUI.
- Shows the raw truth: no tool silently adding headers.
- Imports straight into Postman (Import → Raw text) and other tools.
Check your install: curl --version. On Windows, use curl.exe in PowerShell (plain curl may be an alias for Invoke-WebRequest in older PowerShell), or use Git Bash.
Your First cURL Commands (GET)
BASE=https://sqatools.in/wp-json/sqa-api/v1
# Body only
curl $BASE/users/1
# Status line + headers + body (use this by default when testing)
curl -i $BASE/users/1
# Headers only (HEAD request)
curl -I $BASE/users/1
# Query parameters - quote the URL so the shell doesn't treat & as "run in background"
curl -i "$BASE/users?role=admin&sort=age&order=desc"
# Let curl encode tricky values
curl -i -G "$BASE/users" --data-urlencode "q=gupta" --data-urlencode "per_page=5"
https://sqatools.in/wp-json/sqa-api/v1/users?role=admin&sort=age&order=desccurl -i \
"https://sqatools.in/wp-json/sqa-api/v1/users?role=admin&sort=age&order=desc"POST, PUT, PATCH, DELETE
# POST JSON (create)
curl -i -X POST "$BASE/objects" \
-H "Content-Type: application/json" \
-d '{"name":"cURL object","data":{"qty":1},"ttl":300}'
# POST JSON from a file (great for big payloads)
curl -i -X POST "$BASE/objects" -H "Content-Type: application/json" -d @order.json
# Newer curl (7.82+): --json sets Content-Type and Accept for you
curl -i --json '{"name":"short form","ttl":60}' "$BASE/objects"
# PUT (create/replace with your own id)
curl -i -X PUT "$BASE/objects/curl-demo-1" \
-H "Content-Type: application/json" \
-d '{"name":"Replaced","data":{"qty":5},"ttl":300}'
# PATCH (partial update)
curl -i -X PATCH "$BASE/objects/curl-demo-1" \
-H "Content-Type: application/json" -d '{"data":{"qty":6}}'
# DELETE
curl -i -X DELETE "$BASE/objects/curl-demo-1"
# Form-encoded body (HTML forms, OAuth token endpoints)
curl -i -X POST "$BASE/echo" -d "name=Asha&city=Pune"
# Multipart file upload
curl -i -X POST https://your-app.test/api/upload -F "file=@invoice.pdf" -F "type=invoice"
cmd.exe, single quotes don’t work. Use double quotes and escape inner ones: -d "{\"name\":\"A\"}", or put the JSON in a file and use -d @body.json. Git Bash and WSL accept the Linux style shown here.Headers and Authentication
# Any header
curl -i "$BASE/users/1" -H "Accept: application/xml"
# Basic auth - curl builds the Base64 header for you
curl -i -u sqa_user:sqa@123 "$BASE/basic/profile"
# API key
curl -i -H "X-API-Key: sqa-key-12345" "$BASE/apikey/profile"
# Bearer token: log in, extract the token with jq, then call
TOKEN=$(curl -s -X POST "$BASE/auth/login" -H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin@123"}' | jq -r .access_token)
curl -i -H "Authorization: Bearer $TOKEN" "$BASE/auth/me"
# Conditional request with an ETag
curl -i "$BASE/products/101" -H 'If-None-Match: "c2aee9e25c4daa9445361e1bb4a749f7"'
# Cookies: save them, then send them back
curl -c cookies.txt -i -X POST https://your-app.test/login -d "user=qa&pass=..."
curl -b cookies.txt -i https://your-app.test/account
jq is a small JSON command-line tool (jq .data.name, jq '.data | length') that pairs perfectly with cURL.
https://sqatools.in/wp-json/sqa-api/v1/apikey/profilecurl -i \
-H "X-API-Key: sqa-key-12345" \
"https://sqatools.in/wp-json/sqa-api/v1/apikey/profile"Debugging with -v and -w
# Verbose: DNS, TLS handshake, every request and response header
curl -v "$BASE/health"
# > GET /wp-json/sqa-api/v1/health HTTP/2 (lines with > are what you SENT)
# < HTTP/2 200 (lines with < are what you RECEIVED)
# Print only the status code (perfect for scripts)
curl -s -o /dev/null -w "%{http_code}\n" "$BASE/users/999" # 404
# Timing breakdown: where is the time spent?
curl -s -o /dev/null -w "dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n" "$BASE/delay/2"
# Timeouts
curl -i --max-time 2 "$BASE/delay/5" # curl gives up after 2 s (exit code 28)
curl -i --connect-timeout 5 "$BASE/health"
-w timing breakdown shows connect=0.03 tls=0.08 ttfb=2.95. The network is fine; the server takes almost 3 seconds to produce the first byte. The bug goes to the backend team with evidence, not to the network team.Redirects, Retries and Other Useful Flags
| Flag | Meaning | Example use |
|---|---|---|
-L | Follow redirects | curl -iL $BASE/redirect (without -L you see the 302) |
-o file / -O | Save body to a file | Download a report or export |
-s / -S | Silent (no progress) / still show errors | Scripts |
--retry 3 | Retry transient failures | Flaky test environments |
-k | Skip TLS verification | Only self-signed test servers, never production |
--compressed | Ask for gzip/br and decode | Check compression works |
-x proxy | Send through a proxy | Route via Burp or ZAP for inspection |
--http2 / --http1.1 | Force protocol version | Protocol compatibility tests |
-f / --fail-with-body | Exit non-zero on HTTP errors | Smoke checks in CI scripts |
A 10-Line Smoke Test Script
#!/usr/bin/env bash
BASE=https://sqatools.in/wp-json/sqa-api/v1
fail=0
check() { # usage: check EXPECTED METHOD PATH [extra curl args...]
local expected=$1 method=$2 path=$3; shift 3
local code=$(curl -s -o /dev/null -w "%{http_code}" -X "$method" "$BASE$path" "$@")
if [ "$code" = "$expected" ]; then echo "PASS $method $path $code"; else echo "FAIL $method $path got $code expected $expected"; fail=1; fi
}
check 200 GET /health
check 200 GET /users/1
check 404 GET /users/999
check 400 GET "/users?per_page=51"
check 401 GET /basic/profile
check 200 GET /basic/profile -u sqa_user:sqa@123
check 405 POST /users
exit $fail
Run it after every deployment; a non-zero exit code fails the pipeline.
Converting cURL to Postman, Playwright and Back
- Browser → cURL: DevTools → Network → right-click request → Copy → Copy as cURL (bash). Remove cookies and tokens before sharing.
- cURL → Postman: Import → Raw text → paste.
- Postman → cURL: the
</>Code button → cURL. - cURL → Playwright: map flags to options:
// curl -i -X POST "$BASE/objects" -H "Content-Type: application/json" -d '{"name":"A","ttl":60}'
const res = await request.post(`${BASE}/objects`, { data: { name: 'A', ttl: 60 } });
// curl -u sqa_user:sqa@123 "$BASE/basic/profile"
await request.get(`${BASE}/basic/profile`, {
headers: { Authorization: 'Basic ' + Buffer.from('sqa_user:sqa@123').toString('base64') },
});
// curl --max-time 2 "$BASE/delay/5"
await expect(request.get(`${BASE}/delay/5`, { timeout: 2000 })).rejects.toThrow();
// curl "$BASE/redirect" (no -L)
await request.get(`${BASE}/redirect`, { maxRedirects: 0 });
Common cURL Mistakes
- Forgetting
-iand missing the status code and headers. - Unquoted URLs with
&: the shell cuts the URL at the first&. - Sending JSON without
Content-Type: application/json(cURL’s-ddefaults to form-encoded); the practice API answers 415. - Using
-X GETtogether with-dand expecting query parameters (use-G). - Pasting real tokens and cookies into tickets or chat.
- Using
-kin scripts that later run against production.
cURL Cheat Sheet
| Task | Command |
|---|---|
| GET with headers shown | curl -i URL |
| POST JSON | curl -i -X POST URL -H "Content-Type: application/json" -d '{...}' |
| Body from file | -d @file.json |
| Basic auth | -u user:pass |
| Bearer token | -H "Authorization: Bearer $TOKEN" |
| Status code only | -s -o /dev/null -w "%{http_code}" |
| Response time | -w "%{time_total}" |
| Follow redirects | -L |
| Timeout | --max-time 5 |
| Verbose debug | -v |
Interview Questions: cURL
What is cURL and why do testers use it?
cURL is a command-line tool for transferring data with URLs, used to send HTTP requests. Testers use it to reproduce issues exactly, share requests in bug reports, test from servers without a GUI and write quick smoke scripts.
How do you send a POST request with a JSON body using cURL?
curl -X POST URL -H “Content-Type: application/json” -d ‘{“key”:”value”}’, or with a file using -d @body.json. Newer versions also support –json.
What is the difference between -i, -I and -v in cURL?
-i includes response headers with the body, -I sends a HEAD request and shows headers only, and -v prints verbose details including the request headers, TLS handshake and connection info.
How do you print only the HTTP status code?
curl -s -o /dev/null -w “%{http_code}” URL. The -w option also prints timing values such as %{time_total}.
How do you send Basic authentication with cURL?
Use -u username:password; cURL Base64-encodes it into the Authorization: Basic header.
How do you follow redirects?
Add -L. Without it, cURL shows the redirect response (3xx with a Location header) itself.
How do you set a timeout?
–max-time seconds limits the whole operation; –connect-timeout limits only the connection phase. A timeout gives exit code 28.
How do you get a cURL command for a request made by the browser?
In DevTools Network tab, right-click the request and choose Copy as cURL. Remove sensitive cookies and tokens before sharing it.
Why might a JSON POST via cURL return 415?
Because -d sends application/x-www-form-urlencoded by default. Add -H “Content-Type: application/json” or use –json.
How do you measure API response time with cURL?
Use -w with timing variables such as time_namelookup, time_connect, time_appconnect, time_starttransfer and time_total to see where time is spent.