Dev.to · 4 min read

Rotating Gemini API Keys in Next.js Serverless Cron Jobs Without Hitting Quota Limits

Rotating Gemini API Keys in Next.js Serverless Cron Jobs Without Hitting Quota Limits

The Problem When you run automated YouTube creator data analysis through scheduled background jobs on Vercel, a single high-volume batch processing run can exhaust your free-tier AI API limits in under two minutes. At PulseROI, we run continuous background evaluation tasks using Gemini 3.8 Flash to process campaign transcripts and metadata for Indian D2C brands. During a routine midnight cron execution parsing metrics for over two hundred influencer videos, the primary API client abruptly threw a 429 Too Many Requests error, halting the entire indexing pipeline mid-flight and leaving half our database tables in an inconsistent state. What I Tried First (and Why It Failed) My initial implementation used a single static environment variable (process.env.GEMINI_API_KEY) loaded directly into the Google Gen AI SDK client initialization. When the rate limit error occurred, my first naive attempt was wrapping the API call in a simple try/catch block with an exponential backoff loop. import { GoogleGenAI } from '@google/genai'; const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); async function generateWithRetry(prompt: string, retries = 3) { for (let i = 0; i < retries; i++) { try { return await ai.models.generateContent({ model: 'gemini-3.8-flash', contents: prompt, }); } catch (error: any) { if (error.status === 429 && i < retries - 1) { await new Promise((res) => setTimeout(res, Math.pow(2, i) * 1000)); continue; } throw error; } } } This failed in production because exponential backoff merely delays the inevitable when your total token consumption across a single cron invocation exceeds the strict per-minute request ceilings imposed on a single key. Waiting two or four seconds before retrying against the exact same exhausted token bucket does nothing to solve the quota depletion. The cron job still timed out after hitting Vercel's execution limits. The Actual Fix To bypass per-key rate limits cleanly in serverless environments, I moved away from a single environment variable and implemented a round-robin key rotation helper that cycles through a comma-separated array of API keys stored in process.env.GEMINI_API_KEYS. Each time a generation request is dispatched, the helper selects the next available key index using an atomic counter or modulo arithmetic, falling back gracefully if a specific key encounters an explicit quota rejection. import { GoogleGenAI } from '@google/genai'; class GeminiKeyPool { private keys: string[]; private currentIndex = 0; constructor() { const rawKeys = process.env.GEMINI_API_KEYS || ''; this.keys = rawKeys.split(',').map((k) => k.trim()).filter(Boolean); if (this.keys.length === 0) { throw new Error('No Gemini API keys found in pool configuration.'); } } public getClient(): GoogleGenAI { const apiKey = this.keys[this.currentIndex]; // Rotate index for the next call this.currentIndex = (this.currentIndex + 1) % this.keys.length; return new GoogleGenAI({ apiKey }); } public async executeWithRotation(operation: (client: GoogleGenAI) => Promise): Promise { let attempts = 0; const maxAttempts = this.keys.length; while (attempts < maxAttempts) { const client = this.getClient(); try { return await operation(client); } catch (error: any) { // Check if error is rate limit (429) or quota exhausted if (error?.status === 429 || error?.message?.includes('RESOURCE_EXHAUSTED')) { attempts++; console.warn(`Gemini API key exhausted. Rotating pool (Attempt ${attempts}/${maxAttempts})...`); continue; } throw error; } } throw new Error('All Gemini API keys in the rotation pool have been exhausted.'); } } export const geminiPool = new GeminiKeyPool(); We then invoke this pool wrapper inside our background worker tasks where we process creator transcripts: export async function analyzeCreatorTranscript(transcriptText: string) { return await geminiPool.executeWithRotation(async (client) => { const response = await client.models.generateContent({ model: 'gemini-3.8-flash', contents: `Analyze this influencer campaign transcript for engagement metrics and sentiment: ${transcriptText}`, }); return response.text; }); } What Surprised Me What caught me off guard was that serverless container reuse across Vercel invocations means the currentIndex in memory persists across warm starts. This actually works to our advantage — rather than hammering the first key in the array repeatedly on every new cron execution, the round-robin pointer picks up right where the previous warm invocation left off, naturally load-balancing requests across our entire key array without needing an external state store like Redis or Neon. The Result By distributing our AI processing tasks across a rotated key pool, our nightly cron job completion time for processing over 250 creator video records dropped from sporadic failures with 429 errors down to a consistent 100% success rate. Average batch execution time stabilized at ~14.2 seconds per run well within our 25-second safety timeout limit, completely eliminating manual job restarts. Where to Go From Here If you are scaling background data processing pipelines with LLMs in Next.js, ensure your environment configurations handle multi-key arrays securely. For a deeper look at how we structure our database queries alongside these background worker tasks, take a look at the architecture notes over at PulseROI.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News