요청 제한 · 멱등성

안정적인 운영을 위한 요청 제한과, 네트워크 오류가 나도 중복 발송 없이 안전하게 재시도하는 방법(멱등성)을 설명합니다.

요청 제한 (API 키 단위)

항목한도초과 시
초당 요청평균 10 req/s (순간 최대 20)429 RATE_LIMITED
분당 요청300 req/min429 RATE_LIMITED
동시 처리 중인 발송 요청4건429 CONCURRENCY_LIMIT
한 요청의 수신자 수아래 표 참고422 TOO_MANY_RECIPIENTS
JSON 본문 크기최대 2MB413 PAYLOAD_TOO_LARGE
MMS 이미지파일당 5MB, 메시지당 3장422 INVALID_FILE
예약 시각현재 ~ 180일 이내422 INVALID_SEND_AT
발송 목록 조회 기간한 번에 최대 31일422 INVALID_REQUEST

한 요청의 수신자 수

구분채널한도
동일 문구
(모든 수신자에게 같은 본문·변수)
SMS1,000명
LMS / MMS1,000명
알림톡1,000명
브랜드메시지1,000명
개별 문구
(수신자마다 본문·변수가 다름)
SMS1,000명
LMS / MMS500명
알림톡1,000명
브랜드메시지1,000명

LMS/MMS 개별 문구는 JSON 2MB 한도(한글 장문 기준)를 고려해 500명으로 제한합니다. 초과 시 422 TOO_MANY_RECIPIENTS 이며, 응답 details.max 에 허용 한도가 포함됩니다.

대량 발송은 “요청 수”가 아니라 “수신자 수”로 늘리세요. 동일 문구는 요청 1건에 최대 1,000명, LMS/MMS 개별 문구는 500명까지 담을 수 있습니다.

응답 헤더

모든 인증된 응답에 현재 분당 한도 상태가 포함됩니다.

헤더의미
RateLimit-Limit분당 허용 요청 수
RateLimit-Remaining현재 윈도에서 남은 요청 수
RateLimit-Reset윈도가 초기화되기까지 남은 초
Retry-After(429/503 일 때) 다시 시도하기까지 기다릴 초
X-Request-Id요청 식별자. 본문의 request_id 와 동일. 문의 시 알려 주세요.

429 를 받았을 때

Python — 지수 백오프 예시
import time, random, requests

def post_with_retry(url, headers, payload, max_try=5):
    for attempt in range(max_try):
        r = requests.post(url, headers=headers, json=payload, timeout=15)
        if r.status_code not in (429, 502, 503, 504):
            return r
        wait = float(r.headers.get("Retry-After", 2 ** attempt))
        time.sleep(wait + random.random())      # 지터 추가
    return r

단, 재시도하는 요청에는 반드시 같은 Idempotency-Key 를 사용하세요. 그래야 첫 요청이 실제로는 접수되었는데 응답만 유실된 경우에도 중복 발송되지 않습니다.

멱등성 (Idempotency-Key)

발송 요청(POST /v1/messages/*)에 Idempotency-Key 헤더를 넣으면, 같은 키로 다시 요청해도 한 번만 발송됩니다.

  • 키 형식: 영문·숫자·_ - . : 1~64자. 주문번호처럼 업무 단위로 고유한 값을 권장합니다.
  • 키는 회원 단위로 유일하며 접수 후 90일간 보관됩니다.
상황동작
같은 키 + 같은 본문, 이전 요청이 접수 완료새로 발송하지 않고 원래 접수 결과를 반환 (202, message_id·status·accepted·client_ref 와 "replayed": true, 헤더 Idempotent-Replayed: true. 금액·rejected 상세는 생략되므로 필요하면 GET /v1/messages/{id} 로 조회)
같은 키 + 같은 본문, 이전 요청이 아직 처리 중409 REQUEST_IN_PROGRESS (Retry-After 후 재시도)
같은 키 + 같은 본문, 이전 요청이 오류로 종료새 요청으로 다시 처리 (요금은 접수 성공 시에만 차감)
같은 키 + 다른 본문422 IDEMPOTENCY_KEY_REUSED

Idempotency-Key 없이 타임아웃 재시도를 하면 중복 발송될 수 있습니다. 발송 호출에는 항상 키를 붙이세요.

client_ref (고객사 참조키)

요청 본문의 client_ref(요청 단위)와 recipients[].ref(수신자 단위)에 고객사 주문번호 등을 넣으면, 조회 응답과 Webhook 에 그대로 돌아옵니다. 1~64자, 공백 없는 문자열입니다.

  • 요청 단위: GET /v1/messages?client_ref=… 로 발송 건 검색
  • 수신자 단위: 결과 조회·Webhook 의 client_ref 필드
  • client_ref 는 중복 방지 용도가 아닙니다. 중복 방지는 Idempotency-Key 를 사용하세요.

dry_run (검증 전용)

본문에 "dry_run": true 를 넣으면 저장·과금·발송 없이 모든 검증(발신번호, 수신번호, 길이, 금칙어, 잔액 등)과 예상 금액만 계산해 200 으로 응답합니다. 연동 테스트와 사전 점검에 사용하세요.

JSON — dry_run 응답
{
  "request_id": "req_…",
  "data": {
    "dry_run": true,
    "channel": "sms",
    "accepted": 2,
    "rejected_count": 1,
    "rejected": [{ "index": 2, "to": "0101234", "reason": "INVALID_NUMBER" }],
    "amount": 21.0
  }
}

권장 운영 방식

  • 타임아웃은 연결 5초 / 응답 15~30초로 설정하세요. (서버 처리 한도 60초)
  • 5xx·네트워크 오류·429 는 같은 Idempotency-Key 로 재시도하고, 4xx(429 제외)는 요청을 수정해야 하므로 재시도하지 마세요.
  • 결과는 폴링보다 Webhook 수신을 권장합니다. 누락 대비로 하루 1회 정도 조회 API 로 대사하세요.