Skip to content
Core Concepts

Errors

Every error response from /v1/* follows the same shape so your client code only needs one branch.

{
"error": {
"type": "invalid_request_error",
"code": "invalid_phone_number",
"message": "Phone number must be in E.164 format (e.g. +628123456789).",
"param": "to",
"request_id": "req_01HXYZABCDEFGHJKMNPQRSTVWX"
}
}
FieldStabilityMeaning
typeStableOne of seven broad categories (invalid_request_error, authentication_error, permission_error, not_found, conflict, rate_limit_error, api_error).
codeStable foreverProgrammatic identifier. Branch on this.
messageMay evolveHuman-readable. Display to operators, never branch on its text.
paramWhen relevantJSON pointer-style hint to the offending field.
request_idAlways presentEchoes the X-Request-Id response header. Quote in support tickets.
const res = await fetch(url, { headers, body })
if (!res.ok) {
const { error } = await res.json() as KirimErrorResponse
switch (error.code) {
case 'invalid_phone_number':
// Re-prompt the user, the number is malformed.
return showValidation(error.param!, error.message)
case 'outside_24h_window':
// Conversation window closed. Fall back to a template message.
return sendTemplateFallback()
case 'rate_limit_exceeded':
// Honour Retry-After.
const delay = Number(res.headers.get('retry-after') ?? 1)
return await sleep(delay * 1000), retry()
case 'whatsapp_upstream_error':
// Meta hiccup. Exponential backoff + alert if persists.
return scheduleRetry(error.request_id)
default:
// Log full context, surface request_id to support.
logger.error({ error }, 'Unexpected Kirim API error')
throw new ApplicationError(error.code, error.request_id)
}
}
HTTPtypecodeWhen
400invalid_request_errorinvalid_phone_numberto / phone is not E.164 with leading +.
400invalid_request_errorinvalid_template_nameTemplate name does not exist for any of your accounts.
400invalid_request_errormissing_required_fieldRequired field absent. param set.
400invalid_request_errorinvalid_field_valueField present but constraint violated. param set.
400invalid_request_errorinvalid_limitlimit query param outside 1-100.
400invalid_request_errorinvalid_cursorCursor undecodable or tampered.
400invalid_request_errorinvalid_webhook_urlURL is not https or fails reachability sanity check.
400invalid_request_errorinvalid_event_typeWebhook event name not recognised.
401authentication_errormissing_api_keyNo Authorization header.
401authentication_errorinvalid_api_keyMalformed, unknown, or hash mismatch.
401authentication_errorrevoked_api_keyKey has been revoked.
401authentication_errorexpired_api_keyexpires_at passed.
403permission_errorfeature_not_availableYour plan does not include the Public API.
403permission_errororganization_suspendedOrg-level block. Contact support.
404not_foundresource_not_foundMissing OR belongs to a different organisation. We return 404 (not 403) for cross-org access to avoid leaking existence.
409conflictidempotency_in_progressAn identical request with the same Idempotency-Key is still being processed.
409conflictwebhook_subscription_disabledTrying to operate on a subscription that auto-disabled. Re-enable it first.
422invalid_request_erroridempotency_key_reuseSame Idempotency-Key was used with a different request body.
422invalid_request_errorwhatsapp_number_not_verifiedYour org has no connected, approved WhatsApp account.
422invalid_request_errorcannot_revoke_last_secretTried to revoke the only signing secret on a subscription. Add a new secret first.
422invalid_request_erroroutside_24h_windowFree-form message rejected — the conversation window with this contact has closed. Send a template instead.
422invalid_request_errortemplate_pausedTemplate send rejected — Meta paused this template (quality rating).
422invalid_request_errortemplate_disabledMeta disabled this template (chronic quality issues).
429rate_limit_errorrate_limit_exceededPer-org token bucket empty. Honour Retry-After. See Rate Limits.
500api_errorinternal_errorUnexpected exception. Logged server-side with request_id. Open a ticket if persistent.
502api_errorwhatsapp_upstream_errorMeta returned 5xx. Retry with backoff.
503api_errorservice_unavailableMaintenance or temporary overload. Honour Retry-After if present.
  • code strings never change. New codes are added only for genuinely new failure modes; existing codes keep their meaning forever.
  • message text may evolve — wording polish, i18n, additional hints. Don’t branch on substring matches.
  • HTTP status, type, and code form the v1 contract. Anything else (param, request_id, the response headers) is auxiliary.