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