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:

FieldWhat the agent learns
validStructural validity for the number’s region
e164Canonical +CCNSN form — the key to store and dedupe on
countryISO 3166-1 alpha-2 region that issued the number
national_formatHuman-readable national form, for display
number_typeLine type: mobile, fixed_line, voip, premium_rate, …
issueDiagnostic 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: true means 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.kind is always snapshot, with as_of carrying 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: false and an issue code (TOO_SHORT, NOT_A_NUMBER, INVALID_FOR_REGION, …). The 4xx range 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:

  1. Discover the endpoint and call it; receive the 402.
  2. Pay — sign the chosen rail and replay the request.
  3. Normalize — read valid, e164, country, number_type.
  4. Branch — drop on valid: false; store the e164 as the canonical key; route by number_type; and only escalate the valid numbers to a live /phone/resolve check 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/validateoffline, structural, milliseconds. Use it on every number to normalize and discard the malformed ones.
  • GET /phone/resolvenetwork-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.