API Error Handling
Error response patterns, status code decisions, and retry strategies.
Error Response Patterns
Standard error envelope — consistent structure across APIs
{
"error": {
"code": 422,
"message": "Validation failed",
"details": [
{"field": "vlan", "issue": "must be between 1 and 4094"},
{"field": "name", "issue": "already exists"}
],
"request_id": "req-abc123"
}
}
FastAPI HTTPException pattern — domus-api style
from fastapi import HTTPException
raise HTTPException(
status_code=404,
detail={"message": "Device not found", "device_id": device_id}
)
FastAPI validation error — Pydantic rejects invalid input automatically
{
"detail": [
{
"type": "int_parsing",
"loc": ["body", "vlan"],
"msg": "Input should be a valid integer",
"input": "not-a-number"
}
]
}
HTTP Status Code Decision Tree
When to use which error code
Is the request malformed?
├── Yes → 400 Bad Request (missing field, wrong type)
└── No → Is the client authenticated?
├── No → 401 Unauthorized (missing/invalid credentials)
└── Yes → Is the client authorized?
├── No → 403 Forbidden (valid creds, insufficient perms)
└── Yes → Does the resource exist?
├── No → 404 Not Found
└── Yes → Is there a conflict?
├── Yes → 409 Conflict (duplicate, version mismatch)
└── No → Is input semantically valid?
├── No → 422 Unprocessable Entity
└── Yes → 500 Internal Server Error
Client-Side Error Handling
Bash — check status code and parse error body
response=$(curl -s -w '\n%{http_code}' \
-H "Authorization: Bearer $TOKEN" \
https://api.example.com/v1/devices/999)
status=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
case "$status" in
200) echo "$body" | jq . ;;
401) echo "Auth failed -- token expired?" >&2; exit 1 ;;
404) echo "Device not found" >&2; exit 1 ;;
429) echo "Rate limited -- retry after $(echo "$body" | jq -r '.retry_after // 60')s" >&2 ;;
5??) echo "Server error ($status) -- retry later" >&2; exit 2 ;;
*) echo "Unexpected: HTTP $status" >&2; echo "$body" | jq . >&2 ;;
esac
Python httpx — structured error handling
import httpx
try:
response = httpx.get("https://api.example.com/v1/devices/42",
headers={"Authorization": f"Bearer {token}"})
response.raise_for_status()
device = response.json()
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
print(f"Device not found: {e.response.json()['detail']}")
elif e.response.status_code == 429:
retry_after = int(e.response.headers.get("Retry-After", 60))
print(f"Rate limited, retry after {retry_after}s")
else:
print(f"HTTP {e.response.status_code}: {e.response.text}")
except httpx.ConnectError:
print("Cannot reach API -- network issue or DNS failure")
except httpx.TimeoutException:
print("Request timed out")
Retry Strategies
Exponential backoff with jitter — prevent thundering herd
import httpx
import time
import random
def request_with_retry(url: str, max_retries: int = 5) -> httpx.Response:
for attempt in range(max_retries):
try:
response = httpx.get(url, timeout=10.0)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
time.sleep(retry_after + random.uniform(0, 1))
continue
response.raise_for_status()
return response
except httpx.TransportError:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt + random.uniform(0, 1))
raise RuntimeError(f"Failed after {max_retries} attempts")
Idempotency key — safe retries for POST
idempotency_key=$(uuidgen)
for attempt in 1 2 3; do
status=$(curl -s -o /dev/null -w '%{http_code}' \
-X POST https://api.example.com/v1/orders \
-H "Idempotency-Key: $idempotency_key" \
-H "Content-Type: application/json" \
-d '{"item": "license"}')
[[ "$status" -eq 201 || "$status" -eq 200 ]] && break
sleep $(( 2 ** attempt ))
done
ISE ERS Error Patterns
ISE ERS returns XML errors by default — always request JSON
# Wrong -- gets XML error you cannot easily parse
curl -sk -u "$ISE_USER:$ISE_PASS" \
https://10.50.1.20:9060/ers/config/endpoint/bad-id
# Correct -- JSON error response
curl -sk -u "$ISE_USER:$ISE_PASS" \
-H "Accept: application/json" \
https://10.50.1.20:9060/ers/config/endpoint/bad-id | jq .
Common ISE ERS errors
401 -- ERS admin credentials wrong or ERS API not enabled 404 -- Resource ID does not exist 500 -- ISE internal error (check ise-psc.log on the node)