# API Integration Patterns für Automation

**Meta-Description:** Robuste API Integrationen. Retry Logic, Rate Limiting, Webhooks und Error Handling für zuverlässige Automatisierung.

**Keywords:** API Integration, Retry Logic, Rate Limiting, Webhooks, Error Handling, REST API, HTTP Client

---

## Einführung

Zuverlässige API-Integrationen sind das Fundament jeder Automation. **Retry Logic, Rate Limiting, Circuit Breaker** – diese Patterns machen den Unterschied zwischen fragilen und robusten Systemen.

---

## API Client Architecture

```
┌─────────────────────────────────────────────────────────────┐
│              ROBUST API CLIENT                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Request Flow:                                              │
│                                                             │
│  Application                                                │
│       ↓                                                    │
│  Rate Limiter (Throttle Requests)                          │
│       ↓                                                    │
│  Retry Handler (Exponential Backoff)                       │
│       ↓                                                    │
│  Circuit Breaker (Fail Fast)                               │
│       ↓                                                    │
│  HTTP Client (Axios/Fetch)                                 │
│       ↓                                                    │
│  Response Handler (Parse/Validate)                         │
│       ↓                                                    │
│  Application                                                │
│                                                             │
│  Cross-Cutting Concerns:                                    │
│  ├── Logging                                               │
│  ├── Metrics                                               │
│  └── Caching                                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## Base API Client

```typescript
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';

interface ApiClientConfig {
  baseURL: string;
  timeout?: number;
  headers?: Record<string, string>;
  retries?: number;
  retryDelay?: number;
  rateLimit?: {
    maxRequests: number;
    windowMs: number;
  };
}

class ApiClient {
  private client: AxiosInstance;
  private config: Required<ApiClientConfig>;
  private requestTimestamps: number[] = [];

  constructor(config: ApiClientConfig) {
    this.config = {
      timeout: 30000,
      headers: {},
      retries: 3,
      retryDelay: 1000,
      rateLimit: { maxRequests: 100, windowMs: 60000 },
      ...config
    };

    this.client = axios.create({
      baseURL: this.config.baseURL,
      timeout: this.config.timeout,
      headers: this.config.headers
    });

    this.setupInterceptors();
  }

  private setupInterceptors() {
    // Request Interceptor
    this.client.interceptors.request.use(
      async (config) => {
        await this.waitForRateLimit();
        this.recordRequest();

        console.log(`[API] ${config.method?.toUpperCase()} ${config.url}`);
        return config;
      },
      (error) => Promise.reject(error)
    );

    // Response Interceptor
    this.client.interceptors.response.use(
      (response) => {
        console.log(`[API] Response: ${response.status}`);
        return response;
      },
      async (error: AxiosError) => {
        const config = error.config as AxiosRequestConfig & { _retryCount?: number };

        if (!config || !this.shouldRetry(error, config._retryCount || 0)) {
          return Promise.reject(this.formatError(error));
        }

        config._retryCount = (config._retryCount || 0) + 1;

        const delay = this.calculateDelay(config._retryCount);
        console.log(`[API] Retry ${config._retryCount}/${this.config.retries} after ${delay}ms`);

        await this.sleep(delay);
        return this.client.request(config);
      }
    );
  }

  private shouldRetry(error: AxiosError, retryCount: number): boolean {
    if (retryCount >= this.config.retries) return false;

    // Retry bei Netzwerkfehlern
    if (!error.response) return true;

    // Retry bei bestimmten Status Codes
    const retryableStatuses = [408, 429, 500, 502, 503, 504];
    return retryableStatuses.includes(error.response.status);
  }

  private calculateDelay(retryCount: number): number {
    // Exponential Backoff mit Jitter
    const baseDelay = this.config.retryDelay;
    const exponentialDelay = baseDelay * Math.pow(2, retryCount - 1);
    const jitter = Math.random() * 1000;
    return exponentialDelay + jitter;
  }

  private async waitForRateLimit(): Promise<void> {
    const now = Date.now();
    const windowStart = now - this.config.rateLimit.windowMs;

    // Alte Timestamps entfernen
    this.requestTimestamps = this.requestTimestamps.filter(t => t > windowStart);

    if (this.requestTimestamps.length >= this.config.rateLimit.maxRequests) {
      const oldestRequest = this.requestTimestamps[0];
      const waitTime = oldestRequest + this.config.rateLimit.windowMs - now;

      if (waitTime > 0) {
        console.log(`[API] Rate limit reached, waiting ${waitTime}ms`);
        await this.sleep(waitTime);
      }
    }
  }

  private recordRequest() {
    this.requestTimestamps.push(Date.now());
  }

  private formatError(error: AxiosError): Error {
    if (error.response) {
      return new Error(
        `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
      );
    }
    if (error.request) {
      return new Error(`Network Error: ${error.message}`);
    }
    return error;
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Public Methods
  async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.get<T>(url, config);
    return response.data;
  }

  async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.post<T>(url, data, config);
    return response.data;
  }

  async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.put<T>(url, data, config);
    return response.data;
  }

  async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.delete<T>(url, config);
    return response.data;
  }
}
```

---

## Circuit Breaker Pattern

```typescript
enum CircuitState {
  CLOSED = 'CLOSED',     // Normal Operation
  OPEN = 'OPEN',         // Failing, block requests
  HALF_OPEN = 'HALF_OPEN' // Testing recovery
}

interface CircuitBreakerConfig {
  failureThreshold: number;
  successThreshold: number;
  timeout: number;
}

class CircuitBreaker {
  private state: CircuitState = CircuitState.CLOSED;
  private failureCount = 0;
  private successCount = 0;
  private lastFailureTime?: number;
  private config: CircuitBreakerConfig;

  constructor(config: Partial<CircuitBreakerConfig> = {}) {
    this.config = {
      failureThreshold: 5,
      successThreshold: 2,
      timeout: 30000,
      ...config
    };
  }

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === CircuitState.OPEN) {
      if (this.shouldAttemptReset()) {
        this.state = CircuitState.HALF_OPEN;
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private shouldAttemptReset(): boolean {
    return (
      this.lastFailureTime !== undefined &&
      Date.now() - this.lastFailureTime >= this.config.timeout
    );
  }

  private onSuccess() {
    if (this.state === CircuitState.HALF_OPEN) {
      this.successCount++;

      if (this.successCount >= this.config.successThreshold) {
        this.reset();
      }
    }

    this.failureCount = 0;
  }

  private onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();

    if (this.state === CircuitState.HALF_OPEN) {
      this.state = CircuitState.OPEN;
      this.successCount = 0;
    } else if (this.failureCount >= this.config.failureThreshold) {
      this.state = CircuitState.OPEN;
    }
  }

  private reset() {
    this.state = CircuitState.CLOSED;
    this.failureCount = 0;
    this.successCount = 0;
    this.lastFailureTime = undefined;
  }

  getState(): CircuitState {
    return this.state;
  }
}

// Verwendung
const circuitBreaker = new CircuitBreaker({ failureThreshold: 3 });

async function callExternalApi() {
  return circuitBreaker.execute(async () => {
    return apiClient.get('/data');
  });
}
```

---

## Webhook Handler

```typescript
import { createHmac, timingSafeEqual } from 'crypto';

interface WebhookConfig {
  secret: string;
  tolerance: number;  // Sekunden
}

class WebhookHandler {
  private config: WebhookConfig;
  private handlers: Map<string, (data: any) => Promise<void>> = new Map();

  constructor(config: WebhookConfig) {
    this.config = config;
  }

  register(event: string, handler: (data: any) => Promise<void>) {
    this.handlers.set(event, handler);
  }

  async handle(
    payload: string,
    signature: string,
    timestamp: string
  ): Promise<void> {
    // 1. Signature validieren
    if (!this.verifySignature(payload, signature, timestamp)) {
      throw new Error('Invalid webhook signature');
    }

    // 2. Timestamp validieren (Replay Attack Prevention)
    if (!this.verifyTimestamp(timestamp)) {
      throw new Error('Webhook timestamp too old');
    }

    // 3. Event verarbeiten
    const event = JSON.parse(payload);
    const handler = this.handlers.get(event.type);

    if (handler) {
      await handler(event.data);
    } else {
      console.warn(`No handler for event type: ${event.type}`);
    }
  }

  private verifySignature(
    payload: string,
    signature: string,
    timestamp: string
  ): boolean {
    const signedPayload = `${timestamp}.${payload}`;
    const expectedSignature = createHmac('sha256', this.config.secret)
      .update(signedPayload)
      .digest('hex');

    const sigBuffer = Buffer.from(signature);
    const expectedBuffer = Buffer.from(`sha256=${expectedSignature}`);

    return sigBuffer.length === expectedBuffer.length &&
           timingSafeEqual(sigBuffer, expectedBuffer);
  }

  private verifyTimestamp(timestamp: string): boolean {
    const webhookTime = parseInt(timestamp, 10) * 1000;
    const now = Date.now();
    const tolerance = this.config.tolerance * 1000;

    return Math.abs(now - webhookTime) <= tolerance;
  }
}

// Express Route
import express from 'express';

const app = express();
const webhookHandler = new WebhookHandler({
  secret: process.env.WEBHOOK_SECRET!,
  tolerance: 300  // 5 Minuten
});

// Webhook Events registrieren
webhookHandler.register('order.created', async (data) => {
  await processNewOrder(data);
});

webhookHandler.register('payment.completed', async (data) => {
  await completePayment(data);
});

// Webhook Endpoint
app.post('/webhooks',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const signature = req.headers['x-webhook-signature'] as string;
    const timestamp = req.headers['x-webhook-timestamp'] as string;

    try {
      await webhookHandler.handle(
        req.body.toString(),
        signature,
        timestamp
      );

      res.status(200).send('OK');
    } catch (error) {
      console.error('Webhook error:', error);
      res.status(400).send('Invalid webhook');
    }
  }
);
```

---

## Pagination Handler

```typescript
interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    page: number;
    pageSize: number;
    total: number;
    hasMore: boolean;
  };
}

async function* paginatedFetch<T>(
  apiClient: ApiClient,
  endpoint: string,
  pageSize: number = 100
): AsyncGenerator<T[], void, unknown> {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await apiClient.get<PaginatedResponse<T>>(
      `${endpoint}?page=${page}&pageSize=${pageSize}`
    );

    yield response.data;

    hasMore = response.pagination.hasMore;
    page++;
  }
}

// Cursor-based Pagination
async function* cursorPaginatedFetch<T>(
  apiClient: ApiClient,
  endpoint: string,
  limit: number = 100
): AsyncGenerator<T[], void, unknown> {
  let cursor: string | null = null;

  while (true) {
    const url = cursor
      ? `${endpoint}?cursor=${cursor}&limit=${limit}`
      : `${endpoint}?limit=${limit}`;

    const response = await apiClient.get<{
      data: T[];
      nextCursor: string | null;
    }>(url);

    yield response.data;

    if (!response.nextCursor) break;
    cursor = response.nextCursor;
  }
}

// Verwendung
async function fetchAllProducts() {
  const allProducts: Product[] = [];

  for await (const products of paginatedFetch<Product>(apiClient, '/products')) {
    allProducts.push(...products);
    console.log(`Fetched ${allProducts.length} products so far`);
  }

  return allProducts;
}
```

---

## Response Caching

```typescript
interface CacheEntry<T> {
  data: T;
  expiresAt: number;
  etag?: string;
}

class ResponseCache {
  private cache: Map<string, CacheEntry<any>> = new Map();
  private defaultTTL: number;

  constructor(defaultTTL: number = 60000) {
    this.defaultTTL = defaultTTL;
  }

  set<T>(key: string, data: T, ttl?: number, etag?: string) {
    this.cache.set(key, {
      data,
      expiresAt: Date.now() + (ttl || this.defaultTTL),
      etag
    });
  }

  get<T>(key: string): CacheEntry<T> | null {
    const entry = this.cache.get(key);

    if (!entry) return null;

    if (Date.now() > entry.expiresAt) {
      this.cache.delete(key);
      return null;
    }

    return entry;
  }

  invalidate(pattern: string | RegExp) {
    for (const key of this.cache.keys()) {
      if (typeof pattern === 'string' ? key.includes(pattern) : pattern.test(key)) {
        this.cache.delete(key);
      }
    }
  }
}

// Mit ETag Support
async function fetchWithCache<T>(
  apiClient: ApiClient,
  url: string,
  cache: ResponseCache
): Promise<T> {
  const cacheKey = url;
  const cached = cache.get<T>(cacheKey);

  const headers: Record<string, string> = {};

  if (cached?.etag) {
    headers['If-None-Match'] = cached.etag;
  }

  try {
    const response = await apiClient.get<T>(url, { headers });
    const etag = (response as any).headers?.etag;

    cache.set(cacheKey, response, undefined, etag);
    return response;
  } catch (error: any) {
    if (error.response?.status === 304 && cached) {
      return cached.data;
    }
    throw error;
  }
}
```

---

## Fazit

Robuste API Integrationen brauchen:

1. **Retry Logic**: Exponential Backoff mit Jitter
2. **Rate Limiting**: Client-seitige Throttling
3. **Circuit Breaker**: Fail Fast bei Ausfällen
4. **Caching**: Reduziert Last und Latenz

Production-ready von Anfang an.

---

## Bildprompts

1. "API calls with retry arrows showing resilience, error recovery"
2. "Circuit breaker switch between services, fail fast concept"
3. "Rate limiter queue with request tickets, throttling visualization"

---

## Quellen

- [Axios Documentation](https://axios-http.com/)
- [Circuit Breaker Pattern](https://martinfowler.com/bliki/CircuitBreaker.html)
- [API Rate Limiting Best Practices](https://cloud.google.com/architecture/rate-limiting-strategies)
- [Webhook Security](https://webhooks.fyi/security/hmac)
