# Validate and normalize a phone number offline, in E.164 > 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. Published 2026-06-28 · Updated 2026-08-15 · HTML version: https://invoket.com/blog/normalize-a-phone-number-offline --- 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`](/docs/api/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: 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 — liveness is a separate question this offline endpoint does not attempt to answer. - **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; and route by `number_type` before any downstream action. Each paid call follows the same x402 pattern as every Invoket endpoint. The [Quickstart](/docs/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](https://api.invoket.com/catalog); see the [endpoint reference](/docs/api/phone-validate) for the current figure. ## Fast, offline, on every number 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, with zero network round trips and a cost low enough to run on **every** number. For the full field reference and `issue` codes, see the [`GET /phone/validate` documentation](/docs/api/phone-validate); for how agents discover and call Invoket endpoints, see [For agents](/docs/for-agents).