K-OTP

Issue lifecycle

Verification and delivery statuses, verify result codes, channels and templates

Each issue has a verification status, a delivery status, and a billing status. This page explains how they change and how to read them.

Issue options and defaults

FieldDefaultRange
channelsmssms, alimtalk
templateIdotp_default_krTemplates
expiresInSec180 (3 minutes)30–600
maxAttempts51–10

The code is a server-generated 6-digit number and is never returned by any API. Phone numbers are stored only as hashes and are never returned either.

Verification status

verificationStatus is one of:

ValueMeaning
issuedIssued, not yet verified, and not expired.
verifiedVerified. Cannot be verified again.
expiredThe expiry time has passed.
max_attemptsFailed attempts reached the maximum.
replacedInvalidated by a newer issue for the same phone number and purpose.
         verify succeeds
 issued ───────────────▶ verified
   │  ├── expiry passed ──▶ expired
   │  ├── failures reach maxAttempts ──▶ max_attempts
   └──┴── re-issued for same phone + purpose ──▶ replaced

Verification results

POST /v1/verify returns HTTP 200 even when verification fails. When verified is false, reasonCode tells you why.

reasonCodeMeaningSuggested handling
MISMATCHWrong code. Consumes one attempt.Show attemptsRemaining and ask again.
MAX_ATTEMPTSNo attempts left.Ask the user to request a new code (re-issue with a new key).
EXPIREDThe code expired.Offer to re-issue.
ALREADY_VERIFIEDThe issue was already verified.Treat as a duplicate submit and use the earlier result.
REPLACEDA newer issue invalidated this one.Verify with the latest issueId.
NOT_FOUNDNo issue with this issueId exists for your app.Check how you store and pass issueId.

A blank issueId, or a code that is not a 6-digit numeric string, returns 400 BAD_REQUEST.

Delivery status

deliveryStatus shows delivery progress and is updated asynchronously.

ValueMeaning
unknownNo tracking record yet, or it cannot be read right now.
queuedQueued for sending.
sentHanded to the provider.
deliveredDelivery confirmed.
failedDelivery failed.

Checking status

Use GET /v1/status?issueId=... (sk_ key, otp:status) for a single issue.

curl "https://api.k-otp.dev/v1/status?issueId=0192f3c4-8b7a-7c3e-9a51-2f4d6e8b1a90" \
  -H "Authorization: Bearer $KOTP_SECRET_KEY"
{
	"issueId": "0192f3c4-8b7a-7c3e-9a51-2f4d6e8b1a90",
	"messageId": "0192f3c4-8b7b-7a01-8c2d-3e4f5a6b7c8d",
	"purpose": "login",
	"templateId": "otp_default_kr",
	"verificationStatus": "issued",
	"deliveryStatus": "delivered",
	"overallStatus": "delivered",
	"expiresAt": "2026-09-28T03:03:00.000Z",
	"attemptsUsed": 0,
	"maxAttempts": 5,
	"attemptsRemaining": 5,
	"createdAt": "2026-09-28T03:00:00.000Z",
	"updatedAt": "2026-09-28T03:00:04.000Z"
}

To show a single status in a UI, use overallStatus:

  • If verificationStatus is not issued, it is used as-is (verified, expired, max_attempts, replaced).
  • While issued, it is derived from deliveryStatus:
deliveryStatusoverallStatus
delivereddelivered
faileddelivery_failed
queued, sentin_progress
unknownpending_lookup

Good to know:

  • Right after issuing, the record may still be persisting. For up to 60 seconds after issue you get overallStatus: "pending_lookup" instead of 404; after that a missing record returns 404 NOT_FOUND.
  • providerOutcomeAmbiguous: true means the provider outcome is unknown. Such sends are never retried automatically (Idempotency & retries).
  • An issueId belonging to another app returns 404, exactly like a missing one.

For many issues, use GET /v1/issues (otp:dashboard:read): newest-first cursor pagination (limit 1–100, default 50; pass the response's nextCursor as the next cursor) with verificationStatus and createdFrom/createdTo filters. See List OTP issues.

Channels and templates

channel selects SMS (default) or KakaoTalk AlimTalk. Message bodies come only from pre-registered templates, and every template contains the #{code} placeholder. AlimTalk is sent only with pre-registered provider template codes. If AlimTalk delivery fails, K-OTP automatically resends the same code by SMS at no extra credit; see Channels and failover.

templateIdBody
otp_default_kr (default)[K-OTP] 인증번호는 #{code}입니다. 3분 내에 입력해주세요.
otp_login_kr[K-OTP] 로그인 인증번호는 #{code}입니다. 3분 내에 입력해주세요.
otp_signup_kr[K-OTP] 회원가입 인증번호는 #{code}입니다. 3분 내에 입력해주세요.
otp_payment_kr[K-OTP] 결제 인증번호는 #{code}입니다. 3분 내에 입력해주세요.

List templates and their variables with GET /v1/templates and GET /v1/templates/{templateId} (otp:templates). The server fills #{code}; never include code in templateVariables. New templates are registered on request, not through the API.

The "3분" (3 minutes) in the template bodies is fixed text. If you change expiresInSec, the message and the real lifetime can differ.

On this page