문제 해결가이드
AI API의 429 Too Many Requests: 백오프를 포함한 재시도
AI API의 429가 의미하는 것과 처리 방법: 지터를 포함한 지수 백오프, Retry-After, 에이전트 동시성, Python 및 JavaScript 코드 포함.

이 페이지의 내용
AI API에서 429 Too Many Requests은 너무 많이, 너무 빠르게 요청을 보냈거나 계정 한도에 도달해 요청이 거부되었다는 뜻입니다. 해결책은 지수 백오프와 지터를 사용해 기다렸다가 재시도하고, 응답에 Retry-After 헤더가 있으면 이를 사용하며, 에이전트가 병렬로 보내는 요청 수를 제한하는 것입니다. 실제로 지출 또는 잔액 한도인 429는 재시도하지 마세요. 기다린다고 해결되지 않습니다.
이 가이드는 OpenAI와 Anthropic의 공식 레이트 리밋 가이드를 기반으로 하며, 둘 다 2026년 10월 22일에 확인했습니다. 두 페이지 모두 최종 수정일을 표시하지 않습니다.
429의 의미#
OpenAI는 429를 레이트 리밋 초과로 인해 거부된 요청(트래픽이 너무 빠르게 증가할 때 코드 slow_down와 함께 rate_limit_error), 또는 하드 지출 한도에 도달했기 때문이라고 설명합니다. Anthropic도 429에 rate_limit_error을 사용합니다. 지출 한도 429에는 retry-after 헤더가 없으며 접근이 재개될 때까지 계속 실패합니다.
혼동하기 쉬운 인접한 두 가지가 있습니다. OpenAI의 503 server_과 Anthropic의 529 overloaded_은 과부하 상태이며, 당신의 레이트 리밋이 아닙니다. 보통 동일한 재시도 루프로 처리하지만, 원인에 대해서는 다른 의미를 가집니다.
따라서 세 가지 경우가 있으며, 각각 다른 대응이 필요합니다:
표를 오른쪽으로 스크롤하세요 →
| 응답 | 원인 | 해야 할 일 |
|---|---|---|
| 429 레이트 리밋 | 분당 요청 또는 토큰이 너무 많음 | 기다린 후 백오프로 재시도 |
| 429 지출 또는 잔액 한도 | 계정 한도 도달 | 재시도하지 말고 한도를 조정하거나 잔액을 충전 |
| 503 / 529 과부하 | 제공자 측이 바쁨 | 상한을 두고 백오프로 재시도 |
잔액이 소진되었다면 대신 "not enough tokens" 오류를 읽어보세요. 타임아웃과 연결 거부 같은 네트워크 수준 실패는 일반적인 연결 오류를 참고하세요.
지연 시간을 추측하기 전에 헤더를 읽으세요#
OpenAI는 레이트 리밋 응답에 다음 헤더를 문서화합니다: Retry-After, 그리고 x-, x-, x-와 -tokens 변형. 다른 API는 이 중 일부만 보내거나 전혀 보내지 않을 수 있으므로, 클라이언트는 Retry-After이 있으면 사용하고 없으면 자체 스케줄로 대체하도록 작성하세요.
OpenAI의 재시도 지침을 요약하면:
Retry-After을 최소 대기 시간으로 취급하고 그 위에 작은 무작위 지연을 더하세요.- 없으면 지터를 포함한 지수 백오프를 사용하세요.
- 시도 횟수와 총 재시도 시간 모두에 상한을 두세요.
- SDK의 재시도 위에 자체 재시도를 겹치지 마세요.
- 할당량 또는 결제 오류는 재시도하지 마세요.
- 실패한 요청도 분당 한도에 포함되므로, 공격적인 루프는 상황을 악화시킵니다.
- 스트리밍의 경우 이미 출력을 소비한 후에는 요청을 재생하지 마세요.
Python: 지터를 포함한 백오프#
공식 openai 패키지는 모든 OpenAI 호환 엔드포인트에서 작동합니다. SDK 자체 재시도(max_retries=0)를 꺼서 자체 재시도와 곱해지지 않도록 하세요. 모델 id는 CheapTokens 문서에서 가져옵니다. id는 제공자마다 다릅니다.
import os
import random
import time
from openai import OpenAI, APIStatusError
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
max_retries=0, # we retry ourselves
)
RETRYABLE = {429, 500, 502, 503, 504}
def chat_with_retry(model, messages, max_attempts=5, max_total=60.0):
started = time.monotonic()
for attempt in range(max_attempts):
try:
return client.chat.completions.create(model=model, messages=messages)
except APIStatusError as err:
last = attempt == max_attempts - 1
if err.status_code not in RETRYABLE or last:
raise
header = err.response.headers.get("retry-after")
if header and header.replace(".", "", 1).isdigit():
delay = float(header) + random.uniform(0, 1) # minimum wait + jitter
else:
delay = random.uniform(0, min(30.0, 2.0 ** attempt)) # full jitter
if time.monotonic() - started + delay > max_total:
raise
time.sleep(delay)헤더가 없을 때 지연의 상한은 1, 2, 4, 8초로 증가하며(최대 30), random.uniform은 그 아래에서 무작위 값을 선택하므로 많은 워커가 동시에 깨어나지 않습니다. Retry-After은 HTTP 날짜일 수도 있습니다. 위 코드는 숫자 형식을 처리하고 그렇지 않으면 백오프로 대체합니다.
JavaScript: 동일한 루프#
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.cheaptokens.dev/v1",
apiKey: process.env.CHEAPTOKENS_API_KEY,
maxRetries: 0, // we retry ourselves
});
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function chatWithRetry(model, messages, maxAttempts = 5, maxTotalMs = 60000) {
const started = Date.now();
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await client.chat.completions.create({ model, messages });
} catch (err) {
const last = attempt === maxAttempts - 1;
if (!RETRYABLE.has(err.status) || last) throw err;
const header = Number(err.headers?.get?.("retry-after"));
const delayMs = Number.isFinite(header) && header > 0
? header * 1000 + Math.random() * 1000 // minimum wait + jitter
: Math.random() * Math.min(30000, 2 ** attempt * 1000); // full jitter
if (Date.now() - started + delayMs > maxTotalMs) throw err;
await sleep(delayMs);
}
}
}작동하면 레이트 리밋된 호출은 단순히 더 오래 걸린 후 정상 완료를 반환합니다. 마지막 시도 후에도 실패하면 원래 오류가 발생하므로 코드에서 로그를 남기거나 작업을 나중을 위해 큐에 넣을 수 있습니다.
에이전트가 429를 처리하는 방법#
에이전트 루프는 많은 짧은 호출을 보내며, 여러 에이전트나 도구 호출이 동시에 실행될 수 있습니다. 재시도만으로는 충분하지 않습니다. 소스에서 요청 속도를 낮추세요:
- 동시성에 상한을 두세요. 3–5개의 병렬 호출 세마포어가 합리적인 시작점입니다. 오류가 드물게 유지될 때만 늘리세요.
- 하나의 리미터를 공유하세요. 여러 워커가 같은 키를 사용하면 같은 한도를 공유하므로 워커당 하나의 재시도 루프로는 충분하지 않습니다. 프로세스에 하나의 큐 또는 하나의 세마포어를 사용하세요.
- 전역적으로 백오프하세요. 한 호출이 지연과 함께 429를 받으면 실패한 호출만이 아니라 전체 풀을 그 시간 동안 일시 중지하세요.
- 요청을 더 작게 유지하세요. 요청당 토큰이 적으면 분당 토큰 한도에 대한 압력이 줄어듭니다. 기록과 도구 출력을 줄이세요. 토큰이 어디로 가는지는 AI 에이전트 비용 추정 방법을 참고하세요.
- 재시도할 수 없는 오류에서는 중단하세요. 401, 400 또는 잔액 오류는 스스로 해결되지 않습니다. 빠르게 실패하고 보고하세요.
import asyncio
limit = asyncio.Semaphore(4) # at most 4 requests in flight
async def run(job):
async with limit:
return await call_model(job) # your async call with the retry loop aboveCheapTokens에서 에이전트 루프 실행#
많은 짧은 호출이 있는 에이전트 루프의 경우 CheapTokens는 MiniMax-M2.7을 제공합니다: 하루 중 언제든 동일한 가격, 180K 컨텍스트, 답변당 최대 8,192 토큰, 모든 모델에 하나의 잔액. 출력 비용은 1M 토큰당 $0.15이며, 입력과 출력을 합산한 총액 기준으로 OpenRouter보다 저렴합니다. OpenAI 호환 엔드포인트 https:/를 통해 작동합니다. 위 코드는 이미 이 엔드포인트를 가리키고 있습니다. CheapTokens 문서에서 모델 id를 설정하고 실행하세요.
500,000 입력, 100,000 출력 워크로드의 실시간 가격:
| 모델 | 입력 $/1M | 출력 $/1M | 비용 | OpenRouter |
|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.014 | $0.0282 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.0175 | $0.0394 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.04 | $0.27 |
총 입력 500,000개 및 출력 100,000개 토큰입니다. 100만 토큰당 가격은 현재 기준이며 당사 요금제에 따라 함께 변경됩니다. OpenRouter 열은 동일한 작업량에 대한 참고 가격을 보여줍니다.
모든 가격, 설정 문서를 참고하고, 키를 생성해 첫 요청을 보내세요.
그래도 실패하면#
- 첫 요청에서 429. 응답 본문의 오류 유형과 메시지를 읽으세요. 지출 또는 계정 한도 429는 한도가 올라가거나 접근이 재개될 때까지 재시도에서 계속 실패하는 반면, 레이트 리밋 429는 일시 중지 후 해소됩니다.
- 긴 루프에서 429가 계속 발생. 재시도를 겹치고 있을 수 있습니다: SDK 재시도 곱하기 자체 재시도. 예제처럼 SDK를 0으로 설정하세요.
- 배치 시작 시 오류가 몰려 발생. 모든 워커를 한 번에 실행하지 말고 점진적으로 늘리세요. OpenAI는 너무 빠르게 증가하는 트래픽에 대해
slow_down을 보고합니다. - 스트리밍 호출이 중간에 실패. 출력을 소비한 후에는 무작정 재생하지 마세요. 전체 답변을 다시 시작할지 아니면 지금까지의 내용에서 계속할지 결정하세요.
- 응답에 Retry-After 없음. 코드가 지터를 포함한 백오프로 대체하므로 변경이 필요하지 않습니다.



