Rate limits, quotas, and tiers
On this page
Rate limits, quotas, and tiers
Rate limits define how many requests you can send to the API per minute. Understanding and managing these limits is critical to avoiding service interruptions in production. The nightmare scenario is always the same: an integration that worked flawlessly in development, where you sent three requests per minute, goes live and suddenly processes a queue of several thousand documents. 429 errors arrive in a flood, and half the processing is lost because throughput wasn’t anticipated.
The tier system
OpenAI organizes limits into tiers (levels) based on your billing history. You don’t choose your tier; you unlock it by accumulating spend and waiting out a time window. This progression has an important practical consequence: an account created just for a client demo won’t have the same capacity as your production account, even if the code is identical.
# Tiers determine your limits
# Tier 1: After first successful payment
# Tier 2: After $50 cumulative spend + 7 days
# Tier 3: After $100 cumulative spend + 7 days
# Tier 4: After $250 cumulative spend + 14 days
# Tier 5: After $1000 cumulative spend + 30 days
# Limits increase significantly with each tier
Types of rate limits
Three counters apply simultaneously, and the first one you hit blocks you. Processing short texts will saturate RPM long before TPM; conversely, analyzing long documents will exhaust TPM even if you’ve only sent a few dozen requests. Always check the order of magnitude of your prompts before sizing your parallelism.
# 1. RPM — Requests Per Minute
# 2. TPM — Tokens Per Minute
# 3. RPD — Requests Per Day
# Order of magnitude for Tier 3 (check your actual limits
# at platform.openai.com > Settings > Limits):
# RPM: 5,000 requests/minute
# TPM: 2,000,000 tokens/minute
# RPD: 10,000 requests/day
Reading rate limit headers
You don’t need to guess your consumption: every response carries it in its HTTP headers. The SDK hides them by default to return only the parsed object, but with_raw_response gives you access to the raw response, and parse() reconstructs the usual object. A dashboard fed by these values warns you as you approach the ceiling instead of letting you discover it via an error.
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5.6-terra",
input="Hello!"
)
# Access headers via the raw HTTP response
# Using with_raw_response mode
raw = client.responses.with_raw_response.create(
model="gpt-5.6-terra",
input="Hello!"
)
print(f"RPM limit: {raw.headers.get('x-ratelimit-limit-requests')}")
print(f"RPM remaining: {raw.headers.get('x-ratelimit-remaining-requests')}")
print(f"TPM limit: {raw.headers.get('x-ratelimit-limit-tokens')}")
print(f"TPM remaining: {raw.headers.get('x-ratelimit-remaining-tokens')}")
print(f"Reset in: {raw.headers.get('x-ratelimit-reset-requests')}")
response = raw.parse()
print(f"Response: {response.output_text}")
Client-side rate limiting
It’s better to slow yourself down than to get rejected. Two implementations cover most needs.
Limiting throughput with a semaphore
The first combines a semaphore, which caps the number of simultaneous calls, with a delay calculated from the target throughput. In the example, fifty summaries are launched at once but only five proceed in parallel, spaced by the delay corresponding to 100 requests per minute. The calling code remains a simple asyncio.gather: all the discipline is encapsulated in the class.
import asyncio
from openai import AsyncOpenAI
async_client = AsyncOpenAI()
class RateLimiter:
"""Limits the number of simultaneous requests."""
def __init__(self, max_concurrent: int = 10, rpm: int = 500):
self.semaphore = asyncio.Semaphore(max_concurrent)
self.rpm = rpm
self.delay = 60.0 / rpm # Delay between each request
async def call(self, prompt: str) -> str:
async with self.semaphore:
await asyncio.sleep(self.delay)
response = await async_client.responses.create(
model="gpt-5.6-terra",
input=prompt
)
return response.output_text
async def batch_processing():
limiter = RateLimiter(max_concurrent=5, rpm=100)
prompts = [f"Summarize concept {i}" for i in range(50)]
tasks = [limiter.call(p) for p in prompts]
results = await asyncio.gather(*tasks)
for i, r in enumerate(results):
print(f"Result {i}: {r[:50]}...")
# asyncio.run(batch_processing())
Simple token bucket
The token bucket is more flexible because it allows bursts. The bucket fills at a constant rate and empties with each call: if your application has been idle for a minute, it can restart at full speed for fifty requests, then naturally fall back to the fill rate. This is exactly the behavior you want for a service that alternates between peaks and lulls, and it works in multithreaded environments thanks to the lock.
import time
import threading
class TokenBucket:
"""Rate limiter based on the token bucket algorithm."""
def __init__(self, tokens_per_second: float, max_capacity: int):
self.tokens_per_second = tokens_per_second
self.max_capacity = max_capacity
self.tokens = max_capacity
self.last_refill = time.monotonic()
self.lock = threading.Lock()
def wait(self, tokens_needed: int = 1):
"""Waits until enough tokens are available."""
while True:
with self.lock:
now = time.monotonic()
elapsed = now - self.last_refill
self.tokens = min(
self.max_capacity,
self.tokens + elapsed * self.tokens_per_second
)
self.last_refill = now
if self.tokens >= tokens_needed:
self.tokens -= tokens_needed
return
time.sleep(0.1)
# Usage: max 50 requests per minute
bucket = TokenBucket(tokens_per_second=50/60, max_capacity=50)
def limited_call(prompt: str) -> str:
bucket.wait(1)
response = client.responses.create(
model="gpt-5.6-terra",
input=prompt
)
return response.output_text
Strategies for high volumes
1. Distribute across multiple projects
Each project has its own rate limits. If you hit your ceiling while already on a high tier, distributing calls across multiple projects mechanically multiplies available capacity. The round-robin below illustrates the principle; in production, move the cycle outside the function so it persists between calls.
from openai import OpenAI
# Each project has its own rate limits
clients = [
OpenAI(project="proj-aaaa"), # Project 1
OpenAI(project="proj-bbbb"), # Project 2
OpenAI(project="proj-cccc"), # Project 3
]
def round_robin_call(prompt: str, clients: list) -> str:
"""Distributes calls across multiple projects."""
import itertools
client_cycle = itertools.cycle(clients)
client = next(client_cycle)
return client.responses.create(
model="gpt-5.6-terra",
input=prompt
).output_text
2. Use the right model for the right volume
Model choice is also a capacity decision, not just a quality one. Reserving the heaviest models for critical tasks and routing volume to a model with higher limits frees up throughput where you actually need it.
# Economy models (gpt-5.6-luna) have the highest limits
# Use them for high-volume tasks
# The flagship model (gpt-5.6-sol) has the lowest limits
# Reserve it for critical tasks
3. Queuing
Finally, queuing decouples request production from consumption. A single thread dequeues at a fixed pace while the rest of the application submits freely; the caller blocks on an event until its result arrives. This pattern absorbs peaks without ever exceeding the configured throughput.
import queue
import threading
class APIQueue:
"""Queue for API calls."""
def __init__(self, rpm: int = 100):
self.queue = queue.Queue()
self.delay = 60.0 / rpm
self.running = True
self.worker = threading.Thread(target=self._process, daemon=True)
self.worker.start()
def _process(self):
while self.running:
try:
prompt, future = self.queue.get(timeout=1)
response = client.responses.create(
model="gpt-5.6-terra",
input=prompt
)
future["result"] = response.output_text
future["event"].set()
time.sleep(self.delay)
except queue.Empty:
continue
def submit(self, prompt: str) -> str:
future = {"result": None, "event": threading.Event()}
self.queue.put((prompt, future))
future["event"].wait()
return future["result"]
Key takeaways
- Rate limits apply simultaneously in RPM, TPM, and RPD
- Your tier determines your limits—it increases with your cumulative spend
- Read
x-ratelimit-*headers to know your consumption in real time - Implement a client-side rate limiter to avoid 429 errors
- Distribute load across projects for high volumes
- Luna offers the highest rate limits for bulk processing