← All guides

Claude API 에러 처리 완전 가이드: Rate Limit + 재시도 전략

Claude API 429 Rate Limit 처리법 — exponential backoff, tenacity 재시도, 프로덕션 에러 핸들링 패턴, HTTP 에러 코드 완전 정리.

Claude API 에러 처리 완전 가이드: Rate Limit + 재시도 전략

Claude API에서 가장 자주 만나는 에러는 429(Rate Limit)와 500/529(서버 에러)다. 429는 재시도 가능한 일시적 에러이며, Retry-After 헤더에 대기 시간이 명시된다. 400·401·403은 요청 자체의 문제이므로 재시도해도 같은 에러가 반복된다. 이 가이드에서는 각 HTTP 에러 코드의 원인과 올바른 처리 방법, tenacity를 이용한 자동 재시도 패턴, 그리고 프로덕션 로깅 설정을 실제 Python 코드와 함께 정리한다.


HTTP 에러 코드 일람표

HTTP 상태 에러 타입 원인 처리 방법
400 invalid_request_error 잘못된 JSON, 지원하지 않는 파라미터, 컨텍스트 초과 요청 수정 — 재시도 금지
401 authentication_error 유효하지 않은 API 키 키 확인 — 재시도 금지
403 permission_error 키는 유효하나 권한 부족(모델 미활성화 등) 계정 권한 확인 — 재시도 금지
404 not_found_error 없는 엔드포인트 또는 모델명 오류 모델명/경로 수정 — 재시도 금지
413 request_too_large 요청 본문 32MB 초과 Files API 사용
429 rate_limit_error 분당 요청/토큰 한도 초과 Exponential backoff로 재시도
500 api_error 서버 내부 오류 Backoff 후 재시도(최대 3회)
529 overloaded_error Anthropic 서버 과부하 더 긴 backoff로 재시도

핵심 원칙: 4xx 에러(429 제외)는 요청 자체의 문제다. 코드를 수정하지 않는 한 재시도해도 동일한 에러가 반환된다. 429와 5xx만 재시도 대상이다.


Rate Limit 티어 구조

Anthropic은 계정 사용 이력에 따라 자동으로 티어를 상향한다.

티어 조건 claude-3-5-sonnet RPM TPM
Free 신규 가입(카드 미등록) 5 10,000
Tier 1 카드 등록 완료 50 40,000
Tier 2 $0~$100 결제, 7일 경과 1,000 160,000
Tier 3 $100+ 결제, 14일 경과 2,000 320,000
Tier 4+ $500+ 결제 4,000+ 문의 필요

현재 티어는 console.anthropic.com → Settings → Limits에서 확인할 수 있다. 티어 업그레이드는 자동으로 이루어지며 별도 승인 절차가 없다.

API 응답에는 매번 남은 한도를 알려주는 헤더가 포함된다:

anthropic-ratelimit-requests-remaining: 47
anthropic-ratelimit-tokens-remaining: 38420
retry-after: 12   # 429 응답에만 포함

429 처리: Exponential Backoff with Jitter

방법 1 — 직접 구현 (backoff + jitter)

import anthropic
import time
import random

client = anthropic.Anthropic()

def call_with_backoff(
    messages: list,
    model: str = "claude-sonnet-4-6",
    max_retries: int = 5,
    base_delay: float = 1.0,
) -> anthropic.types.Message:
    """429/5xx에 대해 exponential backoff + jitter로 재시도."""
    for attempt in range(max_retries):
        try:
            return client.messages.create(
                model=model,
                max_tokens=2048,
                messages=messages,
            )
        except anthropic.RateLimitError as e:
            if attempt == max_retries - 1:
                raise  # 최대 재시도 횟수 초과 시 예외 전파

            # retry-after 헤더 우선, 없으면 exponential backoff 사용
            retry_after = 0.0
            if hasattr(e, "response") and e.response:
                retry_after = float(
                    e.response.headers.get("retry-after", 0)
                )
            wait = max(retry_after, base_delay * (2 ** attempt)) + random.uniform(0, 1)
            print(f"[429] Rate limited. {wait:.1f}초 후 재시도 ({attempt + 1}/{max_retries})")
            time.sleep(wait)

        except anthropic.APIStatusError as e:
            if e.status_code >= 500 and attempt < max_retries - 1:
                wait = base_delay * (2 ** attempt) + random.uniform(0, 1)
                print(f"[{e.status_code}] 서버 에러. {wait:.1f}초 후 재시도")
                time.sleep(wait)
            else:
                raise  # 4xx 또는 최종 시도: 예외 전파

방법 2 — tenacity 라이브러리 사용

tenacity는 재시도 로직을 데코레이터로 선언적으로 표현할 수 있어 코드가 깔끔해진다.

pip install tenacity
import anthropic
from tenacity import (
    retry,
    stop_after_attempt,
    wait_exponential_jitter,
    retry_if_exception_type,
    before_sleep_log,
)
import logging

logger = logging.getLogger(__name__)
client = anthropic.Anthropic()

@retry(
    retry=retry_if_exception_type((anthropic.RateLimitError, anthropic.InternalServerError)),
    wait=wait_exponential_jitter(initial=1, max=60, jitter=2),
    stop=stop_after_attempt(5),
    before_sleep=before_sleep_log(logger, logging.WARNING),
)
def create_message(messages: list, model: str = "claude-sonnet-4-6") -> anthropic.types.Message:
    return client.messages.create(
        model=model,
        max_tokens=2048,
        messages=messages,
    )

# 사용 예
response = create_message([{"role": "user", "content": "안녕하세요"}])
print(response.content[0].text)

wait_exponential_jitterinitial * 2^n + random(0, jitter) 공식으로 대기 시간을 계산한다. 여러 클라이언트가 동시에 재시도할 때 thundering herd 문제를 방지하는 데 jitter가 필수적이다.

SDK 내장 재시도도 있다: anthropic.Anthropic(max_retries=4)로 설정하면 SDK가 429·529에 대해 자동으로 재시도한다. 기본값은 2회다. 커스텀 로깅이나 폴백 모델 전환이 필요하지 않다면 SDK 내장 재시도만으로 충분하다.


프로덕션 에러 처리 패턴

에러 종류별 분기 처리

import anthropic

client = anthropic.Anthropic()

def safe_api_call(messages: list) -> str | None:
    """에러 유형별로 적절히 분기하는 프로덕션 패턴."""
    try:
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=2048,
            messages=messages,
        )
        return response.content[0].text

    except anthropic.AuthenticationError:
        # 401: API 키 문제 — 즉시 알람, 재시도 불가
        logger.critical("API 키 인증 실패. 키를 확인하세요.")
        raise

    except anthropic.PermissionDeniedError:
        # 403: 권한 부족 — 모델 활성화 여부 확인
        logger.error("권한 없음. console.anthropic.com에서 모델 활성화 확인.")
        raise

    except anthropic.BadRequestError as e:
        # 400: 요청 오류 — 컨텍스트 초과인지 확인
        if "too long" in str(e).lower() or "context" in str(e).lower():
            logger.warning("컨텍스트 초과. 메시지를 트리밍하세요.")
        raise

    except anthropic.RateLimitError:
        # 429: call_with_backoff()로 위임
        logger.warning("Rate limit 도달. 재시도 로직으로 위임.")
        raise

    except anthropic.APIStatusError as e:
        # 500/529: 서버 에러
        logger.error(f"서버 에러 {e.status_code}. 재시도 필요.")
        raise

비동기(asyncio) + 병렬 처리 시 동시성 제어

병렬 요청이 많을 때 semaphore로 동시 호출 수를 제한해야 TPM 예산을 보호할 수 있다.

import asyncio
import anthropic

async_client = anthropic.AsyncAnthropic()

async def process_batch(items: list[str], max_concurrent: int = 10) -> list[str]:
    semaphore = asyncio.Semaphore(max_concurrent)

    async def process_one(item: str) -> str:
        async with semaphore:
            response = await async_client.messages.create(
                model="claude-haiku-4-5",
                max_tokens=512,
                messages=[{"role": "user", "content": item}],
            )
            return response.content[0].text

    results = await asyncio.gather(
        *[process_one(item) for item in items],
        return_exceptions=True,
    )
    return results

대량 배치(1,000건 이상)는 실시간 Rate Limit 카운팅에서 제외되는 Batch API를 사용하면 50% 가격 할인도 받을 수 있다.


에러 로깅 설정

프로덕션 환경에서는 모든 API 호출의 성공/실패를 구조화된 로그로 기록해야 한다. 재시도 횟수, 소요 시간, 토큰 사용량을 함께 기록하면 비용 분석과 장애 분석이 쉬워진다.

import logging
import time
import anthropic

# 구조화 로그 설정
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(name)s %(levelname)s %(message)s",
)
logger = logging.getLogger("claude_api")

client = anthropic.Anthropic()

def logged_call(
    messages: list,
    model: str = "claude-sonnet-4-6",
    request_id: str | None = None,
) -> anthropic.types.Message:
    """모든 API 호출을 구조화된 로그로 기록."""
    start = time.time()
    try:
        response = client.messages.create(
            model=model,
            max_tokens=2048,
            messages=messages,
        )
        duration_ms = round((time.time() - start) * 1000)
        logger.info(
            "claude_api.success",
            extra={
                "request_id": request_id,
                "model": model,
                "input_tokens": response.usage.input_tokens,
                "output_tokens": response.usage.output_tokens,
                "duration_ms": duration_ms,
                "stop_reason": response.stop_reason,
            },
        )
        return response

    except anthropic.RateLimitError as e:
        duration_ms = round((time.time() - start) * 1000)
        logger.warning(
            "claude_api.rate_limited",
            extra={
                "request_id": request_id,
                "model": model,
                "duration_ms": duration_ms,
                "status_code": 429,
            },
        )
        raise

    except anthropic.APIStatusError as e:
        duration_ms = round((time.time() - start) * 1000)
        logger.error(
            "claude_api.error",
            extra={
                "request_id": request_id,
                "model": model,
                "status_code": e.status_code,
                "error_type": type(e).__name__,
                "duration_ms": duration_ms,
            },
        )
        raise

로그 레벨 가이드:

FastAPI 서버에 Claude API를 연동하는 전체 예제는 FastAPI + Claude API 통합 가이드에서 확인할 수 있다.


Agent SDK Cookbook — 에러 처리가 내장된 실전 Claude API 패턴 12가지를 Python 완성 코드로 제공한다. Rate limit 재시도, 스트리밍 복구, 컨텍스트 트리밍, 배치 처리까지 복사해서 바로 쓸 수 있는 레시피 모음.

→ Agent SDK Cookbook 받기 — $29

30일 환불 보장. 즉시 다운로드.


Frequently Asked Questions

Claude API 429 에러가 발생하면 얼마나 기다려야 하나요?

429 응답에는 retry-after 헤더가 포함되어 있으며 정확한 대기 시간(초)을 알려준다. 헤더가 없을 경우 1초부터 시작하는 exponential backoff(1 → 2 → 4 → 8 → 16초)를 사용하면 된다. Anthropic SDK의 기본 재시도 로직도 이 방식을 따른다.

429와 529 에러의 차이는 무엇인가요?

429(rate_limit_error)는 내 계정의 분당 한도를 초과했을 때 발생한다. 내 사용량 문제이므로 Rate Limit 창이 초기화될 때까지 기다려야 한다. 529(overloaded_error)는 Anthropic 서버 전체의 부하가 높을 때 발생하는 에러로, 내 한도와는 무관하다. 짧은 backoff 후 재시도하면 대부분 해결된다.

400 에러도 재시도하면 해결되나요?

아니다. 400(invalid_request_error)은 요청 자체가 잘못된 경우다. 컨텍스트 창 초과, 잘못된 JSON 형식, 지원하지 않는 파라미터 등이 원인이며, 동일한 요청을 재시도해도 항상 같은 400이 반환된다. 요청 내용을 수정한 후에야 다시 호출해야 한다.

SDK 내장 재시도와 직접 구현한 재시도 중 어떤 것을 써야 하나요?

기본적으로 SDK 내장 재시도(anthropic.Anthropic(max_retries=4))로 충분하다. 커스텀 로깅, 폴백 모델 전환(예: Sonnet 실패 시 Haiku로 전환), 또는 circuit breaker 패턴이 필요한 경우에만 직접 구현을 추가한다. tenacity를 사용하면 재시도 로직을 선언적으로 표현할 수 있어 코드 가독성이 높아진다.

도구와 자료