Before Postman, before Insomnia, there was curl. It’s still the fastest way to test an API, and it’s available on every server you’ll ever SSH into.

Basic Requests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# GET request
curl https://api.example.com/users

# With headers shown
curl -i https://api.example.com/users

# Headers only
curl -I https://api.example.com/users

# Silent (no progress bar)
curl -s https://api.example.com/users

# Follow redirects
curl -L https://api.example.com/old-endpoint

HTTP Methods

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# POST
curl -X POST https://api.example.com/users

# PUT
curl -X PUT https://api.example.com/users/123

# PATCH
curl -X PATCH https://api.example.com/users/123

# DELETE
curl -X DELETE https://api.example.com/users/123

Sending Data

JSON Body

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@example.com"}'

# From file
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d @user.json

# Pretty JSON with jq
curl -s https://api.example.com/users | jq .

Form Data

1
2
3
4
5
6
7
8
# URL-encoded form
curl -X POST https://api.example.com/login \
  -d "username=alice&password=secret"

# Multipart form (file upload)
curl -X POST https://api.example.com/upload \
  -F "file=@photo.jpg" \
  -F "description=My photo"

Query Parameters

1
2
3
4
5
6
7
# In URL
curl "https://api.example.com/search?q=hello&limit=10"

# With --data-urlencode (safer)
curl -G https://api.example.com/search \
  --data-urlencode "q=hello world" \
  --data-urlencode "limit=10"

Headers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Single header
curl -H "Authorization: Bearer token123" https://api.example.com/me

# Multiple headers
curl -H "Authorization: Bearer token123" \
     -H "Accept: application/json" \
     -H "X-Request-ID: abc123" \
     https://api.example.com/me

# Common patterns
curl -H "Content-Type: application/json" ...
curl -H "Accept: application/json" ...
curl -H "Authorization: Basic $(echo -n 'user:pass' | base64)" ...

Authentication

Bearer Token

1
curl -H "Authorization: Bearer eyJhbGc..." https://api.example.com/me

Basic Auth

1
2
3
4
curl -u username:password https://api.example.com/secure

# Or manually
curl -H "Authorization: Basic $(echo -n 'user:pass' | base64)" https://api.example.com/secure

API Key

1
2
3
4
5
# In header
curl -H "X-API-Key: your-api-key" https://api.example.com/data

# In query string
curl "https://api.example.com/data?api_key=your-api-key"

Response Handling

Save to File

1
2
3
4
5
6
7
8
# Save response body
curl -o response.json https://api.example.com/data

# Save with remote filename
curl -O https://example.com/file.zip

# Save headers and body separately
curl -D headers.txt -o body.json https://api.example.com/data

Extract Specific Info

1
2
3
4
5
6
7
8
9
# HTTP status code only
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health

# Response time
curl -s -o /dev/null -w "%{time_total}" https://api.example.com/health

# Multiple metrics
curl -s -o /dev/null -w "Status: %{http_code}\nTime: %{time_total}s\nSize: %{size_download} bytes\n" \
  https://api.example.com/data

Write-Out Variables

1
2
3
4
5
6
7
8
9
curl -w "
  HTTP Code: %{http_code}
  Total Time: %{time_total}s
  DNS Lookup: %{time_namelookup}s
  Connect: %{time_connect}s
  TTFB: %{time_starttransfer}s
  Download Size: %{size_download} bytes
  Download Speed: %{speed_download} bytes/sec
" -o /dev/null -s https://api.example.com/data

Debugging

Verbose Output

1
2
3
4
5
6
7
8
# Show request and response headers
curl -v https://api.example.com/data

# Even more verbose (includes TLS handshake)
curl -vv https://api.example.com/data

# Trace everything (hex dump)
curl --trace - https://api.example.com/data

Debug TLS/SSL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Show certificate info
curl -vI https://api.example.com 2>&1 | grep -A 10 "Server certificate"

# Skip certificate verification (don't use in production!)
curl -k https://self-signed.example.com/data

# Use specific CA cert
curl --cacert /path/to/ca.crt https://api.example.com/data

# Client certificate auth
curl --cert client.crt --key client.key https://api.example.com/data

Timeouts and Retries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Connection timeout (seconds)
curl --connect-timeout 5 https://api.example.com/data

# Max time for entire operation
curl --max-time 30 https://api.example.com/data

# Retry on failure
curl --retry 3 --retry-delay 2 https://api.example.com/data

# Retry only on specific errors
curl --retry 3 --retry-connrefused https://api.example.com/data

Cookies

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Send cookie
curl -b "session=abc123" https://api.example.com/dashboard

# Save cookies to file
curl -c cookies.txt https://api.example.com/login -d "user=alice&pass=secret"

# Use saved cookies
curl -b cookies.txt https://api.example.com/dashboard

# Both save and send
curl -b cookies.txt -c cookies.txt https://api.example.com/page

Proxies

1
2
3
4
5
6
7
8
# HTTP proxy
curl -x http://proxy.example.com:8080 https://api.example.com/data

# SOCKS5 proxy
curl --socks5 localhost:1080 https://api.example.com/data

# Proxy with auth
curl -x http://user:pass@proxy.example.com:8080 https://api.example.com/data

Practical Examples

Health Check Script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash
URL="https://api.example.com/health"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$URL")

if [ "$STATUS" = "200" ]; then
    echo "OK"
    exit 0
else
    echo "FAIL: HTTP $STATUS"
    exit 1
fi

API Test with Error Handling

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/bash
response=$(curl -s -w "\n%{http_code}" \
  -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Test User"}')

body=$(echo "$response" | head -n -1)
status=$(echo "$response" | tail -n 1)

if [ "$status" = "201" ]; then
    echo "Created user: $(echo "$body" | jq -r '.id')"
else
    echo "Error $status: $(echo "$body" | jq -r '.error')"
    exit 1
fi

Pagination Loop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash
page=1
while true; do
    response=$(curl -s "https://api.example.com/items?page=$page&per_page=100")
    count=$(echo "$response" | jq '.items | length')
    
    if [ "$count" = "0" ]; then
        break
    fi
    
    echo "$response" | jq -r '.items[].name'
    ((page++))
done

OAuth Token Flow

1
2
3
4
5
6
7
8
9
# Get token
TOKEN=$(curl -s -X POST https://auth.example.com/oauth/token \
  -d "grant_type=client_credentials" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  | jq -r '.access_token')

# Use token
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data

Config Files

Save common options in ~/.curlrc:

#--s--HDmreae"fxtAa-rcutyclietm2peto:p3t0aipopnlsication/json"

Or use command-line config:

1
curl -K myconfig.txt https://api.example.com/data

Quick Reference

OptionPurpose
-X METHODHTTP method
-H "Header: Value"Add header
-d "data"Send data (POST)
-d @fileSend data from file
-F "field=value"Multipart form
-u user:passBasic auth
-o fileSave output
-sSilent mode
-vVerbose
-iInclude headers
-LFollow redirects
-kSkip TLS verify
--max-time NTimeout
-w "format"Custom output

curl is one of those tools that rewards memorization. The more commands you know by heart, the faster you can debug APIs. Start with -X, -H, -d, and -s — those four cover most API testing scenarios.