HTTP Swiss-army knife for testing REST, JSON-RPC, and LLM APIs from the terminal: request shaping, auth, response parsing with jq, and wire-level debugging.
- Basic Requests
- HTTP Methods
- Headers
- Sending Data (POST / PUT / PATCH)
- Authentication
- Query Parameters
- Response Handling (jq)
- SSL / TLS
- Cookies
- Timeouts & Retry
- Proxy
- Debugging & Timing
- Common API Testing Patterns
- Blockchain / Web3 API Examples
- AI / LLM API Examples
- Useful Flags Reference
- Tips & Gotchas
curl https://example.com # GET request
curl -s https://example.com # Silent (no progress)
curl -i https://example.com # Include response headers
curl -I https://example.com # Headers ONLY (HEAD request)
curl -v https://example.com # Verbose (req + res headers)
curl -vvv https://example.com # Extra verbose (SSL details)
curl -o file.html https://example.com # Save output to file
curl -O https://example.com/file.zip # Save with remote filename
curl -L https://example.com # Follow redirects
curl -w "\n%{http_code}\n" https://example.com # Show HTTP status code
curl --max-time 10 https://example.com # Timeout after 10s
curl -fsS https://example.com # Script default: fail on 4xx/5xx, silent, but show errorscurl https://api.example.com/users # GET (default — no -X needed)
curl -X POST https://api.example.com/users
curl -X PUT https://api.example.com/users/1
curl -X PATCH https://api.example.com/users/1
curl -X DELETE https://api.example.com/users/1💡 -d implies POST automatically — only use -X for PUT/PATCH/DELETE.
See Gotchas for why `-X GET` with a body misbehaves.
curl -H "Authorization: Bearer TOKEN" https://api.example.com
curl -H "Content-Type: application/json" https://api.example.com
curl -H "Accept: application/json" https://api.example.com
curl -H "X-API-Key: mykey" -H "X-Request-ID: abc123" https://api.example.comcurl \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
https://api.example.com/userscurl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d @payload.jsoncurl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d @- <<EOF
{"name": "$NAME", "email": "$EMAIL"}
EOFcurl -X POST https://api.example.com/login \
-d "username=alice&password=secret"curl -X POST https://api.example.com/upload \
-F "file=@/path/to/file.pdf" \
-F "description=My file"curl -X PUT https://api.example.com/file \
-H "Content-Type: application/octet-stream" \
--data-binary @file.bin# Bearer token:
curl -H "Authorization: Bearer eyJhbGciOi..." https://api.example.com
# Basic auth:
curl -u username:password https://api.example.com
curl -H "Authorization: Basic $(echo -n 'user:pass' | base64)" https://api.example.com
# API key in header:
curl -H "X-API-Key: my_api_key" https://api.example.com
# API key in query string (⚠️ leaks into server logs + shell history — prefer headers):
curl "https://api.example.com/data?api_key=my_api_key"
# Credentials from ~/.netrc (keeps secrets out of history entirely):
curl -n https://api.example.comcurl "https://api.example.com/users?page=1&limit=20"
# URL-encode values safely (spaces, special chars):
curl -G https://api.example.com/users \
--data-urlencode "search=alice smith" \
--data-urlencode "page=1"curl -s https://api.example.com/users | jq '.' # Pretty print
curl -s https://api.example.com/users | jq '.[0]' # First element
curl -s https://api.example.com/users | jq '.[].name' # Extract field
curl -s https://api.example.com/users | jq '.[] | select(.active == true)' # Filter# Save response to file:
curl -s https://api.example.com/data -o response.json
# Capture status code separately from body:
response=$(curl -s -o /tmp/body.json -w "%{http_code}" https://api.example.com)
echo "Status: $response"
cat /tmp/body.json | jq '.'curl -k https://self-signed.example.com # Skip SSL verification (insecure)
curl --cacert ca.pem https://example.com # Use custom CA cert
curl --cert client.pem --key client.key https://example.com # Mutual TLS
curl --tlsv1.2 https://example.com # Force TLS versioncurl -c cookies.txt https://example.com # Save cookies to file
curl -b cookies.txt https://example.com # Send cookies from file
curl -b "session=abc123" https://example.com # Send cookie inline
curl -c cookies.txt -b cookies.txt https://example.com # Save + sendcurl --connect-timeout 5 https://example.com # Connection timeout (5s)
curl --max-time 30 https://example.com # Total timeout (30s)
curl --retry 3 https://example.com # Retry 3 times on transient fail
curl --retry 3 --retry-delay 2 https://example.com # Retry with 2s delay
curl --retry 3 --retry-all-errors https://example.com # Retry on ANY error (incl. 4xx/5xx)curl -x http://proxy.example.com:8080 https://api.example.com
curl -x socks5://127.0.0.1:1080 https://api.example.com # SOCKS5 (pairs with ssh -D)
curl --noproxy "localhost,127.0.0.1" https://api.example.com# Where is the time going? (DNS vs connect vs TLS vs server):
curl -s -o /dev/null -w "dns: %{time_namelookup}s | connect: %{time_connect}s | tls: %{time_appconnect}s | ttfb: %{time_starttransfer}s | total: %{time_total}s\n" https://api.example.com
# Test a vhost against a SPECIFIC server (pre-DNS-cutover, or bypass CDN):
curl --resolve example.com:443:203.0.113.10 https://example.com
# Talk to a unix socket (Docker API without exposing a port):
curl --unix-socket /var/run/docker.sock http://localhost/containers/json | jq '.'
# Full wire trace to file:
curl --trace-ascii trace.txt https://api.example.comcurl -s -o /dev/null -w "%{http_code}" https://api.example.com/health
# Scriptable: -f makes curl exit non-zero on HTTP errors
curl -fsS https://api.example.com/health > /dev/null && echo OK || echo DOWNBASE="https://api.example.com"
TOKEN="Bearer mytoken"
# Create
curl -X POST "$BASE/users" \
-H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'
# Read all
curl -s "$BASE/users" -H "Authorization: $TOKEN" | jq '.'
# Read one
curl -s "$BASE/users/1" -H "Authorization: $TOKEN" | jq '.'
# Update
curl -X PATCH "$BASE/users/1" \
-H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Alice Updated"}'
# Delete
curl -X DELETE "$BASE/users/1" -H "Authorization: $TOKEN"curl -X POST https://mainnet.infura.io/v3/YOUR_KEY \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'curl -X POST https://mainnet.infura.io/v3/YOUR_KEY \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xAddress","latest"],"id":1}'curl -s -X POST https://mainnet.infura.io/v3/YOUR_KEY \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
| jq -r '.result' | xargs printf "%d\n"curl -s -X POST https://mainnet.infura.io/v3/YOUR_KEY \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}' \
| jq -r '.result' | xargs printf "%d\n" # Result in weicurl -X POST https://api.devnet.solana.com \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["YourPublicKeyHere"]}'curl -X POST https://YOUR_ENDPOINT.quiknode.pro/TOKEN/ \
-H "Content-Type: application/json" \
-d '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}'curl -s "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd" | jq '.'curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}' | jq -r '.content[0].text'curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Hello"}]
}' | jq -r '.choices[0].message.content'Same payload schema as OpenAI — only the base URL and key change:
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "MODEL_ID", "messages": [{"role": "user", "content": "Hello"}]}'curl -N https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o-mini", "stream": true, "messages": [{"role": "user", "content": "Hello"}]}'
# Without -N curl buffers output — the stream looks frozen, then dumps at oncecurl http://localhost:11434/api/tags | jq '.models[].name' # List installed models
curl http://localhost:11434/api/chat -d '{
"model": "llama3.2",
"stream": false,
"messages": [{"role": "user", "content": "Hello"}]
}' | jq -r '.message.content'| Flag | Description |
|---|---|
-s / --silent |
No progress or error output |
-S / --show-error |
Show error even when -s is used |
-f / --fail |
Exit non-zero on HTTP 4xx/5xx (scripts/CI) |
-i |
Include response headers in output |
-I |
HEAD request (headers only) |
-v |
Verbose (shows full transaction) |
-L |
Follow redirects |
-o file |
Write output to file |
-O |
Write output to filename from URL |
-d / --data |
Request body data (implies POST) |
--data-urlencode |
Body data, URL-encoded |
-G |
Send -d data as query string (GET) |
-F / --form |
Multipart form data |
-H / --header |
Add header |
-u / --user |
Username:password for basic auth |
-n / --netrc |
Read credentials from ~/.netrc |
-X / --request |
HTTP method override |
-b / --cookie |
Send cookie |
-c / --cookie-jar |
Save cookies |
-k / --insecure |
Skip SSL verification |
-w / --write-out |
Custom output format after response |
-N / --no-buffer |
Disable output buffering (SSE/streaming) |
--max-time |
Max total time |
--connect-timeout |
Connection timeout |
--retry |
Retry count (transient errors) |
--retry-all-errors |
Retry on any error incl. HTTP codes |
--resolve |
Pin a hostname to a specific IP |
--unix-socket |
Send request over a unix socket |
--compressed |
Request compressed response (gzip) |
- Quote any URL containing
?or&— unquoted, the shell treats&as "run in background" and silently truncates your query string. - Let curl pick the method —
-dimplies POST;-Iimplies HEAD.-X GETwith-dsends a GET-with-body most servers ignore, and-Xbreaks redirect method handling with-L. - Single-quote JSON bodies — protects the inner double quotes from the shell. Need variables inside? Use the heredoc pattern (
-d @- <<EOF) instead of quote gymnastics. -seats error messages — always pair as-sS(or-fsSin scripts) so failures are silent-but-visible.--retryalone won't retry HTTP 500s — it only covers transient/connection errors; add--retry-all-errorsfor status-code retries.- Secrets hygiene — keys in query strings land in server logs; keys in commands land in shell history. Use env vars,
~/.netrc, or a leading space withHISTCONTROL=ignorespace. - JSON-RPC returns hex —
0x...block numbers and wei balances; pipe throughxargs printf "%d\n"for decimals (see Web3 section). - curl ≥7.82 has
--json '...'— shorthand that sets both Content-Type and Accept. Ubuntu 22.04 ships 7.81, so it's absent there; the-H+-dform works everywhere.
Last Updated: 2026-07