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

# Error Handling

> Gracefully handling errors in your Sticker integration

## Overview

Robust error handling ensures your integration remains reliable and provides a great user experience even when things go wrong.

## Error Response Format

All Sticker API errors follow a consistent format:

```json theme={null}
{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_CODE",
  "details": "Additional context (optional)",
  "field": "problematic_field (for validation errors)"
}
```

## HTTP Status Codes

<CardGroup cols={2}>
  <Card title="4xx Client Errors" icon="user-xmark">
    Errors caused by the request

    Usually don't retry
  </Card>

  <Card title="5xx Server Errors" icon="server">
    Errors on Sticker's side

    Safe to retry with backoff
  </Card>
</CardGroup>

## Common Error Codes

### Authentication Errors (401, 403)

<CodeGroup>
  ```json INVALID_API_KEY theme={null}
  {
    "error": "Invalid API key",
    "code": "INVALID_API_KEY"
  }
  ```

  ```json MISSING_API_KEY theme={null}
  {
    "error": "Missing or invalid API key",
    "code": "UNAUTHORIZED"
  }
  ```

  ```json FORBIDDEN theme={null}
  {
    "error": "Insufficient permissions",
    "code": "FORBIDDEN"
  }
  ```
</CodeGroup>

**How to handle:**

* Verify your API key is correct
* Check you're using the right header (`Authorization: Bearer` vs `X-API-Key`)
* Ensure key hasn't been revoked
* Don't retry without fixing the issue

### Validation Errors (400)

<CodeGroup>
  ```json INVALID_REQUEST theme={null}
  {
    "error": "Missing required field: organization.email",
    "code": "INVALID_REQUEST",
    "field": "organization.email"
  }
  ```

  ```json INVALID_EMAIL theme={null}
  {
    "error": "Invalid email format",
    "code": "INVALID_EMAIL",
    "field": "user.email",
    "value": "notanemail"
  }
  ```

  ```json INVALID_ADDRESS theme={null}
  {
    "error": "Invalid state code",
    "code": "INVALID_ADDRESS",
    "field": "addresses[0].state",
    "details": "Must be a 2-letter US state code"
  }
  ```
</CodeGroup>

**How to handle:**

* Validate input before sending
* Show field-specific error messages
* Don't retry without fixing validation issues

### Resource Errors (404, 409)

<CodeGroup>
  ```json ORGANIZATION_NOT_FOUND theme={null}
  {
    "error": "No profile found with partner_org_id: acme_123",
    "code": "ORGANIZATION_NOT_FOUND",
    "partner_org_id": "acme_123"
  }
  ```

  ```json USER_NOT_FOUND theme={null}
  {
    "error": "User not authorized for this organization",
    "code": "USER_NOT_FOUND",
    "email": "user@example.com"
  }
  ```

  ```json DUPLICATE_ORGANIZATION theme={null}
  {
    "error": "Organization already exists",
    "code": "DUPLICATE_ORGANIZATION",
    "partner_org_id": "acme_123",
    "existing_profile_id": "uuid-here"
  }
  ```
</CodeGroup>

**How to handle:**

* For 404: Ensure setup was completed first
* For 409: Use existing resource ID
* Check your data consistency

### Rate Limiting (429)

```json theme={null}
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED",
  "retry_after": 60
}
```

**How to handle:**

* Implement exponential backoff
* Respect `retry_after` header
* Cache organization data to reduce calls
* Consider request throttling

### Server Errors (500, 503)

```json theme={null}
{
  "error": "Internal server error",
  "code": "INTERNAL_ERROR",
  "details": "Database connection timeout"
}
```

**How to handle:**

* Retry with exponential backoff
* Log error for investigation
* Show user-friendly message
* Alert on persistent failures

## Retry Logic

### Exponential Backoff

Implement smart retries for transient failures:

```javascript theme={null}
async function callStickerWithRetry(
  endpoint,
  options,
  maxRetries = 3
) {
  let lastError;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(endpoint, options);
      
      // Don't retry client errors (except 429)
      if (response.status >= 400 && response.status < 500) {
        if (response.status === 429) {
          // Handle rate limiting
          const retryAfter = response.headers.get('Retry-After') || 60;
          await sleep(parseInt(retryAfter) * 1000);
          continue;
        }
        // Other 4xx errors - don't retry
        throw new Error(`Client error: ${response.status}`);
      }
      
      // Server error - will retry
      if (!response.ok) {
        throw new Error(`Server error: ${response.status}`);
      }
      
      return await response.json();
    } catch (error) {
      lastError = error;
      
      // Don't retry on last attempt
      if (attempt === maxRetries - 1) {
        break;
      }
      
      // Exponential backoff: 1s, 2s, 4s
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
      await sleep(delay);
    }
  }
  
  throw lastError;
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
```

### Retry Decision Tree

```
Error Occurred
    ├─ Network Error? → Retry with backoff
    ├─ Timeout? → Retry with backoff
    ├─ 429 Rate Limit? → Wait retry_after, then retry
    ├─ 5xx Server Error? → Retry with backoff
    ├─ 401/403 Auth Error? → DON'T retry, fix credentials
    ├─ 400 Validation Error? → DON'T retry, fix request
    └─ 404/409 Resource Error? → DON'T retry, fix data
```

## User-Friendly Error Messages

Transform technical errors into helpful messages:

```javascript theme={null}
function getUserMessage(error) {
  const messages = {
    // Authentication
    'INVALID_API_KEY': 'Connection error. Please contact support.',
    'INVALID_SIGNATURE': 'Authentication failed. Please try again.',
    
    // Resources
    'ORGANIZATION_NOT_FOUND': 'Your organization needs to be set up. Please contact your administrator.',
    'USER_NOT_FOUND': 'You don\'t have access to supplies. Please contact your administrator.',
    
    // Validation
    'INVALID_EMAIL': 'Please check your email address.',
    'INVALID_REQUEST': 'Some information is missing. Please check and try again.',
    
    // System
    'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment.',
    'INTERNAL_ERROR': 'Something went wrong on our end. Please try again.',
    'SERVICE_UNAVAILABLE': 'Service temporarily unavailable. Please try again in a few minutes.',
    
    // Session
    'SESSION_EXPIRED': 'Your session expired. Refreshing...',
    'SESSION_INVALID': 'Invalid session. Please reload the page.',
  };
  
  return messages[error.code] || 'An error occurred. Please try again or contact support.';
}
```

## Error Handling Patterns

### Organization Setup

```javascript theme={null}
async function setupOrganization(orgData) {
  try {
    const result = await callStickerAPI(
      'https://api.usesticker.com/v1/organizations/setup',
      orgData
    );
    
    // Success
    return {
      success: true,
      profileId: result.profile.id
    };
    
  } catch (error) {
    // Log for debugging
    console.error('Organization setup failed:', error);
    
    // Handle specific errors
    if (error.code === 'DUPLICATE_ORGANIZATION') {
      // Organization already exists, that's okay
      return {
        success: true,
        profileId: error.existing_profile_id,
        message: 'Organization already exists'
      };
    }
    
    if (error.code === 'INVALID_EMAIL') {
      return {
        success: false,
        error: 'Invalid email address',
        field: error.field
      };
    }
    
    if (error.code === 'INVALID_REQUEST') {
      return {
        success: false,
        error: `Missing required information: ${error.field}`,
        field: error.field
      };
    }
    
    // Unknown error
    return {
      success: false,
      error: 'Failed to set up organization. Please try again.',
      canRetry: true
    };
  }
}
```

### User Handshake

```javascript theme={null}
async function authenticateUser(orgId, user) {
  try {
    const result = await callStickerAPI(
      'https://api.usesticker.com/v1/partner/handshake',
      { internal_user_id: user.id }
    );
    
    return {
      success: true,
      sessionKey: result.session_key,
      iframeUrl: result.iframe_url
    };
    
  } catch (error) {
    console.error('Handshake failed:', error);
    
    // Organization not found
    if (error.code === 'ORGANIZATION_NOT_FOUND') {
      // Try to set up organization automatically
      await setupOrganization(orgData);
      // Retry handshake
      return authenticateUser(orgId, user);
    }
    
    // User not authorized
    if (error.code === 'USER_NOT_FOUND') {
      return {
        success: false,
        error: 'You don\'t have access. Contact your admin.',
        requiresSetup: true
      };
    }
    
    // Session error
    if (error.code === 'SESSION_EXPIRED') {
      return {
        success: false,
        error: 'Session expired. Refreshing...',
        shouldRetry: true
      };
    }
    
    // Rate limited
    if (error.code === 'RATE_LIMIT_EXCEEDED') {
      return {
        success: false,
        error: 'Too many requests. Please wait.',
        retryAfter: error.retry_after
      };
    }
    
    // Generic error
    return {
      success: false,
      error: getUserMessage(error),
      canRetry: true
    };
  }
}
```

### iframe Loading

```javascript theme={null}
function StickerEmbed({ orgId, user }) {
  const [state, setState] = useState('loading');
  const [error, setError] = useState(null);
  const [iframeUrl, setIframeUrl] = useState(null);
  const [retryCount, setRetryCount] = useState(0);

  const loadSupplies = async () => {
    setState('loading');
    setError(null);
    
    try {
      const result = await authenticateUser(orgId, user);
      
      if (!result.success) {
        setError(result.error);
        setState('error');
        
        // Auto-retry for certain errors
        if (result.shouldRetry && retryCount < 3) {
          setTimeout(() => {
            setRetryCount(retryCount + 1);
            loadSupplies();
          }, 2000 * (retryCount + 1)); // Exponential backoff
        }
        return;
      }
      
      setIframeUrl(result.iframeUrl);
      setState('ready');
      
    } catch (error) {
      console.error('Failed to load supplies:', error);
      setError('Failed to load supplies. Please try again.');
      setState('error');
    }
  };

  useEffect(() => {
    loadSupplies();
  }, []);

  if (state === 'loading') {
    return (
      <div className="loading-state">
        <Spinner />
        <p>Loading supplies...</p>
      </div>
    );
  }

  if (state === 'error') {
    return (
      <div className="error-state">
        <ErrorIcon />
        <p>{error}</p>
        <button onClick={() => {
          setRetryCount(0);
          loadSupplies();
        }}>
          Try Again
        </button>
      </div>
    );
  }

  return (
    <iframe
      src={iframeUrl}
      className="w-full h-full border-0"
      title="Sticker Embedded Procurement"
      sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
      allow="payment; publickey-credentials-get; fullscreen"
    />
  );
}
```

## Error Logging

### Structured Logging

Log errors with context for debugging:

```javascript theme={null}
function logError(context, error, additionalInfo = {}) {
  const errorLog = {
    timestamp: new Date().toISOString(),
    context: context,
    error: {
      message: error.message,
      code: error.code,
      stack: error.stack
    },
    api: {
      endpoint: additionalInfo.endpoint,
      method: additionalInfo.method,
      status: additionalInfo.status
    },
    user: {
      org_id: additionalInfo.orgId,
      email: additionalInfo.userEmail
    },
    ...additionalInfo
  };
  
  // Send to your logging service
  logger.error('Sticker Integration Error', errorLog);
  
  // Alert on critical errors
  if (error.code === 'INTERNAL_ERROR' || error.status >= 500) {
    alertOps('Sticker API error', errorLog);
  }
}
```

### Error Metrics

Track error rates and patterns:

```javascript theme={null}
const errorMetrics = {
  total: 0,
  byCode: {},
  byEndpoint: {}
};

function trackError(error, endpoint) {
  errorMetrics.total++;
  errorMetrics.byCode[error.code] = (errorMetrics.byCode[error.code] || 0) + 1;
  errorMetrics.byEndpoint[endpoint] = (errorMetrics.byEndpoint[endpoint] || 0) + 1;
  
  // Alert if error rate is high
  const errorRate = errorMetrics.total / totalRequests;
  if (errorRate > 0.05) { // 5% error rate
    alertOps('High error rate in Sticker integration', {
      error_rate: errorRate,
      total_errors: errorMetrics.total,
      by_code: errorMetrics.byCode
    });
  }
}
```

## Fallback Strategies

### Graceful Degradation

Provide alternatives when integration fails:

```javascript theme={null}
function SuppliesSection({ orgId, user }) {
  const [integrationHealthy, setIntegrationHealthy] = useState(true);
  const [consecutiveFailures, setConsecutiveFailures] = useState(0);

  const checkHealth = async () => {
    try {
      await fetch('https://api.usesticker.com/health');
      setConsecutiveFailures(0);
    } catch (error) {
      setConsecutiveFailures(prev => prev + 1);
      
      // After 3 failures, show fallback
      if (consecutiveFailures >= 3) {
        setIntegrationHealthy(false);
      }
    }
  };

  if (!integrationHealthy) {
    return (
      <div className="fallback-ui">
        <h3>Supplies Currently Unavailable</h3>
        <p>Our supplies system is temporarily unavailable.</p>
        <div className="alternatives">
          <button onClick={() => window.location.href = 'tel:18005551234'}>
            Call to Order: 1-800-555-1234
          </button>
          <button onClick={() => window.location.href = 'mailto:orders@usesticker.com'}>
          Email Support
        </button>
          <button onClick={() => setIntegrationHealthy(true)}>
            Try Again
          </button>
        </div>
      </div>
    );
  }

  return <StickerEmbed orgId={orgId} user={user} />;
}
```

### Circuit Breaker

Prevent cascading failures:

```javascript theme={null}
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    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.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
}

// Usage
const breaker = new CircuitBreaker();

try {
  const result = await breaker.call(() =>
    callStickerAPI('https://api.usesticker.com/v1/partner/handshake', data)
  );
} catch (error) {
  if (error.message === 'Circuit breaker is OPEN') {
    showFallbackUI();
  }
}
```

## Testing Error Handling

Test how your integration handles errors:

```javascript theme={null}
describe('Error Handling', () => {
  it('should retry on 500 errors', async () => {
    mockAPI.fail(500, 2); // Fail twice with 500
    
    const result = await callStickerWithRetry(endpoint, options);
    
    expect(result).toBeDefined();
    expect(mockAPI.callCount).toBe(3);
  });

  it('should not retry on 400 errors', async () => {
    mockAPI.fail(400);
    
    await expect(callStickerWithRetry(endpoint, options))
      .rejects.toThrow();
    
    expect(mockAPI.callCount).toBe(1);
  });

  it('should show user-friendly messages', () => {
    const error = { code: 'ORGANIZATION_NOT_FOUND' };
    const message = getUserMessage(error);
    
    expect(message).not.toContain('ORGANIZATION_NOT_FOUND');
    expect(message).toContain('organization');
  });
});
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Security Guide" icon="shield" href="/advanced/security">
    Security best practices for your integration
  </Card>

  <Card title="Best Practices" icon="lightbulb" href="/integration/best-practices">
    Tips for a robust integration
  </Card>
</CardGroup>
