K-OTP

크레딧과 결제

선불 크레딧, 잔액 부족 시 402, 잔액·원장 조회

K-OTP는 선불 크레딧 방식입니다. 콘솔에서 조직 지갑에 크레딧을 충전하고, 발송 1건마다 크레딧 1개가 차감됩니다. 크레딧은 화폐가 아닌 수량 단위이며 API 응답의 currency는 항상 CREDIT입니다.

  • 지갑은 조직 단위입니다. 조직의 모든 앱(과 그 키)이 하나의 지갑을 공유합니다. 사용량과 발급 이력은 앱별로 유지됩니다.
  • 충전은 콘솔에서 조직 소유자와 관리자가 합니다. 공개 API로는 충전할 수 없습니다.

크레딧 1개 = OTP 발송 1건

OTP 발송 1건(발급 1건의 발송)은 채널과 관계없이 정확히 1 크레딧입니다. 카카오 알림톡 발송은 알림톡으로 전달되든 SMS 대체 발송으로 전달되든 1 크레딧이며, 대체 발송에 크레딧이 추가로 차감되지 않고 smsFallback: false로 꺼도 가격은 같습니다. 채널과 SMS 대체 발송을 참고하세요.

크레딧 팩

팩크레딧
미니250 (보너스 없음)
스타터1,250 (기본 1,000 + 보너스 250)
스케일6,250 (기본 5,000 + 보너스 1,250)

스타터와 스케일은 기본 크레딧의 25%를 보너스 크레딧으로 더 드립니다. 그 전에 구매한 크레딧은 구매 당시의 팩 구성을 그대로 유지합니다. 구독은 없습니다.

현재 가격은 요금 페이지에서 확인하세요. 달러(USD) 가격에는 해당 세금(부가가치세, GST, 판매세 등)이 포함되어 있지 않으며, 판매대행사 Creem이 구매자 위치에 따라 세금을 계산해 결제 화면에 총액을 표시합니다. 한국어 콘솔에서는 원화(KRW) 카드 결제도 선택할 수 있습니다(결제 대행: 크렙). 원화 가격은 요금 페이지에 부가가치세 별도 금액으로 표시되며 결제 시 10%가 더해집니다. 영문 가격은 영문 요금 페이지에 있습니다.

차감 흐름

  1. POST /v1/issue 시점에 1 크레딧을 예약합니다. 잔액이 부족하면 이 단계에서 거절됩니다.
  2. 이후 메시징 발송사의 응답에 따라 비동기로 정산됩니다. 발송사가 메시지를 발송 대상으로 받아들이면 접수, 요청을 거절해 아무것도 발송되지 않으면 거절입니다.
    • 접수: 예약분이 차감(debit) 됩니다.
    • 거절되었거나 발송 큐에 넣지 못한 경우: 예약분이 해제(release) 되어 차감되지 않습니다.
  3. 접수 후 발송사가 전달 실패를 확정하거나 추적 기간 안에 전달 결과가 오지 않으면 차감된 크레딧이 돌아옵니다(원장의 refund 항목, billingStatus는 released). 다만 인증번호 검증에 성공한 발급은 메시지가 도착한 것이 확인되므로, 전달 결과가 오지 않아도 전달된 것으로 보고 크레딧을 돌려주지 않습니다(API 2.1.1부터).
  4. 발급 응답에는 잔액이나 차감 금액이 포함되지 않습니다. 필요하면 아래 조회 API를 사용하세요.

발급 건별 정산 상태(billingStatus)는 GET /v1/issues/{issueId}에서 확인할 수 있습니다.

billingStatus의미
reserved발급 시 크레딧을 예약함
debit_pending → debited차감 진행 중 → 차감 완료
release_pending → released예약 해제 진행 중 → 해제 완료(과금 없음, 차감된 크레딧은 refund 항목으로 반환)

잔액 부족: 402 PAYMENT_REQUIRED

잔액이 부족하면 발급이 402로 거절되고 인증번호는 발송되지 않습니다. data.code로 사유를 구분합니다.

{
	"defined": true,
	"code": "PAYMENT_REQUIRED",
	"status": 402,
	"message": "PAYMENT_REQUIRED",
	"data": { "code": "INSUFFICIENT_CREDIT" }
}
data.code의미
INSUFFICIENT_CREDIT잔액이 부족합니다.
OVERDRAFT_LIMIT_EXCEEDED조직에 허용된 초과 사용(overdraft) 한도를 넘었습니다.

크레딧을 충전한 뒤 다시 시도하세요. 사용자에게는 "잠시 후 다시 시도" 같은 일반 오류로 보여 주고, 운영 알림으로 잔액 부족을 감지하는 것을 권장합니다.

잔액 조회

GET /v1/balance (sk_ 키, otp:balance)는 조직 지갑의 잔액을 반환합니다. 같은 조직의 모든 앱에서 같은 값입니다. appId는 호출한 앱이고, 공유 지갑이면 walletId는 org:<organizationId>, walletScope는 organization이며 organizationId도 함께 반환됩니다. 조직 지갑으로 전환이 끝날 때까지만 남아 있는 앱 단위 지갑은 app:<appId>와 app으로 표시됩니다.

curl "https://api.k-otp.dev/v1/balance" \
  -H "Authorization: Bearer $KOTP_SECRET_KEY"
{
	"appId": "app_...",
	"walletId": "org:...",
	"walletScope": "organization",
	"organizationId": "...",
	"balance": 1200,
	"currency": "CREDIT",
	"updatedAt": "2026-09-28T03:00:05.000Z"
}

원장 조회

GET /v1/credit-ledger (sk_ 키, otp:ledger:read)는 조직 지갑의 변동 내역을 최신순으로 반환합니다. 지갑의 충전·회수(clawback) 내역과 호출한 앱의 차감·환불 내역이 포함되며, 다른 앱의 차감·환불 내역은 보이지 않습니다(콘솔에서는 지갑 전체를 볼 수 있습니다).

entryType의미amountDelta
credit충전양수
debit발급 차감음수
refund환불양수
clawback충전분 회수(결제 환불)음수
  • balanceAfter는 해당 항목 반영 후 지갑 잔액입니다. 다른 앱의 차감 내역은 목록에 없으므로 두 항목의 차이가 amountDelta보다 클 수 있습니다. 초과 사용이 허용된 조직은 음수일 수 있습니다.
  • appId는 귀속 앱입니다. debit·refund는 항상 호출한 앱이며, 지갑의 credit·clawback 항목은 구매가 특정 앱에 귀속된 경우에만 있습니다.
  • issueId는 발급과 연결된 항목(debit, refund)에만 있습니다.
  • 페이지네이션: limit(1–100, 기본 50), 응답의 nextCursor를 다음 요청의 cursor로 그대로 전달합니다. nextCursor가 없으면 마지막 페이지입니다. 페이지를 넘기는 동안 필터를 바꾸지 마세요.
  • 필터: entryType, createdFrom(포함), createdTo(미포함) — RFC 3339 형식.
const API = "https://api.k-otp.dev/v1";

export async function* listLedger(createdFrom: string) {
	let cursor: string | undefined;
	do {
		const params = new URLSearchParams({ limit: "100", createdFrom });
		if (cursor) params.set("cursor", cursor);
		const res = await fetch(`${API}/credit-ledger?${params}`, {
			headers: { Authorization: `Bearer ${process.env.KOTP_SECRET_KEY}` },
		});
		const body = await res.json();
		if (!res.ok) throw new Error(`${body.code}: ${body.message}`);
		yield* body.items;
		cursor = body.nextCursor;
	} while (cursor);
}

환불

사용하지 않은 크레딧은 구매 후 7일 이내에 청약철회할 수 있습니다. 일부 사용한 팩은 남은 크레딧 중 보너스 크레딧을 제외한 비율만큼 환불합니다(결제 금액 × (남은 크레딧 − 보너스 크레딧) ÷ 기본 크레딧). 보너스 크레딧은 환불 대상이 아닙니다. 남은 크레딧은 조직의 모든 앱이 함께 쓰는 조직 지갑 기준으로 계산합니다. 자세한 내용은 환불 정책을 참고하세요.

이 페이지의 내용