API Testing with curl: A Practical Guide

curl is the universal HTTP client available on virtually every system. This guide covers everything you need to test REST APIs effectively: all HTTP methods, authentication, file uploads, cookie handling, response inspection, and scripted API tests.

curl API Testing REST HTTP Linux

Essential curl Flags

FlagPurpose
-sSilent mode (suppress progress bar)
-vVerbose: show request and response headers
-iInclude response headers in output
-o fileSave response to file
-X METHODSet HTTP method (GET, POST, PUT, DELETE, PATCH)
-H "Header: Value"Set a request header
-d '{...}'Request body (raw data)
-F field=valueForm data (multipart)
-u user:passBasic authentication
-LFollow redirects
-kSkip SSL verification (dev only)
-w "%{http_code}"Print response code after request

Basic CRUD Requests

GET

# Basic GET request
curl -s https://jsonplaceholder.typicode.com/posts/1 | jq .

# GET with query parameters
curl -sG https://api.example.com/search \
  --data-urlencode "q=bash scripting" \
  --data-urlencode "limit=10" | jq .

# GET and show only the HTTP status code
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health

POST JSON

# POST JSON body
curl -s -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Alice",
    "email": "alice@example.com",
    "role": "admin"
  }' | jq .

# POST from a JSON file
curl -s -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d @user.json | jq .

PUT and PATCH

# Full update with PUT
curl -s -X PUT https://api.example.com/users/123 \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Smith", "email": "alice@example.com"}'

# Partial update with PATCH
curl -s -X PATCH https://api.example.com/users/123 \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Smith"}'

DELETE

curl -s -X DELETE https://api.example.com/users/123
curl -s -o /dev/null -w "%{http_code}" -X DELETE https://api.example.com/posts/5
# Should print 204 for success

Authentication

Bearer Token (JWT)

TOKEN="eyJhbGciOiJIUzI1NiJ9..."
curl -s https://api.example.com/me \
  -H "Authorization: Bearer ${TOKEN}" | jq .

API Key in Header

curl -s https://api.example.com/data \
  -H "X-API-Key: your-api-key-here" | jq .

Basic Authentication

# curl handles the Base64 encoding automatically with -u
curl -s -u "admin:password" https://api.example.com/admin/stats

Login and Save Cookie

# Login and save session cookie to file
curl -s -c cookies.txt -X POST https://api.example.com/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "secret"}'

# Use saved cookie for subsequent requests
curl -s -b cookies.txt https://api.example.com/dashboard | jq .

File Uploads

# Upload a single file (multipart form)
curl -s -X POST https://api.example.com/upload \
  -F "file=@/path/to/image.jpg" \
  -F "description=Profile photo"

# Upload raw binary (Content-Type: application/octet-stream)
curl -s -X PUT https://api.example.com/storage/file.pdf \
  -H "Content-Type: application/pdf" \
  --data-binary @document.pdf

Debugging Requests

# Show full request and response headers
curl -v https://api.example.com/users 2>&1 | head -40

# Show only response headers
curl -si https://api.example.com/users | head -20

# Measure timing breakdown
curl -s -o /dev/null -w "
DNS: %{time_namelookup}s
Connect: %{time_connect}s
SSL: %{time_appconnect}s
TTFB: %{time_starttransfer}s
Total: %{time_total}s
" https://api.example.com/users

Writing a Simple API Test Script

#!/usr/bin/env bash
set -euo pipefail

BASE_URL="https://api.example.com"
TOKEN="your-token-here"
PASS=0; FAIL=0

assert_status() {
  local desc="$1" expected="$2" actual="$3"
  if [ "${actual}" = "${expected}" ]; then
    echo "✅ ${desc}"
    ((PASS++))
  else
    echo "❌ ${desc} (expected ${expected}, got ${actual})"
    ((FAIL++))
  fi
}

# Test 1: Health check
status=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/health")
assert_status "GET /health returns 200" "200" "${status}"

# Test 2: Authenticated endpoint
status=$(curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer ${TOKEN}" \
  "${BASE_URL}/me")
assert_status "GET /me returns 200 with token" "200" "${status}"

# Test 3: Unauthenticated request rejected
status=$(curl -s -o /dev/null -w "%{http_code}" "${BASE_URL}/me")
assert_status "GET /me returns 401 without token" "401" "${status}"

echo ""
echo "Results: ${PASS} passed, ${FAIL} failed"

Related Tools & Snippets