Skip to main content

Troubleshooting: fan-out and upload errors

Two shapes of failure live on this page. A fan-out failure is one call that becomes several sends — an email blast to many recipients, a Verify send that cascades across channels, a conference that dials many legs — where some or all of the legs fail. An upload rejection never fans out at all: the file fails pre-flight validation before anything is stored or sent. Both leave you holding an error code that names the aggregate, not the cause; the cause is always on an individual leg or on the input.

Step one: identify the boundary your call crosses

The fix lives on one side of the POST boundary, so decide which before touching anything:
  • Upload rejections are input errors. INVALID_FILE_TYPE, MAGIC_BYTE_MISMATCH, FILE_TOO_LARGE, INVALID_AUDIO, and AUDIO_TOO_LARGE are returned before the file is accepted. Nothing was stored and nothing was sent — the boundary is your request payload, and the fix is in the file you send, not in the API.
  • Fan-out failures are per-leg errors wearing an aggregate jacket. A wrapper code like EMAIL_SEND_FAILED_ALL or ALL_CHANNELS_FAILED only exists because individual legs failed underneath it. The boundary is the legs, and diagnostics start by reading the per-recipient or per-channel breakdown in the response, never by retrying the whole call.
  • CONFERENCE_DIAL_PARTIAL is neither an input error nor a failed call. It arrives on a successful 201 inside the response body as data.partial_failure_code: the conference is live with the legs that connected, and the failed legs are listed for you to act on.

Decision table

For any of these, the response’s meta.docs_url points at the per-code anchor on the error code reference, which is regenerated from the platform’s canonical registry — treat that page as the code’s definition of record.

Retry-safety matrix

Retries are safe only when the cause is transient. Each row states when a retry is the right move and when it is wasted work: A fan-out call that failed partway is not idempotent by accident — on POST /messages, recipients inside the succeeded group were delivered, so a whole-call retry re-sends to them. Retry only the failed recipients (culled from details.per_recipient), never the full list.

Reading the tiered failure rows

A mixed email blast returns one envelope whose body already tells you which recipients need which fix. The shape:
Three tiers, three owners:
  • 5xx rows (EMAIL_SEND_FAILED) — provider-side or transient. Wait for the upstream to clear, then retry just those recipients.
  • 4xx policy rows (RECIPIENT_OPTED_OUT, suppression, quiet hours) — tenant-owned compliance controls doing their job. Take the recipient out of the list, or resolve the consent state with the contact before re-sending. These rows never succeed on retry.
  • 4xx input rows (INVALID_EMAIL) — fix the address; the retry with the same payload is deterministic.
The same tiering applies to ALL_CHANNELS_FAILED on Verify, one layer down: each attempted channel is a row to read, and the first 4xx row in the chain is the gate to fix.

When a partial conference dial is acceptable

CONFERENCE_DIAL_PARTIAL earns its keep exactly once you decide which legs mattered. A bridge where seven of eight participants joined is usually a usable meeting — proceed, and re-dial the missing leg from the dashboard or POST /voice/conferences/:id/participants when the eighth person matters. A two-party connect where one of two legs failed is not usable — cancel the conference and re-create it once the leg’s cause (balance, quiet hours, unreachable destination) is fixed. The per-leg codes on the roster come from the same vocabulary as the conference lifecycle failures table.

See also