Advanced API Integration Patterns for Claude Code
This comprehensive guide explores advanced API integration patterns for Claude Code, covering GraphQL, WebSocket/SSE, gRPC, API gateways, rate limiting strategies, and event-driven architectures specifically designed for AI/LLM applications.
Overview
Modern AI applications require sophisticated API integration patterns to handle:
- Real-time streaming responses
- Complex microservice orchestration
- High-throughput data processing
- Multi-protocol communication
- Scalable event-driven architectures
The patterns and code examples in this guide represent best practices for robust API design. These can be implemented efficiently using our project’s templating and scaffolding tools. For more details, see the AI-Powered Code Generation and Template Patterns guide.
Table of Contents
- GraphQL Integration with Claude Code
- WebSocket and Server-Sent Events (SSE) Patterns
- gRPC Service Integration
- API Gateway Patterns for Claude Services
- Rate Limiting and Quota Management Strategies
- Webhook Handling and Event-Driven Architectures
API Pattern Selection Guide
| Feature | GraphQL | WebSocket/SSE | gRPC |
|---|---|---|---|
| Primary Use Case | Flexible data fetching, complex queries | Real-time, bidirectional communication | High-performance, low-latency RPC |
| Data Model | Typed schema, graph-based | Message-based, unstructured | Strongly-typed contracts (Protobuf) |
| Streaming | Subscriptions (server-to-client) | Full bidirectional streaming | Full bidirectional streaming |
| Overhead | Medium (text-based, JSON) | Low (persistent connection) | Very Low (binary, HTTP/2) |
| Best for LLMs | Chained tool calls, complex data retrieval | Streaming token-by-token responses | Internal microservice communication |
| Tooling | Excellent (Apollo, Relay) | Good (Socket.io, ws) | Excellent (gRPC-web, BloomRPC) |
1. GraphQL Integration with Claude Code
Why GraphQL for AI Applications?
GraphQL provides several advantages for AI-powered applications:
- Efficient data fetching: Request only needed fields
- Type safety: Strong typing for AI model inputs/outputs
- Real-time subscriptions: Stream AI responses
- Batching: Combine multiple AI requests
Basic GraphQL Schema for Claude Code
// schema.graphql
type Query {
claudePrompt(input: ClaudePromptInput!): ClaudeResponse!
claudeBatch(inputs: [ClaudePromptInput!]!): [ClaudeResponse!]!
claudeSession(sessionId: ID!): ClaudeSession
}
type Mutation {
createClaudeSession(config: SessionConfig!): ClaudeSession!
sendPrompt(sessionId: ID!, prompt: String!): ClaudeResponse!
clearSession(sessionId: ID!): Boolean!
}
type Subscription {
claudeStream(sessionId: ID!): ClaudeStreamUpdate!
sessionUpdates(sessionId: ID!): SessionUpdate!
}
input ClaudePromptInput {
prompt: String!
model: ClaudeModel
maxTokens: Int
temperature: Float
systemPrompt: String
tools: [String!]
}
enum ClaudeModel {
CLAUDE_3_OPUS
CLAUDE_3_SONNET
CLAUDE_3_HAIKU
}
type ClaudeResponse {
id: ID!
content: String!
usage: TokenUsage!
metadata: ResponseMetadata!
tools: [ToolCall!]
}
type ClaudeStreamUpdate {
type: StreamEventType!
delta: String
toolCall: ToolCall
metadata: StreamMetadata
}GraphQL Resolver Implementation
// resolvers/claude.resolver.ts
import { GraphQLResolveInfo } from 'graphql';
import { Anthropic } from '@anthropic-ai/sdk';
import { PubSub } from 'graphql-subscriptions';
import DataLoader from 'dataloader';
export class ClaudeResolver {
private anthropic: Anthropic;
private pubsub: PubSub;
private promptLoader: DataLoader<string, any>;
constructor() {
this.anthropic = new Anthropic();
this.pubsub = new PubSub();
// Batch loader for efficient API calls
this.promptLoader = new DataLoader(async (prompts) => {
const responses = await Promise.all(
prompts.map(prompt => this.processPrompt(prompt))
);
return responses;
});
}
// Query resolvers
async claudePrompt(
parent: any,
args: { input: ClaudePromptInput },
context: any,
info: GraphQLResolveInfo
) {
// Use field selection to optimize response
const requestedFields = this.getRequestedFields(info);
const response = await this.anthropic.messages.create({
model: this.mapModel(args.input.model),
messages: [{ role: 'user', content: args.input.prompt }],
max_tokens: args.input.maxTokens || 1024,
temperature: args.input.temperature || 0.7,
system: args.input.systemPrompt
});
return this.formatResponse(response, requestedFields);
}
// Mutation resolvers
async createClaudeSession(parent: any, args: any) {
const sessionId = this.generateSessionId();
const session = new ClaudeSession({
id: sessionId,
config: args.config,
anthropic: this.anthropic
});
this.sessions.set(sessionId, session);
return {
id: sessionId,
config: args.config,
createdAt: new Date().toISOString(),
status: 'active'
};
}
// Subscription resolvers
async *claudeStream(parent: any, args: { sessionId: string }) {
const session = this.sessions.get(args.sessionId);
if (!session) throw new Error('Session not found');
const asyncIterator = this.pubsub.asyncIterator(
`claude-stream-${args.sessionId}`
);
// Stream responses
for await (const event of asyncIterator) {
yield event;
}
}
// Helper: Stream handler
async streamResponse(sessionId: string, prompt: string) {
const stream = await this.anthropic.messages.create({
messages: [{ role: 'user', content: prompt }],
model: 'claude-3-sonnet-20240229',
stream: true
});
for await (const event of stream) {
if (event.type === 'content_block_delta') {
this.pubsub.publish(`claude-stream-${sessionId}`, {
claudeStream: {
type: 'CONTENT_DELTA',
delta: event.delta.text
}
});
}
}
}
}GraphQL Federation for Microservices
// federation/claude-subgraph.ts
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@shareable", "@external"])
type ClaudeService @key(fields: "id") {
id: ID!
sessions: [ClaudeSession!]!
usage: ServiceUsage!
}
type User @key(fields: "id", resolvable: false) {
id: ID!
}
extend type User {
claudeSessions: [ClaudeSession!]!
claudeQuota: Quota!
}
`;
export const claudeSubgraph = buildSubgraphSchema({
typeDefs,
resolvers: {
User: {
claudeSessions: async (user) => {
return await sessionService.getSessionsByUserId(user.id);
}
}
}
});2. WebSocket and Server-Sent Events (SSE) Patterns
Bidirectional WebSocket Communication
// websocket/claude-ws-server.ts
import { WebSocketServer } from 'ws';
import { Anthropic } from '@anthropic-ai/sdk';
import { EventEmitter } from 'events';
interface WebSocketSession {
id: string;
ws: WebSocket;
anthropic: Anthropic;
context: Message[];
activeStreams: Map<string, AbortController>;
}
export class ClaudeWebSocketServer extends EventEmitter {
private wss: WebSocketServer;
private sessions: Map<string, WebSocketSession> = new Map();
private rateLimiter: RateLimiter;
constructor(options: { port: number }) {
super();
this.wss = new WebSocketServer({ port: options.port });
this.rateLimiter = new RateLimiter();
this.setupHandlers();
}
private setupHandlers() {
this.wss.on('connection', (ws, req) => {
const sessionId = this.authenticate(req);
if (!sessionId) {
ws.close(1008, 'Unauthorized');
return;
}
const session: WebSocketSession = {
id: sessionId,
ws,
anthropic: new Anthropic(),
context: [],
activeStreams: new Map()
};
this.sessions.set(sessionId, session);
ws.on('message', async (data) => {
try {
const message = JSON.parse(data.toString());
await this.handleMessage(session, message);
} catch (error) {
this.sendError(ws, error);
}
});
ws.on('close', () => {
// Cleanup active streams
session.activeStreams.forEach(controller => controller.abort());
this.sessions.delete(sessionId);
});
// Send connection acknowledgment
this.send(ws, {
type: 'connection',
sessionId,
status: 'connected',
timestamp: new Date().toISOString()
});
});
}
private async handleMessage(session: WebSocketSession, message: any) {
// Rate limiting check
const allowed = await this.rateLimiter.checkLimit(session.id);
if (!allowed) {
return this.send(session.ws, {
type: 'error',
error: 'Rate limit exceeded',
retryAfter: 60
});
}
switch (message.type) {
case 'prompt':
await this.handlePrompt(session, message);
break;
case 'stop':
this.stopStream(session, message.streamId);
break;
case 'clear_context':
session.context = [];
this.send(session.ws, { type: 'context_cleared' });
break;
case 'tool_result':
await this.handleToolResult(session, message);
break;
}
}
private async handlePrompt(session: WebSocketSession, message: any) {
const streamId = this.generateStreamId();
const controller = new AbortController();
session.activeStreams.set(streamId, controller);
try {
const stream = await session.anthropic.messages.create({
messages: [
...session.context,
{ role: 'user', content: message.prompt }
],
model: message.model || 'claude-3-sonnet-20240229',
max_tokens: message.maxTokens || 1024,
stream: true,
tools: message.tools
}, {
signal: controller.signal
});
let fullResponse = '';
let currentToolUse: any = null;
for await (const event of stream) {
if (controller.signal.aborted) break;
switch (event.type) {
case 'content_block_start':
if (event.content_block.type === 'tool_use') {
currentToolUse = {
id: event.content_block.id,
name: event.content_block.name,
input: ''
};
}
break;
case 'content_block_delta':
if (event.delta.type === 'text_delta') {
fullResponse += event.delta.text;
this.send(session.ws, {
type: 'stream_delta',
streamId,
delta: event.delta.text
});
} else if (event.delta.type === 'input_json_delta') {
if (currentToolUse) {
currentToolUse.input += event.delta.partial_json;
}
}
break;
case 'content_block_stop':
if (currentToolUse) {
this.send(session.ws, {
type: 'tool_use',
streamId,
tool: {
id: currentToolUse.id,
name: currentToolUse.name,
input: JSON.parse(currentToolUse.input)
}
});
currentToolUse = null;
}
break;
case 'message_stop':
// Update context
session.context.push(
{ role: 'user', content: message.prompt },
{ role: 'assistant', content: fullResponse }
);
this.send(session.ws, {
type: 'stream_complete',
streamId,
usage: event.usage
});
session.activeStreams.delete(streamId);
break;
}
}
} catch (error) {
session.activeStreams.delete(streamId);
this.sendError(session.ws, error, streamId);
}
}
private stopStream(session: WebSocketSession, streamId: string) {
const controller = session.activeStreams.get(streamId);
if (controller) {
controller.abort();
session.activeStreams.delete(streamId);
this.send(session.ws, {
type: 'stream_stopped',
streamId
});
}
}
}Server-Sent Events for Unidirectional Streaming
// sse/claude-sse-server.ts
import express from 'express';
import { Anthropic } from '@anthropic-ai/sdk';
export class ClaudeSSEServer {
private app: express.Application;
private anthropic: Anthropic;
private activeConnections: Map<string, Response> = new Map();
constructor() {
this.app = express();
this.anthropic = new Anthropic();
this.setupRoutes();
}
private setupRoutes() {
// SSE endpoint for streaming Claude responses
this.app.get('/sse/claude-stream', async (req, res) => {
// Set SSE headers
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no' // Disable Nginx buffering
});
const connectionId = this.generateConnectionId();
this.activeConnections.set(connectionId, res);
// Send initial connection event
this.sendSSE(res, {
event: 'connected',
data: { connectionId, timestamp: new Date().toISOString() }
});
// Handle connection close
req.on('close', () => {
this.activeConnections.delete(connectionId);
});
// Keep connection alive
const keepAlive = setInterval(() => {
res.write(':ping\n\n');
}, 30000);
req.on('close', () => clearInterval(keepAlive));
});
// Endpoint to trigger Claude streaming
this.app.post('/api/claude-prompt', express.json(), async (req, res) => {
const { connectionId, prompt, options = {} } = req.body;
const sseConnection = this.activeConnections.get(connectionId);
if (!sseConnection) {
return res.status(404).json({ error: 'Connection not found' });
}
try {
await this.streamToConnection(sseConnection, prompt, options);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
}
private async streamToConnection(
connection: Response,
prompt: string,
options: any
) {
const stream = await this.anthropic.messages.create({
messages: [{ role: 'user', content: prompt }],
model: options.model || 'claude-3-sonnet-20240229',
max_tokens: options.maxTokens || 1024,
stream: true
});
let buffer = '';
let tokenCount = 0;
for await (const event of stream) {
if (event.type === 'content_block_delta') {
buffer += event.delta.text;
tokenCount++;
// Send in chunks to optimize network usage
if (buffer.length > 100 || tokenCount % 10 === 0) {
this.sendSSE(connection, {
event: 'delta',
data: {
content: buffer,
tokens: tokenCount
}
});
buffer = '';
}
} else if (event.type === 'message_stop') {
// Send any remaining buffer
if (buffer) {
this.sendSSE(connection, {
event: 'delta',
data: { content: buffer, tokens: tokenCount }
});
}
this.sendSSE(connection, {
event: 'complete',
data: {
usage: event.usage,
totalTokens: tokenCount
}
});
}
}
}
private sendSSE(res: Response, payload: { event: string; data: any }) {
res.write(`event: ${payload.event}\n`);
res.write(`data: ${JSON.stringify(payload.data)}\n\n`);
}
}3. gRPC Service Integration
gRPC Protocol Definition
// proto/claude.proto
syntax = "proto3";
package claude;
service ClaudeService {
// Unary RPC for simple requests
rpc SendPrompt(PromptRequest) returns (PromptResponse);
// Server streaming for real-time responses
rpc StreamPrompt(PromptRequest) returns (stream StreamResponse);
// Bidirectional streaming for interactive sessions
rpc InteractiveSession(stream SessionMessage) returns (stream SessionResponse);
// Batch processing
rpc BatchProcess(BatchRequest) returns (BatchResponse);
}
message PromptRequest {
string prompt = 1;
string model = 2;
int32 max_tokens = 3;
float temperature = 4;
repeated Tool tools = 5;
map<string, string> metadata = 6;
}
message PromptResponse {
string id = 1;
string content = 2;
Usage usage = 3;
repeated ToolCall tool_calls = 4;
}
message StreamResponse {
oneof event {
ContentDelta content_delta = 1;
ToolUse tool_use = 2;
StreamComplete complete = 3;
StreamError error = 4;
}
}
message SessionMessage {
oneof message {
StartSession start = 1;
UserPrompt prompt = 2;
ToolResult tool_result = 3;
EndSession end = 4;
}
}
message Tool {
string name = 1;
string description = 2;
Parameters parameters = 3;
}
message Usage {
int32 input_tokens = 1;
int32 output_tokens = 2;
int32 total_tokens = 3;
}gRPC Server Implementation
// grpc/claude-grpc-server.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { Anthropic } from '@anthropic-ai/sdk';
import { CircuitBreaker } from 'opossum';
const PROTO_PATH = './proto/claude.proto';
export class ClaudeGrpcServer {
private server: grpc.Server;
private anthropic: Anthropic;
private circuitBreaker: CircuitBreaker;
constructor() {
this.server = new grpc.Server();
this.anthropic = new Anthropic();
// Circuit breaker for resilience
this.circuitBreaker = new CircuitBreaker(
this.callClaude.bind(this),
{
timeout: 30000,
errorThresholdPercentage: 50,
resetTimeout: 30000
}
);
this.loadProtoAndStart();
}
private async loadProtoAndStart() {
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const claudeProto = grpc.loadPackageDefinition(packageDefinition).claude;
this.server.addService(claudeProto.ClaudeService.service, {
sendPrompt: this.sendPrompt.bind(this),
streamPrompt: this.streamPrompt.bind(this),
interactiveSession: this.interactiveSession.bind(this),
batchProcess: this.batchProcess.bind(this)
});
// Add interceptors
this.server.addService(claudeProto.ClaudeService.service, {
sendPrompt: this.withInterceptors(this.sendPrompt.bind(this))
});
}
// Unary RPC implementation
private async sendPrompt(
call: grpc.ServerUnaryCall<any, any>,
callback: grpc.sendUnaryData<any>
) {
try {
const metadata = this.extractMetadata(call.metadata);
// Validate request
if (!this.validateRequest(call.request)) {
return callback({
code: grpc.status.INVALID_ARGUMENT,
message: 'Invalid request parameters'
});
}
// Use circuit breaker
const response = await this.circuitBreaker.fire(call.request);
callback(null, response);
} catch (error) {
this.handleError(error, callback);
}
}
// Server streaming implementation
private async streamPrompt(call: grpc.ServerWritableStream<any, any>) {
try {
const stream = await this.anthropic.messages.create({
messages: [{ role: 'user', content: call.request.prompt }],
model: call.request.model || 'claude-3-sonnet-20240229',
max_tokens: call.request.max_tokens || 1024,
stream: true
});
for await (const event of stream) {
if (call.cancelled) break;
if (event.type === 'content_block_delta') {
call.write({
content_delta: {
text: event.delta.text
}
});
} else if (event.type === 'message_stop') {
call.write({
complete: {
usage: {
input_tokens: event.usage.input_tokens,
output_tokens: event.usage.output_tokens,
total_tokens: event.usage.input_tokens + event.usage.output_tokens
}
}
});
call.end();
}
}
} catch (error) {
call.emit('error', this.grpcError(error));
}
}
// Bidirectional streaming implementation
private interactiveSession(
call: grpc.ServerDuplexStream<any, any>
) {
const session = {
id: this.generateSessionId(),
context: [],
anthropic: new Anthropic()
};
call.on('data', async (message) => {
try {
if (message.start) {
call.write({
session_started: {
session_id: session.id,
timestamp: new Date().toISOString()
}
});
} else if (message.prompt) {
await this.handleSessionPrompt(call, session, message.prompt);
} else if (message.tool_result) {
await this.handleToolResult(call, session, message.tool_result);
} else if (message.end) {
call.end();
}
} catch (error) {
call.emit('error', this.grpcError(error));
}
});
call.on('end', () => {
// Cleanup session
call.end();
});
}
// Batch processing with parallel execution
private async batchProcess(
call: grpc.ServerUnaryCall<any, any>,
callback: grpc.sendUnaryData<any>
) {
try {
const { requests, parallel_limit = 5 } = call.request;
// Process in batches with concurrency limit
const results = [];
for (let i = 0; i < requests.length; i += parallel_limit) {
const batch = requests.slice(i, i + parallel_limit);
const batchResults = await Promise.all(
batch.map(req => this.processWithRetry(req))
);
results.push(...batchResults);
}
callback(null, {
responses: results,
total_processed: results.length,
timestamp: new Date().toISOString()
});
} catch (error) {
this.handleError(error, callback);
}
}
// Helper: Add interceptors for logging, auth, etc.
private withInterceptors(handler: Function) {
return async (call: any, callback: any) => {
// Logging interceptor
console.log(`gRPC call: ${call.getPeer()} - ${new Date().toISOString()}`);
// Auth interceptor
const authToken = call.metadata.get('authorization')[0];
if (!this.validateAuth(authToken)) {
return callback({
code: grpc.status.UNAUTHENTICATED,
message: 'Invalid authentication'
});
}
// Rate limiting interceptor
const clientId = this.extractClientId(call.metadata);
if (!await this.rateLimiter.checkLimit(clientId)) {
return callback({
code: grpc.status.RESOURCE_EXHAUSTED,
message: 'Rate limit exceeded'
});
}
// Execute handler
return handler(call, callback);
};
}
// Start the server
start(port: number) {
this.server.bindAsync(
`0.0.0.0:${port}`,
grpc.ServerCredentials.createInsecure(),
(err, port) => {
if (err) {
console.error('Failed to start gRPC server:', err);
return;
}
console.log(`Claude gRPC server running on port ${port}`);
this.server.start();
}
);
}
}gRPC Client with Load Balancing
// grpc/claude-grpc-client.ts
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
export class ClaudeGrpcClient {
private clients: any[];
private currentIndex: number = 0;
constructor(servers: string[]) {
const packageDefinition = protoLoader.loadSync('./proto/claude.proto');
const claudeProto = grpc.loadPackageDefinition(packageDefinition).claude;
// Create clients with load balancing
this.clients = servers.map(server =>
new claudeProto.ClaudeService(
server,
grpc.credentials.createInsecure(),
{
'grpc.keepalive_time_ms': 120000,
'grpc.http2.min_time_between_pings_ms': 120000,
'grpc.keepalive_timeout_ms': 20000,
'grpc.http2.max_pings_without_data': 0,
'grpc.keepalive_permit_without_calls': 1
}
)
);
}
// Round-robin load balancing
private getClient() {
const client = this.clients[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.clients.length;
return client;
}
async sendPrompt(request: any): Promise<any> {
return new Promise((resolve, reject) => {
const client = this.getClient();
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 30);
client.sendPrompt(request, { deadline }, (error, response) => {
if (error) {
// Retry with another server
if (error.code === grpc.status.UNAVAILABLE) {
const nextClient = this.getClient();
return nextClient.sendPrompt(request, { deadline },
(err, res) => err ? reject(err) : resolve(res)
);
}
reject(error);
} else {
resolve(response);
}
});
});
}
streamPrompt(request: any): grpc.ClientReadableStream<any> {
const client = this.getClient();
return client.streamPrompt(request);
}
}4. API Gateway Patterns for Claude Services
Kong Gateway Configuration
# kong/claude-gateway.yaml
_format_version: "3.0"
services:
- name: claude-api
url: http://claude-backend:3000
plugins:
- name: rate-limiting
config:
minute: 60
hour: 1000
policy: local
- name: request-transformer
config:
add:
headers:
- X-Claude-Version:v1
- name: response-transformer
config:
add:
headers:
- X-Response-Time:$(latency)
- name: correlation-id
config:
header_name: X-Request-ID
generator: uuid
routes:
- name: claude-route
paths:
- /api/claude
methods:
- GET
- POST
- name: claude-graphql
url: http://claude-graphql:4000
plugins:
- name: graphql-rate-limiting
config:
cost_strategy:
query: 1
mutation: 10
subscription: 5
routes:
- name: graphql-route
paths:
- /graphql
- name: claude-grpc
url: grpc://claude-grpc:50051
protocol: grpc
plugins:
- name: grpc-gateway
config:
proto: /usr/local/kong/claude.proto
routes:
- name: grpc-route
paths:
- /grpc/claudeCustom API Gateway Implementation
// gateway/claude-api-gateway.ts
import express from 'express';
import httpProxy from 'http-proxy-middleware';
import jwt from 'jsonwebtoken';
import { RateLimiter } from './rate-limiter';
import { CircuitBreaker } from './circuit-breaker';
import { Cache } from './cache';
interface ServiceEndpoint {
name: string;
url: string;
healthCheck: string;
weight: number;
circuitBreaker: CircuitBreaker;
}
export class ClaudeAPIGateway {
private app: express.Application;
private services: Map<string, ServiceEndpoint[]> = new Map();
private rateLimiter: RateLimiter;
private cache: Cache;
constructor() {
this.app = express();
this.rateLimiter = new RateLimiter();
this.cache = new Cache();
this.setupMiddleware();
this.registerServices();
}
private setupMiddleware() {
// Request ID
this.app.use((req, res, next) => {
req.id = req.headers['x-request-id'] || this.generateRequestId();
res.setHeader('X-Request-ID', req.id);
next();
});
// Authentication
this.app.use('/api/*', this.authenticate.bind(this));
// Rate limiting
this.app.use('/api/*', this.rateLimit.bind(this));
// Request logging
this.app.use(this.logRequest.bind(this));
// CORS
this.app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
}
private registerServices() {
// Claude main API
this.addService('claude-api', [
{
name: 'claude-1',
url: 'http://claude-1:3000',
healthCheck: '/health',
weight: 1,
circuitBreaker: new CircuitBreaker({ threshold: 5, timeout: 60000 })
},
{
name: 'claude-2',
url: 'http://claude-2:3000',
healthCheck: '/health',
weight: 1,
circuitBreaker: new CircuitBreaker({ threshold: 5, timeout: 60000 })
}
]);
// GraphQL endpoint
this.addService('graphql', [
{
name: 'graphql-1',
url: 'http://graphql:4000',
healthCheck: '/health',
weight: 1,
circuitBreaker: new CircuitBreaker({ threshold: 3, timeout: 30000 })
}
]);
// Setup routes
this.setupRoutes();
}
private setupRoutes() {
// Claude API routes
this.app.use('/api/claude', this.createProxy('claude-api', {
pathRewrite: { '^/api/claude': '' },
onProxyReq: this.onProxyReq.bind(this),
onProxyRes: this.onProxyRes.bind(this)
}));
// GraphQL route with special handling
this.app.use('/graphql', this.createGraphQLProxy());
// WebSocket upgrade for real-time
this.app.use('/ws', this.createWebSocketProxy());
// Health check endpoint
this.app.get('/health', this.healthCheck.bind(this));
// Metrics endpoint
this.app.get('/metrics', this.getMetrics.bind(this));
}
private createProxy(serviceName: string, options: any) {
return async (req, res, next) => {
const endpoint = await this.selectEndpoint(serviceName);
if (!endpoint) {
return res.status(503).json({ error: 'Service unavailable' });
}
// Check circuit breaker
if (endpoint.circuitBreaker.isOpen()) {
const nextEndpoint = await this.selectEndpoint(serviceName, endpoint);
if (!nextEndpoint) {
return res.status(503).json({ error: 'All services unavailable' });
}
endpoint = nextEndpoint;
}
// Check cache for GET requests
if (req.method === 'GET') {
const cached = await this.cache.get(req.originalUrl);
if (cached) {
return res.json(cached);
}
}
// Create proxy
const proxy = httpProxy.createProxyMiddleware({
target: endpoint.url,
changeOrigin: true,
...options,
onError: (err, req, res) => {
endpoint.circuitBreaker.recordFailure();
this.handleProxyError(err, req, res);
},
onProxyRes: (proxyRes, req, res) => {
endpoint.circuitBreaker.recordSuccess();
if (options.onProxyRes) {
options.onProxyRes(proxyRes, req, res);
}
}
});
proxy(req, res, next);
};
}
private async selectEndpoint(
serviceName: string,
exclude?: ServiceEndpoint
): Promise<ServiceEndpoint | null> {
const endpoints = this.services.get(serviceName) || [];
const available = endpoints.filter(e =>
e !== exclude && !e.circuitBreaker.isOpen()
);
if (available.length === 0) return null;
// Weighted random selection
const totalWeight = available.reduce((sum, e) => sum + e.weight, 0);
let random = Math.random() * totalWeight;
for (const endpoint of available) {
random -= endpoint.weight;
if (random <= 0) return endpoint;
}
return available[0];
}
private async authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}
private async rateLimit(req, res, next) {
const key = req.user?.id || req.ip;
const allowed = await this.rateLimiter.checkLimit(key);
if (!allowed) {
return res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: 60
});
}
next();
}
private createGraphQLProxy() {
return async (req, res, next) => {
// GraphQL-specific handling
if (req.body?.query) {
const complexity = this.calculateQueryComplexity(req.body.query);
if (complexity > 1000) {
return res.status(400).json({
error: 'Query too complex'
});
}
}
// Use the standard proxy
return this.createProxy('graphql', {
pathRewrite: { '^/graphql': '/graphql' }
})(req, res, next);
};
}
private async healthCheck(req, res) {
const health = {
status: 'healthy',
services: {},
timestamp: new Date().toISOString()
};
// Check all services
for (const [name, endpoints] of this.services) {
health.services[name] = await Promise.all(
endpoints.map(async endpoint => ({
name: endpoint.name,
url: endpoint.url,
healthy: await this.checkEndpointHealth(endpoint),
circuitBreaker: endpoint.circuitBreaker.getState()
}))
);
}
res.json(health);
}
start(port: number) {
this.app.listen(port, () => {
console.log(`API Gateway running on port ${port}`);
});
}
}5. Rate Limiting and Quota Management Strategies
Advanced Rate Limiting Implementation
// rate-limiting/advanced-rate-limiter.ts
import Redis from 'ioredis';
import { TokenBucket } from './token-bucket';
interface RateLimitRule {
name: string;
limit: number;
window: number;
cost?: (request: any) => number;
}
export class AdvancedRateLimiter {
private redis: Redis;
private rules: Map<string, RateLimitRule> = new Map();
private buckets: Map<string, TokenBucket> = new Map();
constructor(redisUrl: string) {
this.redis = new Redis(redisUrl);
this.setupDefaultRules();
}
private setupDefaultRules() {
// API key-based limits
this.addRule('api-key-minute', { limit: 60, window: 60 });
this.addRule('api-key-hour', { limit: 1000, window: 3600 });
this.addRule('api-key-day', { limit: 10000, window: 86400 });
// Model-specific limits
this.addRule('opus-minute', { limit: 10, window: 60 });
this.addRule('sonnet-minute', { limit: 30, window: 60 });
this.addRule('haiku-minute', { limit: 100, window: 60 });
// Token-based limits
this.addRule('tokens-minute', {
limit: 100000,
window: 60,
cost: (req) => req.estimatedTokens || 1000
});
// Endpoint-specific limits
this.addRule('embedding-minute', { limit: 200, window: 60 });
this.addRule('batch-hour', { limit: 50, window: 3600 });
}
async checkLimit(
key: string,
request: any,
rules: string[] = ['api-key-minute']
): Promise<RateLimitResult> {
const results = await Promise.all(
rules.map(ruleName => this.checkRule(key, ruleName, request))
);
// Find the most restrictive limit
const blocked = results.find(r => !r.allowed);
if (blocked) return blocked;
// Return the limit with least remaining
return results.reduce((min, current) =>
current.remaining < min.remaining ? current : min
);
}
private async checkRule(
key: string,
ruleName: string,
request: any
): Promise<RateLimitResult> {
const rule = this.rules.get(ruleName);
if (!rule) throw new Error(`Unknown rule: ${ruleName}`);
const cost = rule.cost ? rule.cost(request) : 1;
const bucketKey = `${key}:${ruleName}`;
// Use Redis for distributed rate limiting
const result = await this.redisCheckLimit(
bucketKey,
rule.limit,
rule.window,
cost
);
return {
allowed: result.allowed,
limit: rule.limit,
remaining: result.remaining,
reset: result.reset,
retryAfter: result.allowed ? 0 : result.reset - Date.now()
};
}
private async redisCheckLimit(
key: string,
limit: number,
window: number,
cost: number
): Promise<any> {
const now = Date.now();
const clearBefore = now - window * 1000;
const pipeline = this.redis.pipeline();
// Remove old entries
pipeline.zremrangebyscore(key, '-inf', clearBefore);
// Count current entries
pipeline.zcard(key);
// Get the oldest entry
pipeline.zrange(key, 0, 0, 'WITHSCORES');
const results = await pipeline.exec();
const currentCount = results[1][1] as number;
const oldestEntry = results[2][1] as any[];
if (currentCount + cost > limit) {
// Calculate when the oldest entry expires
const reset = oldestEntry.length > 0
? parseInt(oldestEntry[1]) + window * 1000
: now + window * 1000;
return {
allowed: false,
remaining: Math.max(0, limit - currentCount),
reset
};
}
// Add new entries
const addPipeline = this.redis.pipeline();
for (let i = 0; i < cost; i++) {
addPipeline.zadd(key, now, `${now}-${i}`);
}
addPipeline.expire(key, window);
await addPipeline.exec();
return {
allowed: true,
remaining: limit - currentCount - cost,
reset: now + window * 1000
};
}
// Adaptive rate limiting based on system load
async adaptiveLimitCheck(
key: string,
request: any
): Promise<RateLimitResult> {
const systemLoad = await this.getSystemLoad();
// Adjust limits based on load
let adjustedRules = ['api-key-minute'];
if (systemLoad > 0.8) {
adjustedRules = ['api-key-minute-strict'];
} else if (systemLoad < 0.3) {
adjustedRules = ['api-key-minute-relaxed'];
}
return this.checkLimit(key, request, adjustedRules);
}
// Quota management
async checkQuota(
userId: string,
tokensRequested: number
): Promise<QuotaResult> {
const quotaKey = `quota:${userId}`;
const quotaInfo = await this.redis.hgetall(quotaKey);
const dailyLimit = parseInt(quotaInfo.dailyLimit) || 100000;
const monthlyLimit = parseInt(quotaInfo.monthlyLimit) || 1000000;
const dailyUsed = parseInt(quotaInfo.dailyUsed) || 0;
const monthlyUsed = parseInt(quotaInfo.monthlyUsed) || 0;
if (dailyUsed + tokensRequested > dailyLimit) {
return {
allowed: false,
reason: 'Daily quota exceeded',
dailyRemaining: dailyLimit - dailyUsed,
monthlyRemaining: monthlyLimit - monthlyUsed
};
}
if (monthlyUsed + tokensRequested > monthlyLimit) {
return {
allowed: false,
reason: 'Monthly quota exceeded',
dailyRemaining: dailyLimit - dailyUsed,
monthlyRemaining: monthlyLimit - monthlyUsed
};
}
// Update usage
await this.redis.hincrby(quotaKey, 'dailyUsed', tokensRequested);
await this.redis.hincrby(quotaKey, 'monthlyUsed', tokensRequested);
return {
allowed: true,
dailyRemaining: dailyLimit - dailyUsed - tokensRequested,
monthlyRemaining: monthlyLimit - monthlyUsed - tokensRequested
};
}
}6. Webhook Handling and Event-Driven Architectures
Webhook Server with Event Processing
// webhooks/claude-webhook-server.ts
import express from 'express';
import { EventEmitter } from 'events';
import crypto from 'crypto';
import { Queue } from 'bull';
interface WebhookConfig {
url: string;
events: string[];
secret: string;
retries: number;
timeout: number;
}
export class ClaudeWebhookServer extends EventEmitter {
private app: express.Application;
private webhooks: Map<string, WebhookConfig> = new Map();
private eventQueue: Queue;
private processingQueue: Queue;
constructor() {
super();
this.app = express();
this.eventQueue = new Queue('webhook-events');
this.processingQueue = new Queue('webhook-processing');
this.setupRoutes();
this.setupQueues();
}
private setupRoutes() {
// Webhook registration endpoint
this.app.post('/webhooks/register', express.json(), async (req, res) => {
const { url, events, secret } = req.body;
const id = this.generateWebhookId();
const config: WebhookConfig = {
url,
events,
secret,
retries: 3,
timeout: 30000
};
this.webhooks.set(id, config);
res.json({
id,
status: 'registered',
events,
createdAt: new Date().toISOString()
});
});
// Webhook receiver for external events
this.app.post('/webhooks/receive/:source', express.raw({ type: '*/*' }),
async (req, res) => {
const source = req.params.source;
const signature = req.headers['x-webhook-signature'];
// Verify signature
if (!this.verifyWebhookSignature(req.body, signature, source)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Queue event for processing
await this.eventQueue.add('process-webhook', {
source,
headers: req.headers,
body: req.body,
timestamp: new Date().toISOString()
});
res.status(202).json({ status: 'accepted' });
}
);
// Event trigger endpoint (internal)
this.app.post('/events/trigger', express.json(), async (req, res) => {
const { event, data } = req.body;
await this.triggerWebhooks(event, data);
res.json({ status: 'triggered', event });
});
}
private setupQueues() {
// Process incoming webhook events
this.eventQueue.process('process-webhook', async (job) => {
const { source, body, headers } = job.data;
try {
// Parse and validate event
const event = this.parseWebhookEvent(source, body);
// Emit for local handling
this.emit(`webhook:${source}`, event);
// Process with Claude if needed
if (event.requiresAI) {
await this.processWithClaude(event);
}
// Trigger outgoing webhooks
await this.triggerWebhooks(event.type, event);
} catch (error) {
console.error('Webhook processing error:', error);
throw error;
}
});
// Process outgoing webhooks
this.processingQueue.process('send-webhook', async (job) => {
const { webhookId, event, data } = job.data;
const config = this.webhooks.get(webhookId);
if (!config) return;
try {
await this.sendWebhook(config, event, data);
} catch (error) {
// Retry logic handled by Bull
throw error;
}
});
}
private async triggerWebhooks(event: string, data: any) {
const tasks = [];
for (const [id, config] of this.webhooks) {
if (config.events.includes(event) || config.events.includes('*')) {
tasks.push(
this.processingQueue.add('send-webhook', {
webhookId: id,
event,
data
}, {
attempts: config.retries,
backoff: {
type: 'exponential',
delay: 2000
}
})
);
}
}
await Promise.all(tasks);
}
private async sendWebhook(
config: WebhookConfig,
event: string,
data: any
) {
const payload = {
event,
data,
timestamp: new Date().toISOString(),
id: this.generateEventId()
};
const signature = this.generateSignature(payload, config.secret);
const response = await fetch(config.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Event': event,
'X-Webhook-Signature': signature,
'X-Webhook-ID': payload.id
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(config.timeout)
});
if (!response.ok) {
throw new Error(`Webhook failed: ${response.status}`);
}
// Log successful delivery
this.emit('webhook:delivered', {
webhookId: config.url,
event,
responseStatus: response.status
});
}
private generateSignature(payload: any, secret: string): string {
return crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
}
private verifyWebhookSignature(
body: Buffer,
signature: string,
source: string
): boolean {
// Implementation depends on source
switch (source) {
case 'github':
return this.verifyGitHubSignature(body, signature);
case 'stripe':
return this.verifyStripeSignature(body, signature);
default:
return true; // Or implement generic verification
}
}
}Event-Driven Architecture with Kafka
// event-driven/claude-event-processor.ts
import { Kafka, Consumer, Producer, EachMessagePayload } from 'kafkajs';
import { Anthropic } from '@anthropic-ai/sdk';
import { EventStore } from './event-store';
interface ClaudeEvent {
id: string;
type: string;
aggregateId: string;
data: any;
metadata: {
userId: string;
timestamp: string;
version: number;
};
}
export class ClaudeEventProcessor {
private kafka: Kafka;
private consumer: Consumer;
private producer: Producer;
private anthropic: Anthropic;
private eventStore: EventStore;
constructor() {
this.kafka = new Kafka({
clientId: 'claude-event-processor',
brokers: ['kafka:9092']
});
this.consumer = this.kafka.consumer({
groupId: 'claude-processing-group'
});
this.producer = this.kafka.producer();
this.anthropic = new Anthropic();
this.eventStore = new EventStore();
}
async start() {
await this.consumer.connect();
await this.producer.connect();
// Subscribe to topics
await this.consumer.subscribe({
topics: [
'claude.requests',
'claude.commands',
'system.events'
],
fromBeginning: false
});
// Process events
await this.consumer.run({
eachMessage: this.handleMessage.bind(this)
});
}
private async handleMessage({ topic, partition, message }: EachMessagePayload) {
const event: ClaudeEvent = JSON.parse(message.value.toString());
try {
// Store event
await this.eventStore.append(event);
// Route based on event type
switch (event.type) {
case 'PromptRequested':
await this.handlePromptRequest(event);
break;
case 'AnalysisRequested':
await this.handleAnalysisRequest(event);
break;
case 'BatchProcessingRequested':
await this.handleBatchRequest(event);
break;
case 'SystemMetricReceived':
await this.handleSystemMetric(event);
break;
}
// Acknowledge processing
await this.consumer.commitOffsets([{
topic,
partition,
offset: (parseInt(message.offset) + 1).toString()
}]);
} catch (error) {
// Send to error topic
await this.producer.send({
topic: 'claude.errors',
messages: [{
key: event.id,
value: JSON.stringify({
originalEvent: event,
error: error.message,
timestamp: new Date().toISOString()
})
}]
});
}
}
private async handlePromptRequest(event: ClaudeEvent) {
const { prompt, model, options } = event.data;
// Process with Claude
const response = await this.anthropic.messages.create({
messages: [{ role: 'user', content: prompt }],
model: model || 'claude-3-sonnet-20240229',
...options
});
// Publish response event
await this.publishEvent({
type: 'PromptCompleted',
aggregateId: event.aggregateId,
data: {
requestId: event.id,
response: response.content[0].text,
usage: response.usage
},
metadata: {
...event.metadata,
processingTime: Date.now() - new Date(event.metadata.timestamp).getTime()
}
});
}
private async handleAnalysisRequest(event: ClaudeEvent) {
const { documents, analysisType } = event.data;
// Prepare analysis prompt
const prompt = this.buildAnalysisPrompt(documents, analysisType);
// Get analysis from Claude
const response = await this.anthropic.messages.create({
messages: [{ role: 'user', content: prompt }],
model: 'claude-3-opus-20240229', // Use more capable model
max_tokens: 4000
});
// Extract structured data
const analysis = this.parseAnalysisResponse(response.content[0].text);
// Publish analysis completed event
await this.publishEvent({
type: 'AnalysisCompleted',
aggregateId: event.aggregateId,
data: {
requestId: event.id,
analysis,
confidence: analysis.confidence,
recommendations: analysis.recommendations
},
metadata: event.metadata
});
// Trigger follow-up actions
if (analysis.requiresAction) {
await this.publishEvent({
type: 'ActionRequired',
aggregateId: event.aggregateId,
data: {
analysisId: event.id,
actions: analysis.suggestedActions
},
metadata: event.metadata
});
}
}
private async publishEvent(event: Partial<ClaudeEvent>) {
const fullEvent: ClaudeEvent = {
id: this.generateEventId(),
...event,
metadata: {
...event.metadata,
timestamp: new Date().toISOString()
}
};
await this.producer.send({
topic: `claude.${event.type.toLowerCase()}`,
messages: [{
key: fullEvent.aggregateId,
value: JSON.stringify(fullEvent),
headers: {
eventType: event.type,
version: '1.0'
}
}]
});
}
// Event Sourcing with Projections
async rebuildProjection(aggregateId: string, toVersion?: number) {
const events = await this.eventStore.getEvents(aggregateId, toVersion);
const projection = events.reduce((state, event) => {
return this.applyEvent(state, event);
}, {});
return projection;
}
private applyEvent(state: any, event: ClaudeEvent): any {
switch (event.type) {
case 'PromptRequested':
return {
...state,
prompts: [...(state.prompts || []), event.data]
};
case 'PromptCompleted':
return {
...state,
responses: [...(state.responses || []), event.data]
};
default:
return state;
}
}
}Event Orchestration with Temporal
// orchestration/claude-temporal-workflow.ts
import { proxyActivities, sleep } from '@temporalio/workflow';
import type * as activities from './activities';
const { processWithClaude, validateResult, notifyWebhook } = proxyActivities<
typeof activities
>({
startToCloseTimeout: '5 minutes',
retry: {
initialInterval: '1s',
backoffCoefficient: 2,
maximumAttempts: 5
}
});
export async function claudeProcessingWorkflow(input: {
requestId: string;
prompt: string;
webhookUrl: string;
validationRules: any[];
}): Promise<any> {
// Step 1: Process with Claude
const claudeResult = await processWithClaude({
prompt: input.prompt,
requestId: input.requestId
});
// Step 2: Validate result
const validationResult = await validateResult({
result: claudeResult,
rules: input.validationRules
});
if (!validationResult.isValid) {
// Retry with modified prompt
const retryResult = await processWithClaude({
prompt: `${input.prompt}\n\nPlease address these issues: ${validationResult.errors.join(', ')}`,
requestId: input.requestId
});
// Re-validate
const retryValidation = await validateResult({
result: retryResult,
rules: input.validationRules
});
if (!retryValidation.isValid) {
throw new Error('Failed validation after retry');
}
}
// Step 3: Notify webhook
await notifyWebhook({
url: input.webhookUrl,
data: {
requestId: input.requestId,
result: claudeResult,
status: 'completed'
}
});
return {
requestId: input.requestId,
result: claudeResult,
completedAt: new Date().toISOString()
};
}
// Saga pattern for complex workflows
export async function claudeSagaWorkflow(input: {
orderId: string;
tasks: Array<{ type: string; data: any }>;
}): Promise<any> {
const compensations: Array<() => Promise<void>> = [];
const results: any[] = [];
try {
for (const task of input.tasks) {
switch (task.type) {
case 'analyze':
const analysis = await processWithClaude({
prompt: `Analyze: ${JSON.stringify(task.data)}`,
requestId: `${input.orderId}-analyze`
});
results.push(analysis);
// Add compensation
compensations.push(async () => {
await notifyWebhook({
url: task.data.rollbackUrl,
data: { orderId: input.orderId, action: 'rollback-analysis' }
});
});
break;
case 'generate':
const generation = await processWithClaude({
prompt: `Generate: ${JSON.stringify(task.data)}`,
requestId: `${input.orderId}-generate`
});
results.push(generation);
compensations.push(async () => {
await notifyWebhook({
url: task.data.rollbackUrl,
data: { orderId: input.orderId, action: 'rollback-generation' }
});
});
break;
}
}
return { orderId: input.orderId, results, status: 'completed' };
} catch (error) {
// Execute compensations in reverse order
for (const compensation of compensations.reverse()) {
await compensation();
}
throw error;
}
}Best Practices and Recommendations
1. API Design Principles
- Use appropriate protocols: GraphQL for flexible queries, gRPC for internal services, REST for public APIs
- Implement versioning: Support multiple API versions simultaneously
- Design for idempotency: Ensure operations can be safely retried
- Use consistent error formats: Standardize error responses across all APIs
2. Performance Optimization
- Implement caching: Cache Claude responses where appropriate
- Use connection pooling: Maintain persistent connections for better performance
- Batch requests: Combine multiple small requests into batches
- Implement circuit breakers: Protect against cascading failures
3. Security Considerations
- Authenticate all requests: Use JWT tokens or API keys
- Implement rate limiting: Protect against abuse and ensure fair usage
- Encrypt sensitive data: Use TLS for all communications
- Validate inputs: Sanitize and validate all user inputs
4. Monitoring and Observability
- Implement distributed tracing: Track requests across services
- Log all API interactions: Maintain audit trails
- Monitor performance metrics: Track latency, throughput, and error rates
- Set up alerts: Notify on anomalies or threshold breaches
5. Testing Strategies
- Unit test all components: Test individual functions and methods
- Integration testing: Test API endpoints and service interactions
- Load testing: Ensure system can handle expected traffic
- Chaos engineering: Test resilience to failures
Conclusion
Advanced API integration patterns enable building robust, scalable AI applications with Claude Code. By combining GraphQL’s flexibility, WebSocket’s real-time capabilities, gRPC’s performance, and event-driven architectures, developers can create sophisticated systems that leverage Claude’s capabilities effectively.
Key takeaways:
- Choose the right protocol for each use case
- Implement proper rate limiting and quota management
- Design for resilience with circuit breakers and retries
- Use event-driven architectures for scalability
- Monitor and observe all API interactions
Related Resources
- Claude Code API Reference
- Real-Time Streaming Patterns
- Rate Limit Management
- Event Sourcing Patterns
- TypeScript SDK Documentation
External References
- GraphQL Best Practices
- gRPC Documentation
- WebSocket API MDN
- Microservices Patterns
- Event-Driven Architecture
Last Updated: 2025-07-22 Status: Experimental - Patterns and implementations are subject to change