BotPulsar

Home/ Developers/Errors & rate limits

Errors & rate limits

One error shape, everywhere

Every failed request — a validation error, a missing scope, a closed messaging window, a rate limit — comes back with the same envelope, so error handling is one code path, not one per endpoint.

Every error response is {"error": {"code": "...", "details": "..."}}, with an HTTP status that tells you the category (400 bad input, 401/403 auth, 404 not found, 409 the request conflicts with the resource's current state, 429 rate limited) and error.code a stable string you can branch on without parsing the message.

Envelope

The shape

400 — a field failed validation
{
  "error": {
    "code": "invalid",
    "details": {
      "contact_id": ["Contact was not found."]
    }
  }
}
409 — the request conflicts with current state
{
  "error": {
    "code": "whatsapp_template_required",
    "details": {
      "detail": "The 24-hour WhatsApp customer-service window is closed; send an approved template instead."
    }
  }
}

details is either a field-name-keyed object (validation errors — each value is a list of messages for that field) or {"detail": "..."} (everything else). error.code is the field to branch on in either case — except a plain 404 for a resource that just doesn't exist, which carries the generic api_error rather than a dedicated code. Branch on the 404 status itself for "not found", not on error.code.

Status codes

What each one means here

StatusMeaningTypical error.code
400The request body or query failed validation.invalid
401No credential was sent.not_authenticated
401The credential was sent but is malformed, expired, revoked, or doesn't match the X-Tenant-ID given.authentication_failed
402The workspace's plan or quota can't cover this request.billing_limit_reached
403Authenticated, but this key's scopes (or the actor's role) don't allow this action.permission_denied
404The resource doesn't exist, or exists in a different workspace than your X-Tenant-ID.api_error
409Valid request, but it conflicts with the resource's current state — see below.contact_suppressed, channel_unavailable, whatsapp_template_required, message_idempotency_conflict, outbound_content_unsupported, media_unavailable
429Rate limited. Respect Retry-After before retrying.throttled

A 409 from sending a message is almost always one of five causes: the contact opted out (contact_suppressed), the channel isn't active (channel_unavailable), the 24-hour window is closed (whatsapp_template_required), a duplicate client_message_id was reused (message_idempotency_conflict), or the content type isn't supported on that channel (outbound_content_unsupported). None of these queue a message — check error.code and handle each case rather than retrying blindly.

Rate limits

429 responses

429 Too Many Requests
Retry-After: 42

{
  "error": {
    "code": "throttled",
    "details": {
      "detail": "Request was throttled. Expected available in 42 seconds."
    }
  }
}

Limits are per-scope (contacts/conversations/messages share one limit; webhook management has its own; automation triggers their own) rather than a single global ceiling, and apply per authenticated identity — a rate-limited API key doesn't affect anyone else's requests against the same workspace. Read Retry-After off the response rather than hardcoding a backoff; it changes if the underlying limit does.

Start on WhatsApp today — from ₹300/month

Unlimited team members on every plan. 14-day trial, no setup fee, no annual lock-in.