Troubleshooting: validation gate codes
Four codes share one behaviour: they refuse the request deterministically, before anything bills, queues, or reaches a carrier. The payload is the problem, never the platform — so a retry loop earns the same422 every
time, and the only working fix is to change the value the envelope names.
None of these is retriable as-is. The gate runs pre-send — nothing is
billed, nothing is dispatched — so re-sending the unchanged body is a
guaranteed second refusal. Fix the value first; re-send once.
Decode the envelope
A validation refusal always carries the field-level hint inerror.details
and a sentence in error.message. Read both before touching anything:
error.codetells you which gate tripped.error.messagenames the field in prose.error.details.fieldanderror.details.value(when present) echo the exact value your integration sent — the Delivery Log records the refused body too, so you can compare against what you intended.meta.request_idis what support needs if the value looks right to you but still refuses.
The canonical shape: E.164 and friends
Every phone gate on the platform enforces the same format: E.164 — a+ followed by 2–15 digits, with a non-zero country code. The frequent
rejections, in order of how often they appear:
Email gates enforce a simpler shape: a non-empty local part, one
@, and a
domain with at least one dot. NOT AN EMAIL, " " (whitespace-only), and
user@ all refuse with INVALID_EMAIL; user@example.com passes.
Pre-flight the value
You never need to learn a recipient is invalid from a refused send — two checks run before you send anything.Number lookup
GET /api/v1/numbers/lookup/{phoneNumber} resolves a number’s line type,
carrier, and validity. It is the authoritative answer for “will this value
pass?”: a valid: false response means the E.164 gate refuses that value
on every send surface, and the national-format echo shows the normalized
form to store. URL-encode the leading + as %2B:
E.164 formatting at collection time
Normalize to E.164 the moment a number enters your system — at form collect, CSV import, or webhook intake — and store only the normalized form. Any libphonenumber port does the parse; pass the region the user typed the number in:Node.js (libphonenumber-js)
Python (phonenumbers)
curl (normalize via lookup when a stored value can't be trusted)
The from_number source-id resolution post-check
INVALID_FROM_NUMBER is not a format gate — it is a source gate. The
from you passed did not resolve to an active, dial- or send-capable
source on your org: a caller-ID id that was never verified, a numeric
sender that was released or disabled, or a field left empty where the
endpoint needs one. The post-check pattern is: read the exact from out
of the refused body, confirm it exists and is active for your org, then
re-resolve — never the same value blind.
- List what your org can actually send from, and pick from that list; the softphone caller-ID dropdown and Make a Call dialog pre-filter to selectable, dial-ready sources.
- For platform numbers, confirm the row shows the capability you need
(
voicefor calls,smsfor messages) on Settings → Numbers. - For an external caller ID, complete the register → challenge → confirm
flow once (
POST /api/v1/voice/caller-ids/verify) — the confirmed id is then selectable like an owned number.
from is usable at all, voice dial fails closed with a
INVALID_FROM_NUMBER reading “No caller ID available” — buy a
voice-capable number or set a default caller ID before dialing. That
fail-closed behaviour is deliberate: guessing a source would route from
the wrong identity.
The fix, per code
Every fix follows the same shape: find the rejected body, correct the one field the envelope names, re-send once.INVALID_PHONE_NUMBER — recipient format
Rejected body:
to is a UK national-format number with spaces — it fails the E.164
gate (+ plus 2–15 digits). Corrected body, which is accepted:
POST /api/v1/messages/sms and the per-channel send variants, voice dial
endpoints, POST /api/v1/voice/caller-ids/verify, and flow send nodes
(to must be a valid E.164 phone number).
INVALID_FROM_NUMBER — the source you sent from
Rejected body — the numeric source passed in from is not an active
source on your org:
INVALID_EMAIL — email recipient format
The unified-send 422 naming a malformed recipient:
"noreply@ " (a trailing whitespace, no domain) refused; "noreply@example.com"
passes. Trim whitespace and refuse empties in your own form validation so
the bad value never reaches the API.
MISSING_REQUIRED_FIELD — an absent field
The endpoint’s schema requires a field your body omitted. The message names
the field; the refusal is 400 or 422 depending on surface:
VALIDATION_ERROR siblings whose details.issues array lists every
offending field at once — fix all of them in one edit, then a single
re-send succeeds.
What not to do
- Do not retry an unchanged body. Every gate above is deterministic; the second attempt returns the byte-identical refusal and burns your rate-limit budget on a decision only you can change.
- Do not store national-format numbers and normalize at send time. Normalize at collection, store only E.164, and send the stored form — a send-time formatter is one more thing that can disagree with the gate.
- Do not hand-roll a regex as your only E.164 validator. The gate
checks country-code and numbering-plan validity, not just shape; a
libphonenumber port (or the lookup endpoint for stored values you cannot
trust) matches the gate. A regex accepts
+999999999— the gate does not. - Do not “fix”
INVALID_FROM_NUMBERby trying other values in a loop. Pick from the selectable list of sources the dashboard already filters for you; a loop of guesses is a rate-limited way to learn the same thing.
Paste this into a support ticket
When the envelope’sdetails.value looks correct to you but the refusal
persists — or lookup returns valid: false for a number you believe is
assigned — open a ticket with:
Where these codes link
- Error Code Reference — Validation family — the full catalog where these four codes sit, with HTTP status per code.
- FAQ — “Why did my API call reject a phone number with an E.164 validation error?” — the short-form summary that hands off here.
- Number Lookup — the pre-flight endpoint’s field set.
- E.164 and the send surfaces — the shared format answer on the FAQ.
- Troubleshooting hub — every other runbook, grouped by surface.