> ## Documentation Index
> Fetch the complete documentation index at: https://docs.satsuma.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Comprehensive guide to handling API errors and implementing retry logic

## Overview

The Satsuma API uses standard HTTP status codes and detailed error responses to help you diagnose and handle issues. This guide covers common error scenarios and best practices for robust error handling.

## HTTP Status Codes

### Success Codes (2xx)

* `200 OK` - Request succeeded
* `201 Created` - Resource created successfully
* `202 Accepted` - Request accepted for processing
* `204 No Content` - Request succeeded with no response body

### Client Error Codes (4xx)

* `400 Bad Request` - Invalid request syntax or parameters
* `401 Unauthorized` - Invalid or missing API key
* `403 Forbidden` - Insufficient permissions
* `404 Not Found` - Resource does not exist
* `409 Conflict` - Request conflicts with current state
* `422 Unprocessable Entity` - Validation errors
* `429 Too Many Requests` - Rate limit exceeded

### Server Error Codes (5xx)

* `500 Internal Server Error` - Unexpected server error
* `502 Bad Gateway` - Upstream service error
* `503 Service Unavailable` - Temporary service outage
* `504 Gateway Timeout` - Request timeout

## Error Response Format

All errors return a consistent JSON structure:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "One or more fields failed validation",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format"
      },
      {
        "field": "quantity", 
        "message": "Must be greater than 0"
      }
    ],
    "request_id": "req_abc123xyz789"
  }
}
```

## Common Error Codes

### Authentication Errors

<AccordionGroup>
  <Accordion title="INVALID_API_KEY">
    **Status**: 401\
    **Cause**: API key is malformed, expired, or doesn't exist\
    **Solution**: Verify your API key and ensure it's not truncated

    ```json theme={null}
    {
      "error": {
        "code": "INVALID_API_KEY",
        "message": "The provided API key is invalid or expired"
      }
    }
    ```
  </Accordion>

  <Accordion title="INSUFFICIENT_PERMISSIONS">
    **Status**: 403\
    **Cause**: API key lacks required permissions for the endpoint\
    **Solution**: Use a key with appropriate permissions

    ```json theme={null}
    {
      "error": {
        "code": "INSUFFICIENT_PERMISSIONS",
        "message": "Your API key does not have permission to access this resource",
        "required_permission": "orders"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

### Validation Errors

<AccordionGroup>
  <Accordion title="VALIDATION_ERROR">
    **Status**: 422\
    **Cause**: Request data fails validation rules\
    **Solution**: Fix the invalid fields listed in the details array

    ```json theme={null}
    {
      "error": {
        "code": "VALIDATION_ERROR", 
        "message": "Request validation failed",
        "details": [
          {
            "field": "latitude",
            "message": "Must be between -90 and 90"
          }
        ]
      }
    }
    ```
  </Accordion>

  <Accordion title="MISSING_REQUIRED_FIELD">
    **Status**: 400\
    **Cause**: Required request parameter is missing\
    **Solution**: Include all required fields in your request

    ```json theme={null}
    {
      "error": {
        "code": "MISSING_REQUIRED_FIELD",
        "message": "Required field is missing",
        "field": "merchant_id"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

### Resource Errors

<AccordionGroup>
  <Accordion title="RESOURCE_NOT_FOUND">
    **Status**: 404\
    **Cause**: Requested resource doesn't exist or has been deleted\
    **Solution**: Verify the resource ID and ensure it exists

    ```json theme={null}
    {
      "error": {
        "code": "RESOURCE_NOT_FOUND",
        "message": "Product not found",
        "resource_type": "product",
        "resource_id": "prod_nonexistent123"
      }
    }
    ```
  </Accordion>

  <Accordion title="RESOURCE_CONFLICT">
    **Status**: 409\
    **Cause**: Request conflicts with current resource state\
    **Solution**: Check resource state and retry with correct data

    ```json theme={null}
    {
      "error": {
        "code": "RESOURCE_CONFLICT",
        "message": "Cannot modify order that has already been fulfilled",
        "resource_state": "completed"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

### Business Logic Errors

<AccordionGroup>
  <Accordion title="INSUFFICIENT_INVENTORY">
    **Status**: 422\
    **Cause**: Requested quantity exceeds available inventory\
    **Solution**: Reduce quantity or choose different product

    ```json theme={null}
    {
      "error": {
        "code": "INSUFFICIENT_INVENTORY",
        "message": "Not enough inventory available",
        "available_quantity": 3,
        "requested_quantity": 5,
        "product_id": "prod_123"
      }
    }
    ```
  </Accordion>

  <Accordion title="PAYMENT_FAILED">
    **Status**: 422\
    **Cause**: Payment processing failed\
    **Solution**: Use valid payment method or contact payment provider

    ```json theme={null}
    {
      "error": {
        "code": "PAYMENT_FAILED", 
        "message": "Payment could not be processed",
        "payment_error": "card_declined",
        "decline_reason": "insufficient_funds"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

### Rate Limiting

<AccordionGroup>
  <Accordion title="RATE_LIMIT_EXCEEDED">
    **Status**: 429\
    **Cause**: Too many requests in time window\
    **Solution**: Implement backoff and retry after specified time

    ```json theme={null}
    {
      "error": {
        "code": "RATE_LIMIT_EXCEEDED",
        "message": "Rate limit exceeded",
        "retry_after": 60,
        "limit": 100,
        "reset": "2024-01-15T11:00:00Z"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Implementing Error Handling

### Basic Error Handling

<CodeGroup>
  ```bash curl theme={null}
  curl -X GET 'https://api.satsuma.ai/product?latitude=37.7749&longitude=-122.4194' \
    -H 'Authorization: your-api-key' \
    -H 'Content-Type: application/json' \
    -w "Response Code: %{http_code}\n" \
    -s | jq '.'

  # Example error response for invalid API key:
  # {
  #   "error": {
  #     "code": "INVALID_API_KEY",
  #     "message": "The provided API key is invalid or expired",
  #     "request_id": "req_abc123xyz789"
  #   }
  # }
  ```

  ```python Python theme={null}
  import requests
  from requests.exceptions import RequestException

  class SatsumaError(Exception):
      def __init__(self, error_data, status_code):
          self.code = error_data.get('code')
          self.message = error_data.get('message')
          self.status_code = status_code
          self.details = error_data.get('details', [])
          self.request_id = error_data.get('request_id')
          super().__init__(self.message)

  def make_request(method, url, **kwargs):
      try:
          response = requests.request(method, url, **kwargs)
          
          if not response.ok:
              error_data = response.json().get('error', {})
              raise SatsumaError(error_data, response.status_code)
              
          return response.json()
          
      except RequestException as e:
          handle_network_error(e)
          raise
      except SatsumaError as e:
          handle_satsuma_error(e)
          raise
  ```

  ```typescript TypeScript theme={null}
  interface ErrorDetails {
    field: string;
    message: string;
  }

  interface ErrorData {
    code: string;
    message: string;
    details?: ErrorDetails[];
    request_id?: string;
  }

  class SatsumaError extends Error {
    public code: string;
    public statusCode: number;
    public details?: ErrorDetails[];
    public requestId?: string;

    constructor(errorData: ErrorData, statusCode: number) {
      super(errorData.message);
      this.code = errorData.code;
      this.statusCode = statusCode;
      this.details = errorData.details;
      this.requestId = errorData.request_id;
    }
  }

  async function makeRequest(url: string, options: RequestInit): Promise<any> {
    try {
      const response = await fetch(url, options);
      
      if (!response.ok) {
        const error = await response.json();
        throw new SatsumaError(error.error, response.status);
      }
      
      return await response.json();
    } catch (error) {
      if (error instanceof SatsumaError) {
        handleSatsumaError(error);
      } else {
        handleNetworkError(error);
      }
      throw error;
    }
  }
  ```

  ```java Java theme={null}
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.net.URI;
  import java.time.Duration;
  import java.util.List;

  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.annotation.JsonProperty;

  public class SatsumaClient {
      private final HttpClient httpClient;
      private final ObjectMapper objectMapper;
      
      public SatsumaClient() {
          this.httpClient = HttpClient.newBuilder()
              .connectTimeout(Duration.ofSeconds(10))
              .build();
          this.objectMapper = new ObjectMapper();
      }
      
      public static class SatsumaError extends Exception {
          private final String code;
          private final int statusCode;
          private final List<ErrorDetail> details;
          private final String requestId;
          
          public SatsumaError(ErrorData errorData, int statusCode) {
              super(errorData.getMessage());
              this.code = errorData.getCode();
              this.statusCode = statusCode;
              this.details = errorData.getDetails();
              this.requestId = errorData.getRequestId();
          }
          
          // Getters
          public String getCode() { return code; }
          public int getStatusCode() { return statusCode; }
          public List<ErrorDetail> getDetails() { return details; }
          public String getRequestId() { return requestId; }
      }
      
      public static class ErrorData {
          private String code;
          private String message;
          private List<ErrorDetail> details;
          @JsonProperty("request_id")
          private String requestId;
          
          // Getters and setters
          public String getCode() { return code; }
          public String getMessage() { return message; }
          public List<ErrorDetail> getDetails() { return details; }
          public String getRequestId() { return requestId; }
      }
      
      public static class ErrorDetail {
          private String field;
          private String message;
          
          // Getters and setters
          public String getField() { return field; }
          public String getMessage() { return message; }
      }
      
      public <T> T makeRequest(HttpRequest request, Class<T> responseType) throws SatsumaError {
          try {
              HttpResponse<String> response = httpClient.send(request, 
                  HttpResponse.BodyHandlers.ofString());
              
              if (response.statusCode() >= 400) {
                  ErrorResponse errorResponse = objectMapper.readValue(
                      response.body(), ErrorResponse.class);
                  throw new SatsumaError(errorResponse.getError(), response.statusCode());
              }
              
              return objectMapper.readValue(response.body(), responseType);
              
          } catch (Exception e) {
              if (e instanceof SatsumaError) {
                  throw (SatsumaError) e;
              }
              throw new RuntimeException("Network error", e);
          }
      }
      
      private static class ErrorResponse {
          private ErrorData error;
          
          public ErrorData getError() { return error; }
          public void setError(ErrorData error) { this.error = error; }
      }
  }
  ```
</CodeGroup>

### Retry Logic with Backoff

Implement exponential backoff for transient errors:

<CodeGroup>
  ```bash curl theme={null}
  # Retry with exponential backoff using a simple script
  #!/bin/bash
  url="https://api.satsuma.ai/product?latitude=37.7749&longitude=-122.4194"
  api_key="your-api-key"
  max_retries=3

  for attempt in $(seq 0 $max_retries); do
    response=$(curl -s -w "%{http_code}" \
      -H "Authorization: $api_key" \
      -H "Content-Type: application/json" \
      "$url")
    
    http_code="${response: -3}"
    body="${response%???}"
    
    # Success
    if [[ "$http_code" =~ ^2[0-9][0-9]$ ]]; then
      echo "$body" | jq '.'
      exit 0
    fi
    
    # Don't retry 4xx errors (except 429)
    if [[ "$http_code" =~ ^4[0-9][0-9]$ ]] && [ "$http_code" != "429" ]; then
      echo "Client error $http_code: $body" >&2
      exit 1
    fi
    
    # Calculate delay for retry
    if [ "$attempt" -lt "$max_retries" ]; then
      delay=$((2**attempt))
      echo "Retrying in ${delay}s (attempt $((attempt + 1))/$((max_retries + 1)))" >&2
      sleep $delay
    fi
  done

  echo "Max retries exceeded: $body" >&2
  exit 1
  ```

  ```python Python theme={null}
  import time
  import random
  from typing import Optional

  def request_with_retry(
      method: str, 
      url: str, 
      max_retries: int = 3,
      **kwargs
  ) -> dict:
      last_error = None
      
      for attempt in range(max_retries + 1):
          try:
              return make_request(method, url, **kwargs)
          except SatsumaError as error:
              last_error = error
              
              # Don't retry client errors except rate limits
              if 400 <= error.status_code < 500 and error.code != 'RATE_LIMIT_EXCEEDED':
                  raise
                  
              # Don't retry on final attempt  
              if attempt == max_retries:
                  break
                  
              # Calculate delay with jitter
              if error.code == 'RATE_LIMIT_EXCEEDED':
                  delay = getattr(error, 'retry_after', 60)
              else:
                  delay = (2 ** attempt) + random.uniform(0, 1)
              
              print(f"Retrying in {delay}s (attempt {attempt + 1}/{max_retries + 1})")
              time.sleep(delay)
      
      raise last_error
  ```

  ```typescript TypeScript theme={null}
  interface RetryOptions {
    maxRetries?: number;
    retryDelay?: (attempt: number) => number;
  }

  async function requestWithRetry(
    url: string, 
    options: RequestInit, 
    retryOptions: RetryOptions = {}
  ): Promise<any> {
    const { maxRetries = 3, retryDelay } = retryOptions;
    let lastError: Error;
    
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        return await makeRequest(url, options);
      } catch (error) {
        lastError = error as Error;
        
        if (error instanceof SatsumaError) {
          // Don't retry client errors (4xx) except rate limits
          if (error.statusCode >= 400 && error.statusCode < 500 && 
              error.code !== 'RATE_LIMIT_EXCEEDED') {
            throw error;
          }
          
          // Don't retry on final attempt
          if (attempt === maxRetries) {
            break;
          }
          
          // Calculate delay
          const delay = error.code === 'RATE_LIMIT_EXCEEDED' 
            ? ((error as any).retryAfter || 60) * 1000  // Use server-provided delay
            : retryDelay ? retryDelay(attempt) : Math.pow(2, attempt) * 1000;
          
          console.log(`Retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries + 1})`);
          await new Promise(resolve => setTimeout(resolve, delay));
        } else {
          // Network or other error - retry with exponential backoff
          if (attempt < maxRetries) {
            const delay = retryDelay ? retryDelay(attempt) : Math.pow(2, attempt) * 1000;
            console.log(`Network error, retrying in ${delay}ms`);
            await new Promise(resolve => setTimeout(resolve, delay));
          }
        }
      }
    }
    
    throw lastError;
  }
  ```

  ```java Java theme={null}
  import java.util.concurrent.CompletableFuture;
  import java.util.concurrent.TimeUnit;
  import java.util.function.IntFunction;

  public class RetryClient {
      private final SatsumaClient client;
      private final int maxRetries;
      private final IntFunction<Long> retryDelayFunction;
      
      public RetryClient(SatsumaClient client) {
          this(client, 3, attempt -> (long) Math.pow(2, attempt) * 1000);
      }
      
      public RetryClient(SatsumaClient client, int maxRetries, IntFunction<Long> retryDelayFunction) {
          this.client = client;
          this.maxRetries = maxRetries;
          this.retryDelayFunction = retryDelayFunction;
      }
      
      public <T> T requestWithRetry(HttpRequest request, Class<T> responseType) throws SatsumaError {
          SatsumaError lastError = null;
          
          for (int attempt = 0; attempt <= maxRetries; attempt++) {
              try {
                  return client.makeRequest(request, responseType);
              } catch (SatsumaError error) {
                  lastError = error;
                  
                  // Don't retry client errors (4xx) except rate limits
                  if (error.getStatusCode() >= 400 && error.getStatusCode() < 500 
                      && !"RATE_LIMIT_EXCEEDED".equals(error.getCode())) {
                      throw error;
                  }
                  
                  // Don't retry on final attempt
                  if (attempt == maxRetries) {
                      break;
                  }
                  
                  // Calculate delay
                  long delay;
                  if ("RATE_LIMIT_EXCEEDED".equals(error.getCode())) {
                      // Use server-provided delay (assume 60s if not available)
                      delay = 60 * 1000;
                  } else {
                      delay = retryDelayFunction.apply(attempt);
                  }
                  
                  System.out.println(String.format(
                      "Retrying in %dms (attempt %d/%d)", 
                      delay, attempt + 1, maxRetries + 1
                  ));
                  
                  try {
                      Thread.sleep(delay);
                  } catch (InterruptedException e) {
                      Thread.currentThread().interrupt();
                      throw new RuntimeException("Retry interrupted", e);
                  }
              }
          }
          
          throw lastError;
      }
      
      public <T> CompletableFuture<T> requestWithRetryAsync(HttpRequest request, Class<T> responseType) {
          return CompletableFuture.supplyAsync(() -> {
              try {
                  return requestWithRetry(request, responseType);
              } catch (SatsumaError e) {
                  throw new RuntimeException(e);
              }
          });
      }
  }
  ```
</CodeGroup>

### Error-Specific Handling

Handle different error types appropriately:

<CodeGroup>
  ```bash curl theme={null}
  # Error handling in bash script
  handle_error() {
    local error_code="$1"
    local error_body="$2"
    
    case "$error_code" in
      "INVALID_API_KEY")
        echo "ERROR: Invalid API key. Please check your credentials." >&2
        # Log security incident
        logger "Security: Invalid API key used"
        exit 1
        ;;
      "INSUFFICIENT_INVENTORY")
        available=$(echo "$error_body" | jq -r '.error.available_quantity // "unknown"')
        echo "ERROR: Insufficient inventory. Available: $available" >&2
        exit 1
        ;;
      "PAYMENT_FAILED")
        reason=$(echo "$error_body" | jq -r '.error.decline_reason // "unknown"')
        echo "ERROR: Payment failed. Reason: $reason" >&2
        exit 1
        ;;
      "RATE_LIMIT_EXCEEDED")
        retry_after=$(echo "$error_body" | jq -r '.error.retry_after // 60')
        echo "Rate limit exceeded. Retry after ${retry_after}s" >&2
        return $retry_after
        ;;
      *)
        echo "ERROR: Unexpected API error: $error_code" >&2
        exit 1
        ;;
    esac
  }
  ```

  ```python Python theme={null}
  def handle_satsuma_error(error: SatsumaError) -> None:
      """Handle different types of Satsuma API errors appropriately."""
      
      if error.code == 'INVALID_API_KEY':
          # Log security incident and refresh API key
          logger.error('Invalid API key used', extra={'request_id': error.request_id})
          refresh_api_key()
          
      elif error.code == 'INSUFFICIENT_INVENTORY':
          # Update UI to show limited availability
          product_id = getattr(error, 'product_id', None)
          available = getattr(error, 'available_quantity', 0)
          if product_id:
              update_product_availability(product_id, available)
              
      elif error.code == 'PAYMENT_FAILED':
          # Show payment error to user
          payment_error = getattr(error, 'payment_error', None)
          decline_reason = getattr(error, 'decline_reason', None)
          show_payment_error(payment_error, decline_reason)
          
      elif error.code == 'RATE_LIMIT_EXCEEDED':
          # Implement backoff strategy
          retry_after = getattr(error, 'retry_after', 60)
          schedule_retry(retry_after)
          
      elif error.code == 'VALIDATION_ERROR':
          # Show field-specific validation errors
          show_validation_errors(error.details)
          
      else:
          # Generic error handling
          logger.error('Unexpected API error', extra={
              'error_code': error.code,
              'error_message': error.message,
              'request_id': error.request_id
          })
          show_generic_error()
  ```

  ```typescript TypeScript theme={null}
  function handleSatsumaError(error: SatsumaError): void {
    switch (error.code) {
      case 'INVALID_API_KEY':
        // Log security incident and refresh API key
        logger.error('Invalid API key used', { requestId: error.requestId });
        refreshAPIKey();
        break;
        
      case 'INSUFFICIENT_INVENTORY':
        // Update UI to show limited availability
        const productId = (error as any).productId;
        const availableQuantity = (error as any).availableQuantity;
        if (productId) {
          updateProductAvailability(productId, availableQuantity);
        }
        break;
        
      case 'PAYMENT_FAILED':
        // Show payment error to user
        const paymentError = (error as any).paymentError;
        const declineReason = (error as any).declineReason;
        showPaymentError(paymentError, declineReason);
        break;
        
      case 'RATE_LIMIT_EXCEEDED':
        // Implement backoff strategy
        const retryAfter = (error as any).retryAfter || 60;
        scheduleRetry(retryAfter);
        break;
        
      case 'VALIDATION_ERROR':
        // Show field-specific validation errors
        if (error.details) {
          showValidationErrors(error.details);
        }
        break;
        
      default:
        // Generic error handling
        logger.error('Unexpected API error', {
          errorCode: error.code,
          errorMessage: error.message,
          requestId: error.requestId
        });
        showGenericError();
    }
  }
  ```

  ```java Java theme={null}
  public class ErrorHandler {
      private static final Logger logger = LoggerFactory.getLogger(ErrorHandler.class);
      
      public static void handleSatsumaError(SatsumaError error) {
          switch (error.getCode()) {
              case "INVALID_API_KEY":
                  // Log security incident and refresh API key
                  logger.error("Invalid API key used. Request ID: {}", error.getRequestId());
                  refreshAPIKey();
                  break;
                  
              case "INSUFFICIENT_INVENTORY":
                  // Update UI to show limited availability
                  // Note: These would typically be available in error details
                  updateProductAvailability(error);
                  break;
                  
              case "PAYMENT_FAILED":
                  // Show payment error to user
                  showPaymentError(error);
                  break;
                  
              case "RATE_LIMIT_EXCEEDED":
                  // Implement backoff strategy
                  int retryAfter = extractRetryAfter(error);
                  scheduleRetry(retryAfter);
                  break;
                  
              case "VALIDATION_ERROR":
                  // Show field-specific validation errors
                  if (error.getDetails() != null && !error.getDetails().isEmpty()) {
                      showValidationErrors(error.getDetails());
                  }
                  break;
                  
              default:
                  // Generic error handling
                  logger.error("Unexpected API error. Code: {}, Message: {}, Request ID: {}", 
                      error.getCode(), error.getMessage(), error.getRequestId());
                  showGenericError();
          }
      }
      
      private static int extractRetryAfter(SatsumaError error) {
          // This would typically be extracted from error details or headers
          return 60; // Default to 60 seconds
      }
      
      private static void refreshAPIKey() {
          // Implementation for refreshing API key
      }
      
      private static void updateProductAvailability(SatsumaError error) {
          // Implementation for updating product availability
      }
      
      private static void showPaymentError(SatsumaError error) {
          // Implementation for showing payment errors to user
      }
      
      private static void scheduleRetry(int retryAfter) {
          // Implementation for scheduling retry
      }
      
      private static void showValidationErrors(List<SatsumaClient.ErrorDetail> details) {
          // Implementation for showing validation errors
      }
      
      private static void showGenericError() {
          // Implementation for showing generic error message
      }
  }
  ```
</CodeGroup>

### Circuit Breaker Pattern

Prevent cascade failures with a circuit breaker:

<CodeGroup>
  ```bash curl theme={null}
  # Simple circuit breaker state management in bash
  #!/bin/bash
  CIRCUIT_STATE_FILE="/tmp/satsuma_circuit_state"
  FAILURE_THRESHOLD=5
  TIMEOUT_DURATION=60

  get_circuit_state() {
    if [ -f "$CIRCUIT_STATE_FILE" ]; then
      cat "$CIRCUIT_STATE_FILE"
    else
      echo "CLOSED:0:0"
    fi
  }

  update_circuit_state() {
    local state="$1"
    local failures="$2"
    local next_attempt="$3"
    echo "$state:$failures:$next_attempt" > "$CIRCUIT_STATE_FILE"
  }

  execute_with_circuit_breaker() {
    local url="$1"
    local api_key="$2"
    
    IFS=':' read -r state failures next_attempt <<< "$(get_circuit_state)"
    current_time=$(date +%s)
    
    # Check if circuit is open and timeout hasn't expired
    if [ "$state" = "OPEN" ] && [ "$current_time" -lt "$next_attempt" ]; then
      echo "Circuit breaker is OPEN. Retry after: $((next_attempt - current_time))s" >&2
      return 1
    fi
    
    # Make the request
    response=$(curl -s -w "%{http_code}" \
      -H "Authorization: $api_key" \
      "$url")
    
    http_code="${response: -3}"
    
    if [[ "$http_code" =~ ^2[0-9][0-9]$ ]]; then
      # Success - reset circuit breaker
      update_circuit_state "CLOSED" "0" "0"
      echo "${response%???}"  # Return response body
      return 0
    else
      # Failure - increment counter
      failures=$((failures + 1))
      if [ "$failures" -ge "$FAILURE_THRESHOLD" ]; then
        # Open circuit
        next_attempt=$((current_time + TIMEOUT_DURATION))
        update_circuit_state "OPEN" "$failures" "$next_attempt"
        echo "Circuit breaker opened due to failures" >&2
      else
        update_circuit_state "CLOSED" "$failures" "0"
      fi
      return 1
    fi
  }
  ```

  ```python Python theme={null}
  import time
  from typing import Callable, Any, TypeVar, Generic
  from enum import Enum

  T = TypeVar('T')

  class CircuitState(Enum):
      CLOSED = "CLOSED"
      OPEN = "OPEN"
      HALF_OPEN = "HALF_OPEN"

  class CircuitBreaker(Generic[T]):
      def __init__(self, threshold: int = 5, timeout: int = 60):
          self.threshold = threshold
          self.timeout = timeout  # seconds
          self.failure_count = 0
          self.state = CircuitState.CLOSED
          self.next_attempt = 0
      
      async def execute(self, func: Callable[[], T]) -> T:
          if self.state == CircuitState.OPEN and time.time() < self.next_attempt:
              raise Exception("Circuit breaker is OPEN")
          
          try:
              result = await func()
              self.on_success()
              return result
          except Exception as error:
              self.on_failure()
              raise error
      
      def on_success(self):
          self.failure_count = 0
          self.state = CircuitState.CLOSED
      
      def on_failure(self):
          self.failure_count += 1
          
          if self.failure_count >= self.threshold:
              self.state = CircuitState.OPEN
              self.next_attempt = time.time() + self.timeout

  # Usage
  circuit_breaker = CircuitBreaker[dict]()

  async def search_products(query: str) -> dict:
      return await circuit_breaker.execute(
          lambda: make_request('GET', '/product', params={'query': query, 'latitude': 37.7749, 'longitude': -122.4194, 'distance': '10mi'})
      )
  ```

  ```typescript TypeScript theme={null}
  enum CircuitState {
    CLOSED = 'CLOSED',
    OPEN = 'OPEN',
    HALF_OPEN = 'HALF_OPEN'
  }

  interface CircuitBreakerOptions {
    threshold?: number;
    timeout?: number;
  }

  class CircuitBreaker {
    private threshold: number;
    private timeout: number;
    private failureCount: number = 0;
    private state: CircuitState = CircuitState.CLOSED;
    private nextAttempt: number = 0;

    constructor(options: CircuitBreakerOptions = {}) {
      this.threshold = options.threshold || 5;
      this.timeout = options.timeout || 60000; // milliseconds
    }
    
    async execute<T>(fn: () => Promise<T>): Promise<T> {
      if (this.state === CircuitState.OPEN && Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      
      try {
        const result = await fn();
        this.onSuccess();
        return result;
      } catch (error) {
        this.onFailure();
        throw error;
      }
    }
    
    private onSuccess(): void {
      this.failureCount = 0;
      this.state = CircuitState.CLOSED;
    }
    
    private onFailure(): void {
      this.failureCount++;
      
      if (this.failureCount >= this.threshold) {
        this.state = CircuitState.OPEN;
        this.nextAttempt = Date.now() + this.timeout;
      }
    }
    
    getState(): CircuitState {
      return this.state;
    }
    
    getFailureCount(): number {
      return this.failureCount;
    }
  }

  // Usage
  const circuitBreaker = new CircuitBreaker({ threshold: 5, timeout: 60000 });

  async function searchProducts(query: string): Promise<any> {
    return circuitBreaker.execute(() =>
      makeRequest('/product?' + new URLSearchParams({
        query: query,
        latitude: '37.7749',
        longitude: '-122.4194',
        distance: '10mi'
      }), { 
        method: 'GET',
        headers: { 'Content-Type': 'application/json' }
      })
    );
  }
  ```

  ```java Java theme={null}
  import java.util.concurrent.Callable;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.concurrent.atomic.AtomicLong;
  import java.util.concurrent.atomic.AtomicReference;

  public class CircuitBreaker {
      public enum State {
          CLOSED, OPEN, HALF_OPEN
      }
      
      private final int threshold;
      private final long timeoutMillis;
      private final AtomicInteger failureCount = new AtomicInteger(0);
      private final AtomicReference<State> state = new AtomicReference<>(State.CLOSED);
      private final AtomicLong nextAttempt = new AtomicLong(0);
      
      public CircuitBreaker() {
          this(5, 60000); // 5 failures, 60 second timeout
      }
      
      public CircuitBreaker(int threshold, long timeoutMillis) {
          this.threshold = threshold;
          this.timeoutMillis = timeoutMillis;
      }
      
      public <T> T execute(Callable<T> operation) throws Exception {
          if (state.get() == State.OPEN && System.currentTimeMillis() < nextAttempt.get()) {
              throw new RuntimeException("Circuit breaker is OPEN");
          }
          
          try {
              T result = operation.call();
              onSuccess();
              return result;
          } catch (Exception error) {
              onFailure();
              throw error;
          }
      }
      
      private void onSuccess() {
          failureCount.set(0);
          state.set(State.CLOSED);
      }
      
      private void onFailure() {
          int failures = failureCount.incrementAndGet();
          
          if (failures >= threshold) {
              state.set(State.OPEN);
              nextAttempt.set(System.currentTimeMillis() + timeoutMillis);
          }
      }
      
      public State getState() {
          return state.get();
      }
      
      public int getFailureCount() {
          return failureCount.get();
      }
  }

  // Usage
  public class ProductService {
      private final CircuitBreaker circuitBreaker = new CircuitBreaker();
      private final SatsumaClient client;
      
      public ProductService(SatsumaClient client) {
          this.client = client;
      }
      
      public ProductSearchResponse searchProducts(String query) throws Exception {
          return circuitBreaker.execute(() -> {
              HttpRequest request = HttpRequest.newBuilder()
                  .uri(URI.create("https://api.satsuma.ai/product?query=" + query + "&latitude=37.7749&longitude=-122.4194&distance=10mi"))
                  .header("Authentication", getApiKey())
                  .header("Content-Type", "application/json")
                  .GET()
                  .build();
              
              return client.makeRequest(request, ProductSearchResponse.class);
          });
      }
      
      private String getApiKey() {
          // Return your API key
          return System.getenv("SATSUMA_API_KEY");
      }
  }
  ```
</CodeGroup>

## Monitoring & Alerting

### Error Tracking

Track errors for monitoring and debugging:

<CodeGroup>
  ```bash curl theme={null}
  # Error tracking in bash with logging
  track_error() {
    local error_code="$1"
    local error_message="$2"
    local status_code="$3"
    local request_id="$4"
    local endpoint="$5"
    local user_id="${6:-unknown}"
    local retry_attempt="${7:-0}"
    
    # Create error data JSON
    local error_data=$(jq -n \
      --arg timestamp "$(date -Iseconds)" \
      --arg error_code "$error_code" \
      --arg error_message "$error_message" \
      --arg status_code "$status_code" \
      --arg request_id "$request_id" \
      --arg user_id "$user_id" \
      --arg endpoint "$endpoint" \
      --arg retry_attempt "$retry_attempt" \
      '{
        timestamp: $timestamp,
        error_code: $error_code,
        error_message: $error_message,
        status_code: ($status_code | tonumber),
        request_id: $request_id,
        user_id: $user_id,
        endpoint: $endpoint,
        retry_attempt: ($retry_attempt | tonumber)
      }')
    
    # Log to file
    echo "$error_data" >> /var/log/satsuma_errors.log
    
    # Send to monitoring service (replace with your monitoring endpoint)
    curl -s -X POST "https://monitoring.yourservice.com/api/errors" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer $MONITORING_TOKEN" \
      -d "$error_data" > /dev/null
    
    # Alert on critical errors
    if [ "$status_code" -ge 500 ] || [ "$error_code" = "PAYMENT_FAILED" ]; then
      # Send alert (replace with your alerting service)
      curl -s -X POST "https://alerts.yourservice.com/api/critical" \
        -H "Content-Type: application/json" \
        -d "$error_data" > /dev/null
    fi
  }
  ```

  ```python Python theme={null}
  import json
  import logging
  from datetime import datetime
  from typing import Optional, Dict, Any
  import requests

  class ErrorTracker:
      def __init__(self, monitoring_url: str = None, alerting_url: str = None):
          self.monitoring_url = monitoring_url
          self.alerting_url = alerting_url
          self.logger = logging.getLogger(__name__)
      
      def track_error(
          self, 
          error: SatsumaError, 
          context: Dict[str, Any]
      ) -> None:
          """Track API errors for monitoring and debugging."""
          
          error_data = {
              'timestamp': datetime.utcnow().isoformat(),
              'error_code': error.code,
              'error_message': error.message,
              'status_code': error.status_code,
              'request_id': error.request_id,
              'user_id': context.get('user_id'),
              'endpoint': context.get('endpoint'),
              'retry_attempt': context.get('retry_attempt', 0)
          }
          
          # Send to monitoring service
          if self.monitoring_url:
              try:
                  requests.post(
                      f"{self.monitoring_url}/api/errors",
                      json=error_data,
                      timeout=5
                  )
              except requests.RequestException as e:
                  self.logger.warning(f"Failed to send error to monitoring: {e}")
          
          # Log for debugging
          self.logger.error('API Error', extra=error_data)
          
          # Alert on critical errors
          if (error.status_code >= 500 or 
              error.code in ['PAYMENT_FAILED', 'INVALID_API_KEY']):
              self._send_alert(error_data)
      
      def _send_alert(self, error_data: Dict[str, Any]) -> None:
          """Send critical error alert."""
          if self.alerting_url:
              try:
                  requests.post(
                      f"{self.alerting_url}/api/critical",
                      json={
                          'alert_type': 'critical_api_error',
                          'data': error_data
                      },
                      timeout=5
                  )
              except requests.RequestException as e:
                  self.logger.warning(f"Failed to send alert: {e}")

  # Usage
  error_tracker = ErrorTracker(
      monitoring_url="https://monitoring.yourservice.com",
      alerting_url="https://alerts.yourservice.com"
  )

  def handle_api_error(error: SatsumaError, context: Dict[str, Any]):
      error_tracker.track_error(error, context)
  ```

  ```typescript TypeScript theme={null}
  interface ErrorContext {
    userId?: string;
    endpoint?: string;
    retryAttempt?: number;
  }

  interface ErrorData {
    timestamp: string;
    error_code: string;
    error_message: string;
    status_code: number;
    request_id?: string;
    user_id?: string;
    endpoint?: string;
    retry_attempt: number;
  }

  class ErrorTracker {
    private monitoringUrl?: string;
    private alertingUrl?: string;
    
    constructor(options: { monitoringUrl?: string; alertingUrl?: string } = {}) {
      this.monitoringUrl = options.monitoringUrl;
      this.alertingUrl = options.alertingUrl;
    }
    
    async trackError(error: SatsumaError, context: ErrorContext): Promise<void> {
      const errorData: ErrorData = {
        timestamp: new Date().toISOString(),
        error_code: error.code,
        error_message: error.message,
        status_code: error.statusCode,
        request_id: error.requestId,
        user_id: context.userId,
        endpoint: context.endpoint,
        retry_attempt: context.retryAttempt || 0
      };
      
      // Send to monitoring service
      if (this.monitoringUrl) {
        try {
          await fetch(`${this.monitoringUrl}/api/errors`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(errorData),
            signal: AbortSignal.timeout(5000)
          });
        } catch (e) {
          console.warn('Failed to send error to monitoring:', e);
        }
      }
      
      // Log for debugging
      console.error('API Error', errorData);
      
      // Alert on critical errors
      if (error.statusCode >= 500 || 
          ['PAYMENT_FAILED', 'INVALID_API_KEY'].includes(error.code)) {
        await this.sendAlert(errorData);
      }
    }
    
    private async sendAlert(errorData: ErrorData): Promise<void> {
      if (!this.alertingUrl) return;
      
      try {
        await fetch(`${this.alertingUrl}/api/critical`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            alert_type: 'critical_api_error',
            data: errorData
          }),
          signal: AbortSignal.timeout(5000)
        });
      } catch (e) {
        console.warn('Failed to send alert:', e);
      }
    }
  }

  // Usage
  const errorTracker = new ErrorTracker({
    monitoringUrl: 'https://monitoring.yourservice.com',
    alertingUrl: 'https://alerts.yourservice.com'
  });

  function handleApiError(error: SatsumaError, context: ErrorContext): void {
    errorTracker.trackError(error, context);
  }
  ```

  ```java Java theme={null}
  import com.fasterxml.jackson.databind.ObjectMapper;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;

  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.net.URI;
  import java.time.Duration;
  import java.time.Instant;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Set;

  public class ErrorTracker {
      private static final Logger logger = LoggerFactory.getLogger(ErrorTracker.class);
      private static final Set<String> CRITICAL_ERROR_CODES = Set.of("PAYMENT_FAILED", "INVALID_API_KEY");
      
      private final HttpClient httpClient;
      private final ObjectMapper objectMapper;
      private final String monitoringUrl;
      private final String alertingUrl;
      
      public ErrorTracker() {
          this(null, null);
      }
      
      public ErrorTracker(String monitoringUrl, String alertingUrl) {
          this.httpClient = HttpClient.newBuilder()
              .connectTimeout(Duration.ofSeconds(5))
              .build();
          this.objectMapper = new ObjectMapper();
          this.monitoringUrl = monitoringUrl;
          this.alertingUrl = alertingUrl;
      }
      
      public void trackError(SatsumaError error, ErrorContext context) {
          Map<String, Object> errorData = createErrorData(error, context);
          
          // Send to monitoring service
          if (monitoringUrl != null) {
              sendToMonitoring(errorData);
          }
          
          // Log for debugging
          logger.error("API Error: {} - {} (Status: {}, Request ID: {})", 
              error.getCode(), error.getMessage(), error.getStatusCode(), error.getRequestId());
          
          // Alert on critical errors
          if (error.getStatusCode() >= 500 || CRITICAL_ERROR_CODES.contains(error.getCode())) {
              sendAlert(errorData);
          }
      }
      
      private Map<String, Object> createErrorData(SatsumaError error, ErrorContext context) {
          Map<String, Object> data = new HashMap<>();
          data.put("timestamp", Instant.now().toString());
          data.put("error_code", error.getCode());
          data.put("error_message", error.getMessage());
          data.put("status_code", error.getStatusCode());
          data.put("request_id", error.getRequestId());
          data.put("user_id", context.getUserId());
          data.put("endpoint", context.getEndpoint());
          data.put("retry_attempt", context.getRetryAttempt());
          return data;
      }
      
      private void sendToMonitoring(Map<String, Object> errorData) {
          try {
              String json = objectMapper.writeValueAsString(errorData);
              HttpRequest request = HttpRequest.newBuilder()
                  .uri(URI.create(monitoringUrl + "/api/errors"))
                  .header("Content-Type", "application/json")
                  .POST(HttpRequest.BodyPublishers.ofString(json))
                  .timeout(Duration.ofSeconds(5))
                  .build();
              
              httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                  .exceptionally(throwable -> {
                      logger.warn("Failed to send error to monitoring: {}", throwable.getMessage());
                      return null;
                  });
          } catch (Exception e) {
              logger.warn("Failed to serialize error data for monitoring: {}", e.getMessage());
          }
      }
      
      private void sendAlert(Map<String, Object> errorData) {
          if (alertingUrl == null) return;
          
          try {
              Map<String, Object> alertPayload = new HashMap<>();
              alertPayload.put("alert_type", "critical_api_error");
              alertPayload.put("data", errorData);
              
              String json = objectMapper.writeValueAsString(alertPayload);
              HttpRequest request = HttpRequest.newBuilder()
                  .uri(URI.create(alertingUrl + "/api/critical"))
                  .header("Content-Type", "application/json")
                  .POST(HttpRequest.BodyPublishers.ofString(json))
                  .timeout(Duration.ofSeconds(5))
                  .build();
              
              httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                  .exceptionally(throwable -> {
                      logger.warn("Failed to send alert: {}", throwable.getMessage());
                      return null;
                  });
          } catch (Exception e) {
              logger.warn("Failed to send alert: {}", e.getMessage());
          }
      }
      
      public static class ErrorContext {
          private String userId;
          private String endpoint;
          private int retryAttempt = 0;
          
          public ErrorContext(String userId, String endpoint) {
              this.userId = userId;
              this.endpoint = endpoint;
          }
          
          public ErrorContext(String userId, String endpoint, int retryAttempt) {
              this.userId = userId;
              this.endpoint = endpoint;
              this.retryAttempt = retryAttempt;
          }
          
          // Getters
          public String getUserId() { return userId; }
          public String getEndpoint() { return endpoint; }
          public int getRetryAttempt() { return retryAttempt; }
      }
  }

  // Usage
  ErrorTracker errorTracker = new ErrorTracker(
      "https://monitoring.yourservice.com",
      "https://alerts.yourservice.com"
  );

  public void handleApiError(SatsumaError error, String userId, String endpoint, int retryAttempt) {
      ErrorTracker.ErrorContext context = new ErrorTracker.ErrorContext(userId, endpoint, retryAttempt);
      errorTracker.trackError(error, context);
  }
  ```
</CodeGroup>

### Health Checks

Implement health checks to monitor API status:

<CodeGroup>
  ```bash curl theme={null}
  #!/bin/bash
  # Health check script
  API_KEY="your-api-key"
  HEALTH_ENDPOINT="https://api.satsuma.ai/product?latitude=37.7749&longitude=-122.4194"
  METRICS_ENDPOINT="https://metrics.yourservice.com/api/gauge"

  health_check() {
    local timestamp=$(date +%s)
    
    # Make health check request with timeout
    response=$(timeout 5 curl -s -w "%{http_code}" \
      -H "Authorization: $API_KEY" \
      "$HEALTH_ENDPOINT")
    
    local exit_code=$?
    local http_code="${response: -3}"
    
    if [ $exit_code -eq 0 ] && [[ "$http_code" =~ ^2[0-9][0-9]$ ]]; then
      # Healthy
      local health_data=$(jq -n \
        --arg status "healthy" \
        --arg timestamp "$timestamp" \
        '{ status: $status, timestamp: ($timestamp | tonumber) }')
      
      echo "$health_data"
      
      # Send metrics (1 = healthy)
      curl -s -X POST "$METRICS_ENDPOINT" \
        -H "Content-Type: application/json" \
        -d '{"metric": "api_health", "value": 1, "timestamp": '$timestamp'}' \
        > /dev/null
      
      return 0
    else
      # Unhealthy
      local error_msg="HTTP $http_code or timeout"
      local health_data=$(jq -n \
        --arg status "unhealthy" \
        --arg error "$error_msg" \
        --arg timestamp "$timestamp" \
        '{ status: $status, error: $error, timestamp: ($timestamp | tonumber) }')
      
      echo "$health_data"
      
      # Send metrics (0 = unhealthy)
      curl -s -X POST "$METRICS_ENDPOINT" \
        -H "Content-Type: application/json" \
        -d '{"metric": "api_health", "value": 0, "timestamp": '$timestamp'}' \
        > /dev/null
      
      return 1
    fi
  }

  # Run health check
  health_check

  # For continuous monitoring, add to cron:
  # */1 * * * * /path/to/health_check.sh
  ```

  ```python Python theme={null}
  import asyncio
  import time
  import logging
  from typing import Dict, Any
  import aiohttp

  class HealthChecker:
      def __init__(self, api_key: str, metrics_client=None):
          self.api_key = api_key
          self.metrics_client = metrics_client
          self.logger = logging.getLogger(__name__)
          self.health_endpoint = "https://api.satsuma.ai/product?latitude=37.7749&longitude=-122.4194"
      
      async def health_check(self) -> Dict[str, Any]:
          """Perform a health check against the Satsuma API."""
          timestamp = int(time.time())
          
          try:
              timeout = aiohttp.ClientTimeout(total=5.0)
              async with aiohttp.ClientSession(timeout=timeout) as session:
                  headers = {
                      'Authorization': self.api_key,
                      'Content-Type': 'application/json'
                  }
                  
                  async with session.get(self.health_endpoint, headers=headers) as response:
                      if response.status == 200:
                          result = {'status': 'healthy', 'timestamp': timestamp}
                          await self._send_metric('api_health', 1)
                          return result
                      else:
                          result = {
                              'status': 'unhealthy',
                              'error': f'HTTP {response.status}',
                              'timestamp': timestamp
                          }
                          await self._send_metric('api_health', 0)
                          return result
          
          except asyncio.TimeoutError:
              result = {
                  'status': 'unhealthy',
                  'error': 'Request timeout',
                  'timestamp': timestamp
              }
              await self._send_metric('api_health', 0)
              return result
          
          except Exception as e:
              result = {
                  'status': 'unhealthy',
                  'error': str(e),
                  'timestamp': timestamp
              }
              await self._send_metric('api_health', 0)
              return result
      
      async def _send_metric(self, metric_name: str, value: int) -> None:
          """Send health metric to monitoring system."""
          if self.metrics_client:
              try:
                  await self.metrics_client.gauge(metric_name, value)
              except Exception as e:
                  self.logger.warning(f"Failed to send metric: {e}")
      
      async def start_monitoring(self, interval: int = 60) -> None:
          """Start continuous health monitoring."""
          self.logger.info(f"Starting health monitoring every {interval}s")
          
          while True:
              try:
                  health_status = await self.health_check()
                  self.logger.info(f"Health check result: {health_status}")
              except Exception as e:
                  self.logger.error(f"Health check failed: {e}")
              
              await asyncio.sleep(interval)

  # Usage
  health_checker = HealthChecker(api_key="your-api-key")

  # Run once
  health_status = await health_checker.health_check()
  print(health_status)

  # Start continuous monitoring
  # await health_checker.start_monitoring(interval=60)
  ```

  ```typescript TypeScript theme={null}
  interface HealthStatus {
    status: 'healthy' | 'unhealthy';
    timestamp: number;
    error?: string;
  }

  interface MetricsClient {
    gauge(metric: string, value: number): Promise<void>;
  }

  class HealthChecker {
    private apiKey: string;
    private metricsClient?: MetricsClient;
    private healthEndpoint = 'https://api.satsuma.ai/product?latitude=37.7749&longitude=-122.4194';
    
    constructor(apiKey: string, metricsClient?: MetricsClient) {
      this.apiKey = apiKey;
      this.metricsClient = metricsClient;
    }
    
    async healthCheck(): Promise<HealthStatus> {
      const timestamp = Date.now();
      
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 5000);
        
        const response = await fetch(this.healthEndpoint, {
          method: 'GET',
          headers: {
            'Authorization': this.apiKey,
            'Content-Type': 'application/json'
          },
          signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        
        if (response.ok) {
          await this.sendMetric('api_health', 1);
          return { status: 'healthy', timestamp };
        } else {
          await this.sendMetric('api_health', 0);
          return {
            status: 'unhealthy',
            error: `HTTP ${response.status}`,
            timestamp
          };
        }
      } catch (error) {
        await this.sendMetric('api_health', 0);
        
        if (error instanceof Error) {
          if (error.name === 'AbortError') {
            return {
              status: 'unhealthy',
              error: 'Request timeout',
              timestamp
            };
          }
          return {
            status: 'unhealthy',
            error: error.message,
            timestamp
          };
        }
        
        return {
          status: 'unhealthy',
          error: 'Unknown error',
          timestamp
        };
      }
    }
    
    private async sendMetric(metricName: string, value: number): Promise<void> {
      if (this.metricsClient) {
        try {
          await this.metricsClient.gauge(metricName, value);
        } catch (error) {
          console.warn('Failed to send metric:', error);
        }
      }
    }
    
    async startMonitoring(intervalMs: number = 60000): Promise<void> {
      console.log(`Starting health monitoring every ${intervalMs}ms`);
      
      while (true) {
        try {
          const healthStatus = await this.healthCheck();
          console.log('Health check result:', healthStatus);
        } catch (error) {
          console.error('Health check failed:', error);
        }
        
        await new Promise(resolve => setTimeout(resolve, intervalMs));
      }
    }
  }

  // Usage
  const healthChecker = new HealthChecker('your-api-key');

  // Run once
  const healthStatus = await healthChecker.healthCheck();
  console.log(healthStatus);

  // Start continuous monitoring
  // healthChecker.startMonitoring(60000); // Every minute
  ```

  ```java Java theme={null}
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.net.URI;
  import java.time.Duration;
  import java.time.Instant;
  import java.util.concurrent.Executors;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.concurrent.TimeUnit;

  public class HealthChecker {
      private final HttpClient httpClient;
      private final String apiKey;
      private final MetricsClient metricsClient;
      private final String healthEndpoint = "https://api.satsuma.ai/product?latitude=37.7749&longitude=-122.4194";
      private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
      
      public HealthChecker(String apiKey) {
          this(apiKey, null);
      }
      
      public HealthChecker(String apiKey, MetricsClient metricsClient) {
          this.httpClient = HttpClient.newBuilder()
              .connectTimeout(Duration.ofSeconds(5))
              .build();
          this.apiKey = apiKey;
          this.metricsClient = metricsClient;
      }
      
      public HealthStatus healthCheck() {
          long timestamp = Instant.now().getEpochSecond();
          
          try {
              HttpRequest request = HttpRequest.newBuilder()
                  .uri(URI.create(healthEndpoint))
                  .header("Authorization", apiKey)
                  .header("Content-Type", "application/json")
                  .timeout(Duration.ofSeconds(5))
                  .GET()
                  .build();
              
              HttpResponse<String> response = httpClient.send(
                  request, HttpResponse.BodyHandlers.ofString());
              
              if (response.statusCode() == 200) {
                  sendMetric("api_health", 1);
                  return new HealthStatus("healthy", timestamp);
              } else {
                  sendMetric("api_health", 0);
                  return new HealthStatus("unhealthy", timestamp, 
                      "HTTP " + response.statusCode());
              }
              
          } catch (Exception e) {
              sendMetric("api_health", 0);
              return new HealthStatus("unhealthy", timestamp, e.getMessage());
          }
      }
      
      private void sendMetric(String metricName, int value) {
          if (metricsClient != null) {
              try {
                  metricsClient.gauge(metricName, value);
              } catch (Exception e) {
                  System.err.println("Failed to send metric: " + e.getMessage());
              }
          }
      }
      
      public void startMonitoring(long intervalSeconds) {
          System.out.println("Starting health monitoring every " + intervalSeconds + "s");
          
          scheduler.scheduleAtFixedRate(() -> {
              try {
                  HealthStatus status = healthCheck();
                  System.out.println("Health check result: " + status);
              } catch (Exception e) {
                  System.err.println("Health check failed: " + e.getMessage());
              }
          }, 0, intervalSeconds, TimeUnit.SECONDS);
      }
      
      public void stopMonitoring() {
          scheduler.shutdown();
      }
      
      public static class HealthStatus {
          private final String status;
          private final long timestamp;
          private final String error;
          
          public HealthStatus(String status, long timestamp) {
              this(status, timestamp, null);
          }
          
          public HealthStatus(String status, long timestamp, String error) {
              this.status = status;
              this.timestamp = timestamp;
              this.error = error;
          }
          
          // Getters
          public String getStatus() { return status; }
          public long getTimestamp() { return timestamp; }
          public String getError() { return error; }
          
          @Override
          public String toString() {
              return String.format("HealthStatus{status='%s', timestamp=%d, error='%s'}", 
                  status, timestamp, error);
          }
      }
      
      public interface MetricsClient {
          void gauge(String metric, int value) throws Exception;
      }
  }

  // Usage
  public class Main {
      public static void main(String[] args) {
          HealthChecker healthChecker = new HealthChecker("your-api-key");
          
          // Run once
          HealthChecker.HealthStatus status = healthChecker.healthCheck();
          System.out.println(status);
          
          // Start continuous monitoring (every 60 seconds)
          healthChecker.startMonitoring(60);
          
          // Remember to call healthChecker.stopMonitoring() when shutting down
      }
  }
  ```
</CodeGroup>

## Best Practices

### Error Handling Strategy

1. **Fail Fast**: Return errors immediately for invalid requests
2. **Graceful Degradation**: Provide fallback functionality when possible
3. **User-Friendly Messages**: Show helpful error messages to end users
4. **Detailed Logging**: Log full error context for debugging

### Retry Guidelines

* **Retry transient errors**: Network issues, 5xx errors, rate limits
* **Don't retry permanent failures**: 4xx errors (except 429)
* **Use exponential backoff**: With jitter to prevent thundering herd
* **Respect rate limits**: Use server-provided retry-after values

### Security Considerations

* **Never log sensitive data**: API keys, payment info, personal data
* **Validate all inputs**: Even when retrying failed requests
* **Monitor for anomalies**: Unusual error patterns may indicate attacks
* **Rotate compromised keys**: Immediately if invalid key errors occur

## Troubleshooting Common Issues

<AccordionGroup>
  <Accordion title="Getting 401 errors with valid API key">
    * Ensure API key is not truncated in environment variables
    * Check that you're using the correct header name (`Authentication`)
    * Verify you're hitting the correct environment (staging vs production)
    * Try regenerating your API key if the issue persists
  </Accordion>

  <Accordion title="Intermittent timeout errors">
    * Implement retry logic with exponential backoff
    * Check your network connectivity and DNS resolution
    * Consider increasing request timeout limits
    * Monitor our [status page](https://status.satsuma.ai) for incidents
  </Accordion>

  <Accordion title="High error rates during peak traffic">
    * Review your rate limiting and implement proper backoff
    * Consider request batching to reduce API calls
    * Implement caching for frequently accessed data
    * Contact support for rate limit increases if needed
  </Accordion>
</AccordionGroup>

## Getting Help

When reporting errors, include:

* Request ID from the error response
* Full error message and code
* Request payload (without sensitive data)
* Timestamp and retry attempts
* Your API key prefix (first 8 characters)

Contact [support@satsuma.ai](mailto:support@satsuma.ai) for assistance with persistent errors or unexpected behavior.
