ignitionstack.pro v1.0 is out! Read the announcement →
Skip to Content

Remote Integration

ignitionstack.pro connects to cloud LLM providers through secure, resilient adapters. This guide covers authentication, API key management, cost optimization, and production best practices.

Supported Providers

ProviderSDKAPI BaseStatus
OpenAIopenaiapi.openai.comFull support
Google Gemini@google/generative-aigenerativelanguage.googleapis.comFull support
Anthropic@anthropic-ai/sdkapi.anthropic.comReady

Authentication

Platform API Keys

For platform-wide usage (server-side operations):

# .env.local / .env.production OPENAI_API_KEY=sk-... GOOGLE_AI_API_KEY=... ANTHROPIC_API_KEY=sk-ant-...

User-Provided API Keys

Users can bring their own API keys for direct billing:

// Server action: Save user's API key // src/app/actions/ai/save-api-key.ts import { encrypt } from '@/lib/ai/utils/encryption' export async function saveAPIKey(provider: string, apiKey: string) { const { user } = await requireAuth() // Encrypt before storage const encryptedKey = encrypt(apiKey, process.env.ENCRYPTION_KEY!) await db.from('ai_user_api_keys').upsert({ user_id: user.id, provider, encrypted_key: encryptedKey, key_hash: hash(apiKey), // For validation without decryption is_active: true, }) }

API Key Encryption

All user API keys are encrypted with AES-256-GCM:

// src/app/lib/ai/utils/encryption.ts // Structure: salt (64 bytes) || iv (16 bytes) || authTag (16 bytes) || ciphertext // - Random salt per encryption for key derivation // - Random IV per encryption for uniqueness // - Auth tag for integrity verification export function encrypt(plaintext: string, masterKey: string): string { const salt = crypto.randomBytes(64) const iv = crypto.randomBytes(16) const key = crypto.pbkdf2Sync(masterKey, salt, 100000, 32, 'sha256') const cipher = crypto.createCipheriv('aes-256-gcm', key, iv) const encrypted = Buffer.concat([ cipher.update(plaintext, 'utf8'), cipher.final(), ]) const authTag = cipher.getAuthTag() return Buffer.concat([salt, iv, authTag, encrypted]).toString('base64') }

Provider Configuration

OpenAI

// Full OpenAI setup const openai = await ProviderFactory.create('openai', { apiKey: process.env.OPENAI_API_KEY, organization: process.env.OPENAI_ORG_ID, // Optional baseURL: process.env.OPENAI_BASE_URL, // For proxies/Azure timeout: 30000, maxRetries: 3, }) // With user's own key const openai = await ProviderFactory.createWithUserKey(userId, 'openai')

Azure OpenAI

# .env.local OPENAI_BASE_URL=https://your-resource.openai.azure.com OPENAI_API_KEY=your-azure-key OPENAI_API_VERSION=2024-02-01

Google Gemini

const gemini = await ProviderFactory.create('gemini', { apiKey: process.env.GOOGLE_AI_API_KEY, })

Vertex AI (Enterprise)

# For Google Cloud Vertex AI instead of AI Studio GOOGLE_CLOUD_PROJECT=your-project-id GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

Anthropic

const anthropic = await ProviderFactory.create('anthropic', { apiKey: process.env.ANTHROPIC_API_KEY, baseURL: 'https://api.anthropic.com', // Or proxy URL })

Cost Management

Token-Based Pricing

// Cost estimation is built into each adapter const response = await provider.chat(messages, options) console.log({ promptTokens: response.usage.promptTokens, completionTokens: response.usage.completionTokens, estimatedCost: response.usage.estimatedCost, // In USD })

Pricing Reference

ProviderModelInput (1M)Output (1M)
OpenAIgpt-4o$2.50$10.00
OpenAIgpt-4o-mini$0.15$0.60
Geminigemini-2.0-flash$0.075$0.30
Geminigemini-1.5-pro$1.25$5.00
Anthropicclaude-3.5-sonnet$3.00$15.00

Usage Tracking

-- Query usage from logs SELECT provider, model, DATE(created_at) as date, SUM(prompt_tokens) as prompt_tokens, SUM(completion_tokens) as completion_tokens, SUM(estimated_cost) as total_cost FROM ai_usage_logs WHERE user_id = ? GROUP BY provider, model, DATE(created_at) ORDER BY date DESC;

Budget Limits

// Implement spending limits per user const usage = await getMonthlyUsage(userId) if (usage.totalCost >= user.plan.monthlyLimit) { throw new Error('Monthly AI budget exceeded') } // Or per-request limits if (estimatedCost > user.plan.maxRequestCost) { throw new Error('Request would exceed cost limit') }

Rate Limiting

Provider Rate Limits

ProviderRequests/minTokens/minTokens/day
OpenAI (Tier 1)50030,000200,000
OpenAI (Tier 4)10,000800,00010M
Gemini (Free)151M1.5M
Gemini (Paid)1,0004MUnlimited

Application Rate Limiting

// src/app/lib/api-rate-limit.ts import { rateLimit } from '@/lib/rate-limit' // Per-user limits const aiRateLimit = rateLimit({ interval: 60 * 1000, // 1 minute uniqueTokenPerInterval: 500, }) export async function checkAIRateLimit(userId: string) { const limit = await aiRateLimit.check(userId, 20) // 20 req/min if (!limit.success) { throw new RateLimitError({ retryAfter: limit.reset, remaining: limit.remaining, }) } }

Streaming Responses

Server-Sent Events (SSE)

// src/app/api/ai/chat/route.ts export async function POST(request: Request) { // ... authentication & validation ... const stream = new TransformStream() const writer = stream.writable.getWriter() const encoder = new TextEncoder() // Start streaming in background (async () => { try { for await (const chunk of provider.chatStream(messages, options)) { await writer.write( encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`) ) } await writer.write(encoder.encode('data: [DONE]\n\n')) } finally { await writer.close() } })() return new Response(stream.readable, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, }) }

Client-Side Consumption

// React hook for streaming const response = await fetch('/api/ai/chat', { method: 'POST', body: JSON.stringify({ message }), }) const reader = response.body.getReader() const decoder = new TextDecoder() while (true) { const { done, value } = await reader.read() if (done) break const chunk = decoder.decode(value) const lines = chunk.split('\n') for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6)) if (data !== '[DONE]') { onToken(data.content) } } } }

Error Handling

Common Errors

try { const response = await provider.chat(messages, options) } catch (error) { switch (error.code) { case 'rate_limit_exceeded': // Wait and retry await sleep(error.retryAfter * 1000) return retry() case 'invalid_api_key': // Notify user to update key throw new AuthError('Invalid API key') case 'context_length_exceeded': // Truncate messages and retry return retry(truncateMessages(messages)) case 'content_filter': // Content was blocked by safety filters throw new ModerationError('Content blocked') default: // Log and fallback to another provider logger.error('Provider error', { error }) return fallbackProvider.chat(messages, options) } }

Retry Strategy

// Built into adapters with exponential backoff const response = await retry( () => provider.chat(messages, options), { retries: 3, factor: 2, minTimeout: 1000, maxTimeout: 10000, onRetry: (error, attempt) => { logger.warn('Retrying AI request', { attempt, error }) }, } )

Security Best Practices

1. Never Expose Keys Client-Side

// BAD - Never do this const response = await fetch('https://api.openai.com/...', { headers: { Authorization: `Bearer ${apiKey}` }, // Exposed! }) // GOOD - Always proxy through your API const response = await fetch('/api/ai/chat', { body: JSON.stringify({ message }), })

2. Validate All Inputs

// Server action with Zod validation const schema = z.object({ message: z.string().min(1).max(10000), conversationId: z.string().uuid(), model: z.enum(['gpt-4o', 'gpt-4o-mini', 'gemini-1.5-pro']), }) export async function sendMessage(input: unknown) { const data = schema.parse(input) // ... proceed with validated data }

3. Content Moderation

// Check content before sending to LLM const moderation = await openai.moderations.create({ input: userMessage, }) if (moderation.results[0].flagged) { throw new ModerationError('Content violates guidelines') }

4. Audit Logging

// Log all AI operations await db.from('ai_usage_logs').insert({ user_id: userId, provider, model, operation: 'chat', prompt_tokens: usage.promptTokens, completion_tokens: usage.completionTokens, estimated_cost: cost, request_id: requestId, ip_address: getClientIP(request), })

Failover & Resilience

The Circuit Breaker automatically handles provider failures:

// Automatic failover when OpenAI is down const router = new StrategyRouter({ fallbackOrder: ['openai', 'gemini', 'ollama'], }) // If OpenAI circuit is open, routes to Gemini const decision = await router.route(context)