Error Handling Patterns
Robust API calls require proper error handling, retries, and backoff.
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
200-299 |
Success |
Process response |
400 |
Bad Request |
Fix request syntax |
401 |
Unauthorized |
Check credentials |
403 |
Forbidden |
Check permissions |
404 |
Not Found |
Check URL/resource |
429 |
Rate Limited |
Wait and retry |
500-599 |
Server Error |
Retry with backoff |
Check HTTP Status in Bash
# Check HTTP status before processing
HTTP_CODE=$(curl -ks -o /tmp/response.json -w '%{http_code}' \
-u "$USER:$PASS" "https://api.example.com/resource")
case $HTTP_CODE in
200|201) jq '.' /tmp/response.json ;;
401) echo "ERROR: Authentication failed" >&2; exit 1 ;;
403) echo "ERROR: Permission denied" >&2; exit 1 ;;
404) echo "ERROR: Resource not found" >&2; exit 1 ;;
5*) echo "ERROR: Server error ($HTTP_CODE)" >&2; exit 1 ;;
*) echo "ERROR: Unexpected status $HTTP_CODE" >&2; exit 1 ;;
esac
Retry with Exponential Backoff
# Exponential backoff retry
retry_request() {
local url="$1"
local max_attempts=5
local timeout=2
for ((i=1; i<=max_attempts; i++)); do
HTTP_CODE=$(curl -ks -o /tmp/response.json -w '%{http_code}' \
--max-time 30 "$url")
if [[ $HTTP_CODE =~ ^2 ]]; then
cat /tmp/response.json
return 0
fi
if [[ $HTTP_CODE == 429 ]] || [[ $HTTP_CODE =~ ^5 ]]; then
echo "Attempt $i failed ($HTTP_CODE), retrying in ${timeout}s..." >&2
sleep $timeout
((timeout *= 2))
else
echo "Non-retryable error: $HTTP_CODE" >&2
return 1
fi
done
echo "Max retries exceeded" >&2
return 1
}
JSON Validation
# Validate JSON response
if ! jq -e '.' /tmp/response.json >/dev/null 2>&1; then
echo "ERROR: Invalid JSON response" >&2
echo "Raw response:" >&2
cat /tmp/response.json >&2
exit 1
fi
# Check for API-level error field
if jq -e '.error' /tmp/response.json >/dev/null 2>&1; then
ERROR=$(jq -r '.error.message // .error' /tmp/response.json)
echo "API Error: $ERROR" >&2
exit 1
fi
ISE-Specific Errors
# ISE ERS error handling
RESP=$(curl -ks -o /tmp/ise.json -w '%{http_code}' \
-u "$ISE_USER:$ISE_PASS" \
"https://$ISE_HOST:9060/ers/config/endpoint")
if [[ $RESP != "200" ]]; then
# ERS returns error details in ERSResponse
ERROR=$(jq -r '.ERSResponse.messages[0].title // "Unknown error"' /tmp/ise.json)
echo "ISE ERS Error: $ERROR" >&2
exit 1
fi
Vault-Specific Errors
# Vault error handling
RESP=$(curl -ks -o /tmp/vault.json -w '%{http_code}' \
-H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/kv/data/mypath")
case $RESP in
200) jq '.data.data' /tmp/vault.json ;;
403)
ERRORS=$(jq -r '.errors[]' /tmp/vault.json)
echo "Vault permission denied: $ERRORS" >&2
exit 1 ;;
404) echo "Secret not found" >&2; exit 1 ;;
esac
Timeout Handling
# Handle connection timeouts
if ! curl -ks --connect-timeout 5 --max-time 30 \
-o /tmp/response.json \
"https://api.example.com/resource"; then
echo "ERROR: Request timed out or connection failed" >&2
exit 1
fi
netapi Error Handling
# netapi error handling
if ! netapi ise ers endpoints --format json > /tmp/endpoints.json 2>&1; then
echo "netapi command failed" >&2
cat /tmp/endpoints.json >&2
exit 1
fi
# Check for empty results
if [[ $(jq 'length' /tmp/endpoints.json) -eq 0 ]]; then
echo "WARNING: No endpoints found" >&2
fi
Script-Level Error Handling
# Script-level error handling
set -euo pipefail
trap 'echo "Error on line $LINENO" >&2' ERR
# Now any failed command will exit
curl -ks -u "$USER:$PASS" "https://api.example.com/resource" | jq '.'