Claude Code Plugins

Community-maintained marketplace

Feedback

api-integration

@j0KZ/mcp-agents
0
0

Master third-party API integration in ANY language with best practices and patterns

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name api-integration
description Master third-party API integration in ANY language with best practices and patterns

API Integration - Connect to Any Service

🎯 When to Use This Skill

Use when you need to:

  • Integrate payment providers (Stripe, PayPal)
  • Connect social media APIs (Twitter, Facebook)
  • Use cloud services (AWS, Google Cloud)
  • Implement OAuth authentication
  • Handle webhooks
  • Sync data between systems
  • Build API clients

âš¡ Quick Integration (15 minutes)

WITH MCP (API Designer):

"Create integration client for [API name] with authentication and error handling"
"Generate TypeScript types from this OpenAPI spec"

WITHOUT MCP - Universal Pattern:

// The Universal API Client Pattern
class APIClient {
  constructor(config) {
    this.baseURL = config.baseURL;
    this.headers = {
      'Content-Type': 'application/json',
      ...config.headers
    };
    this.timeout = config.timeout || 30000;
    this.retries = config.retries || 3;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;

    try {
      // 1. Add authentication
      const headers = await this.authenticate(options);

      // 2. Make request with timeout
      const response = await this.fetchWithTimeout(url, {
        ...options,
        headers: { ...this.headers, ...headers }
      });

      // 3. Handle response
      return await this.handleResponse(response);

    } catch (error) {
      // 4. Retry logic
      if (this.shouldRetry(error) && this.retries > 0) {
        this.retries--;
        return this.request(endpoint, options);
      }
      throw this.handleError(error);
    }
  }
}

📚 API Integration Patterns

1. Authentication Types

API Key Authentication

class APIKeyClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
  }

  async authenticate() {
    return {
      // Header authentication
      'X-API-Key': this.apiKey,
      // Or query parameter
      // params: { api_key: this.apiKey }
    };
  }
}

// Usage
const client = new APIKeyClient(process.env.API_KEY);

OAuth 2.0 Flow

class OAuth2Client {
  constructor(config) {
    this.clientId = config.clientId;
    this.clientSecret = config.clientSecret;
    this.redirectUri = config.redirectUri;
    this.tokenEndpoint = config.tokenEndpoint;
  }

  // Step 1: Get authorization URL
  getAuthorizationUrl(state) {
    const params = new URLSearchParams({
      client_id: this.clientId,
      redirect_uri: this.redirectUri,
      response_type: 'code',
      state: state,
      scope: 'read write'
    });

    return `${this.authEndpoint}?${params}`;
  }

  // Step 2: Exchange code for token
  async getAccessToken(code) {
    const response = await fetch(this.tokenEndpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: code,
        client_id: this.clientId,
        client_secret: this.clientSecret,
        redirect_uri: this.redirectUri
      })
    });

    const data = await response.json();
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresIn: data.expires_in
    };
  }

  // Step 3: Refresh token when expired
  async refreshAccessToken(refreshToken) {
    const response = await fetch(this.tokenEndpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
        client_id: this.clientId,
        client_secret: this.clientSecret
      })
    });

    return await response.json();
  }
}

JWT Bearer Token

class JWTClient {
  constructor(secret) {
    this.secret = secret;
  }

  generateToken(payload) {
    const jwt = require('jsonwebtoken');
    return jwt.sign(payload, this.secret, {
      expiresIn: '1h',
      algorithm: 'HS256'
    });
  }

  async authenticate() {
    const token = this.generateToken({
      sub: this.userId,
      iat: Math.floor(Date.now() / 1000)
    });

    return {
      'Authorization': `Bearer ${token}`
    };
  }
}

2. Error Handling & Retries

class ResilientAPIClient {
  constructor() {
    this.maxRetries = 3;
    this.baseDelay = 1000;  // 1 second
  }

  async requestWithRetry(fn, retries = this.maxRetries) {
    try {
      return await fn();
    } catch (error) {
      if (retries === 0 || !this.isRetryable(error)) {
        throw this.enhanceError(error);
      }

      const delay = this.calculateBackoff(this.maxRetries - retries);
      await this.sleep(delay);

      return this.requestWithRetry(fn, retries - 1);
    }
  }

  isRetryable(error) {
    // Retry on network errors and 5xx status codes
    const retryableStatuses = [408, 429, 500, 502, 503, 504];
    return (
      error.code === 'ECONNRESET' ||
      error.code === 'ETIMEDOUT' ||
      retryableStatuses.includes(error.statusCode)
    );
  }

  calculateBackoff(attempt) {
    // Exponential backoff with jitter
    const exponentialDelay = this.baseDelay * Math.pow(2, attempt);
    const jitter = Math.random() * 1000;
    return exponentialDelay + jitter;
  }

  enhanceError(error) {
    // Add context to errors
    return {
      ...error,
      timestamp: new Date().toISOString(),
      context: {
        endpoint: error.config?.url,
        method: error.config?.method,
        requestId: error.config?.headers?.['X-Request-ID']
      },
      suggestion: this.getErrorSuggestion(error)
    };
  }

  getErrorSuggestion(error) {
    const suggestions = {
      401: 'Check API credentials',
      403: 'Verify permissions/scopes',
      404: 'Check endpoint URL',
      429: 'Implement rate limiting',
      500: 'API server error - contact support',
      ECONNREFUSED: 'Check if API is accessible'
    };

    return suggestions[error.code] || suggestions[error.statusCode] || 'Unknown error';
  }

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

3. Rate Limiting

class RateLimitedClient {
  constructor(requestsPerSecond = 10) {
    this.queue = [];
    this.processing = false;
    this.interval = 1000 / requestsPerSecond;
    this.lastRequestTime = 0;
  }

  async request(fn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ fn, resolve, reject });
      this.process();
    });
  }

  async process() {
    if (this.processing || this.queue.length === 0) return;

    this.processing = true;

    while (this.queue.length > 0) {
      const timeSinceLastRequest = Date.now() - this.lastRequestTime;
      const timeToWait = Math.max(0, this.interval - timeSinceLastRequest);

      if (timeToWait > 0) {
        await this.sleep(timeToWait);
      }

      const { fn, resolve, reject } = this.queue.shift();

      try {
        const result = await fn();
        resolve(result);
      } catch (error) {
        reject(error);
      }

      this.lastRequestTime = Date.now();
    }

    this.processing = false;
  }

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

// Usage
const rateLimited = new RateLimitedClient(5);  // 5 requests per second
await rateLimited.request(() => fetch('/api/endpoint'));

4. Webhook Handling

class WebhookHandler {
  constructor(secret) {
    this.secret = secret;
    this.handlers = new Map();
  }

  // Register webhook endpoint
  register(app) {
    app.post('/webhooks/:provider', express.raw({ type: '*/*' }),
      (req, res) => this.handle(req, res)
    );
  }

  async handle(req, res) {
    try {
      // 1. Verify signature
      if (!this.verifySignature(req)) {
        return res.status(401).json({ error: 'Invalid signature' });
      }

      // 2. Parse event
      const event = JSON.parse(req.body);

      // 3. Idempotency check
      if (await this.isDuplicate(event.id)) {
        return res.status(200).json({ status: 'already_processed' });
      }

      // 4. Process event
      await this.processEvent(event);

      // 5. Store event ID
      await this.storeEventId(event.id);

      res.status(200).json({ status: 'success' });

    } catch (error) {
      console.error('Webhook error:', error);
      res.status(500).json({ error: 'Processing failed' });
    }
  }

  verifySignature(req) {
    const crypto = require('crypto');

    // Example: Stripe signature verification
    const signature = req.headers['stripe-signature'];
    const payload = req.body;

    const hmac = crypto.createHmac('sha256', this.secret);
    hmac.update(payload);
    const expectedSignature = hmac.digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );
  }

  async isDuplicate(eventId) {
    // Check if event already processed
    // Use database or cache
    return await redis.exists(`webhook:${eventId}`);
  }

  async storeEventId(eventId) {
    // Store with TTL to prevent infinite growth
    await redis.setex(`webhook:${eventId}`, 86400, '1');  // 24 hours
  }

  async processEvent(event) {
    const handler = this.handlers.get(event.type);
    if (!handler) {
      console.warn(`No handler for event type: ${event.type}`);
      return;
    }

    await handler(event);
  }

  on(eventType, handler) {
    this.handlers.set(eventType, handler);
  }
}

// Usage
const webhook = new WebhookHandler(process.env.WEBHOOK_SECRET);
webhook.on('payment.succeeded', async (event) => {
  await updateOrder(event.data.order_id, 'paid');
});

5. Response Caching

class CachedAPIClient {
  constructor(ttl = 300000) {  // 5 minutes default
    this.cache = new Map();
    this.ttl = ttl;
  }

  async get(endpoint, options = {}) {
    const cacheKey = this.getCacheKey(endpoint, options);

    // Check cache
    const cached = this.cache.get(cacheKey);
    if (cached && cached.expires > Date.now()) {
      return cached.data;
    }

    // Fetch fresh data
    const data = await this.fetch(endpoint, options);

    // Cache response
    this.cache.set(cacheKey, {
      data,
      expires: Date.now() + this.ttl
    });

    // Cleanup old entries
    this.cleanup();

    return data;
  }

  getCacheKey(endpoint, options) {
    return `${endpoint}:${JSON.stringify(options)}`;
  }

  cleanup() {
    const now = Date.now();
    for (const [key, value] of this.cache.entries()) {
      if (value.expires < now) {
        this.cache.delete(key);
      }
    }
  }

  invalidate(pattern) {
    // Invalidate cache entries matching pattern
    for (const key of this.cache.keys()) {
      if (key.includes(pattern)) {
        this.cache.delete(key);
      }
    }
  }
}

🔄 Common API Integrations

Stripe Payment Integration

class StripeIntegration {
  constructor(apiKey) {
    this.stripe = require('stripe')(apiKey);
  }

  async createPaymentIntent(amount, currency = 'usd') {
    try {
      const paymentIntent = await this.stripe.paymentIntents.create({
        amount: amount * 100,  // Convert to cents
        currency,
        automatic_payment_methods: {
          enabled: true,
        },
        metadata: {
          integration_version: '1.0.0'
        }
      });

      return {
        clientSecret: paymentIntent.client_secret,
        paymentIntentId: paymentIntent.id
      };
    } catch (error) {
      throw this.handleStripeError(error);
    }
  }

  handleStripeError(error) {
    const errors = {
      card_declined: 'Your card was declined',
      expired_card: 'Your card has expired',
      insufficient_funds: 'Insufficient funds',
      processing_error: 'Processing error, please try again'
    };

    return {
      message: errors[error.code] || 'Payment failed',
      code: error.code,
      type: error.type
    };
  }
}

OpenAI API Integration

class OpenAIIntegration {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseURL = 'https://api.openai.com/v1';
  }

  async complete(prompt, options = {}) {
    const response = await fetch(`${this.baseURL}/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`
      },
      body: JSON.stringify({
        model: options.model || 'gpt-3.5-turbo',
        messages: [{ role: 'user', content: prompt }],
        max_tokens: options.maxTokens || 150,
        temperature: options.temperature || 0.7
      })
    });

    if (!response.ok) {
      throw new Error(`OpenAI API error: ${response.status}`);
    }

    const data = await response.json();
    return data.choices[0].message.content;
  }
}

📊 API Integration Testing

// Mock API for testing
class MockAPIClient {
  constructor() {
    this.responses = new Map();
    this.calls = [];
  }

  mockResponse(endpoint, response, options = {}) {
    const key = `${options.method || 'GET'}:${endpoint}`;
    this.responses.set(key, {
      response,
      delay: options.delay || 0,
      error: options.error
    });
  }

  async request(endpoint, options = {}) {
    const key = `${options.method || 'GET'}:${endpoint}`;
    const mock = this.responses.get(key);

    this.calls.push({ endpoint, options, timestamp: Date.now() });

    if (!mock) {
      throw new Error(`No mock for ${key}`);
    }

    if (mock.delay) {
      await this.sleep(mock.delay);
    }

    if (mock.error) {
      throw mock.error;
    }

    return mock.response;
  }

  getCallHistory() {
    return this.calls;
  }

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

// Usage in tests
describe('API Integration', () => {
  let client;

  beforeEach(() => {
    client = new MockAPIClient();
    client.mockResponse('/users', { users: [] });
  });

  test('should fetch users', async () => {
    const result = await client.request('/users');
    expect(result.users).toEqual([]);
    expect(client.getCallHistory()).toHaveLength(1);
  });
});

🎯 Integration Best Practices

Environment Configuration

// config/api.js
const config = {
  development: {
    baseURL: 'https://sandbox.api.example.com',
    timeout: 30000,
    retries: 3
  },
  production: {
    baseURL: 'https://api.example.com',
    timeout: 10000,
    retries: 5
  }
};

export default config[process.env.NODE_ENV || 'development'];

Logging & Monitoring

class MonitoredAPIClient {
  async request(endpoint, options) {
    const startTime = Date.now();
    const requestId = this.generateRequestId();

    try {
      console.log(`[${requestId}] API Request: ${endpoint}`);

      const response = await this.makeRequest(endpoint, options);

      const duration = Date.now() - startTime;
      console.log(`[${requestId}] Success: ${duration}ms`);

      // Send metrics
      this.metrics.record('api.request', {
        endpoint,
        duration,
        status: response.status,
        success: true
      });

      return response;

    } catch (error) {
      const duration = Date.now() - startTime;
      console.error(`[${requestId}] Error: ${error.message} (${duration}ms)`);

      // Send error metrics
      this.metrics.record('api.error', {
        endpoint,
        duration,
        error: error.code,
        success: false
      });

      throw error;
    }
  }

  generateRequestId() {
    return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

💡 Quick Reference

// Universal API client initialization
const api = new APIClient({
  baseURL: process.env.API_BASE_URL,
  headers: {
    'X-API-Version': '2.0'
  },
  timeout: 10000,
  retries: 3,
  cache: true,
  rateLimit: 10  // requests per second
});

// Make requests
const users = await api.get('/users');
const user = await api.post('/users', { name: 'John' });
const updated = await api.put('/users/1', { name: 'Jane' });
const deleted = await api.delete('/users/1');

// Handle webhooks
api.onWebhook('user.created', async (event) => {
  await processNewUser(event.data);
});

Remember: Good API integration is invisible to users but crucial for developers! 🔌