Skip to main content

Troubleshoot push broadcast pre-send gates

BROADCAST_TOO_LARGE (422) and CHANNEL_RATE_LIMITED (429) are deterministic pre-send refusals, not provider failures: your broadcast never reaches APNs / FCM / HMS / Web Push, so nothing is dispatched and no wallet hold is taken. Both gates fire on the whole-org broadcast surface — user_ids: ["*"] on POST /api/v1/push/send, or channel: "push" on the unified POST /api/v1/messages pipeline — and both are tenant-owned controls you can deliberately size. This page answers: which gate fired, where the ceiling is sized, how to read the cause from the details envelope, and the one re-send that verifies the fix.
A push wildcard send is user_ids: ["*"], not device_token_ids. The audience cap (this page) counts the deliverable audience — enabled device tokens minus push/all suppression opt-outs — not the raw row count. Provider-side per-device verdicts (APNs 410 / FCM UNREGISTERED, token expiry) return 201 and grade per device in notifications[]; decode those on Push token expiry.

The two gates

Distinguish the envelope a refusal came from:
  • Dedicated push route (POST /api/v1/push/send with user_ids: ["*"]) → only BROADCAST_TOO_LARGE (audience gate) applies pre-flight. Neither gate fires per-device; per-device sent/skipped/failed rows sit in the notifications[] array of a 201.
  • Unified pipeline (POST /api/v1/messages with channel: "push") → every per-channel fraud gate (rate cap → spend cap → blocklist) runs before the dedicated send executes; CHANNEL_RATE_LIMITED surfaces here.
A broadcast that fans out through the unified pipeline (for example a POST /api/v1/messages fan-out) can also trip the spend-cap codes (SMS_DAILY_SPEND_CAP / CHANNEL_DAILY_SPEND_CAP / VOICE_DAILY_SPEND_CAP) on the same request — the per-channel ops ceilings. Those are covered on Spend-cap refusals; this page covers the rate and audience caps only.

Cause table

Read the error.details envelope from the refusal to scope which lever to pull.

Fix table

Fix the gate at its own surface, then re-send once.

What NOT to try

  • Retrying the identical payload. BROADCAST_TOO_LARGE never clears itself: the audience gate is a pure function of your token registry — re-running the same wildcard computes the same refusal and burns per-minute rate headroom on the whole API surface. Retry only after the audience is under the cap.
  • Splitting a wildcard into multiple wildcard sends. Two user_ids: ["*"] sends target the same 100,001+ devices and both refuse. Only explicit user_ids (or suppression narrowing) reduces the projected count.
  • Looping a 429 retry. The rate gate is a hard per-minute window — a tight retry loop just spends your own envelope inside the window. Back off ≥ the window (60s) before the first retry after a raise.
  • Assuming the refusal is a provider fault. Both codes are platform pre-flights; the provider was never called. If you saw 201 with per-device failed entries, this page is the wrong runbook — decode per-device verdicts on Push token expiry.

Surface the cause

Read the refused response’s error.details envelope — the ERROR_CODES member for both codes declares the same diagnostic shape:

BROADCAST_TOO_LARGE (422)

The message already counts the deliverable audience; the request_id is the handle to cite on escalation.

CHANNEL_RATE_LIMITED (429)

details.channel scopes the lever: push here — raise push, never sms. The window: "1m" and limit values tell you what the sliding window evaluated.

Recovery sequence

  1. Read the refusal envelope — confirm the exact code and details.channel before touching anything.
  2. Fix the gate.
    • BROADCAST_TOO_LARGE: re-send to explicit user_ids —
    • CHANNEL_RATE_LIMITED on push: either throttle the burst and re-send ≥60s later, or raise the push override (owner-only) —
    Remember: PUT replaces the whole overrides map — send every channel you want active, or omitted channels revert to the cluster default instantly.
  3. Re-send once. One clean request after the fix proves the gate re-evaluated; do not parallel-fan the re-send.
  4. Verify. For the rate gate, GET /api/v1/settings/compliance/channel-rate-overrides must show your push override alongside the defaults entry. For the audience gate, the same payload now returns 201 with per-device results in notifications[].

When to escalate

Escalate only when the refusal persists after the fix on the surface the envelope names. Include:
  • Your tenant ID (GET /api/v1/me → organizationId).
  • The full error envelope — request_id, code, message, and details exactly as returned.
  • For BROADCAST_TOO_LARGE: the deliverable device count the message reported and the suppression scope in use.
  • For CHANNEL_RATE_LIMITED: the current push override value (GET /api/v1/settings/compliance/channel-rate-overrides) and the observed send rate.
A 422-versus-429 distinction matters to support: 422 (audience) never clears without a shape change; 429 (rate) clears on the next window once the cap room exists.

See also

  • Push channel — the broadcast surface, its common-errors table, and the per-device verdict model.
  • Per-channel rate overrides — the tenant-owned envelope the CHANNEL_RATE_LIMITED gate reads.
  • Fraud caps — fraud floors, blocks, and the channel velocity model.
  • Spend-cap refusals — the daily-envelope codes a wildcard send can also trip (SMS_DAILY_SPEND_CAP, CHANNEL_DAILY_SPEND_CAP, VOICE_DAILY_SPEND_CAP).
  • Push token expiry — per-device provider verdicts inside a 201.
  • Error codes reference — the canonical list of every enforcement code.