K-OTP

오류

오류 응답 형식, HTTP 상태 코드별 의미와 재시도 여부

2xx가 아닌 모든 응답은 같은 JSON 형식을 사용합니다.

{
	"defined": true,
	"code": "CONFLICT",
	"status": 409,
	"message": "Conflict",
	"data": {}
}
필드설명
defined스펙에 정의된 오류면 true, 예상하지 못한 오류면 false입니다. false여도 형식은 같습니다.
code오류 코드 문자열(아래 표). 분기 처리에는 message가 아니라 code를 사용하세요.
statusHTTP 상태 코드와 같은 값입니다.
message사람이 읽기 위한 설명입니다. 문구는 바뀔 수 있습니다.
data추가 정보(선택). 402(data.code)와 429(data.limit, data.policy, data.retryAfterMs)에 정의되어 있습니다.

POST /v1/verify의 검증 실패(코드 불일치, 만료 등)는 오류가 아니라 200 + verified: false입니다. 검증 결과를 참고하세요.

상태 코드

상태code주요 원인재시도
400BAD_REQUEST요청 형식 오류, 멱등 키 누락·공백, 헤더와 본문의 멱등 키 불일치, 알 수 없는 템플릿·템플릿 변수, expiresInSec/maxAttempts/cost 범위 초과, code가 6자리 숫자가 아님, 잘못된 limit·cursor·기간아니요. 요청을 고치세요.
401UNAUTHORIZEDAuthorization: Bearer 키가 없음, 형식 오류, 비활성(폐기·만료) 또는 인식할 수 없는 키아니요. 키를 확인하세요.
402PAYMENT_REQUIRED크레딧 부족. data.code가 INSUFFICIENT_CREDIT 또는 OVERDRAFT_LIMIT_EXCEEDED (POST /v1/issue만 해당)충전 후 재시도
403FORBIDDEN키에 필요한 scope가 없음, pk_ 키로 서버 전용 API 호출, pk_ 키 요청에 Origin이 없거나 허용 목록과 불일치아니요. 키 설정을 확인하세요.
404NOT_FOUND해당 앱에 없는 issueId(다른 앱의 것 포함) 또는 templateId아니요
409CONFLICT같은 멱등 키를 다른 요청 본문으로 재사용, 또는 그 키의 issue가 이미 새 발급으로 교체됨아니요
429TOO_MANY_REQUESTSPOST /v1/issue·POST /v1/verify가 키당·IP당·휴대폰 번호당 호출 제한을 넘음(호출 제한 참고). data.limit은 perKey, perIp, perPhone(verify는 perKey만)예. Retry-After·data.retryAfterMs 뒤에 같은 멱등 키로
500INTERNAL_SERVER_ERROR예상하지 못한 서버 오류같은 멱등 키로 백오프 재시도
503SERVICE_UNAVAILABLE인증 또는 내부 의존성 일시 장애, pk_ 키에서 호출 제한기를 쓸 수 없음(요청을 거절하는 fail-closed, Retry-After 포함), 같은 멱등 키의 이전 요청이 아직 처리 중같은 멱등 키로 백오프 재시도

테스트 모드에는 400 BAD_REQUEST 원인이 두 가지 더 있습니다. data.code가 TEST_NUMBER_REQUIRED이면 테스트 키로 실제 번호를 보낸 것이고, TEST_NUMBER_IN_LIVE_MODE이면 라이브 키로 매직 테스트 번호를 보낸 것입니다. 테스트 모드를 참고하세요.

발급에는 400 BAD_REQUEST 원인이 두 가지 더 있으며 data.code로 구분합니다. CHANNEL_NOT_SUPPORTED는 channel: "sms"를 요청한 경우(alimtalk만 받습니다)이고, TEMPLATE_NOT_AVAILABLE은 템플릿에 승인된 알림톡 템플릿이 아직 없는 경우입니다(채널과 SMS 대체 발송 참고).

호출 제한: 429와 Retry-After

호출 제한을 넘은 요청은 아무것도 발송하기 전에 429 TOO_MANY_REQUESTS로 거절되며, 제한 횟수(또는 검증 시도 횟수)에 포함되지 않습니다.

{
	"defined": true,
	"code": "TOO_MANY_REQUESTS",
	"status": 429,
	"message": "Too Many Requests",
	"data": { "limit": "perPhone", "policy": "key", "retryAfterMs": 1500 }
}
필드의미
data.limit요청을 거절한 규칙: perKey, perIp, perPhone(perIp와 perPhone은 발급에만 적용됩니다).
data.policyAPI 키에 설정한 규칙이면 key, pk_ 기본값이나 플랫폼 상한이면 platform입니다.
data.retryAfterMs재시도가 허용될 때까지 남은 시간(밀리초).
Retry-After같은 대기 시간을 초 단위(올림, 최소 1)로 담은 헤더입니다. 허용된 브라우저 출처에서도 읽을 수 있어 pk_ 브라우저 클라이언트가 따를 수 있습니다.

그만큼 기다린 뒤 issue는 같은 멱등 키로 다시 시도하세요.

503 SERVICE_UNAVAILABLE는 호출 제한기 자체를 쓸 수 없어 요청을 거절한 경우(pk_ 키)에만 Retry-After가 포함됩니다. 그 밖의 503에는 헤더가 없으므로 백오프로 재시도하세요. 두 헤더는 API 레퍼런스의 엔드포인트별 페이지에 나와 있습니다.

엔드포인트별 오류 설명은 API 레퍼런스의 각 페이지에 있습니다.

처리 예제

type ApiError = {
	defined: boolean;
	code: string;
	status: number;
	message: string;
	data?: { code?: "INSUFFICIENT_CREDIT" | "OVERDRAFT_LIMIT_EXCEEDED"; retryAfterMs?: number };
};

function handleIssueError(error: ApiError) {
	switch (error.code) {
		case "PAYMENT_REQUIRED":
			// 운영 알림: 크레딧 충전 필요 (error.data?.code)
			return "잠시 후 다시 시도해 주세요.";
		case "CONFLICT":
			// 멱등 키를 다른 요청에 재사용했는지 확인
			return "요청을 처리할 수 없습니다.";
		case "TOO_MANY_REQUESTS":
			// error.data?.retryAfterMs(Retry-After 헤더)만큼 기다린 뒤 같은 멱등 키로 재시도
			return "시도가 너무 많습니다. 잠시 후 다시 시도해 주세요.";
		case "SERVICE_UNAVAILABLE":
		case "INTERNAL_SERVER_ERROR":
			// 같은 멱등 키로 재시도 (/ko/guides/idempotency)
			return "잠시 후 다시 시도해 주세요.";
		default:
			return "인증번호를 보낼 수 없습니다.";
	}
}

이 페이지의 내용