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

-s

Silent (no progress)

curl -s …​;

-S

Show errors (with -s)

curl -sS …​;

-f

Fail on HTTP errors

curl -sf …​;

-o

Output to file

curl -o file.json …​;

-O

Save with remote filename

curl -O …​/file.zip

-L

Follow redirects

curl -L …​;

-k

Skip TLS verification

curl -k self-signed/

-v

Verbose (debug)

curl -v …​;

-I

Headers only (HEAD)

curl -I …​;

-X

HTTP method

curl -X POST …​;

-H

Add header

curl -H "Auth: token"

-d

POST data

curl -d '{"k":"v"}'

-u

Basic auth

curl -u user:pass

-w

Output format

curl -w "%{http_code}"


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

Query Parameters

# Inline
curl -s "https://api.example.com/users?limit=10&offset=20"

# URL-encoded (safer)
curl -s -G https://api.example.com/search \
  --data-urlencode "q=search term with spaces" \
  --data-urlencode "filter=active"

Headers

# Accept header
curl -s -H "Accept: application/json" https://api.example.com/users

# Multiple headers
curl -s \
  -H "Accept: application/json" \
  -H "X-Custom-Header: value" \
  https://api.example.com/users

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 '.'

Parallel with GNU Parallel

# Parallel with progress
parallel -j4 --progress curl -s "https://api.example.com/users/{}" ::: 1 2 3 4 5

# From file with output files
parallel -j4 curl -s "https://api.example.com/users/{}" -o "user_{}.json" :::: user_ids.txt

Rate-Limited Requests

# 1 request per second
for id in $(seq 1 100); do
  curl -s "https://api.example.com/users/$id" | jq '.name'
  sleep 1
done

# With limit-rate flag (bandwidth limiting)
curl -s --limit-rate 100K https://example.com/largefile.zip -O

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

curl: (60) SSL certificate problem

Add -k for self-signed, or use --cacert ca.pem

curl: (7) Failed to connect

Check host/port, firewall, DNS

curl: (28) Operation timed out

Add --connect-timeout 10 or --max-time 30

Empty response

Remove -s to see errors, add -v for debug

JSON parse error

Check Content-Type header, verify response is JSON

401 Unauthorized

Check auth header/credentials

403 Forbidden

Check permissions, API key scope

429 Too Many Requests

Add rate limiting, check retry-after header

Test Without Sending

# Dry run (show what would be sent)
curl -v -X POST \
  -H "Content-Type: application/json" \
  -d '{"test": "data"}' \
  --trace-ascii /dev/stdout \
  --connect-timeout 0.001 \
  https://api.example.com/test 2>&1 | head -50

Configuration File

.curlrc

Create ~/.curlrc for defaults:

# Always silent with errors shown
-sS

# Always follow redirects
-L

# Connection timeout
--connect-timeout 10

# Max time per request
--max-time 60

# Default user agent
-A "curl/infrastructure-automation"

Netrc for Credentials

Create ~/.netrc:

machine api.example.com
  login myuser
  password mypass

machine vault.inside.domusdigitalis.dev
  login admin
  password vault-password

Use with:

curl -s -n https://api.example.com/users

Quick Reference

Task Command

GET with JSON

curl -s URL | jq '.'

POST JSON

curl -s -X POST -H "Content-Type: application/json" -d '{}' URL

Bearer auth

curl -s -H "Authorization: Bearer $TOKEN" URL

Basic auth

curl -s -u user:pass URL

Skip TLS verify

curl -sk URL

Get status code

curl -so /dev/null -w "%{http_code}" URL

Follow redirects

curl -sL URL

Download file

curl -O URL or curl -o name URL

Upload file

curl -F "file=@path" URL

Custom method

curl -X DELETE URL

Timing info

curl -w "%{time_total}" URL

Headers only

curl -I URL