Agent skill
api-integration
Master third-party API integration in ANY language with best practices and patterns. Use when connecting to external services, handling OAuth, or implementing webhooks.
Install this agent skill to your Project
npx add-skill https://github.com/j0KZ/mcp-agents/tree/main/docs/universal-skills/api-integration
SKILL.md
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! 🔌
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
debug-detective
Systematic debugging approach for ANY codebase, ANY language, ANY bug type
tool-discovery
Guide for discovering and using @j0kz MCP tools efficiently through search, filtering, and progressive exploration. Use when exploring available capabilities, finding the right tool for a task, or ...
project-standardization
Guides correct usage of @j0kz/mcp-agents standardization and automation scripts including version.json single source of truth, test count automation, URL casing rules, and critical workflow pattern...
api-integration
Master third-party API integration in ANY language with best practices and patterns
documentation-generation
Documentation generation and maintenance patterns including README structure, CHANGELOG management, API documentation, wiki synchronization, and badge updates. Use when creating package docs, updat...
competitive-ads-extractor
Extracts and analyzes competitor ads from ad libraries (Facebook, LinkedIn, TikTok, Google). Use when researching competitor messaging, creative patterns, campaign strategies, or ad inspiration. Ch...
Didn't find tool you were looking for?