Nurture TechnologiesNurture Tech
AI14 min read·July 24, 2026

OpenAI API Rate Limits Breaking Your Application

TypeScriptNode.jsOpenAI SDKRedisBullMQ

Your application is throwing 429 RateLimitError at random intervals, bringing down request pipelines and frustrating users. Here is the systematic fix.

Problem Summary

OpenAI enforces two distinct rate limit dimensions: Requests Per Minute (RPM) and Tokens Per Minute (TPM). When either limit is exceeded the API returns HTTP 429 with a RateLimitError. Applications that fire requests in tight loops, batch-process large documents, or share a single API key across multiple server instances commonly hit these walls without warning especially after traffic spikes or when a new feature dramatically increases prompt size.

Symptoms

  • Intermittent 429 responses from the OpenAI API, often clustering during peak traffic windows
  • openai.RateLimitError thrown in logs with message 'Rate limit reached for gpt-4o on tokens per min'
  • Requests succeeding for the first 30–60 seconds then failing in bursts
  • Batch processing jobs completing the first N items then stalling entirely
  • Different error rates on different server instances sharing the same API key
  • Successful retries after a 20–60 second pause suggesting TPM reset cycle
  • High latency spikes immediately before the 429 cascade starts
  • Embedding pipelines failing mid-batch when tokenizing large documents

Business Impact

Rate limit failures translate directly into broken user-facing features. Chat completions that never resolve, document summaries that silently return empty strings, and search results that degrade to keyword matching while the embedding pipeline is down all of these erode user trust and generate support tickets. For B2B products with SLA commitments, sustained 429 cascades during business hours can trigger contractual penalties.

Root Cause

OpenAI rate limits are applied per API key per model per minute. The TPM limit is often the first to blow not RPM because developers underestimate how quickly tokens accumulate when system prompts are large, conversation history is included verbatim, or document chunks are sent without truncation. At the Tier 1 level gpt-4o allows 30,000 TPM. A single request with a 10,000-token system prompt plus a 5,000-token document leaves only 15,000 TPM for the rest of your traffic.

TPM limits reset every 60 seconds from your first request in that window, not on the clock minute. Sleeping for 60 seconds after a 429 is not always enough check the Retry-After header in the response for the exact wait time.

Diagnosis

Step 1: Read the 429 Response Headers

The OpenAI API returns informative headers on every 429 response. Log the full error object including headers to understand whether you are hitting RPM or TPM limits.

diagnose-rate-limit.ts
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function diagnoseRateLimit() {
  try {
    const response = await client.chat.completions.create({
      model: "gpt-4o",
      messages: [{ role: "user", content: "Hello" }],
    });
    console.log("Success:", response.usage);
  } catch (err) {
    if (err instanceof OpenAI.RateLimitError) {
      console.error("Rate limit hit:", {
        status: err.status,         // 429
        message: err.message,       // "Rate limit reached for tokens per min..."
        headers: err.headers,       // { "retry-after": "20", "x-ratelimit-limit-tokens": "30000" }
        type: err.type,             // "requests" | "tokens"
      });
    }
    throw err;
  }
}

Step 2: Count Tokens Before Sending

Install tiktoken to measure token consumption before the request leaves your server. This lets you gate requests that would blow your per-minute budget.

token-counter.ts
import { encoding_for_model } from "tiktoken";

export function countTokens(model: string, messages: { role: string; content: string }[]): number {
  const enc = encoding_for_model(model as any);
  let total = 0;

  for (const msg of messages) {
    // Each message adds 4 tokens for formatting overhead
    total += 4;
    total += enc.encode(msg.role).length;
    total += enc.encode(msg.content).length;
  }

  // Every reply is primed with 3 tokens
  total += 3;
  enc.free();
  return total;
}

// Usage
const tokens = countTokens("gpt-4o", [
  { role: "system", content: systemPrompt },
  { role: "user", content: userMessage },
]);
console.log(`Request will consume ~${tokens} tokens`);

Step 3: Implement Exponential Backoff with Jitter

Naive retry logic retries immediately, which floods the API with the same requests that just failed. Exponential backoff with full jitter spreads retries across time and respects the Retry-After header.

backoff.ts
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function withBackoff<T>(
  fn: () => Promise<T>,
  maxRetries = 5,
  baseDelayMs = 1000
): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (!(err instanceof OpenAI.RateLimitError) || attempt === maxRetries) {
        throw err;
      }

      // Respect Retry-After header if present
      const retryAfter = err.headers?.["retry-after"];
      const retryAfterMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : null;

      // Exponential backoff with full jitter: random(0, base * 2^attempt)
      const exponential = baseDelayMs * Math.pow(2, attempt);
      const jitter = Math.random() * exponential;
      const delay = retryAfterMs ?? jitter;

      console.warn(
        `RateLimitError on attempt ${attempt + 1}/${maxRetries + 1}. Retrying in ${Math.round(delay)}ms...`
      );

      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
  throw new Error("Unreachable");
}

// Usage
const completion = await withBackoff(() =>
  client.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: "Summarize this document." }],
  })
);

Step 4: Add a Token Budget Queue with BullMQ

For batch workloads, a simple retry loop is not enough. Use a queue with rate limiter support to cap throughput at the API tier limit.

openai-queue.ts
import { Queue, Worker, RateLimiterOptions } from "bullmq";
import OpenAI from "openai";
import IORedis from "ioredis";

const connection = new IORedis();
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// Tier 1 gpt-4o limits: 500 RPM, 30,000 TPM
// Being conservative: cap at 400 RPM = ~6-7 per second
const rateLimiter: RateLimiterOptions = {
  max: 6,       // max jobs per duration
  duration: 1000, // 1 second window
};

export const aiQueue = new Queue("openai-completions", {
  connection,
  defaultJobOptions: {
    attempts: 5,
    backoff: { type: "exponential", delay: 2000 },
  },
});

const worker = new Worker(
  "openai-completions",
  async (job) => {
    const { messages, model = "gpt-4o-mini" } = job.data;
    const result = await client.chat.completions.create({ model, messages });
    return result.choices[0].message.content;
  },
  { connection, limiter: rateLimiter }
);

worker.on("failed", (job, err) => {
  console.error(`Job ${job?.id} failed:`, err.message);
});

Step 5: Check Your Tier Limits in the OpenAI Dashboard

Navigate to platform.openai.com > Settings > Limits to see your current RPM, TPM, and daily token limits per model. Tier upgrades happen automatically when you cross spending thresholds ($5, $50, $100, $500) confirm you are on the expected tier and request a manual limit increase if needed.

$curl https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY" | jq '.data[].id' | grep gpt-4

Production Failure Scenarios

Scenario 1: Thundering Herd After Server Restart

When a server restarts after a deploy, all queued requests fire simultaneously. The burst saturates both RPM and TPM in the first 5 seconds of the minute window, causing a cascade of 429 errors for the next 55 seconds. Fix: add startup jitter randomize the initial delay before worker threads begin processing jobs.

Scenario 2: Shared API Key Across Microservices

Three services share one API key. Service A (chat) uses 20k TPM, Service B (embeddings) uses 8k TPM, and Service C (batch summaries) spikes to 15k TPM at night. The combined 43k TPM exceeds the 30k limit. Fix: either use separate API keys per service (separate billing sub-accounts), or centralize all OpenAI calls through a single API gateway that enforces a shared token budget.

Scenario 3: Embedding Pipeline Using gpt-4o for Embeddings

A developer confused completion models with embedding models and sent chunks to chat completions instead of text-embedding-3-small. Each chunk consumed 10–50x more tokens than necessary. Fix: use text-embedding-3-small or text-embedding-3-large for all embedding workloads they have separate, often larger TPM limits and cost 96% less per token.

Scenario 4: Context Window Growing Unbounded in Chat

A chat feature appended every user and assistant message to the history array indefinitely. After 30 turns the history alone consumed 25,000 tokens, leaving almost no room before hitting TPM. Fix: implement a sliding window keep only the last N turns, or summarize older turns with a cheap gpt-4o-mini call.

Prevention Checklist

  • Count tokens with tiktoken before every API call and log the result
  • Implement exponential backoff with jitter on all OpenAI API calls
  • Respect the Retry-After header parse it from error.headers['retry-after']
  • Use separate API keys per service to isolate rate limit consumption
  • Route batch jobs through a BullMQ queue with a rate limiter matching your tier
  • Cap conversation history with a sliding window or summarization strategy
  • Use gpt-4o-mini instead of gpt-4o for tasks that do not require frontier capability
  • Use text-embedding-3-small for all embedding operations
  • Set up OpenAI usage alerts in the dashboard to trigger at 70% and 90% of daily spend
  • Monitor x-ratelimit-remaining-tokens and x-ratelimit-remaining-requests response headers in production

Resolution Summary

Resolving OpenAI rate limit errors requires changes at three layers: pre-request token counting to avoid submitting oversized payloads, request-level backoff logic that respects the Retry-After header, and infrastructure-level queuing that caps throughput to your tier's RPM and TPM limits. Once those three layers are in place, isolated 429 errors become rare and recoverable rather than cascade failures.

Lessons Learned

  • TPM limits are almost always the binding constraint, not RPM count tokens first
  • Exponential backoff without jitter causes retry storms that make the problem worse
  • Sharing an API key across microservices creates invisible resource contention
  • The Retry-After header gives you the exact wait time always read it instead of guessing
  • Tier upgrades are automatic with spend thresholds but manual limit increases are available via support
  • BullMQ rate limiters are the most reliable mechanism for sustained high-throughput pipelines

Conclusion

Rate limit errors are not a sign that you are doing something wrong they are a normal part of operating at scale with metered APIs. The difference between a system that handles them gracefully and one that falls over is a combination of proactive token budgeting, robust retry logic, and queue-based throughput control. Invest in these patterns early and they will save you from emergency incidents during your highest-traffic moments.

Nurture Technologies

NEED HELP WITH YOUR STACK?

Nurture Technologies builds and maintains production-quality software for startups and businesses. If engineering problems are slowing you down, our team can help.

Talk to our team →
FAQ

FREQUENTLY ASKED QUESTIONS

What is the difference between RPM and TPM rate limits?+

RPM (Requests Per Minute) limits the number of API calls regardless of size. TPM (Tokens Per Minute) limits the total number of tokens input plus output across all requests in a 60-second window. TPM is usually the first limit you hit because large prompts can consume thousands of tokens in a single request.

How do I know if I am hitting TPM or RPM limits from the error message?+

The error message will say either 'Rate limit reached for requests per min' or 'Rate limit reached for tokens per min'. You can also inspect the x-ratelimit-limit-requests and x-ratelimit-limit-tokens headers on the 429 response to see both limits.

What is the Retry-After header and how should I use it?+

The Retry-After header in a 429 response contains the number of seconds you must wait before the API will accept requests again. Always parse this value with parseInt(err.headers['retry-after'], 10) * 1000 and use it as the sleep duration. It is more accurate than a fixed or calculated backoff delay.

Should I use exponential backoff or fixed delays for retries?+

Always use exponential backoff with full jitter. Fixed delays cause synchronized retry storms when multiple instances all hit the limit and retry at the same time. Full jitter (random value between 0 and the exponential ceiling) spreads retries across the window and significantly reduces retry collisions.

How do I upgrade my OpenAI API tier?+

Tier upgrades are automatic based on cumulative spend. Tier 1 requires $5 lifetime spend, Tier 2 requires $50, Tier 3 requires $100, and Tier 4 requires $500. After meeting the spend threshold, upgrades typically apply within 24 hours. For higher limits before meeting thresholds, contact OpenAI support directly.

Can I get separate rate limits for each of my services?+

Yes. Create separate projects in the OpenAI platform dashboard and generate a unique API key per project. Each project has its own independent rate limits. This prevents one noisy service from starving others and also gives you per-service cost visibility.

What is the most token-efficient embedding model?+

text-embedding-3-small offers the best cost-to-quality ratio for most use cases at $0.02 per million tokens. It supports up to 8,191 input tokens per request and has a separate TPM limit from your chat completion models. text-embedding-3-large is available when you need higher-dimensional embeddings for precision-critical retrieval.

How should I handle rate limit errors in a streaming response?+

A rate limit error on a streaming request will interrupt the stream mid-response. Wrap the stream iteration in a try-catch, detect RateLimitError, wait for the Retry-After duration, then restart the request from the beginning. If you need resumable streams, buffer partial output to a cache key and retry with a context message summarizing what was already generated.

Is there a way to pre-check my remaining quota before sending a request?+

OpenAI does not expose a quota-check endpoint, but every API response includes x-ratelimit-remaining-requests and x-ratelimit-remaining-tokens headers. Log these on every successful response and track a rolling minimum to predict when you are approaching limits before hitting them.

Does using the Assistants API or Batch API bypass rate limits?+

The Batch API (for async file-based requests) runs at 50% of standard cost and has its own separate rate limits, making it ideal for bulk workloads that are not latency-sensitive. The Assistants API still uses the same underlying model rate limits as the Chat Completions API.