Core Concepts
Errors
Every error response from /v1/* follows the same shape so your
client code only needs one branch.
Envelope
Section titled “Envelope”{ "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" }}| Field | Stability | Meaning |
|---|---|---|
type | Stable | One of seven broad categories (invalid_request_error, authentication_error, permission_error, not_found, conflict, rate_limit_error, api_error). |
code | Stable forever | Programmatic identifier. Branch on this. |
message | May evolve | Human-readable. Display to operators, never branch on its text. |
param | When relevant | JSON pointer-style hint to the offending field. |
request_id | Always present | Echoes the X-Request-Id response header. Quote in support tickets. |
Client recipe
Section titled “Client recipe”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) }}Full catalogue
Section titled “Full catalogue”| HTTP | type | code | When |
|---|---|---|---|
| 400 | invalid_request_error | invalid_phone_number | to / phone is not E.164 with leading +. |
| 400 | invalid_request_error | invalid_template_name | Template name does not exist for any of your accounts. |
| 400 | invalid_request_error | missing_required_field | Required field absent. param set. |
| 400 | invalid_request_error | invalid_field_value | Field present but constraint violated. param set. |
| 400 | invalid_request_error | invalid_limit | limit query param outside 1-100. |
| 400 | invalid_request_error | invalid_cursor | Cursor undecodable or tampered. |
| 400 | invalid_request_error | invalid_webhook_url | URL is not https or fails reachability sanity check. |
| 400 | invalid_request_error | invalid_event_type | Webhook event name not recognised. |
| 401 | authentication_error | missing_api_key | No Authorization header. |
| 401 | authentication_error | invalid_api_key | Malformed, unknown, or hash mismatch. |
| 401 | authentication_error | revoked_api_key | Key has been revoked. |
| 401 | authentication_error | expired_api_key | expires_at passed. |
| 403 | permission_error | feature_not_available | Your plan does not include the Public API. |
| 403 | permission_error | organization_suspended | Org-level block. Contact support. |
| 404 | not_found | resource_not_found | Missing OR belongs to a different organisation. We return 404 (not 403) for cross-org access to avoid leaking existence. |
| 409 | conflict | idempotency_in_progress | An identical request with the same Idempotency-Key is still being processed. |
| 409 | conflict | webhook_subscription_disabled | Trying to operate on a subscription that auto-disabled. Re-enable it first. |
| 422 | invalid_request_error | idempotency_key_reuse | Same Idempotency-Key was used with a different request body. |
| 422 | invalid_request_error | whatsapp_number_not_verified | Your org has no connected, approved WhatsApp account. |
| 422 | invalid_request_error | cannot_revoke_last_secret | Tried to revoke the only signing secret on a subscription. Add a new secret first. |
| 422 | invalid_request_error | outside_24h_window | Free-form message rejected — the conversation window with this contact has closed. Send a template instead. |
| 422 | invalid_request_error | template_paused | Template send rejected — Meta paused this template (quality rating). |
| 422 | invalid_request_error | template_disabled | Meta disabled this template (chronic quality issues). |
| 429 | rate_limit_error | rate_limit_exceeded | Per-org token bucket empty. Honour Retry-After. See Rate Limits. |
| 500 | api_error | internal_error | Unexpected exception. Logged server-side with request_id. Open a ticket if persistent. |
| 502 | api_error | whatsapp_upstream_error | Meta returned 5xx. Retry with backoff. |
| 503 | api_error | service_unavailable | Maintenance or temporary overload. Honour Retry-After if present. |
Stability commitment
Section titled “Stability commitment”codestrings never change. New codes are added only for genuinely new failure modes; existing codes keep their meaning forever.messagetext may evolve — wording polish, i18n, additional hints. Don’t branch on substring matches.- HTTP status,
type, andcodeform the v1 contract. Anything else (param,request_id, the response headers) is auxiliary.