Skip to content
Back to Blog
Tutorials

curl Cheat Sheet: 40+ Command Examples for HTTP & APIs

Complete curl cheat sheet for developers: GET/POST, headers, bearer auth, file upload/download, and API testing — 40+ copy-paste examples. Try our tools.

14 min read

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.

FlagMeaningExample
-XSet HTTP methodcurl -X DELETE https://api.example.com/items/42
-HAdd a request header (repeatable)curl -H "Accept: application/json" https://httpbin.org/get
-dSend a request body (implies POST)curl -d "name=alice" https://httpbin.org/post
--jsonSend a JSON body + set JSON headerscurl --json '{"id":1}' https://httpbin.org/post
-FSend a multipart form field / filecurl -F "file=@report.pdf" https://httpbin.org/post
-oSave output to a named filecurl -o page.html https://example.com
-OSave using the remote filenamecurl -O https://example.com/archive.zip
-LFollow redirectscurl -L https://httpbin.org/redirect/2
-uBasic auth user:passcurl -u alice:s3cret https://httpbin.org/basic-auth/alice/s3cret
-iInclude response headers in outputcurl -i https://httpbin.org/get
-IFetch headers only (HEAD)curl -I https://example.com
-vVerbose: show request + TLS handshakecurl -v https://example.com
-sSilent (no progress meter)curl -s https://httpbin.org/get
-wWrite out variables after transfercurl -s -o /dev/null -w "%{http_code}" https://example.com
-bSend cookies (string or file)curl -b cookies.txt https://httpbin.org/cookies
-cSave received cookies to a jarcurl -c cookies.txt https://httpbin.org/cookies/set/a/1
-kSkip TLS certificate verificationcurl -k https://self-signed.example.com
--http2Request HTTP/2curl --http2 https://example.com
--http3Request HTTP/3 (QUIC)curl --http3 https://example.com
-TUpload a file with PUTcurl -T backup.tar https://api.example.com/files/backup.tar
--data-urlencodeURL-encode a body fieldcurl --data-urlencode "q=hello world" https://httpbin.org/get -G
--max-timeCap the whole transfer (seconds)curl --max-time 10 https://example.com
--connect-timeoutCap connection setup (seconds)curl --connect-timeout 5 https://example.com
--retryRetry a failed transfer N timescurl --retry 3 https://example.com
--limit-rateCap the transfer bandwidthcurl --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.

  1. 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".

  2. -d does not URL-encode. A space or & in a -d value corrupts the body. Switch to --data-urlencode for any value that isn’t already encoded.

  3. Redundant -X POST with -d. -d already implies POST. 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.

  4. Forgetting @ when reading from a file. -d @body.json reads the file; -d body.json sends the literal text body.json. Same trap with -F "file=@upload.png" versus -F "file=upload.png".

  5. Reaching for -k on certificate errors. -k disables 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 -k for a self-signed dev server you fully control.

  6. -s swallowing errors. Silent mode hides failures in scripts. Always use -sS so errors still surface.

  7. [ ] { } 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 like arr[]=1) gets mangled. Disable globbing with -g: curl -g "https://api.example.com/items?id[]=1&id[]=2".

  8. 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 -H rather 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:

TaskcurlwgetHTTPie
Quick API call / debuggingExcellentLimitedExcellent
JSON bodiesGood (--json)AwkwardExcellent (native)
Recursive site download / mirroringNoExcellent (-r)No
Resume + retry large downloadsGood (-C -)Excellent (built-in)No
Scripting / CI (exit codes, -w)ExcellentGoodGood
Pretty, colorized output by defaultNoNoExcellent
Preinstalled almost everywhereYesOftenRarely

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.

Tags: curl http rest-api command-line developer-reference

Related Articles

View all articles