Skip to main content
Every failure returns JSON with a message:
Send refusals also carry a stable code so your client can branch without string-matching:
Check service_window on GET /v1/contacts/{contact} before sending to avoid the closed-window refusal.

Stability contract

  • code values are stable once shipped. Build retries and i18n against them.
  • The message string can change freely. Treat it as display-only.
  • New codes may be added at any time. Removing a code is a breaking change.
  • Validation failures use the Laravel errors map (field → messages), not code.
Validation failures add a per-field errors map:

402 plan limit reached

LazyApp’s plans decide what a workspace can do in live mode. When a request asks for something the plan does not include, the API answers 402 Payment Required instead of doing part of the work:
That exact message is what a live key gets on any workspace below Pro. The API and webhooks are Pro features, so the check runs on every live request, before the endpoint itself. Other plan limits come back differently through the API today: a broadcast that would go past the month’s live broadcast sends, or connecting a number past your plan’s numbers, returns 422 with a plain message (and, for numbers, an errors entry on phone_number_id), not plan_limit_reached. Test keys are never plan-limited. Test mode runs on the shared test number, so a sk_test_ key keeps working on any plan, or none. Build against test, then upgrade before you switch to sk_live_. The body is the same message + code shape as a send refusal above, with no errors map. Branch on code, not on the status alone: a 402 can also mean insufficient_credits on a partner-managed number. Retrying will not help until the plan changes. See Plans and limits.

Status codes

Which failures are worth retrying

LazyApp already retries retryable provider failures internally, and does not retry policy or invalid-recipient failures — retrying those would burn quota to get the same answer. Apply the same rule to your own client:

Retry with backoff

409, 429, 500, 502, 503, and network timeouts. Always with an Idempotency-Key so a retry cannot double-send.

Do not retry

400, 401, 402, 403, 404, 422. The same request will fail the same way. Surface it and fix the cause. A 402 clears only once the plan or wallet changes.

A send can fail after it is accepted

POST /v1/messages returning 201 means LazyApp accepted the message and the provider took it. Delivery is asynchronous: the recipient’s phone may be off, the number may not exist on WhatsApp, or the user may have blocked you. So treat 201 as accepted, not delivered, and get the outcome from webhooks — message.delivered, message.read, message.failed. See webhook events. When a send fails immediately and synchronously, you get 422 with the provider’s reason:

Common causes

You can only send free-form content within 24 hours of the contact’s last message. Outside it, send an approved template. This is enforced server-side, not just in the UI.
A template must be approved by Meta before it can be sent. Check status with GET /v1/templates, and remember approval can be revoked later — a template can move to paused or disabled on quality grounds.
Calling, block lists and India payments are gated by Meta per account. Check GET /v1/capabilities instead of assuming.
A contact who opted out is excluded from marketing sends. That is deliberate; do not work around it.

Best practices

  • Branch on code (and field keys under errors), never on message text — wording can change.
  • Handle 429 by honouring Retry-After; see rate limits.
  • Treat provider-style 422 bodies as caller-fixable; treat 500 / 502 / 503 as transient.
  • Prefer webhooks over polling for delivery outcomes.
  • Always send an Idempotency-Key when retrying writes.
  • Monitor GET /v1/capabilities for quality and messaging-limit tier changes.
  • Log the full response body plus X-Request-Id for support.

Getting help

Quote the X-Request-Id from the failing response in your email to support@lazyapp.in. It identifies the exact request, including the provider call it made and the error the provider returned.