Plans and reliability
Errors and retries
Handle Yanola error envelopes consistently and retry only transient or rate-limited failures.
What this guide covers
- Branch on stable error codes for validation, auth, permission, billing, and Sender failures.
- Do not retry deterministic client errors without changing the request.
- Use SDK error classes and safe retry behavior in server-side applications.
One error envelope
Every documented API failure uses the same JSON envelope. Branch on error.type or the more specific stable error.code; do not parse message text.
{
"error": {
"type": "business_state_error",
"code": "service_window_required",
"message": "A text, media, location, or contact-card message requires an active Customer Service Window. Send an approved template instead.",
"requestId": "req_...",
"retryable": false,
"details": {
"from": "+15557654321",
"to": "+15551234567"
}
}
}| Field | How to use it |
|---|---|
type | Broad error family used by SDK subclasses. |
code | Stable machine-readable reason for application branching. |
message | Human-readable diagnostic; wording can evolve. |
requestId | Correlation ID to log and include in support requests. |
retryable | Whether the same operation may succeed later; still apply a bounded retry policy. |
details | Structured, code-specific context. Handle additive fields. |
HTTP status and error types
| HTTP status | Error type | Typical cause | Retry guidance |
|---|---|---|---|
400, 404, 413, 415 | invalid_request_error | Invalid fields, identifiers, media size, or content type. | Change the request; do not retry unchanged. |
401 | authentication_error | Missing, invalid, expired, or revoked API key. | Replace the credential. |
402 | billing_error | Plan Message Cap reached (plan_message_limit_reached; not retryable). | wait for the next period or upgrade the Plan; replies inside the 24-hour window, free-form or template, still go out. |
403 | permission_error | Missing scope, Sender restriction, or entitlement. | Change key access or account configuration. |
409 | idempotency_error / business_state_error | Conflicting reuse or transient resource state. | Retry only when retryable is true; preserve the same logical operation identity. |
422 | business_state_error | Sender, template, service-window, or other valid-but-unavailable state. | Follow code-specific recovery. |
429 | rate_limit_error | A rate-limit bucket is exhausted. | Wait for Retry-After. |
500 | api_error | Yanola server failure. | Use bounded backoff; the SDK retries automatically. |
503 | provider_error / api_error | Provider or Yanola dependency is temporarily unavailable. | Retry only when retryable is true. |
Use typed SDK errors
The SDK converts a valid error envelope into a YanolaAPIError subclass and exposes statusCode, type, code, requestId, retryable, and details. Connection and timeout failures use separate classes because no API error envelope was received.
import {
WhatsAppClient,
YanolaAPIError,
YanolaBillingError,
YanolaConnectionError,
YanolaRateLimitError,
} from "@yanola/whatsapp"
const apiKey = process.env.YANOLA_API_KEY
if (!apiKey) throw new Error("Missing YANOLA_API_KEY")
const yanola = new WhatsAppClient({ apiKey })
try {
await yanola.messages.sendText({
from: "+15557654321",
to: "+15551234567",
body: "Your order is ready",
})
} catch (error) {
if (error instanceof YanolaBillingError) {
await alertBillingOwner(error.requestId)
return
}
if (error instanceof YanolaRateLimitError) {
console.error("SDK retry budget exhausted", error.requestId)
return
}
if (error instanceof YanolaAPIError) {
console.error(error.code, error.retryable, error.requestId)
return
}
if (error instanceof YanolaConnectionError) {
console.error(error.requestId, error.attempts)
return
}
throw error
}What the SDK retries
WhatsAppClient defaults to a 30-second timeout and two retries after the first attempt. It automatically retries connection failures, HTTP 408, HTTP 429, HTTP 500 and higher, and HTTP 409 only when the error envelope has retryable: true.
The SDK honors Retry-After when present. Otherwise it uses capped exponential backoff with jitter. A caller AbortSignal cancels both requests and pending retry waits.
| Failure | Automatic retry | Reason |
|---|---|---|
Connection failure or timeout | Yes, within maxRetries | The server may not have produced a usable response. |
408 Request Timeout | Yes | The request timed out at the HTTP layer. |
409 with retryable: true | Yes | The conflict represents transient business state. |
429 Too Many Requests | Yes | The SDK waits according to Retry-After when provided. |
500 or 503 | Yes | Server or provider failure may be transient. |
400, 401, 402, 403, 404, 413, 415, 422 | No | The request, credential, funds, permissions, or business state must change. |
The default maxRetries: 2 permits up to three attempts. Set maxRetries: 0 when an outer queue owns all retry scheduling.
Configure timeout, retries, and cancellation
A supplied requestId must start with req_, contain only letters, numbers, underscores, or hyphens, and be at most 64 characters. Yanola returns a request ID in responses and error envelopes for correlation.
import { WhatsAppClient } from "@yanola/whatsapp"
const apiKey = process.env.YANOLA_API_KEY
if (!apiKey) throw new Error("Missing YANOLA_API_KEY")
const yanola = new WhatsAppClient({
apiKey,
timeoutMs: 10_000,
maxRetries: 1,
})
const controller = new AbortController()
const page = await yanola.messages.list(
{ limit: 20 },
{ signal: controller.signal, requestId: "req_reconcile_1042" }
)Retry sends without duplicates
Every SDK send generates an idempotency key and preserves it across the client's automatic retries. When a job runner or workflow can call the SDK again later, supply and persist your own stable idempotencyKey for that logical send.
Reusing the same key with the same body returns the original accepted result. Reusing it with a different body returns 409 idempotency_key_reused.
- Create one idempotency key for one recipient, Sender, message type, and content.
- Persist it before the first network attempt.
- Reuse it for retries of that exact logical send.
- Generate a new key when the intended message changes.
- After an ambiguous failure, retrieve or reconcile by the stored operation identity before creating a replacement send.
Production error policy
- Log error.type, error.code, error.requestId, statusCode, and safe structured details.
- Never log the Authorization header, API key, webhook secret, signed media URL, or unredacted customer content.
- Alert immediately on authentication, permission, billing, Sender reconnect, and reconciliation-needed conditions.
- Bound retries by attempts and elapsed time; move exhausted work to a visible failure or dead-letter state.
- Do not retry deterministic 4xx responses until the request or account state changes.