StateSet API uses standard HTTP response codes and provides detailed error information to help you handle issues gracefully.

Error Response Format

All errors follow a consistent JSON structure to make error handling predictable:
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "type": "error_type",
    "details": {
      // Additional context-specific information
    },
    "request_id": "req_1NXWPnCo6bFb1KQto6C8OWvE",
    "timestamp": "2024-01-15T10:30:00Z",
    "documentation_url": "https://docs.stateset.com/errors/ERROR_CODE"
  }
}

HTTP Status Codes

Status CodeMeaningCommon Causes
200OKRequest succeeded
201CreatedResource successfully created
202AcceptedRequest accepted for async processing
204No ContentRequest succeeded with no response body
400Bad RequestInvalid request parameters or malformed syntax
401UnauthorizedMissing or invalid authentication
403ForbiddenValid auth but insufficient permissions
404Not FoundResource doesn’t exist
409ConflictResource conflict or duplicate
422Unprocessable EntityValid syntax but semantic errors
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer error - retry with backoff
502Bad GatewayUpstream service error
503Service UnavailableTemporary service outage

Error Types and Codes

Authentication Errors (401)

Error CodeDescriptionResolution
INVALID_API_KEYAPI key is invalid or revokedCheck key in dashboard
EXPIRED_API_KEYAPI key has expiredGenerate new API key
MISSING_AUTH_HEADERNo Authorization header providedInclude Bearer token
INVALID_TOKEN_FORMATToken format is incorrectUse Bearer sk_... format
INACTIVE_ACCOUNTAccount is suspended or inactiveContact support

Authorization Errors (403)

Error CodeDescriptionResolution
INSUFFICIENT_PERMISSIONSAPI key lacks required permissionsUse key with proper scopes
RESOURCE_ACCESS_DENIEDCannot access this specific resourceCheck resource ownership
PLAN_LIMIT_EXCEEDEDFeature not available on current planUpgrade plan
IP_NOT_ALLOWEDRequest from unauthorized IPAdd IP to allowlist

Validation Errors (400/422)

Error CodeDescriptionResolution
VALIDATION_ERRORRequest validation failedCheck field requirements
MISSING_REQUIRED_FIELDRequired field not providedInclude all required fields
INVALID_FIELD_VALUEField value doesn’t meet requirementsValidate field format
INVALID_ENUM_VALUEValue not in allowed enum listUse accepted values
FIELD_TOO_LONGField exceeds maximum lengthTruncate field value
INVALID_DATE_FORMATDate format incorrectUse ISO 8601 format

Resource Errors (404/409)

Error CodeDescriptionResolution
RESOURCE_NOT_FOUNDRequested resource doesn’t existVerify resource ID
RESOURCE_ALREADY_EXISTSDuplicate resource creation attemptUse existing resource
RESOURCE_LOCKEDResource is locked for editingWait and retry
RESOURCE_ARCHIVEDResource has been archivedUnarchive or use different resource
PARENT_NOT_FOUNDParent resource doesn’t existCreate parent first

Business Logic Errors (422)

Error CodeDescriptionResolution
INSUFFICIENT_INVENTORYNot enough inventory availableReduce quantity or check stock
PAYMENT_FAILEDPayment processing failedVerify payment details
INVALID_STATE_TRANSITIONInvalid status changeCheck allowed transitions
OUTSIDE_RETURN_WINDOWReturn period expiredCheck return policy
DUPLICATE_REQUESTDuplicate request detectedUse idempotency key

Rate Limiting Errors (429)

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded",
    "type": "rate_limit_error",
    "details": {
      "limit": 100,
      "remaining": 0,
      "reset_at": "2024-01-15T10:35:00Z",
      "retry_after": 300
    }
  }
}

Error Handling Best Practices

1. Implement Exponential Backoff

class APIClient {
  async requestWithRetry(url, options, maxRetries = 3) {
    let lastError;
    
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        const response = await fetch(url, options);
        
        if (response.status === 429) {
          const retryAfter = response.headers.get('X-RateLimit-Retry-After');
          const delay = retryAfter 
            ? parseInt(retryAfter) * 1000 
            : Math.min(1000 * Math.pow(2, attempt), 32000);
          
          if (attempt < maxRetries) {
            await new Promise(resolve => setTimeout(resolve, delay));
            continue;
          }
        }
        
        if (!response.ok) {
          const error = await response.json();
          throw new APIError(error);
        }
        
        return response.json();
      } catch (error) {
        lastError = error;
        
        if (attempt < maxRetries && this.isRetryable(error)) {
          const delay = Math.min(1000 * Math.pow(2, attempt), 32000);
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
        
        throw error;
      }
    }
    
    throw lastError;
  }
  
  isRetryable(error) {
    const retryableCodes = [429, 500, 502, 503, 504];
    return error.status && retryableCodes.includes(error.status);
  }
}

2. Handle Specific Error Types

class ErrorHandler {
  handle(error) {
    switch (error.code) {
      case 'INVALID_API_KEY':
        // Refresh API key or prompt for new one
        return this.refreshApiKey();
        
      case 'INSUFFICIENT_PERMISSIONS':
        // Inform user about permission requirements
        return this.showPermissionError(error.details);
        
      case 'VALIDATION_ERROR':
        // Show field-specific errors
        return this.showValidationErrors(error.details);
        
      case 'RATE_LIMITED':
        // Implement backoff and queue
        return this.queueRequest(error.details.retry_after);
        
      case 'INSUFFICIENT_INVENTORY':
        // Update UI with available quantity
        return this.updateInventory(error.details);
        
      default:
        // Generic error handling
        return this.showGenericError(error.message);
    }
  }
}

3. Implement Circuit Breaker Pattern

class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.failureThreshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED';
    this.nextAttempt = Date.now();
  }
  
  async call(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      this.state = 'HALF_OPEN';
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }
  
  onFailure() {
    this.failureCount++;
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
}

4. Log and Monitor Errors

class ErrorLogger {
  log(error, context) {
    const errorData = {
      timestamp: new Date().toISOString(),
      request_id: error.request_id,
      error_code: error.code,
      message: error.message,
      context: {
        user_id: context.userId,
        endpoint: context.endpoint,
        method: context.method,
        ...context
      },
      stack_trace: error.stack
    };
    
    // Send to logging service
    logger.error(errorData);
    
    // Track in analytics
    analytics.track('API_ERROR', errorData);
    
    // Alert on critical errors
    if (this.isCritical(error)) {
      alerting.notify(errorData);
    }
  }
  
  isCritical(error) {
    const criticalCodes = [
      'PAYMENT_FAILED',
      'SERVICE_UNAVAILABLE',
      'DATABASE_ERROR'
    ];
    return criticalCodes.includes(error.code);
  }
}

Error Recovery Strategies

Idempotency for Safe Retries

// Use idempotency keys to safely retry requests
const createOrder = async (orderData) => {
  const idempotencyKey = `order-${uuidv4()}`;
  
  try {
    return await api.post('/orders', orderData, {
      headers: {
        'Idempotency-Key': idempotencyKey
      }
    });
  } catch (error) {
    if (error.code === 'DUPLICATE_REQUEST') {
      // Return existing order from previous request
      return error.details.existing_resource;
    }
    throw error;
  }
};

Graceful Degradation

class ResilientClient {
  async getProductWithFallback(productId) {
    try {
      // Try primary API
      return await this.api.get(`/products/${productId}`);
    } catch (error) {
      if (error.code === 'SERVICE_UNAVAILABLE') {
        // Fall back to cache
        const cached = await this.cache.get(`product:${productId}`);
        if (cached) {
          return { ...cached, from_cache: true };
        }
        
        // Fall back to secondary service
        return await this.secondaryApi.get(`/products/${productId}`);
      }
      throw error;
    }
  }
}

Common Error Scenarios

Handling Validation Errors

// Request
POST /v1/orders
{
  "customer_email": "invalid-email",
  "items": []
}

// Response (422)
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "type": "validation_error",
    "details": {
      "errors": [
        {
          "field": "customer_email",
          "message": "Invalid email format",
          "expected": "valid email address"
        },
        {
          "field": "items",
          "message": "At least one item is required",
          "expected": "non-empty array"
        }
      ]
    },
    "request_id": "req_abc123"
  }
}

Handling Resource Conflicts

// Request
POST /v1/customers
{
  "email": "existing@example.com"
}

// Response (409)
{
  "error": {
    "code": "RESOURCE_ALREADY_EXISTS",
    "message": "Customer with this email already exists",
    "type": "conflict_error",
    "details": {
      "existing_id": "cus_123abc",
      "field": "email",
      "value": "existing@example.com"
    },
    "request_id": "req_def456"
  }
}

Error Webhooks

Configure error webhooks to receive notifications for critical failures:
{
  "event": "api.error",
  "created": "2024-01-15T10:30:00Z",
  "data": {
    "error_code": "PAYMENT_FAILED",
    "request_id": "req_xyz789",
    "customer_id": "cus_123",
    "order_id": "ord_456",
    "amount": 9999,
    "currency": "usd"
  }
}

SDK Error Handling

Our SDKs provide built-in error handling:
try {
  const order = await stateset.orders.create({
    customer_email: 'customer@example.com',
    items: [...]
  });
} catch (error) {
  if (error.code === 'INSUFFICIENT_INVENTORY') {
    // Handle inventory shortage
    const available = error.details.available_items;
    console.log(`Only ${available} items available`);
  } else if (error.code === 'VALIDATION_ERROR') {
    // Handle validation errors
    error.details.errors.forEach(err => {
      console.log(`${err.field}: ${err.message}`);
    });
  } else {
    // Generic error handling
    console.error('Unexpected error:', error.message);
  }
}

Testing Error Scenarios

Use test mode to simulate error conditions:
# Trigger specific error in test mode
curl -X POST https://api.sandbox.stateset.com/v1/orders \
  -H "Authorization: Bearer sk_test_..." \
  -H "X-Test-Error-Code: INSUFFICIENT_INVENTORY" \
  -d '{...}'

Support

If you encounter persistent errors or need help with error handling:
Related: Rate Limiting | Authentication | Webhooks