curl Cheat Sheet: 40+ Command Examples for HTTP & APIs
You are three SSH hops deep into a staging box, an API is returning the wrong thing, and the only HTTP client installed is curl. Or you are reading a CI script that health-checks a service and want to know what -fsS actually does. Or a coworker pasted a one-liner in Slack and you need to adapt it. curl is everywhere, and the flags are terse enough that nobody remembers all of them.
This curl cheat sheet is built for exactly those moments. You get a quick-reference table of the dozen-or-so flags you use daily, then copy-paste command examples for every common task: GET and POST requests, sending headers, bearer token auth, uploading and downloading files, and API testing in scripts. Every command uses a real, runnable URL (httpbin.org or an api.example.com placeholder), so you can paste and try. The behavior is grounded in the official curl documentation and RFC 9110, the current HTTP semantics standard.
Quick reference: the curl flags you’ll actually use
Ninety percent of daily curl is a dozen flags. Bookmark this table; the rest of the guide expands each one with runnable curl command examples.
| Flag | Meaning | Example |
|---|---|---|
-X | Set HTTP method | curl -X DELETE https://api.example.com/items/42 |
-H | Add a request header (repeatable) | curl -H "Accept: application/json" https://httpbin.org/get |
-d | Send a request body (implies POST) | curl -d "name=alice" https://httpbin.org/post |
--json | Send a JSON body + set JSON headers | curl --json '{"id":1}' https://httpbin.org/post |
-F | Send a multipart form field / file | curl -F "file=@report.pdf" https://httpbin.org/post |
-o | Save output to a named file | curl -o page.html https://example.com |
-O | Save using the remote filename | curl -O https://example.com/archive.zip |
-L | Follow redirects | curl -L https://httpbin.org/redirect/2 |
-u | Basic auth user:pass | curl -u alice:s3cret https://httpbin.org/basic-auth/alice/s3cret |
-i | Include response headers in output | curl -i https://httpbin.org/get |
-I | Fetch headers only (HEAD) | curl -I https://example.com |
-v | Verbose: show request + TLS handshake | curl -v https://example.com |
-s | Silent (no progress meter) | curl -s https://httpbin.org/get |
-w | Write out variables after transfer | curl -s -o /dev/null -w "%{http_code}" https://example.com |
-b | Send cookies (string or file) | curl -b cookies.txt https://httpbin.org/cookies |
-c | Save received cookies to a jar | curl -c cookies.txt https://httpbin.org/cookies/set/a/1 |
-k | Skip TLS certificate verification | curl -k https://self-signed.example.com |
--http2 | Request HTTP/2 | curl --http2 https://example.com |
--http3 | Request HTTP/3 (QUIC) | curl --http3 https://example.com |
-T | Upload a file with PUT | curl -T backup.tar https://api.example.com/files/backup.tar |
--data-urlencode | URL-encode a body field | curl --data-urlencode "q=hello world" https://httpbin.org/get -G |
--max-time | Cap the whole transfer (seconds) | curl --max-time 10 https://example.com |
--connect-timeout | Cap connection setup (seconds) | curl --connect-timeout 5 https://example.com |
--retry | Retry a failed transfer N times | curl --retry 3 https://example.com |
--limit-rate | Cap the transfer bandwidth | curl --limit-rate 2M -O https://example.com/big.iso |
curl syntax basics: anatomy of a request
A curl command is the binary, some flags, and a URL. The URL can come before or after the flags; curl does not care about order. What it does care about is which flags imply a method or a body, because those interact.
A simple GET request
With no method flag, curl sends a GET. This is the whole command:
curl https://httpbin.org/get
httpbin echoes your request back as JSON, which makes it ideal for testing. Add a query string the same way you would in a browser:
curl "https://httpbin.org/get?page=2&sort=desc"
Quote the URL. A bare & tells your shell to background the command and silently drops everything after it — one of the most common curl mistakes, covered in the pitfalls section below.
Seeing the response: body, headers, both
By default curl prints only the response body. Three flags change what you see:
# Body + status line + response headers
curl -i https://httpbin.org/get
# Headers only — sends a HEAD request, no body
curl -I https://example.com
# Full trace: request line, request headers, TLS handshake, response
curl -v https://example.com
Use -i when you want the body plus a quick look at Content-Type or a Set-Cookie. Use -I to check a resource without downloading it (file size, last-modified, redirect target). Reach for -v when something is wrong and you need to see exactly what curl sent.
Following redirects
curl does not follow redirects unless you ask. Hit a URL that returns 301 or 302 without -L and you get the redirect response itself, not the destination:
# Stops at the 302, prints nothing useful
curl https://httpbin.org/redirect/1
# Follows the chain to the final 200
curl -L https://httpbin.org/redirect/1
If you are unsure why a request lands where it does, -IL shows every hop’s status code. For what each of those codes means — and why 301 and 302 are not interchangeable — see the HTTP status codes cheat sheet.
HTTP methods with curl (GET, POST, PUT, PATCH, DELETE)
curl picks the method automatically based on your flags: -d or --json implies POST, plain URLs imply GET. Use -X only when you need a method that does not match the body you are sending.
GET with query parameters
Building a query string by hand is error-prone once values contain spaces or &. The -G flag tells curl to append --data-urlencode fields to the URL as a properly encoded query string:
curl -G https://httpbin.org/get \
--data-urlencode "q=hello world" \
--data-urlencode "tag=c++"
This produces ?q=hello%20world&tag=c%2B%2B — curl handles the percent-encoding so you don’t ship a broken URL.
POST: form data vs JSON
A curl post request comes in two common shapes. Form-encoded (the classic HTML form body):
curl -d "name=alice&role=admin" https://httpbin.org/post
-d sets Content-Type: application/x-www-form-urlencoded and switches the method to POST for you. For a JSON API, send JSON instead:
curl -d '{"name":"alice","role":"admin"}' \
-H "Content-Type: application/json" \
https://httpbin.org/post
There is a cleaner way to do that, covered in the next section.
PUT and PATCH
PUT replaces a resource; PATCH updates part of it. PUT is idempotent — sending it twice leaves the same state.
# Replace the whole resource
curl -X PUT -d '{"name":"alice","role":"owner"}' \
-H "Content-Type: application/json" \
https://api.example.com/users/7
# Update one field
curl -X PATCH -d '{"role":"owner"}' \
-H "Content-Type: application/json" \
https://api.example.com/users/7
DELETE
DELETE usually carries no body, so -X is all you need:
curl -X DELETE https://api.example.com/users/7
Sending headers and JSON bodies
Most real API work is headers and JSON. Two flags carry the weight: -H and --json.
Custom curl headers with -H
-H adds one header and can be repeated as many times as you need. This is how you set Accept, Authorization, custom X- headers, and request IDs:
curl https://httpbin.org/headers \
-H "Accept: application/json" \
-H "X-Request-Id: 9f3c1a" \
-H "User-Agent: my-cli/1.0"
To remove a default header curl would otherwise send, give it an empty value (-H "User-Agent:"). To send a header with no value, use a semicolon (-H "X-Empty;").
POST JSON: -d vs the modern --json flag
Since curl 7.82, --json is the idiomatic way to send a JSON curl post request. It does three things at once: sets Content-Type: application/json, sets Accept: application/json, and sends the body verbatim.
# Verbose, old way — three pieces to keep in sync
curl -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name":"alice"}' \
https://httpbin.org/post
# Modern equivalent
curl --json '{"name":"alice"}' https://httpbin.org/post
--json is repeatable and concatenates, and --json @file.json reads the body from a file. Prefer it for any JSON API.
Content-Type and Accept
These two headers are where 415 and 406 errors come from. Content-Type describes the body you send; Accept says what you want back. Send JSON to an endpoint that only takes form data and you get 415 Unsupported Media Type. Ask for XML from a JSON-only API and you may get 406 Not Acceptable. (Those codes are unpacked in the HTTP status codes guide.)
When the response comes back as a wall of minified JSON, pretty-print it with the JSON formatter, or pipe it straight into jq to extract one field:
curl -s https://httpbin.org/json | jq '.slideshow.title'
For the full set of jq filters — selecting, mapping, and reshaping API responses — see the jq command-line JSON cheat sheet.
Authentication with curl
Four auth styles cover almost every API: basic auth, bearer tokens, API keys, and cookies.
Basic auth (-u user:pass)
-u sends an HTTP Authorization: Basic header with your credentials Base64-encoded:
curl -u alice:s3cret https://httpbin.org/basic-auth/alice/s3cret
Omit the password (-u alice) and curl prompts for it, keeping it out of your shell history.
Bearer tokens & OAuth
Most modern APIs use a curl bearer token: an OAuth 2.0 access token or API token in the Authorization header. curl has a shorthand, --oauth2-bearer, that is equivalent to writing the header by hand:
# Explicit header
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.abc" \
https://api.example.com/me
# Shorthand
curl --oauth2-bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.abc" \
https://api.example.com/me
If the backend rejects the token, decode it with the JWT decoder to check the exp (expiration), aud (audience), and iss (issuer) claims before blaming curl — an expired or wrong-audience token is the usual cause.
API keys (header vs query param)
Some APIs want the key in a header, others in a query parameter. Headers are safer because URLs leak into logs and browser history:
# Preferred: key in a header
curl -H "X-API-Key: sk_live_a1b2c3" https://api.example.com/data
# Less safe: key in the URL (ends up in access logs)
curl "https://api.example.com/data?api_key=sk_live_a1b2c3"
Cookies (-b to send, -c to save)
-c writes a cookie jar; -b reads one back. This is how you carry a session across requests:
# Log in and save the session cookie
curl -c jar.txt -d "user=alice&pass=s3cret" https://httpbin.org/cookies/set/session/abc123
# Reuse it on the next call
curl -b jar.txt https://httpbin.org/cookies
Security note: tokens and credentials belong only on HTTPS connections — TLS encrypts the headers, plain HTTP does not. Avoid putting secrets directly on the command line, where they land in your shell history and in ps output. Read a header from a file with -H @authfile or pull the value from an environment variable (-H "Authorization: Bearer $TOKEN").
Downloading and uploading files
curl was named “client URL” because moving files is its original job. Downloads and uploads each have a couple of flags worth knowing.
Download: -O vs -o vs -C -
-O (capital O) saves the file under its remote name. -o (lowercase) lets you choose the name. -C - resumes a partial download where it left off:
# Save as the remote filename: archive.zip
curl -O https://example.com/downloads/archive.zip
# Save under a name you pick
curl -o backup.zip https://example.com/downloads/archive.zip
# Resume an interrupted download
curl -C - -O https://example.com/downloads/archive.zip
# Follow redirects to the real file (common with CDNs)
curl -OL https://example.com/latest/archive.zip
To curl download file content that sits behind a redirect, add -L — release pages and CDN links almost always redirect.
Upload: -T vs -F
-T uploads a file with PUT, sending the raw bytes as the body (common for object storage and REST file endpoints). -F sends a multipart/form-data request, the same format a browser uses for file inputs:
# PUT raw bytes
curl -T report.pdf https://api.example.com/files/report.pdf
# multipart/form-data upload (note the @ prefix)
curl -F "file=@report.pdf" -F "title=Q2 report" https://httpbin.org/post
The @ prefix tells curl to read the file’s contents. Without it, -F "file=report.pdf" sends the literal string report.pdf, not the file.
--data-urlencode and percent-encoding
When a value contains spaces, &, =, or non-ASCII characters, -d sends it as-is and breaks your request. --data-urlencode encodes it correctly:
# Wrong: the & splits the body, the space is invalid
curl -d "q=hello world&filter=a&b" https://httpbin.org/post
# Right: each field is percent-encoded
curl --data-urlencode "q=hello world" \
--data-urlencode "filter=a&b" \
https://httpbin.org/post
If you need to understand what those %20 and %26 sequences mean — or debug a value that is double-encoded — paste it into the URL decoder/encoder or read the byte-level walkthrough in the URL encoding and decoding guide.
API testing & inspecting responses
curl shines in scripts and CI because -w exposes everything about the transfer and exit codes make failures detectable.
Status code only
To grab just the HTTP status, discard the body with -o /dev/null and print the code with -w:
curl -s -o /dev/null -w "%{http_code}\n" https://httpbin.org/status/204
# → 204
This is the core of any curl api testing health check. To interpret the number you get back, the HTTP status codes cheat sheet covers every range.
Timing breakdown
-w exposes timing variables, so you can see where the time goes — DNS, TCP connect, TLS, or the server itself:
curl -s -o /dev/null \
-w "dns=%{time_namelookup}s connect=%{time_connect}s tls=%{time_appconnect}s total=%{time_total}s\n" \
https://example.com
# → dns=0.004s connect=0.021s tls=0.058s total=0.142s
A high time_appconnect points at TLS, a high time_starttransfer points at a slow backend.
Silent but show errors
-s hides the progress meter, but it also hides error messages — a trap in scripts. Pair it with -S so curl stays quiet on success and still reports failures:
curl -sS https://api.example.com/health
curl in CI / scripts: fail on HTTP errors
By default curl exits 0 even on a 404 or 500, because the transfer itself succeeded. In a health check that is the opposite of what you want. -f (fail) makes curl exit non-zero on HTTP errors so your pipeline catches them:
# Fails the build if the endpoint returns 4xx/5xx
curl -fsS https://api.example.com/health || exit 1
-fsS (fail, silent, show-errors) is the canonical health-check combination. For richer diagnostics, --fail-with-body (curl 7.76+) still prints the error response before exiting.
Timeouts and retries
A health check that hangs forever is worse than one that fails fast. Bound every scripted request with --max-time (a ceiling on the whole transfer) and --connect-timeout (a ceiling on just the connection setup):
# Give up after 5s connecting, 10s total
curl --connect-timeout 5 --max-time 10 -fsS https://api.example.com/health
On a flaky network, --retry re-attempts a failed transfer with exponential backoff. By default it retries only transient failures (timeouts, 5xx); add --retry-all-errors to also retry connection refusals:
# Up to 3 attempts, backing off between each
curl --retry 3 --retry-all-errors --max-time 30 -fsS https://api.example.com/health
When a script pulls a large file and you don’t want to saturate the link, --limit-rate caps the bandwidth — --limit-rate 2M holds it to 2 MB/s.
HTTP/1.1, HTTP/2 & HTTP/3 with curl
curl negotiates the HTTP version over TLS via ALPN. You can force a version when you need to test a specific protocol path.
--http2 and --http3
# Request HTTP/2 (falls back to 1.1 if unavailable)
curl --http2 https://example.com
# Request HTTP/3 over QUIC
curl --http3 https://example.com
HTTP/3 runs over QUIC and requires TLS 1.3. It works only if your curl was built with an HTTP/3-capable backend and the server advertises it via an Alt-Svc header. Run curl --version to confirm HTTP3 appears in the features line; if it does not, --http3 will error.
Verbose TLS / ALPN info
-v shows the negotiated protocol and the TLS handshake, which is how you confirm a version actually took effect:
curl -v --http2 https://example.com 2>&1 | grep -i "ALPN\|HTTP/2"
# → * ALPN: server accepted h2
# → > GET / HTTP/2
Look for ALPN: server accepted h2 (HTTP/2) or h3 (HTTP/3) in the handshake output.
8 common curl pitfalls (and how to fix them)
These eight trip up everyone eventually.
-
Single vs double quotes. Double quotes let the shell expand
$VARS; single quotes pass everything literally. Use single quotes for JSON bodies so$and!are not interpreted:curl --json '{"price":"$5"}'. Use double quotes when you want expansion:-H "Authorization: Bearer $TOKEN". -
-ddoes not URL-encode. A space or&in a-dvalue corrupts the body. Switch to--data-urlencodefor any value that isn’t already encoded. -
Redundant
-X POSTwith-d.-dalready impliesPOST. Writing-X POST -d ...is harmless but redundant; worse,-X GET -d ...sends a body with a GET, which surprises some servers. Let the body flag set the method. -
Forgetting
@when reading from a file.-d @body.jsonreads the file;-d body.jsonsends the literal textbody.json. Same trap with-F "file=@upload.png"versus-F "file=upload.png". -
Reaching for
-kon certificate errors.-kdisables TLS verification and hides real problems (expired certs, wrong hostname, missing intermediate). Fix the root cause: install the CA with--cacert ca.pem, or update your system trust store. Save-kfor a self-signed dev server you fully control. -
-sswallowing errors. Silent mode hides failures in scripts. Always use-sSso errors still surface. -
[]{}in URLs getting globbed. curl treats[1-5]and{a,b}as URL ranges/lists. A URL with literal brackets (common in array query params likearr[]=1) gets mangled. Disable globbing with-g:curl -g "https://api.example.com/items?id[]=1&id[]=2". -
Header case and duplicates. HTTP header names are case-insensitive, but sending the same header twice usually sends both — some servers take the first, some the last, some reject it. If you override a default like
User-Agent, set it once with-Hrather than relying on order.
curl vs wget vs HTTPie — which to use
All three fetch over HTTP, but they optimize for different jobs. Here is the curl vs wget decision (plus HTTPie) at a glance:
| Task | curl | wget | HTTPie |
|---|---|---|---|
| Quick API call / debugging | Excellent | Limited | Excellent |
| JSON bodies | Good (--json) | Awkward | Excellent (native) |
| Recursive site download / mirroring | No | Excellent (-r) | No |
| Resume + retry large downloads | Good (-C -) | Excellent (built-in) | No |
Scripting / CI (exit codes, -w) | Excellent | Good | Good |
| Pretty, colorized output by default | No | No | Excellent |
| Preinstalled almost everywhere | Yes | Often | Rarely |
The short version: reach for curl for API debugging and scripts (it is everywhere and -w is unmatched), HTTPie when you want readable JSON ergonomics on your own machine, and wget for mirroring sites or batch-downloading files with automatic retries.
Frequently asked questions
What is curl used for?
curl is a command-line tool for transferring data to and from a server over HTTP, HTTPS, FTP, and many other protocols. Developers use it to call and debug APIs, download and upload files, and run health checks in scripts and CI pipelines.
How do I send a POST request with curl?
Use -d for form data or --json for JSON: curl --json '{"name":"alice"}' https://httpbin.org/post. Both flags set the method to POST automatically, so you do not need -X POST.
How do I add a header in curl?
Use -H "Name: value", and repeat it for multiple headers: curl -H "Accept: application/json" -H "X-Request-Id: 9f3c1a" https://httpbin.org/headers. There is no limit on how many times you can pass -H.
How do I send a bearer token with curl?
Pass an Authorization header: curl -H "Authorization: Bearer YOUR_TOKEN" https://api.example.com/me, or use the shorthand --oauth2-bearer YOUR_TOKEN. Send tokens only over HTTPS, and decode them with the JWT decoder when debugging.
How do I download a file with curl?
Use -O to keep the remote filename or -o name to choose your own: curl -O https://example.com/archive.zip. Add -L to follow redirects and -C - to resume an interrupted download.
How do I see only the HTTP status code with curl?
Discard the body and print the code: curl -s -o /dev/null -w "%{http_code}" https://example.com. This is the standard pattern for health checks in scripts.
What’s the difference between curl and wget?
curl transfers a single resource and writes to stdout by default, making it ideal for API calls and scripting. wget specializes in downloading, including recursive mirroring and automatic retries. For API testing, use curl; for bulk file downloads, use wget.
Is curl available on Windows?
Yes. curl ships with Windows 10 (build 1803+) and Windows 11, available as curl in Command Prompt and PowerShell. Note that PowerShell historically aliased curl to Invoke-WebRequest, so call curl.exe explicitly if flags behave unexpectedly.
Wrapping up
curl rewards a small amount of memorization: the flag table at the top covers the vast majority of what you will type, and the rest is knowing which flags imply a method, which need quoting, and which lie to you in scripts (looking at you, bare -s). Keep this curl cheat sheet open next to your terminal. If you prefer assembling commands interactively, try our cURL command builder to generate them visually.
The natural next steps in an API workflow live one click away. Send the request with curl, read what came back with the HTTP status codes cheat sheet, then pretty-print the JSON in the JSON formatter or slice it with the jq command-line JSON cheat sheet. For the full option list, the curl manual is exhaustive, and MDN’s HTTP reference explains the semantics behind every method and header.