Seeing API Traffic: curl -v and the Network Tab🔗
Part of a Learning Path
This article is part of the How APIs Actually Work pathway on bradpenney.io — a guided sequence through the topic. It also stands on its own.
You curl APIs every day. You read the JSON, you check the status code, you move on. But when something's wrong — a 401 you can't explain, a request that "works in curl but not the browser," a redirect you didn't expect — the body of the response tells you almost nothing. The answer is in the parts you usually don't look at: the headers, the handshake, the redirects, the preflight.
This article isn't about learning curl — you've got that. It's about learning to see: turning curl and your browser's DevTools into x-ray glasses for HTTP, so the invisible parts of a request become visible and debuggable.
If the concepts underneath (methods, headers, status families) are fuzzy, the Anatomy of an HTTP Request and Response on the CS site is the companion read. Here we make those parts observable on real traffic.
Quick Start: The Flags That Reveal Everything🔗
Three curl flags turn a silent request into a full transcript. Memorize these and you'll diagnose 90% of API issues without leaving the terminal.
| The diagnostic curl flags | |
|---|---|
-v(verbose) shows the entire conversation: DNS, TLS handshake, request headers sent (>), response headers received (<), then the body.-iincludes the response headers above the body — lighter than-vwhen you only care about the response side.-w(write-out) prints just the bits you ask for — here only the status code — perfect for scripts and health checks.
Reading a Verbose Request, Line by Line🔗
The power of -v is that it labels who said what. Lines starting with * are curl's own notes (connection, TLS), > is what curl sent, and < is what the server returned.
- DNS already resolved the host to this IP, and
curlis connecting to port 443. - The TLS handshake succeeded and negotiated TLS 1.3 — if TLS were broken, it would fail here, before any HTTP.
- The certificate the server presented — useful for spotting hostname mismatches or who terminates TLS.
- The request line
curlsent: method, path, HTTP version. - The headers
curlsent — confirm yourAuthorizationactually went out as intended. - The status line:
401, an authentication failure. - The response header that explains why —
invalid_token. This line is invisible without-v/-i, and it's the actual answer. - Only now, the body — which by itself ("token expired") you might have missed the cause of.
The lesson: the body said "token expired," but the headers (WWW-Authenticate, the 401 status) told the real story. Most API debugging lives above the body.
Inspecting Specific Layers🔗
Different problems hide in different layers. Match the flag to the question.
-
Confirm what you're sending
Why it matters: half of "auth doesn't work" is a header that never left.
Send and show an auth header Check the
>lines: did your header actually go out, spelled correctly? -
Follow redirects
Why it matters: a silent
301/302can drop your headers.Follow and trace redirects -Lfollows redirects;-vshows each hop, including thehttp→httpsupgrade. -
Send a real POST
Why it matters: reproduce a write the way the app does it.
POST JSON with the right content type Forgetting
Content-Typeis a top cause of a rejected-but-valid body. -
Measure the timing
Why it matters: separate slow DNS/TLS from a slow app.
Break down where the time goes Tells you if latency is in name resolution, the connection, or the server.
The Browser's Network Tab: Seeing What curl Can't🔗
curl is perfect for the request you construct. But some problems only exist in a real browser — most famously CORS and the automatic requests browsers make on their own. For those, open DevTools → Network (F12 in Chrome/Firefox).
What the Network tab shows that curl won't:
- Every request the page made, including ones your code didn't explicitly write — analytics, redirects, and crucially
OPTIONSpreflight requests the browser sends before cross-origin calls. - CORS failures, with the blocked request marked and the missing
Access-Control-Allow-*header named in the console. - The full request/response per row — click any request to see its headers, payload, status, timing, and response, the GUI equivalent of
curl -v.
The 'works in curl, fails in the browser' workflow
When an API works in curl but fails in the browser, it's almost always CORS — a browser-only rule curl doesn't enforce. Reproduce it in the Network tab: look for a red request, check the Console for the CORS message, and look for a preceding OPTIONS request. That tells you instantly it's a server CORS-header problem, not a curl-reproducible bug. Right-click any request → Copy as cURL to turn a browser request back into a terminal command you can iterate on.
Why This Matters for Platform Work🔗
- It collapses guesswork during incidents. "The API is broken" becomes a precise question: did DNS resolve? did TLS negotiate? what status and headers came back? Each is one flag away, so you bisect instead of speculate.
- It distinguishes layers fast.
curl -vfailing at the TLS line is a certificate problem; a clean handshake but a401is an auth problem; a502is a gateway/backend problem. Same tool, different layer, instantly narrowed. - It bridges the front-end/back-end divide. When a front-end dev reports a failure you can't reproduce in
curl, the Network tab is the shared ground where you see their browser's view — preflights, CORS, dropped credentials — and fix the right thing.
Common Scenarios🔗
The body just says "unauthorized." Run curl -v and read two things: the > lines (did your Authorization header actually send, and is it spelled right?) and the < WWW-Authenticate header (the server's reason — expired, malformed, wrong scheme). The cause is almost always in those header lines, not the body.
Auth works against the final URL but fails against the public one. curl -v -L reveals a 301/302 in between — and curl (like browsers) may drop the Authorization header when redirected to a different host. Fix the client to call the canonical URL directly, or confirm the redirect target is the same host.
curl returns 200, the app shows an error. Open DevTools → Network, find the red request, read the Console. A CORS message plus a preceding OPTIONS confirms a server-side CORS config issue — invisible to curl by design.
Practice Problems🔗
Practice Problem 1: Did the Header Send?
A script gets 401 from an API, but the engineer swears the token is correct. Which curl flag confirms whether the Authorization header actually left the client, and where in the output do you look?
Solution
Use curl -v. In the output, look at the lines beginning with > — these are exactly what curl sent. If > Authorization: Bearer ... isn't there (or the variable expanded to empty, e.g. > Authorization: Bearer), the header never went out — the bug is client-side construction, not the token's validity. This separates "wrong token" from "token never sent," which look identical from the response body alone.
Practice Problem 2: TLS or Auth?
Two failing requests: one errors before any HTTP status appears; the other returns 403. Using curl -v, how do you tell a TLS/certificate problem from an authorization problem?
Solution
Watch where curl -v stops. A TLS/certificate problem fails during the handshake — you'll see * lines about SSL and an error before any > request lines or < status line ever appear (no HTTP happened). An authorization problem completes the handshake and the request fully, then returns < HTTP/1.1 403 Forbidden — the connection was fine, the server just refused the action. TLS fails before HTTP; 403 is HTTP working and saying "no."
Practice Problem 3: Reproducing a Browser Request
A front-end developer's request fails in the browser but you can't reproduce it with the curl command you typed by hand. What DevTools feature gets you an exact, runnable reproduction, and why does it matter?
Solution
In DevTools → Network, right-click the failing request and choose Copy as cURL. This generates a curl command with the exact headers, cookies, and body the browser actually sent — which often differ from what you typed (extra headers, auth cookies, content types). Running it reproduces the real request in the terminal where you can iterate with -v. If it now succeeds in curl but failed in the browser, that's a strong signal the issue is browser-only — typically CORS — rather than something wrong with the request itself.
Key Takeaways🔗
| Concept | What It Means |
|---|---|
curl -v |
Full transcript: DNS, TLS, sent headers (>), received headers (<), body |
curl -i |
Response headers above the body — lighter than -v |
curl -w |
Print just what you ask for (status code, timings) — script-friendly |
| Read the headers | Most API failures are explained above the body, not in it |
| Network tab | Sees browser-only traffic: preflights, CORS, dropped credentials |
| Copy as cURL | Turn a real browser request into a terminal command you can iterate on |
You already speak curl — this is about listening as well as talking. The headers, the handshake, the redirects, the preflights: they're all right there the moment you add -v or open the Network tab. Stop reading only the body, start reading the whole conversation, and the APIs you've been using on faith become systems you can actually see.
Further Reading🔗
Related Tools & Concepts🔗
- JQ: Parsing API Responses and Logs (coming soon) — once you can see the JSON body,
jqslices it apart.
Computer Science Fundamentals🔗
- Anatomy of an HTTP Request and Response (cs.bradpenney.io) — the methods, headers, and status families you're inspecting.
- Authentication vs Authorization (cs.bradpenney.io) — what
401vs403in the output mean.
Networking Deep Dives🔗
- CORS Explained (networking.bradpenney.io) — the browser-only failures the Network tab reveals.
- HTTPS for APIs (networking.bradpenney.io) — the TLS handshake
curl -vshows you.
External Resources🔗
- curl documentation — every flag, authoritative.
- Everything curl (free book) — the definitive guide by curl's author.
- Chrome DevTools: Network features — the Network tab in depth.