curl Mastery: HTTP Requests from the Command Line

curl is the universal HTTP client. It’s installed everywhere, works with any API, and once mastered, becomes your go-to tool for testing, debugging, and scripting HTTP interactions. Basic Requests 1 2 3 4 5 6 7 8 # GET (default) curl https://api.example.com/users # Explicit methods curl -X POST https://api.example.com/users curl -X PUT https://api.example.com/users/1 curl -X DELETE https://api.example.com/users/1 curl -X PATCH https://api.example.com/users/1 Adding Headers 1 2 3 4 5 6 7 8 # Single header curl -H "Authorization: Bearer token123" https://api.example.com/me # Multiple headers curl -H "Authorization: Bearer token123" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ https://api.example.com/users Sending Data JSON Body 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # Inline JSON 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 @payload.json # From stdin echo '{"name": "Alice"}' | curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d @- Form Data 1 2 3 4 5 6 7 8 # URL-encoded (default for -d without Content-Type) curl -X POST https://api.example.com/login \ -d "username=alice&password=secret" # Multipart form (file uploads) curl -X POST https://api.example.com/upload \ -F "file=@document.pdf" \ -F "description=My document" Response Handling Show Headers 1 2 3 4 5 6 7 8 # Response headers only curl -I https://api.example.com/health # Headers + body curl -i https://api.example.com/users # Verbose (request + response headers) curl -v https://api.example.com/users Output Control 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # Save to file curl -o response.json https://api.example.com/users # Save with remote filename curl -O https://example.com/file.zip # Silent (no progress bar) curl -s https://api.example.com/users # Silent but show errors curl -sS https://api.example.com/users # Only output body (suppress all else) curl -s https://api.example.com/users | jq '.' Extract Specific Info 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # HTTP status code only curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health # Multiple variables curl -s -o /dev/null -w "Status: %{http_code}\nTime: %{time_total}s\nSize: %{size_download} bytes\n" \ https://api.example.com/users # Available variables # %{http_code} - HTTP status code # %{time_total} - Total time in seconds # %{time_connect} - Time to establish connection # %{time_starttransfer} - Time to first byte # %{size_download} - Downloaded bytes # %{url_effective} - Final URL after redirects Authentication 1 2 3 4 5 6 7 8 9 10 11 # Basic auth curl -u username:password https://api.example.com/secure # Bearer token curl -H "Authorization: Bearer eyJhbG..." https://api.example.com/me # API key in header curl -H "X-API-Key: abc123" https://api.example.com/data # API key in query string curl "https://api.example.com/data?api_key=abc123" Following Redirects 1 2 3 4 5 6 7 8 # Follow redirects (disabled by default) curl -L https://short.url/abc # Limit redirect count curl -L --max-redirs 5 https://example.com # Show redirect chain curl -L -v https://short.url/abc 2>&1 | grep "< location" 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 # Total operation timeout curl --max-time 30 https://api.example.com/slow-endpoint # Retry on failure curl --retry 3 --retry-delay 2 https://api.example.com # Retry on specific HTTP codes curl --retry 3 --retry-all-errors https://api.example.com SSL/TLS Options 1 2 3 4 5 6 7 8 # Skip certificate verification (development only!) curl -k https://self-signed.example.com # Use specific CA certificate curl --cacert /path/to/ca.crt https://api.example.com # Client certificate authentication curl --cert client.crt --key client.key https://api.example.com Cookies 1 2 3 4 5 6 7 8 9 10 11 # Send cookies curl -b "session=abc123; token=xyz" https://api.example.com # Save cookies to file curl -c cookies.txt https://api.example.com/login -d "user=alice&pass=secret" # Load cookies from file curl -b cookies.txt https://api.example.com/dashboard # Both (maintain session) curl -b cookies.txt -c cookies.txt https://api.example.com/action Useful Patterns Health Check Script 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash check_health() { local url=$1 local status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url") if [ "$status" = "200" ]; then echo "✓ $url" return 0 else echo "✗ $url (HTTP $status)" return 1 fi } check_health "https://api.example.com/health" check_health "https://web.example.com" API Testing 1 2 3 4 5 6 7 8 9 10 # Create resource and capture ID ID=$(curl -s -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name": "Test User"}' | jq -r '.id') # Use captured ID curl -s https://api.example.com/users/$ID | jq '.' # Delete curl -X DELETE https://api.example.com/users/$ID Download with Progress 1 2 3 4 5 # Show progress bar curl -# -O https://example.com/large-file.zip # Resume interrupted download curl -C - -O https://example.com/large-file.zip Parallel Requests 1 2 3 4 5 6 7 8 # Using xargs echo -e "url1\nurl2\nurl3" | xargs -P 4 -I {} curl -s {} -o /dev/null -w "{}: %{http_code}\n" # Using curl's parallel feature (7.68+) curl --parallel --parallel-immediate \ https://api1.example.com \ https://api2.example.com \ https://api3.example.com Debugging Trace All Details 1 2 # Full trace including SSL handshake curl -v --trace-ascii debug.txt https://api.example.com Common Issues 1 2 3 4 5 6 7 8 9 # DNS resolution problems curl -v --resolve api.example.com:443:1.2.3.4 https://api.example.com # Force IPv4 or IPv6 curl -4 https://api.example.com # IPv4 only curl -6 https://api.example.com # IPv6 only # Use specific interface curl --interface eth0 https://api.example.com Config File Save common options in ~/.curlrc: ...

February 26, 2026 Â· 6 min Â· 1117 words Â· Rob Washington

LLM API Integration Patterns: Building Reliable AI-Powered Applications

Integrating LLM APIs into production applications requires more than just making API calls. These patterns address the real challenges: rate limits, token costs, latency, and reliability. Basic Client Setup 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import os from anthropic import Anthropic client = Anthropic( api_key=os.environ.get("ANTHROPIC_API_KEY"), timeout=60.0, max_retries=3, ) def chat(message: str, system: str = None) -> str: """Simple completion with sensible defaults.""" messages = [{"role": "user", "content": message}] response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=system or "You are a helpful assistant.", messages=messages, ) return response.content[0].text Retry with Exponential Backoff Built-in retries help, but custom logic handles edge cases: ...

February 25, 2026 Â· 7 min Â· 1291 words Â· Rob Washington

curl Deep Dive: HTTP Requests from the Command Line

curl is the universal language of HTTP. Every API doc includes curl examples. Every debugging session starts with “can you curl it?” If you’re not comfortable with curl, you’re missing the most portable tool in your kit. Basic Requests 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # GET (default) 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://example.com/redirect # Verbose (debug mode) curl -v https://api.example.com/users HTTP Methods 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # POST curl -X POST https://api.example.com/users # PUT curl -X PUT https://api.example.com/users/1 # PATCH curl -X PATCH https://api.example.com/users/1 # DELETE curl -X DELETE https://api.example.com/users/1 # HEAD (headers only, like -I) curl -X HEAD https://api.example.com/users Sending Data Form Data 1 2 3 4 5 6 7 # URL-encoded form curl -X POST https://api.example.com/login \ -d "username=admin&password=secret" # From file curl -X POST https://api.example.com/login \ -d @credentials.txt JSON Data 1 2 3 4 5 6 7 8 9 10 11 12 # Inline JSON 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 # Using --json (curl 7.82+) curl --json '{"name":"Alice"}' https://api.example.com/users File Upload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # Single file curl -X POST https://api.example.com/upload \ -F "file=@document.pdf" # Multiple files curl -X POST https://api.example.com/upload \ -F "file1=@doc1.pdf" \ -F "file2=@doc2.pdf" # File with custom filename curl -X POST https://api.example.com/upload \ -F "file=@localname.pdf;filename=remote.pdf" # File with content type curl -X POST https://api.example.com/upload \ -F "file=@image.png;type=image/png" # Mixed form data and files curl -X POST https://api.example.com/upload \ -F "title=My Document" \ -F "file=@document.pdf" Headers 1 2 3 4 5 6 7 8 9 10 11 12 13 # Custom header curl -H "X-Custom-Header: value" https://api.example.com # Multiple headers curl -H "Accept: application/json" \ -H "X-API-Version: 2" \ https://api.example.com # User agent curl -A "MyApp/1.0" https://api.example.com # Referer curl -e "https://example.com" https://api.example.com Authentication Basic Auth 1 2 3 4 5 6 7 8 # Username and password curl -u username:password https://api.example.com # Prompt for password curl -u username https://api.example.com # In URL (not recommended) curl https://username:password@api.example.com Bearer Token 1 curl -H "Authorization: Bearer YOUR_TOKEN" https://api.example.com API Key 1 2 3 4 5 # In header curl -H "X-API-Key: YOUR_KEY" https://api.example.com # In query string curl "https://api.example.com?api_key=YOUR_KEY" OAuth 2.0 Flow 1 2 3 4 5 6 7 8 9 # Get access token curl -X POST https://auth.example.com/oauth/token \ -d "grant_type=client_credentials" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_SECRET" # Use token TOKEN="eyJ..." curl -H "Authorization: Bearer $TOKEN" https://api.example.com/resource Digest Auth 1 curl --digest -u username:password https://api.example.com Output Options 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Save to file curl -o output.html https://example.com # Save with remote filename curl -O https://example.com/file.zip # Save multiple files curl -O https://example.com/file1.zip -O https://example.com/file2.zip # Append to file curl https://example.com >> output.txt # Write headers to file curl -D headers.txt https://example.com # Output to stdout and file curl https://example.com | tee output.html Timeouts and Retries 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # Connection timeout (seconds) curl --connect-timeout 5 https://api.example.com # Max time for entire operation curl -m 30 https://api.example.com # Retry on failure curl --retry 3 https://api.example.com # Retry with delay curl --retry 3 --retry-delay 5 https://api.example.com # Retry on specific errors curl --retry 3 --retry-all-errors https://api.example.com SSL/TLS 1 2 3 4 5 6 7 8 9 10 11 12 # Skip certificate verification (insecure!) curl -k https://self-signed.example.com # Use specific CA certificate curl --cacert /path/to/ca.crt https://api.example.com # Client certificate curl --cert client.crt --key client.key https://api.example.com # Force TLS version curl --tlsv1.2 https://api.example.com curl --tlsv1.3 https://api.example.com Proxy 1 2 3 4 5 6 7 8 9 10 11 # HTTP proxy curl -x http://proxy:8080 https://api.example.com # SOCKS5 proxy curl --socks5 localhost:1080 https://api.example.com # Proxy with auth curl -x http://user:pass@proxy:8080 https://api.example.com # No proxy for specific hosts curl --noproxy "localhost,*.internal" https://api.example.com Cookies 1 2 3 4 5 6 7 8 9 10 11 # Send cookie curl -b "session=abc123" https://api.example.com # Send cookies from file curl -b cookies.txt https://api.example.com # Save cookies to file curl -c cookies.txt https://api.example.com/login # Full session (save and send) curl -b cookies.txt -c cookies.txt https://api.example.com Response Inspection 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # HTTP status code only curl -s -o /dev/null -w "%{http_code}" https://api.example.com # Response time curl -s -o /dev/null -w "%{time_total}s" https://api.example.com # Detailed 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 Size: %{size_download} bytes Speed: %{speed_download} bytes/sec " https://api.example.com # Content type curl -s -o /dev/null -w "%{content_type}" https://api.example.com Scripting Patterns Health Check 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}" "$URL") if [ "$STATUS" -eq 200 ]; then echo "OK" exit 0 else echo "FAIL: HTTP $STATUS" exit 1 fi API Wrapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #!/bin/bash API_BASE="https://api.example.com" API_KEY="${API_KEY:?API_KEY required}" api_get() { curl -s -H "Authorization: Bearer $API_KEY" "$API_BASE$1" } api_post() { curl -s -X POST \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d "$2" \ "$API_BASE$1" } # Usage api_get "/users" | jq '.' api_post "/users" '{"name":"Alice"}' Retry with Backoff 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #!/bin/bash URL="$1" MAX_RETRIES=5 RETRY_DELAY=1 for i in $(seq 1 $MAX_RETRIES); do RESPONSE=$(curl -s -w "\n%{http_code}" "$URL") STATUS=$(echo "$RESPONSE" | tail -1) BODY=$(echo "$RESPONSE" | sed '$d') if [ "$STATUS" -eq 200 ]; then echo "$BODY" exit 0 fi echo "Attempt $i failed (HTTP $STATUS), retrying in ${RETRY_DELAY}s..." >&2 sleep $RETRY_DELAY RETRY_DELAY=$((RETRY_DELAY * 2)) done echo "Failed after $MAX_RETRIES attempts" >&2 exit 1 Parallel Requests 1 2 3 4 5 # Using xargs cat urls.txt | xargs -P 10 -I {} curl -s -o /dev/null -w "{}: %{http_code}\n" {} # Using GNU parallel parallel -j 10 curl -s -o /dev/null -w "{}: %{http_code}\n" ::: $(cat urls.txt) Config Files Create ~/.curlrc for defaults: ...

February 25, 2026 Â· 8 min Â· 1541 words Â· Rob Washington

curl for API Testing: The Essential Commands

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

February 24, 2026 Â· 6 min Â· 1094 words Â· Rob Washington

LLM API Integration Patterns: Building Reliable AI Features

LLM APIs are deceptively simple: send a prompt, get text back. But building reliable AI features requires handling rate limits, managing costs, structuring outputs, and gracefully degrading when things go wrong. Here are the patterns that work in production. The Basic Client Start with a wrapper that handles common concerns: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import os import time from typing import Optional import anthropic from tenacity import retry, stop_after_attempt, wait_exponential class LLMClient: def __init__(self): self.client = anthropic.Anthropic() self.default_model = "claude-sonnet-4-20250514" self.max_tokens = 4096 @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=60) ) def complete( self, prompt: str, system: Optional[str] = None, model: Optional[str] = None, max_tokens: Optional[int] = None ) -> str: messages = [{"role": "user", "content": prompt}] response = self.client.messages.create( model=model or self.default_model, max_tokens=max_tokens or self.max_tokens, system=system or "", messages=messages ) return response.content[0].text The tenacity library handles retries with exponential backoff — essential for rate limits and transient errors. ...

February 24, 2026 Â· 6 min Â· 1104 words Â· Rob Washington

API Versioning Strategies: Breaking Changes Without Breaking Clients

Your API will change. Features will be added, mistakes will be corrected, and sometimes you’ll need to break things. The question isn’t whether to version — it’s how. Why Version? Breaking changes are changes that make existing clients fail: Removing a field from a response Changing a field’s type (string → integer) Requiring a new parameter Changing the meaning of a value Removing an endpoint Without versioning, every change risks breaking someone’s integration. With versioning, you can evolve the API while giving clients time to adapt. ...

February 24, 2026 Â· 7 min Â· 1384 words Â· Rob Washington

Webhook Reliability: Building Event Delivery That Actually Works

Webhooks are the internet’s way of saying “hey, something happened.” Simple in concept, surprisingly tricky in practice. The challenge: HTTP is unreliable, servers go down, networks flake out, and your webhook payload might arrive zero times, once, or five times. Building reliable webhook infrastructure means handling all of these gracefully. The Sender Side Retry with Exponential Backoff First attempt fails? Try again. But not immediately. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 RETRY_DELAYS = [60, 300, 900, 3600, 14400, 43200] # seconds def deliver_webhook(event, attempt=0): try: response = requests.post( event.webhook_url, json=event.payload, timeout=30, headers={ "Content-Type": "application/json", "X-Webhook-ID": event.id, "X-Webhook-Timestamp": str(int(time.time())), "X-Webhook-Signature": sign_payload(event.payload) } ) if response.status_code >= 200 and response.status_code < 300: mark_delivered(event) return if response.status_code >= 500: # Server error, retry schedule_retry(event, attempt) else: # Client error (4xx), don't retry mark_failed(event, response.status_code) except requests.Timeout: schedule_retry(event, attempt) except requests.ConnectionError: schedule_retry(event, attempt) def schedule_retry(event, attempt): if attempt >= len(RETRY_DELAYS): mark_failed(event, "max_retries") return delay = RETRY_DELAYS[attempt] # Add jitter to prevent thundering herd jitter = random.uniform(0, delay * 0.1) queue_at = time.time() + delay + jitter enqueue(event, attempt + 1, queue_at) Key decisions: ...

February 24, 2026 Â· 6 min Â· 1276 words Â· Rob Washington

API Pagination Patterns: Getting Large Result Sets Right

Every API eventually needs to return more data than fits in a single response. How you handle that pagination affects performance, reliability, and developer experience. Let’s look at the common patterns, their tradeoffs, and when to use each. The Three Main Approaches 1. Offset Pagination The classic approach: skip N records, return M records. G G G E E E T T T / / / a a a p p p i i i / / / i i i t t t e e e m m m s s s ? ? ? o o o f f f f f f s s s e e e t t t = = = 0 2 4 & 0 0 l & & i l l m i i i m m t i i = t t 2 = = 0 2 2 0 0 # # # P P P a a a g g g e e e 1 2 3 Implementation: ...

February 24, 2026 Â· 9 min Â· 1726 words Â· Rob Washington

API Client Design: Building SDKs That Developers Love

A well-designed API client turns complex HTTP interactions into simple method calls. It handles authentication, retries, errors, and serialization — so users don’t have to. These patterns create clients that developers actually enjoy using. Basic Structure 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import httpx from typing import Optional from dataclasses import dataclass @dataclass class APIConfig: base_url: str api_key: str timeout: float = 30.0 max_retries: int = 3 class APIClient: def __init__(self, config: APIConfig): self.config = config self._client = httpx.Client( base_url=config.base_url, timeout=config.timeout, headers={"Authorization": f"Bearer {config.api_key}"} ) def _request(self, method: str, path: str, **kwargs) -> dict: response = self._client.request(method, path, **kwargs) response.raise_for_status() return response.json() def close(self): self._client.close() def __enter__(self): return self def __exit__(self, *args): self.close() Resource-Based Design Organize by resource, not by HTTP method: ...

February 24, 2026 Â· 7 min Â· 1401 words Â· Rob Washington

JSON API Design: Conventions That Make Integration Easy

A well-designed API feels obvious. Endpoints are predictable, responses are consistent, errors are helpful. A poorly designed API creates confusion, support tickets, and workarounds. These conventions create APIs that are intuitive to integrate. Resource Naming Use nouns, not verbs. Plural for collections: ✅ G G P P D ❌ G P G P E E O U E E O E O G T T S T L B T S T S o T E a T T o T d d E : : / / / / / / / / / u u u u u g c u u s s s s s e r s s e e e e e t e e e r r r r r U a r r s s s s s s t / s / / / e e 1 / 1 1 1 r U 2 1 2 2 2 s s 3 2 3 3 3 e 3 r / d e l e t # # # # # e L G C U D i e r p e s t e d l t a a e u t t t u s e e e s e e r u u u r s s s s 1 e e e 2 r r r 3 1 1 2 2 3 3 Nested resources for relationships: ...

February 23, 2026 Â· 13 min Â· 2611 words Â· Rob Washington