A valid number is not a working number. Everything here is decided from a pinned snapshot of libphonenumber's numbering-plan metadata (release v9.0.35, 2026-07-17). There is no HLR or LRN lookup, no carrier query, no SMS or voice probe and no network call of any kind while your request is served, so isValid: true means exactly one thing: the national significant number falls inside a range the numbering-plan authority has published for that country. The line may be unassigned, ported, disconnected or simply switched off. libphonenumber's own FAQ says not to rely on validity to decide whether a number is currently assigned and reachable, and neither should you.
Possible vs valid is the distinction reachability vendors blur. isPossible is a length-only test against the lengths a region publishes; isValid additionally requires the digits to match one of that region's published type patterns. possibleReason carries libphonenumber's own ValidationResult verbatim — IS_POSSIBLE, IS_POSSIBLE_LOCAL_ONLY, TOO_SHORT, INVALID_LENGTH, TOO_LONG — so you can see which check decided. +4511111111 is possible and invalid; a seven-digit +1 number is IS_POSSIBLE_LOCAL_ONLY and invalid.
Do not confuse this with /api/phones. The names are close and the promises are opposite: /api/phones generates mock numbers for fixtures, while this endpoint answers questions about a number you supply and generates nothing. Nothing here is invented — every verdict, format and example number comes out of the pinned metadata snapshot.
The four routes
| Route |
What it answers |
/parse |
one number → region, national significant number, possible-vs-valid verdict, number type, all four formats |
/format |
the four canonical presentations, which format rule produced the grouping, and how to dial the number from another country |
/countries |
every numbering plan in the snapshot: calling code, trunk prefix, lengths per type, example numbers |
/calling-code |
one calling code: which regions share it, in the order a number is resolved against them |
Paging /countries. A row carries every range the territory publishes, with its own lengths and example number, so 254 of them is a large payload. At most 100 rows come back per request; offset skips that many matches (?offset=100 for the second page) and a warning reports how many matched in total.
Things that surprise people, and that this endpoint states rather than hides
Trunk prefixes are not universal. 110 of the 254 territories publish no nationalPrefix at all — Denmark and Italy among them. 0151 12345678 with country=DE becomes +4915112345678 and reports nationalPrefixStripped: "0", because Germany publishes nationalPrefix="0". The same shape in Denmark does not: 032 12 34 56 with country=DK keeps its zero, the national significant number is the nine-digit 032123456, and the honest answer is TOO_LONG — Denmark publishes exactly one possible length, 8. An Italian leading zero is likewise part of the number: +39 0432 123456 has the national significant number 0432123456.
fixedLineOrMobile is the common case. Whenever a region's fixed-line and mobile ranges both cover a number, that is what getNumberType returns — including for the Danish example above and for most of the United States. A test that expects fixedLine for a specific number has usually not been run against the actual patterns.
Formatting uses the main region of the calling code, not the resolved region. 44 territories publish no formatting rules of their own; for 39 of them the rules come from a different region, so a Bermuda number is grouped by the United States' NANP rules and formatSourceRegion says so. Five calling codes — +247, +290, +500, +686, +690 — have no rules anywhere, so their national form is the ungrouped national significant number and formatRuleUsed is null.
A +1 number can belong to any of 25 regions. region is resolved in libphonenumber's own order (main region US first): a region that publishes a region-level leadingDigits prefix — 36 rows do, including most of the NANP — wins on that prefix, and the rest win by matching one of their published ranges. regionCandidates always lists the full ordered set so an ambiguous answer is visible. When no region on a shared code claims the number, region is null and isValid is false rather than a region being guessed — that is what happens to +44 1481 123456. The order is not simply alphabetical either: six of the twelve shared codes list their main region later in the source file, and /calling-code exposes the real resolutionOrder.
The national significant number can be 17 digits, not 15. ITU-T E.164 caps a number at 15 digits; libphonenumber's MAX_LENGTH_FOR_NSN is 17, because longer numbers were found in service in Germany. This endpoint follows the implementation, and says so.
possibleLengths is computed, not read. No generalDesc in the XML carries possibleLengths; the union of the ten number types' lengths is derived exactly as libphonenumber's build tool derives it, with noInternationalDialling excluded and the local-only union reduced by the national one.
Region ids are CLDR, not ISO 3166-1. They include the exceptionally reserved AC and TA and the user-assigned XK, plus 001 for the nine non-geographical calling codes (800, 808, 870, 878, 881, 882, 883, 888, 979). In the other direction, seven ISO countries have no numbering metadata at all — AQ, BV, GS, HM, PN, TF and UM. The XML explains two of them: French Southern Territories (TF) for lack of information about its numbering plan, and Pitcairn (PN) because its fifty inhabitants use satellite phones. countryName is null for the twelve rows with no ISO 3166-1 officially assigned entry rather than invented.
Deliberately not included
Reachability of any kind. libphonenumber's carrier and geocoding datasets: prefix-to-carrier is broken by number portability (107 regions flag it, and mobileNumberPortableRegion surfaces that per answer) and prefix-to-city is wrong for every ported and every mobile number. Time zone for a prefix — use /api/timezones. Short numbers: ShortNumberMetadata.xml is not bundled, so 112, 911 and premium short codes come back TOO_SHORT with a detail that says why. Alternate formats: PhoneNumberAlternateFormats.xml is not bundled, which affects nothing about parsing (only digits are ever examined) and means national uses the primary rule only. As-you-type formatting, free-text number finding and number matching. Vanity letters — 1-800-FLOWERS is not converted; the letter is named as NOT_A_NUMBER. Extension spellings other than a trailing ;ext=. Carrier-selection codes are reported when a region's parsing rule captures one and never re-inserted into a formatted number. Example numbers are upstream's own, and libphonenumber admits some of them reach real people — do not call them. And this is not a generator: for fake numbers in fixtures, use /apis/phones.
Divergences from libphonenumber
Six behavioural divergences, plus one narrowing of the input grammar:
MISSING_REGION — upstream folds "national form, no default region" into INVALID_COUNTRY_CODE; it gets its own reason here because it is a caller mistake with a different fix.
UNSUPPORTED_TEL_PARAMETER — upstream accepts ;phone-context= and several extension spellings. Only a single trailing ;ext= with one to seven digits is parsed here, so this endpoint's own rfc3966 output round-trips through its own value parser.
rfc3966 carries no visual separators — upstream emits tel:+45-32-12-34-56; this endpoint emits tel:+4532123456. Both satisfy RFC 3966 global-number-digits; only the second one round-trips.
- An unknown or
001 country/fromRegion is a 400 — upstream logs a warning and silently falls back to the international format. A silent fallback in an HTTP API is a wrong answer with a 200 on it.
- A 32-character input cap instead of upstream's 250.
- Viability is measured in digits, not in punctuation. Upstream's
isViablePhoneNumber refuses an input as NOT_A_NUMBER before parsing begins unless it is either exactly two bare digits or holds at least three digits. This endpoint applies the digit half of that rule — fewer than two digits anywhere, or a leading plus with fewer than three, is NOT_A_NUMBER — but not the punctuation half, so a two-digit input written with a separator (45- with country=DK) is parsed here and answered with possibleReason: "TOO_SHORT" where upstream would refuse it outright.
The accepted separator set is this endpoint's own. The characters listed under value are the whole grammar. Upstream's VALID_PUNCTUATION is wider in most places — it tolerates ~, [, ], an x used as a separator, U+00AD, U+200B, U+2060, U+2212, the full-width . and /, and the ideographic space U+3000, among others — and narrower in one: it does not accept a backslash, which this endpoint does. So a handful of exotically punctuated inputs that upstream parses come back here as NOT_A_NUMBER with the offending character named, which is the answer this endpoint prefers to a silent reinterpretation.
Everything else is a record, not an exception: any value of 1–32 characters comes back with parsed, failureReason and failureDetail filled in. The filters on /countries are filters — zero matches is an empty list with a warning, never a substitute row — and /calling-code answers an unassigned code with an honest 404.
Provenance. The upstream XML is pinned by release tag and SHA-256 (d23c3c086390be1d…, 981,394 bytes) and regenerated by a committed parity script that also re-checks every structural invariant this page claims. metadataVersion and metadataUpdated ride on every /parse and /format record, so a refresh is visible in the response and not only in a changelog. libphonenumber is Apache-2.0; the notice, the statement of our modifications and the non-affiliation statement are in the dataset headers and on /notices.