# OpenAI 429 on bulk jobs: compute the token budget before you launch

## The symptom

A backfill, migration, or eval script runs fine for a minute, then 429s cascade and throughput collapses. Retries with backoff keep it alive but slow. The job was never budgeted against the limits.

## Confirm the cause

Do the arithmetic before blaming the API:

1. **Estimate per-request cost.** Tokens per request times requests per minute at your concurrency. Example: 50 workers each doing one 4k-token request every 10 seconds is 300 requests per minute and 1.2M tokens per minute.
2. **Read the headers on the 429.** `x-ratelimit-remaining-requests` vs `x-ratelimit-remaining-tokens` tells you which limit binds. Token-bound (big prompts) and request-bound (many small calls) need different fixes.
3. **Check the reset headers.** `x-ratelimit-reset-requests` and `x-ratelimit-reset-tokens` tell you when each window resets, so you can see whether you are slightly over or an order of magnitude over.

## The fix

- **Set concurrency from the budget, not from vibes.** concurrency = floor(limit_per_minute / (tokens_per_request * safety_margin)). Leave a 20 to 30 percent margin for retries and variance.
- **Throttle to a fixed rate.** A token bucket or a simple sleep between dispatches beats "spawn N workers and hope". Smooth the start too: staggering worker startup avoids the slow_down ramp guard (see the slow_down skill).
- **Shrink the per-request cost.** Shorter prompts, smaller output budgets, cheaper models for bulk classification or extraction. Halving tokens per request doubles the requests you can afford.
- **Consider the Batch API** for non-urgent bulk work: 24-hour window, 50 percent discount, separate queue limits.

## Verify the fix

Dry-run the job at the computed concurrency and confirm zero 429s over 10 minutes. Log the remaining-tokens header periodically; it should stay comfortably above zero. Alert on 429 rate during the real run.