K-OTP

Errors

Error envelope, HTTP status codes, and which errors to retry

Every non-2xx response uses the same JSON envelope.

{
	"defined": true,
	"code": "CONFLICT",
	"status": 409,
	"message": "Conflict",
	"data": {}
}
FieldDescription
definedtrue for errors defined in the spec, false for unexpected ones. The shape is the same either way.
codeError code string (table below). Branch on code, not on message.
statusSame as the HTTP status code.
messageHuman-readable description. Wording may change.
dataOptional details. Currently only data.code on 402 is defined.

Failed verifications on POST /v1/verify (wrong code, expired, …) are not errors: they return 200 with verified: false. See Verification results.

Status codes

StatuscodeTypical causesRetry?
400BAD_REQUESTInvalid payload, missing/blank idempotency key, header/body key mismatch, unknown template or template variables, expiresInSec/maxAttempts/cost out of range, code not 6 digits, invalid limit/cursor/date rangeNo. Fix the request.
401UNAUTHORIZEDAuthorization: Bearer key missing, malformed, inactive (revoked/expired), or unrecognizedNo. Check the key.
402PAYMENT_REQUIREDOut of credit; data.code is INSUFFICIENT_CREDIT or OVERDRAFT_LIMIT_EXCEEDED (POST /v1/issue only)After topping up
403FORBIDDENKey lacks the required scope, a pk_ key called a server-only API, or a pk_ request had no Origin / an unlisted OriginNo. Check the key settings.
404NOT_FOUNDUnknown issueId for your app (including other apps' ids) or unknown templateIdNo
409CONFLICTIdempotency key reused with a different body, or the key's issue has since been replacedNo
500INTERNAL_SERVER_ERRORUnexpected server failureYes, with backoff and the same idempotency key
503SERVICE_UNAVAILABLEAuthentication or an internal dependency temporarily unavailable, or an earlier request with the same idempotency key is still being resolvedYes, with backoff and the same idempotency key

Per-endpoint error descriptions are on each page of the API reference.

Handling example

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

function handleIssueError(error: ApiError) {
	switch (error.code) {
		case "PAYMENT_REQUIRED":
			// Alert operators: top-up needed (error.data?.code)
			return "Please try again later.";
		case "CONFLICT":
			// Check whether an idempotency key was reused for a different request
			return "The request could not be processed.";
		case "SERVICE_UNAVAILABLE":
		case "INTERNAL_SERVER_ERROR":
			// Retry with the same idempotency key (/guides/idempotency)
			return "Please try again later.";
		default:
			return "We could not send a verification code.";
	}
}

On this page