"curl is on every Mac, every Linux box, every container image. It speaks every version of HTTP. It shows you what your library hides. When an API call misbehaves, curl is the first move — not the last."
The Flags You Use Daily
A handful of flags cover 90% of debugging:
-v— verbose. Shows request lines (>), response lines (<), and curl's connection/TLS commentary (*). Your first debugging move.-i— include response headers in output. Just headers; not the connection chatter.-I(capital) — HEAD request only. Get headers without downloading the body.-X METHOD— use this HTTP method.-X POST,-X DELETE, etc.-H 'Name: value'— add a request header. Can repeat for multiple.-d 'body'— send a body.-d @file.jsonreads from a file. Implies POST.--data-binary @file— send file bytes verbatim, no line-ending translation. Use for binary uploads.-u user:pass— HTTP Basic auth (shortcut for-H 'Authorization: Basic ...').-N— no buffering of curl's own output. Essential for SSE / streaming.--compressed— request and decompress gzip/br. Lets you compare compressed vs uncompressed sizes.-L— follow redirects automatically. Without it, you see the 3xx response and stop.-o file/-O— write body to a file (named or from URL).-w '%{http_code}\n'— print specific transfer info (status code, size, time) after the response.
The X-Ray Move
curl -v is the canonical "why isn't this working" tool:
# Watch every byte sent and received
curl -v -X POST https://api.example.com/users \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer abc.def.ghi' \
-d '{"name":"Pippa"}'
# Output shape:
# * Trying 1.2.3.4:443... (TCP connect)
# * TLSv1.3 (OUT), TLS handshake, ... (TLS handshake)
# > POST /users HTTP/1.1 (request line)
# > Host: api.example.com (request header)
# > Authorization: Bearer abc.def.ghi (request header)
# > Content-Type: application/json (request header)
# > Content-Length: 17 (request header)
# >
# > {"name":"Pippa"} (request body)
# < HTTP/1.1 201 Created (status line)
# < Location: /users/u_abc (response header)
# < Content-Type: application/json (response header)
# < {"id":"u_abc","name":"Pippa"} (response body)
You see EXACTLY what went on the wire and EXACTLY what came back. No library magic, no framework abstractions. Half of "my httpx call returns 401 but my curl returns 200" mysteries dissolve when you compare the verbose output.
When library calls misbehave, copy the request to curl FIRST. If curl works but your library doesn't, the bug is in how the library is calling the API — not in the API itself. If curl also fails, the API is at fault (or your understanding of it is). The split tells you where to look.
The Power Combos
Timing breakdown — see DNS / TCP / TLS / response phases separately:
curl -w 'dns=%{time_namelookup}s connect=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n' -o /dev/null -s https://api.example.com/
SSE stream with no buffering:
curl -N -H 'Accept: text/event-stream' https://api.example.com/stream
Test rate limiting — hammer an endpoint 100 times, count statuses:
for i in {1..100}; do
curl -s -o /dev/null -w '%{http_code}\n' https://api.example.com/
done | sort | uniq -c
Reproduce a browser fetch — Chrome DevTools' Network panel has "Copy as cURL" — paste straight into terminal and reproduce the bug locally.
cwkPippa's curl Usage
When cwkPippa's WebUI hangs or returns 500, my first move is curl against the backend:
curl -v http://localhost:8000/api/conversations/{id}. Reveals whether it's the backend dying (connection refused), CORS blocking (browser console only — curl wouldn't see CORS), auth failing (401), or actual content issue (200 + bad body). I've also Copy-as-cURL'd from Chrome DevTools countless times to reproduce a browser-only bug from the terminal where I have tools.