curl: HTTP Client & API Automation
Every API speaks HTTP. curl is how you talk to them.
Core Concepts
HTTP Request Anatomy
┌─────────────────────────────────────────────────────────────────┐
│ HTTP REQUEST │
├─────────────────────────────────────────────────────────────────┤
│ │
│ METHOD URL │
│ ────── ─── │
│ GET https://api.example.com/users │
│ POST https://api.example.com/users │
│ PUT https://api.example.com/users/123 │
│ DELETE https://api.example.com/users/123 │
│ │
│ HEADERS │
│ ─────── │
│ Authorization: Bearer eyJhbG... │
│ Content-Type: application/json │
│ Accept: application/json │
│ │
│ BODY (POST/PUT/PATCH) │
│ ──── │
│ {"name": "value", "key": "data"} │
│ │
└─────────────────────────────────────────────────────────────────┘
Essential Flags
| Flag | Purpose | Example |
|---|---|---|
|
Silent (no progress) |
|
|
Show errors (with -s) |
|
|
Fail on HTTP errors |
|
|
Output to file |
|
|
Save with remote filename |
|
|
Follow redirects |
|
|
Skip TLS verification |
|
|
Verbose (debug) |
|
|
Headers only (HEAD) |
|
|
HTTP method |
|
|
Add header |
|
|
POST data |
|
|
Basic auth |
|
|
Output format |
|
GET Requests
Basic GET
# Simple GET
curl -s https://api.example.com/users
# With jq processing
curl -s https://api.example.com/users | jq '.[] | {id, name}'
# Follow redirects
curl -sL https://short.url/abc
Authentication
Basic Auth
# Username:password
curl -s -u admin:secretpass https://api.example.com/users
# From environment (safer)
curl -s -u "$API_USER:$API_PASS" https://api.example.com/users
# Prompt for password
curl -s -u admin https://api.example.com/users
Bearer Token
# JWT or OAuth token
curl -s -H "Authorization: Bearer $TOKEN" https://api.example.com/users
# From file
curl -s -H "Authorization: Bearer $(cat ~/.tokens/api.token)" https://api.example.com/users
API Key
# In header
curl -s -H "X-API-Key: $API_KEY" https://api.example.com/users
# In query string (less secure)
curl -s "https://api.example.com/users?api_key=$API_KEY"
Client Certificate (mTLS)
# Certificate + key
curl -s --cert client.pem --key client.key https://mtls.example.com/api
# PKCS12 bundle
curl -s --cert client.p12:password https://mtls.example.com/api
# With CA verification
curl -s --cacert ca.pem --cert client.pem --key client.key https://mtls.example.com/api
POST/PUT/PATCH Requests
JSON Body
# POST with JSON
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"name": "test", "email": "test@example.com"}' \
https://api.example.com/users
# From variable
DATA='{"name": "test"}'
curl -s -X POST -H "Content-Type: application/json" -d "$DATA" https://api.example.com/users
# From file
curl -s -X POST -H "Content-Type: application/json" -d @payload.json https://api.example.com/users
# From stdin
echo '{"name": "test"}' | curl -s -X POST -H "Content-Type: application/json" -d @- https://api.example.com/users
Form Data
# URL-encoded form
curl -s -X POST \
-d "username=admin" \
-d "password=secret" \
https://api.example.com/login
# Multipart form (file upload)
curl -s -X POST \
-F "file=@document.pdf" \
-F "description=My document" \
https://api.example.com/upload
PUT and PATCH
# PUT (full update)
curl -s -X PUT \
-H "Content-Type: application/json" \
-d '{"name": "updated", "email": "new@example.com"}' \
https://api.example.com/users/123
# PATCH (partial update)
curl -s -X PATCH \
-H "Content-Type: application/json" \
-d '{"email": "new@example.com"}' \
https://api.example.com/users/123
Response Handling
Status Code Extraction
# Just the status code
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health
# Status code with body
curl -s -w "\n%{http_code}" https://api.example.com/users
# Conditional logic
status=$(curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health)
if [[ "$status" -eq 200 ]]; then
echo "API healthy"
else
echo "API returned $status"
fi
Headers and Body Separation
# Response headers only
curl -s -I https://api.example.com/users
# Headers + body (verbose)
curl -s -D - https://api.example.com/users
# Save headers to file, body to stdout
curl -s -D headers.txt https://api.example.com/users | jq '.'
Error Handling
# Fail on HTTP errors (4xx, 5xx)
curl -sf https://api.example.com/users || echo "Request failed"
# Capture both output and status
response=$(curl -s -w "\n%{http_code}" https://api.example.com/users)
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [[ "$http_code" -eq 200 ]]; then
echo "$body" | jq '.'
else
echo "Error: HTTP $http_code"
echo "$body"
fi
Timing Information
# Connection timing
curl -s -o /dev/null -w "
DNS: %{time_namelookup}s
Connect: %{time_connect}s
TLS: %{time_appconnect}s
Start: %{time_starttransfer}s
Total: %{time_total}s
" https://api.example.com/users
# JSON format for processing
curl -s -o /dev/null -w '{
"dns": %{time_namelookup},
"connect": %{time_connect},
"tls": %{time_appconnect},
"total": %{time_total}
}' https://api.example.com/users | jq '.'
Infrastructure API Patterns
ISE ERS API
# Get endpoints
curl -s -k -u "$ISE_USER:$ISE_PASS" \
-H "Accept: application/json" \
"https://ise-01.inside.domusdigitalis.dev:9060/ers/config/endpoint" | \
jq '.SearchResult.resources[] | {id, name}'
# Create endpoint
curl -s -k -u "$ISE_USER:$ISE_PASS" \
-X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"ERSEndPoint": {
"mac": "AA:BB:CC:DD:EE:FF",
"groupId": "group-id-here",
"staticGroupAssignment": true
}
}' \
"https://ise-01.inside.domusdigitalis.dev:9060/ers/config/endpoint"
Vault API
# Read secret
curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/secret/data/myapp" | jq '.data.data'
# Write secret
curl -s -X POST \
-H "X-Vault-Token: $VAULT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"data": {"password": "newsecret"}}' \
"$VAULT_ADDR/v1/secret/data/myapp"
# List secrets
curl -s -X LIST -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/secret/metadata/" | jq '.data.keys[]'
Wazuh API
# Get JWT token
TOKEN=$(curl -s -k -u "$WAZUH_USER:$WAZUH_PASS" \
-X POST "https://wazuh.inside.domusdigitalis.dev:55000/security/user/authenticate" | \
jq -r '.data.token')
# List agents
curl -s -k -H "Authorization: Bearer $TOKEN" \
"https://wazuh.inside.domusdigitalis.dev:55000/agents" | \
jq '.data.affected_items[] | {id, name, status}'
# Get alerts
curl -s -k -H "Authorization: Bearer $TOKEN" \
"https://wazuh.inside.domusdigitalis.dev:55000/security/events?limit=100" | \
jq '.data.affected_items[] | {timestamp, rule: .rule.description, level: .rule.level}'
GitHub API
# List repos
curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/user/repos?per_page=100" | \
jq '.[] | {name, private, default_branch}'
# Create issue
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-d '{"title": "Bug report", "body": "Description here"}' \
"https://api.github.com/repos/owner/repo/issues"
# Dependabot alerts
curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/owner/repo/dependabot/alerts" | \
jq '.[] | {package: .dependency.package.name, severity: .security_advisory.severity}'
pfSense API
# Get system info (with pfSense-API package)
curl -s -k -u "$PFSENSE_USER:$PFSENSE_PASS" \
"https://pfsense.inside.domusdigitalis.dev/api/v1/system/info" | jq '.'
# Get firewall rules
curl -s -k -u "$PFSENSE_USER:$PFSENSE_PASS" \
"https://pfsense.inside.domusdigitalis.dev/api/v1/firewall/rule" | \
jq '.data[] | {interface, type, source, destination}'
Parallel and Batch Requests
Sequential Loop
# Simple loop
for id in 1 2 3 4 5; do
curl -s "https://api.example.com/users/$id" | jq '.name'
done
Parallel with xargs
# Parallel requests (4 at a time)
echo -e "1\n2\n3\n4\n5" | \
xargs -P4 -I{} curl -s "https://api.example.com/users/{}"
# From file
cat user_ids.txt | xargs -P4 -I{} curl -s "https://api.example.com/users/{}" | jq -s '.'
Debugging
Verbose Output
# Full request/response debug
curl -v https://api.example.com/users 2>&1
# Even more verbose (hex dump)
curl --trace - https://api.example.com/users
# Trace to file
curl --trace-ascii debug.log https://api.example.com/users
Common Issues
| Symptom | Solution |
|---|---|
|
Add |
|
Check host/port, firewall, DNS |
|
Add |
Empty response |
Remove |
JSON parse error |
Check |
|
Check auth header/credentials |
|
Check permissions, API key scope |
|
Add rate limiting, check retry-after header |
Configuration File
Quick Reference
| Task | Command |
|---|---|
GET with JSON |
|
POST JSON |
|
Bearer auth |
|
Basic auth |
|
Skip TLS verify |
|
Get status code |
|
Follow redirects |
|
Download file |
|
Upload file |
|
Custom method |
|
Timing info |
|
Headers only |
|
Related
-
jq Mastery - Process curl JSON output
-
openssl - TLS debugging