요청 제한 · 멱등성
안정적인 운영을 위한 요청 제한과, 네트워크 오류가 나도 중복 발송 없이 안전하게 재시도하는 방법(멱등성)을 설명합니다.
요청 제한 (API 키 단위)
| 항목 | 한도 | 초과 시 |
|---|---|---|
| 초당 요청 | 평균 10 req/s (순간 최대 20) | 429 RATE_LIMITED |
| 분당 요청 | 300 req/min | 429 RATE_LIMITED |
| 동시 처리 중인 발송 요청 | 4건 | 429 CONCURRENCY_LIMIT |
| 한 요청의 수신자 수 | 아래 표 참고 | 422 TOO_MANY_RECIPIENTS |
| JSON 본문 크기 | 최대 2MB | 413 PAYLOAD_TOO_LARGE |
| MMS 이미지 | 파일당 5MB, 메시지당 3장 | 422 INVALID_FILE |
| 예약 시각 | 현재 ~ 180일 이내 | 422 INVALID_SEND_AT |
| 발송 목록 조회 기간 | 한 번에 최대 31일 | 422 INVALID_REQUEST |
한 요청의 수신자 수
| 구분 | 채널 | 한도 |
|---|---|---|
| 동일 문구 (모든 수신자에게 같은 본문·변수) | SMS | 1,000명 |
| LMS / MMS | 1,000명 | |
| 알림톡 | 1,000명 | |
| 브랜드메시지 | 1,000명 | |
| 개별 문구 (수신자마다 본문·변수가 다름) | SMS | 1,000명 |
| LMS / MMS | 500명 | |
| 알림톡 | 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 를 받았을 때
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 으로 응답합니다. 연동 테스트와 사전 점검에 사용하세요.
{
"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
}
}