API Integration Prompt Templates
AI prompt templates for API integrations. Connect and work with external services and APIs.
Overview
Integrating third-party APIs connects your application to external services, payments, email, analytics, and more. These prompts help you build solid integrations that handle authentication, rate limits, errors, and data transformation. The goal is integrations that work reliably in production, not just in happy-path testing.
Best Practices
Specify the API you're integrating (with version if relevant) for accurate implementation details
Include your authentication method and any SDK/library preferences
Describe your error handling requirements, retries, fallbacks, user notifications
Mention rate limits and how you plan to handle them
Include data transformation needs, how API data maps to your domain models
Prompt Templates
1. API Client Implementation
Create an API client for this service: **API:** - Service: [API NAME] - Base URL: [ENDPOINT] - Documentation: [LINK IF AVAILABLE] **Authentication:** - Method: [API KEY / OAUTH / JWT / etc.] - Token location: [HEADER / QUERY / BODY] **Operations needed:** - [OPERATION 1 - e.g. list users] - [OPERATION 2 - e.g. create payment] **Requirements:** - Language: [LANGUAGE] - Error handling: [RETRY STRATEGY] - Rate limiting: [LIMITS IF KNOWN] - Logging: [WHAT TO LOG] **Provide:** 1. API client class/module 2. Request/response types 3. Error handling 4. Usage examples
API: Stripe Payments API Auth: API key in Authorization header (Bearer) Operations: Create customer, create payment intent, list payments Language: TypeScript Error handling: Retry on 5xx and rate limits, throw on 4xx Rate limiting: 100 requests/second Logging: Log request/response for debugging
## Stripe API Client
```typescript
// stripe-client.ts
import Stripe from 'stripe';
// Types
export interface CreateCustomerInput {
email: string;
name?: string;
metadata?: Record<string, string>;
}
export interface CreatePaymentIntentInput {
amount: number; // in cents
currency: string;
customerId: string;
metadata?: Record<string, string>;
}
export interface PaymentListOptions {
customerId?: string;
limit?: number;
startingAfter?: string;
}
// Client configuration
interface StripeClientConfig {
apiKey: string;
maxRetries?: number;
timeout?: number;
logRequests?: boolean;
}
export class StripeClient {
private stripe: Stripe;
private config: Required<StripeClientConfig>;
private logger: Console;
constructor(config: StripeClientConfig) {
this.config = {
maxRetries: 3,
timeout: 30000,
logRequests: false...config,
};
this.stripe = new Stripe(config.apiKey, {
apiVersion: '2023-10-16',
maxNetworkRetries: this.config.maxRetries,
timeout: this.config.timeout,
});
this.logger = console;
}
// ==================== CUSTOMERS ====================
async createCustomer(input: CreateCustomerInput): Promise<Stripe.Customer> {
this.logRequest('createCustomer', input);
try {
const customer = await this.stripe.customers.create({
email: input.email,
name: input.name,
metadata: input.metadata,
});
this.logResponse('createCustomer', customer);
return customer;
} catch (error) {
throw this.handleError('createCustomer', error);
}
}
async getCustomer(customerId: string): Promise<Stripe.Customer | null> {
this.logRequest('getCustomer', { customerId });
try {
const customer = await this.stripe.customers.retrieve(customerId);
if (customer.deleted) {
return null;
}
this.logResponse('getCustomer', customer);
return customer as Stripe.Customer;
} catch (error) {
if (this.isNotFoundError(error)) {
return null;
}
throw this.handleError('getCustomer', error);
}
}
// ==================== PAYMENT INTENTS ====================
async createPaymentIntent(
input: CreatePaymentIntentInput
): Promise<Stripe.PaymentIntent> {
this.logRequest('createPaymentIntent', input);
try {
const paymentIntent = await this.stripe.paymentIntents.create({
amount: input.amount,
currency: input.currency,
customer: input.customerId,
metadata: input.metadata,
automatic_payment_methods: {
enabled: true,
},
});
this.logResponse('createPaymentIntent', paymentIntent);
return paymentIntent;
} catch (error) {
throw this.handleError('createPaymentIntent', error);
}
}
async confirmPaymentIntent(
paymentIntentId: string,
paymentMethodId: string
): Promise<Stripe.PaymentIntent> {
this.logRequest('confirmPaymentIntent', { paymentIntentId, paymentMethodId });
try {
const paymentIntent = await this.stripe.paymentIntents.confirm(
paymentIntentId,
{
payment_method: paymentMethodId,
}
);
this.logResponse('confirmPaymentIntent', paymentIntent);
return paymentIntent;
} catch (error) {
throw this.handleError('confirmPaymentIntent', error);
}
}
// ==================== PAYMENTS LIST ====================
async listPayments(
options: PaymentListOptions = {}
): Promise<Stripe.ApiList<Stripe.PaymentIntent>> {
this.logRequest('listPayments', options);
try {
const payments = await this.stripe.paymentIntents.list({
customer: options.customerId,
limit: options.limit || 10,
starting_after: options.startingAfter,
});
this.logResponse('listPayments', { count: payments.data.length });
return payments;
} catch (error) {
throw this.handleError('listPayments', error);
}
}
// ==================== ERROR HANDLING ====================
private handleError(operation: string, error: unknown): StripeApiError {
if (error instanceof Stripe.errors.StripeError) {
const apiError = new StripeApiError(
error.message,
error.code || 'unknown',
error.statusCode || 500,
operation
);
this.logger.error('Stripe API error', {
operation,
code: error.code,
statusCode: error.statusCode,
message: error.message,
requestId: error.requestId,
});
return apiError;
}
// Unknown error
this.logger.error('Unknown Stripe error', { operation, error });
return new StripeApiError(
'An unexpected error occurred',
'unknown_error',
500,
operation
);
}
private isNotFoundError(error: unknown): boolean {
return (
error instanceof Stripe.errors.StripeError &&
error.statusCode === 404
);
}
// ==================== LOGGING ====================
private logRequest(operation: string, params: unknown): void {
if (this.config.logRequests) {
this.logger.debug(`Stripe ${operation} request`, {
operation,
params: this.sanitizeForLogging(params),
});
}
}
private logResponse(operation: string, response: unknown): void {
if (this.config.logRequests) {
this.logger.debug(`Stripe ${operation} response`, {
operation,
response: this.sanitizeForLogging(response),
});
}
}
private sanitizeForLogging(data: unknown): unknown {
// Remove sensitive fields from logs
if (typeof data === 'object' && data !== null) {
const sanitized = {...data } as Record<string, unknown>;
delete sanitized.client_secret;
return sanitized;
}
return data;
}
}
// Custom error class
export class StripeApiError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number,
public operation: string
) {
super(message);
this.name = 'StripeApiError';
}
isRetryable(): boolean {
return this.statusCode >= 500 || this.code === 'rate_limit';
}
}
```
### Usage Example
```typescript
import { StripeClient, StripeApiError } from './stripe-client';
const stripe = new StripeClient({
apiKey: process.env.STRIPE_SECRET_KEY!,
logRequests: process.env.NODE_ENV === 'development',
});
// Create customer and payment
async function processPayment(email: string, amount: number) {
try {
// Create or get customer
const customer = await stripe.createCustomer({ email });
// Create payment intent
const paymentIntent = await stripe.createPaymentIntent({
amount: amount * 100, // Convert to cents
currency: 'usd',
customerId: customer.id,
metadata: { orderId: 'order_123' },
});
return {
clientSecret: paymentIntent.client_secret,
paymentIntentId: paymentIntent.id,
};
} catch (error) {
if (error instanceof StripeApiError) {
if (error.code === 'card_declined') {
throw new PaymentDeclinedError(error.message);
}
}
throw error;
}
}
```When integrating with external APIs, building SDK wrappers, or creating reusable API clients
- •Use the official SDK when available, it handles auth, retries, and serialization correctly
- •Wrap SDK errors in your own error types for consistent error handling across your app
2. Webhook Handler
Create a webhook handler for this service: **Service:** [API NAME] **Events to handle:** - [EVENT 1 - e.g. payment.completed] - [EVENT 2 - e.g. subscription.cancelled] **Verification:** - Method: [SIGNATURE / SECRET / NONE] - Header: [WHERE SIGNATURE IS] **Requirements:** - Framework: [EXPRESS / FASTAPI / etc.] - Idempotency: [HOW TO HANDLE DUPLICATES] - Error handling: [WHAT HAPPENS ON FAILURE] **Provide:** 1. Webhook endpoint 2. Signature verification 3. Event routing 4. Idempotency handling 5. Error responses
Service: Stripe Events: payment_intent.succeeded, payment_intent.failed, customer.subscription.deleted Verification: Signature in Stripe-Signature header Framework: Express.js Idempotency: Store processed event IDs in Redis (24h TTL) Error handling: Return 200 to Stripe, handle errors async
## Stripe Webhook Handler
```typescript
// webhook-handler.ts
import express, { Request, Response } from 'express';
import Stripe from 'stripe';
import { Redis } from 'ioredis';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const redis = new Redis(process.env.REDIS_URL!);
const WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;
// Important: Use raw body for signature verification
const router = express.Router();
router.post(
'/webhooks/stripe',
express.raw({ type: 'application/json' }),
async (req: Request, res: Response) => {
const signature = req.headers['stripe-signature'] as string;
// 1. Verify signature
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
WEBHOOK_SECRET
);
} catch (err) {
console.error('Webhook signature verification failed:', err);
return res.status(400).send('Invalid signature');
}
// 2. Check idempotency (prevent duplicate processing)
const idempotencyKey = `webhook:${event.id}`;
const alreadyProcessed = await redis.get(idempotencyKey);
if (alreadyProcessed) {
console.log(`Webhook ${event.id} already processed, skipping`);
return res.status(200).json({ received: true, duplicate: true });
}
// 3. Mark as processing (with short TTL in case of crash)
await redis.setex(idempotencyKey, 60, 'processing');
// 4. Handle event (async - don't block response)
// Always return 200 to Stripe quickly, process async
handleWebhookEvent(event)
.then(async () => {
// Mark as completed with 24h TTL
await redis.setex(idempotencyKey, 86400, 'completed');
})
.catch(async (error) => {
console.error(`Error processing webhook ${event.id}:`, error);
// Remove idempotency key so it can be retried
await redis.del(idempotencyKey);
// Alert on-call
await alertWebhookFailure(event, error);
});
// 5. Respond immediately
return res.status(200).json({ received: true });
}
);
// Event handler router
async function handleWebhookEvent(event: Stripe.Event): Promise<void> {
console.log(`Processing webhook: ${event.type} (${event.id})`);
switch (event.type) {
case 'payment_intent.succeeded':
await handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent);
break;
case 'payment_intent.payment_failed':
await handlePaymentFailed(event.data.object as Stripe.PaymentIntent);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCancelled(event.data.object as Stripe.Subscription);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
// Individual event handlers
async function handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent): Promise<void> {
const orderId = paymentIntent.metadata.orderId;
if (!orderId) {
console.warn('Payment succeeded but no orderId in metadata');
return;
}
// Update order status
await db.orders.update({
where: { id: orderId },
data: {
status: 'paid',
stripePaymentIntentId: paymentIntent.id,
paidAt: new Date(),
},
});
// Send confirmation email
await emailService.sendOrderConfirmation(orderId);
// Trigger fulfillment
await fulfillmentService.initiate(orderId);
console.log(`Order ${orderId} marked as paid`);
}
async function handlePaymentFailed(paymentIntent: Stripe.PaymentIntent): Promise<void> {
const orderId = paymentIntent.metadata.orderId;
if (!orderId) return;
const lastError = paymentIntent.last_payment_error;
await db.orders.update({
where: { id: orderId },
data: {
status: 'payment_failed',
paymentFailureReason: lastError?.message || 'Unknown error',
},
});
// Notify customer
await emailService.sendPaymentFailedEmail(orderId, lastError?.message);
console.log(`Order ${orderId} payment failed: ${lastError?.message}`);
}
async function handleSubscriptionCancelled(subscription: Stripe.Subscription): Promise<void> {
const userId = subscription.metadata.userId;
if (!userId) {
console.warn('Subscription cancelled but no userId in metadata');
return;
}
// Update user subscription status
await db.users.update({
where: { id: userId },
data: {
subscriptionStatus: 'cancelled',
subscriptionEndDate: new Date(subscription.current_period_end * 1000),
},
});
// Send cancellation email
await emailService.sendSubscriptionCancelledEmail(userId);
console.log(`User ${userId} subscription cancelled`);
}
// Alerting for failed webhooks
async function alertWebhookFailure(event: Stripe.Event, error: Error): Promise<void> {
await slack.send({
channel: '#alerts',
text: `⚠️ Webhook processing failed\nEvent: ${event.type}\nID: ${event.id}\nError: ${error.message}`,
});
}
export default router;
```
### Express App Setup
```typescript
// app.ts
import express from 'express';
import webhookRouter from './webhook-handler';
const app = express();
// IMPORTANT: Webhook route must come BEFORE express.json() middleware
// because it needs the raw body for signature verification
app.use('/api', webhookRouter);
// Regular JSON parsing for other routes
app.use(express.json());
//... other routes
```
### Testing Webhooks Locally
```bash
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to local server
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Trigger test events
stripe trigger payment_intent.succeeded
```When receiving events from external services via webhooks, handling async notifications
- •Always return 200 quickly and process async, webhook providers retry on timeouts
- •Store processed event IDs to handle duplicate deliveries (webhooks are at-least-once)
3. OAuth Integration
Implement OAuth authentication flow for this service: **Provider:** [GOOGLE / GITHUB / CUSTOM] **Flow type:** [AUTHORIZATION CODE / IMPLICIT / CLIENT CREDENTIALS] **Configuration:** - Client ID: [HOW TO OBTAIN] - Scopes needed: [PERMISSIONS REQUIRED] - Callback URL: [YOUR REDIRECT URI] **Requirements:** - Framework: [EXPRESS / NEXT.JS / etc.] - Token storage: [SESSION / DATABASE / etc.] - Refresh handling: [HOW TO REFRESH TOKENS] **Provide:** 1. OAuth flow implementation 2. Token management 3. Protected route middleware 4. User info fetching
Provider: GitHub Flow: Authorization Code with PKCE Scopes: read:user, user:email Framework: Next.js 14 (App Router) Token storage: HTTP-only cookies + database Refresh: GitHub tokens don't expire, but need to handle revocation
## GitHub OAuth Implementation (Next.js 14)
### Environment Setup
```bash
#.env.local
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=random_secret_at_least_32_chars
```
### Auth Configuration
```typescript
// lib/auth.ts
import { NextAuthOptions } from 'next-auth';
import GithubProvider from 'next-auth/providers/github';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from './prisma';
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
authorization: {
params: {
scope: 'read:user user:email',
},
},
}),
],
callbacks: {
async jwt({ token, account, profile }) {
// Initial sign in
if (account) {
token.accessToken = account.access_token;
token.githubId = profile?.id;
}
return token;
},
async session({ session, token }) {
// Add access token to session for API calls
session.accessToken = token.accessToken as string;
session.user.githubId = token.githubId as number;
return session;
},
},
pages: {
signIn: '/login',
error: '/auth/error',
},
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
};
```
### API Route Handler
```typescript
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import { authOptions } from '@/lib/auth';
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
```
### Session Types
```typescript
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface Session {
accessToken: string;
user: {
githubId: number;
} & DefaultSession['user'];
}
}
declare module 'next-auth/jwt' {
interface JWT {
accessToken?: string;
githubId?: number;
}
}
```
### Server Component Usage
```typescript
// app/dashboard/page.tsx
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await getServerSession(authOptions);
if (!session) {
redirect('/login');
}
// Fetch user's GitHub repos using access token
const repos = await fetchGitHubRepos(session.accessToken);
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<p>Your GitHub repos:</p>
<ul>
{repos.map((repo) => (
<li key={repo.id}>{repo.name}</li>
))}
</ul>
</div>
);
}
async function fetchGitHubRepos(accessToken: string) {
const response = await fetch('https://api.github.com/user/repos', {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/vnd.github.v3+json',
},
next: { revalidate: 60 }, // Cache for 60 seconds
});
if (!response.ok) {
throw new Error('Failed to fetch repos');
}
return response.json();
}
```
### Client Component Usage
```typescript
// components/LoginButton.tsx
'use client';
import { signIn, signOut, useSession } from 'next-auth/react';
export function LoginButton() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <button disabled>Loading...</button>;
}
if (session) {
return (
<div className="flex items-center gap-4">
<img
src={session.user.image || ''}
alt={session.user.name || ''}
className="w-8 h-8 rounded-full"
/>
<span>{session.user.name}</span>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
return (
<button onClick={() => signIn('github')}>
Sign in with GitHub
</button>
);
}
```
### Protected API Route
```typescript
// app/api/user/repos/route.ts
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { NextResponse } from 'next/server';
export async function GET() {
const session = await getServerSession(authOptions);
if (!session?.accessToken) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const response = await fetch('https://api.github.com/user/repos', {
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
});
if (!response.ok) {
if (response.status === 401) {
// Token might be revoked
return NextResponse.json(
{ error: 'GitHub token invalid' },
{ status: 401 }
);
}
throw new Error('GitHub API error');
}
const repos = await response.json();
return NextResponse.json(repos);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch repos' },
{ status: 500 }
);
}
}
```
### Token Revocation Handling
```typescript
// lib/github.ts
export async function checkTokenValidity(accessToken: string): Promise<boolean> {
const response = await fetch('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.ok;
}
// Middleware to check token on each request
export async function validateGitHubToken(
accessToken: string
): Promise<boolean> {
const isValid = await checkTokenValidity(accessToken);
if (!isValid) {
// Token was revoked, sign user out
// (handle this in your session callback)
}
return isValid;
}
```When implementing 'Sign in with X' functionality, accessing user data from external services
- •Use NextAuth.js or similar libraries, OAuth has many security pitfalls if implemented from scratch
- •Always verify tokens haven't been revoked before making API calls with them
4. Rate Limit Handler
Implement rate limit handling for this API: **API:** [SERVICE NAME] **Rate limits:** - [LIMIT 1 - e.g. 100 requests/minute] - [LIMIT 2 - e.g. 1000 requests/day] **Headers returned:** - [HEADER 1 - e.g. X-RateLimit-Remaining] - [HEADER 2 - e.g. Retry-After] **Requirements:** - Respect limits proactively: [YES / NO] - Queuing strategy: [FIFO / PRIORITY / etc.] - Distributed: [SINGLE INSTANCE / MULTIPLE INSTANCES] **Provide:** 1. Rate limiter implementation 2. Request queue/throttling 3. Retry logic 4. Monitoring/alerting
API: OpenAI API Limits: 60 requests/minute, 90K tokens/minute Headers: x-ratelimit-remaining-requests, x-ratelimit-reset-requests Requirements: Proactive limiting, priority queue (paid users first), distributed across 3 servers Language: TypeScript/Node.js
## OpenAI Rate Limit Handler
```typescript
// rate-limiter.ts
import { Redis } from 'ioredis';
import { EventEmitter } from 'events';
interface RateLimitConfig {
requestsPerMinute: number;
tokensPerMinute: number;
redis: Redis;
instanceId: string;
}
interface QueuedRequest {
id: string;
priority: number; // Higher = more priority
estimatedTokens: number;
execute: () => Promise<any>;
resolve: (value: any) => void;
reject: (error: Error) => void;
enqueuedAt: number;
}
export class OpenAIRateLimiter extends EventEmitter {
private config: RateLimitConfig;
private queue: QueuedRequest[] = [];
private processing = false;
// Redis keys
private readonly REQUEST_COUNT_KEY = 'openai:requests';
private readonly TOKEN_COUNT_KEY = 'openai:tokens';
constructor(config: RateLimitConfig) {
super();
this.config = {
requestsPerMinute: 60,
tokensPerMinute: 90000...config,
};
// Start queue processor
this.processQueue();
}
/**
* Queue a request to be executed respecting rate limits
*/
async execute<T>(
fn: () => Promise<T>,
options: {
priority?: number;
estimatedTokens?: number;
userId?: string;
} = {}
): Promise<T> {
const { priority = 5, estimatedTokens = 1000 } = options;
return new Promise((resolve, reject) => {
const request: QueuedRequest = {
id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
priority,
estimatedTokens,
execute: fn,
resolve,
reject,
enqueuedAt: Date.now(),
};
// Insert by priority (higher priority = earlier in queue)
const insertIndex = this.queue.findIndex(r => r.priority < priority);
if (insertIndex === -1) {
this.queue.push(request);
} else {
this.queue.splice(insertIndex, 0, request);
}
this.emit('queued', { queueLength: this.queue.length, requestId: request.id });
});
}
/**
* Check if we can make a request now
*/
private async canMakeRequest(estimatedTokens: number): Promise<boolean> {
const multi = this.config.redis.multi();
// Get current counts (sliding window)
const now = Date.now();
const windowStart = now - 60000; // 1 minute ago
multi.zremrangebyscore(this.REQUEST_COUNT_KEY, '-inf', windowStart);
multi.zcard(this.REQUEST_COUNT_KEY);
multi.zremrangebyscore(this.TOKEN_COUNT_KEY, '-inf', windowStart);
multi.zrangebyscore(this.TOKEN_COUNT_KEY, windowStart, '+inf', 'WITHSCORES');
const results = await multi.exec();
const requestCount = results![1][1] as number;
const tokenEntries = results![3][1] as string[];
// Calculate token count from entries
let tokenCount = 0;
for (let i = 0; i < tokenEntries.length; i += 2) {
tokenCount += parseInt(tokenEntries[i]);
}
const canMakeRequest =
requestCount < this.config.requestsPerMinute &&
tokenCount + estimatedTokens <= this.config.tokensPerMinute;
if (!canMakeRequest) {
this.emit('rate-limited', {
requestCount,
tokenCount,
requestLimit: this.config.requestsPerMinute,
tokenLimit: this.config.tokensPerMinute,
});
}
return canMakeRequest;
}
/**
* Record a completed request
*/
private async recordRequest(tokensUsed: number): Promise<void> {
const now = Date.now();
const multi = this.config.redis.multi();
// Add to request count
multi.zadd(this.REQUEST_COUNT_KEY, now, `${this.config.instanceId}:${now}`);
multi.expire(this.REQUEST_COUNT_KEY, 120); // 2 min expiry
// Add to token count
multi.zadd(this.TOKEN_COUNT_KEY, now, `${tokensUsed}:${now}`);
multi.expire(this.TOKEN_COUNT_KEY, 120);
await multi.exec();
}
/**
* Process queued requests
*/
private async processQueue(): Promise<void> {
while (true) {
if (this.queue.length === 0) {
await this.sleep(100);
continue;
}
const request = this.queue[0];
// Check if we can make request
const canProceed = await this.canMakeRequest(request.estimatedTokens);
if (!canProceed) {
// Wait and retry
await this.sleep(1000);
continue;
}
// Remove from queue
this.queue.shift();
// Execute request
try {
const result = await this.executeWithRetry(request);
request.resolve(result);
} catch (error) {
request.reject(error as Error);
}
}
}
/**
* Execute request with retry on rate limit
*/
private async executeWithRetry(request: QueuedRequest, attempt = 1): Promise<any> {
try {
const result = await request.execute();
// Extract actual token usage from response
const tokensUsed = result.usage?.total_tokens || request.estimatedTokens;
await this.recordRequest(tokensUsed);
this.emit('success', {
requestId: request.id,
tokensUsed,
waitTime: Date.now() - request.enqueuedAt,
});
return result;
} catch (error: any) {
// Handle rate limit response
if (error.status === 429) {
const retryAfter = this.parseRetryAfter(error.headers);
this.emit('rate-limit-hit', {
requestId: request.id,
retryAfter,
attempt,
});
if (attempt < 3) {
await this.sleep(retryAfter * 1000);
return this.executeWithRetry(request, attempt + 1);
}
}
throw error;
}
}
private parseRetryAfter(headers: any): number {
// Try Retry-After header first
const retryAfter = headers?.['retry-after'];
if (retryAfter) {
return parseInt(retryAfter) || 60;
}
// Fall back to reset headers
const resetRequests = headers?.['x-ratelimit-reset-requests'];
if (resetRequests) {
// Parse "1m30s" format
const match = resetRequests.match(/(\d+)m?(\d+)?s?/);
if (match) {
return (parseInt(match[1]) || 0) * 60 + (parseInt(match[2]) || 0);
}
}
return 60; // Default 1 minute
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Get current queue stats
*/
getStats(): { queueLength: number; oldestWaitTime: number } {
return {
queueLength: this.queue.length,
oldestWaitTime: this.queue.length > 0
? Date.now() - this.queue[0].enqueuedAt
: 0,
};
}
}
```
### Usage
```typescript
import OpenAI from 'openai';
import { OpenAIRateLimiter } from './rate-limiter';
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const rateLimiter = new OpenAIRateLimiter({
requestsPerMinute: 60,
tokensPerMinute: 90000,
redis,
instanceId: process.env.INSTANCE_ID || 'default',
});
// Monitor rate limiter
rateLimiter.on('rate-limited', (data) => {
console.log('Approaching rate limit:', data);
});
rateLimiter.on('rate-limit-hit', (data) => {
console.warn('Rate limit hit, retrying:', data);
});
// Use in application
export async function generateCompletion(
prompt: string,
userId: string,
isPremium: boolean
) {
return rateLimiter.execute(
() => openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
}),
{
priority: isPremium ? 10 : 5, // Premium users get priority
estimatedTokens: prompt.length / 4 + 500, // Rough estimate
userId,
}
);
}
```When integrating with APIs that have rate limits, especially when multiple instances share limits
- •Use Redis for distributed rate limiting, in-memory counters don't work across instances
- •Estimate tokens conservatively, then track actual usage to refine estimates
Common Mistakes to Avoid
Not handling rate limits, your integration breaks under load or gets blocked
Ignoring webhook signature verification, security vulnerability for spoofed events
Storing API keys in code instead of environment variables, security risk
Frequently Asked Questions
Integrating third-party APIs connects your application to external services, payments, email, analytics, and more. These prompts help you build solid integrations that handle authentication, rate limits, errors, and data transformation. The goal is integrations that work reliably in production, not just in happy-path testing.
Related Templates
Code Review Prompt Templates
AI prompt templates for thorough code reviews. Get comprehensive feedback on code quality, security, and best practices.
Debugging Prompt Templates
AI prompt templates for debugging code. Identify issues, understand errors, and find solutions faster.
Code Documentation Prompt Templates
AI prompt templates for writing code documentation. Create clear comments, READMEs, and API docs.
Have your own prompt to optimize?