Normalize a phone number offline before an agent acts
Before an agent stores, dials or texts a number, it should know the number is well-formed, in which region, and what line type it is. One offline call answers all four in milliseconds.
An agent that collects a phone number — from a form, a scraped page, an OCR’d
document, an LLM extraction — holds a string, not a number. Before it stores,
dials, or texts that string it needs four facts: is it structurally valid,
what is its canonical E.164 form, which region issued it, and what
line type is it (mobile, fixed line, VoIP, premium rate). All four come from
one offline call — GET /phone/validate — that does
zero network round trips and answers in milliseconds.
The problem: a string is not a dialable number
"06 12 34 56 78", "+33 6 12 34 56 78", "0612345678" and
"033612345678" can all denote the same French mobile, and a database that
stores them verbatim cannot dedupe, match, or reliably dial any of them. Worse,
many strings that look like numbers are not valid for any region, and an agent
that pushes them straight into an OTP send or a dialer pays for — and logs — a
failure that was knowable up front.
Normalization is the step that turns the raw string into a single canonical key
(E.164) plus the metadata to route it. The numbering plans that make this
possible are bulky, versioned reference data (Google’s libphonenumber
metadata); the value of an endpoint here is that it carries a known snapshot of
that data so the agent does not have to embed and maintain it.
The call: validity, E.164, region and line type in one answer
GET /phone/validate takes a number and an optional country (the default
region for a national-form number), and returns a single result:
| Field | What the agent learns |
|---|---|
valid | Structural validity for the number’s region |
e164 | Canonical +CCNSN form — the key to store and dedupe on |
country | ISO 3166-1 alpha-2 region that issued the number |
national_format | Human-readable national form, for display |
number_type | Line type: mobile, fixed_line, voip, premium_rate, … |
issue | Diagnostic code when valid: false |
A national number resolves cleanly once you supply the region:
GET /phone/validate?number=06 12 34 56 78&country=FR
→ { "valid": true, "e164": "+33612345678", "country": "FR",
"national_format": "06 12 34 56 78", "number_type": "mobile", "issue": null }
The number_type is the field a messaging agent keys on: sending an SMS OTP to a
fixed_line or a premium_rate number is a routing mistake the agent can catch
before it spends anything on delivery.
Reading the result honestly
The endpoint is precise about what “offline” means and where the line sits:
- It is structural, not a liveness check.
valid: truemeans the number is well-formed and assignable for its region. It does not mean the line is active, reachable, or that anyone answers it. That question — is the number live? — is a separate, network-backed call:GET /phone/resolve. - The answer is a snapshot.
provenance.freshness.kindis alwayssnapshot, withas_ofcarrying the embedded metadata version. There are no network round trips, which is exactly why it is fast and cheap enough to run on every number. - Per the x402 golden rule, the agent pays for the answer to its question.
“This number is invalid” is a successful answer → 200 with
valid: falseand anissuecode (TOO_SHORT,NOT_A_NUMBER,INVALID_FOR_REGION, …). The4xxrange is reserved for requests the service genuinely cannot process.
Where it sits in the x402 loop
Validation is the cheap pre-filter that runs before any expensive action. The agent’s loop:
- Discover the endpoint and call it; receive the
402. - Pay — sign the chosen rail and replay the request.
- Normalize — read
valid,e164,country,number_type. - Branch — drop on
valid: false; store thee164as the canonical key; route bynumber_type; and only escalate the valid numbers to a live/phone/resolvecheck when reachability matters.
Each paid call follows the same x402 pattern as every Invoket endpoint. The
Quickstart walks the whole discover → 402 → pay → replay
cycle with runnable snippets. Price and accepted rails are not pinned in this
article — they are served live by the
catalog; see the
endpoint reference for the current figure.
Validate first, resolve only when you must
The two phone endpoints form a cheap-then-expensive funnel:
GET /phone/validate— offline, structural, milliseconds. Use it on every number to normalize and discard the malformed ones.GET /phone/resolve— network-backed, answers whether a number is live before an agent spends on an OTP. Use it only on the numbers that already passed validation.
Spending a live lookup on a string that validation would have rejected for free
is the per-number waste this funnel removes. Used for what it is — fast offline
normalization — /phone/validate gives an agent a clean canonical key and a line
type before it stores or dials anything. For the full field reference and issue
codes, see the GET /phone/validate documentation;
for how agents discover and call Invoket endpoints, see
For agents.