Phone Number Validator - Bulk Carrier & Line Type Lookup
Pricing
$1.00 / 1,000 per number validateds
Phone Number Validator - Bulk Carrier & Line Type Lookup
Validate and normalise phone numbers in bulk. Returns E.164, country, region, timezone, line type and carrier. For US & Canada it adds the assigned LERG carrier, wireless/landline/VoIP type, rate centre and LATA from free public data - no API key, no per-lookup fees.
Pricing
$1.00 / 1,000 per number validateds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
☎️ Phone Number Validator — Bulk Carrier & Line Type Lookup
Validate, normalise and enrich phone numbers in bulk. Every number comes back as E.164 with its country, region, timezone, formats and line type — and for US and Canada numbers it also comes back with the assigned carrier, the wireless / landline / VoIP class, the rate centre and the LATA.
65 fields per row. No API key. No per-lookup fee. Nothing to sign up for.
🎯 Why this one, and not a plain libphonenumber wrapper
Google's libphonenumber is the standard for phone validation, and it is genuinely excellent — everywhere except North America. Ask it what kind of line +1 212 736 5000 is and it answers FIXED_LINE_OR_MOBILE. It answers that for every geographic US and Canada number, because the NANP numbering plan does not reserve mobile ranges the way most countries do. Carrier comes back null for the same reason.
That is exactly the answer a lead list, an SMS campaign or a TCPA screen needs, and it is the one field a libphonenumber-only validator cannot give you.
This actor closes it with a second, free data source: the public LERG / NPA-NXX block assignment tables, which record who each 1,000-number block was allocated to and what class of carrier they are.
Every row below is a real value from a real Apify run on 2026-09-02, not an illustration:
| Number | libphonenumber alone | This actor |
|---|---|---|
+1 212 736 5000 | FIXED_LINE_OR_MOBILE, carrier null | landline-ilec · VERIZON NEW YORK, INC. · rate centre New York City Zone 01 · LATA 132 |
+1 415 937 2000 | FIXED_LINE_OR_MOBILE, carrier null | wireless · T-MOBILE USA, INC. · rate centre Belvedere · LATA 722 |
+1 415 937 1234 | FIXED_LINE_OR_MOBILE, carrier null | clec-or-voip · BANDWIDTH.COM CLEC, LLC — CA |
+1 303 913 1311 | FIXED_LINE_OR_MOBILE, carrier null | wireless · CELLCO PARTNERSHIP DBA VERIZON WIRELESS — CO |
+1 604 685 3300 | FIXED_LINE_OR_MOBILE, carrier null | landline-ilec · TELUS COMMUNICATIONS, INC. — BC |
Note the first three rows: 415-937-2000 and 415-937-1234 share an area code and an exchange, and still belong to different carriers of different classes. Block assignment is per thousands-block, not per exchange, and this actor resolves it at that granularity.
🚀 Quick start
{"phoneNumbers": ["+1 415 937 2000","+1 212 736 5000","(604) 685-3300","4155552671","+44 20 7183 8750"],"defaultCountry": "US" // how to read numbers written without a "+"}
Have more than a handful? Use any of these instead — they all merge into one run:
| Input | What it takes |
|---|---|
phoneNumbers | a list of strings |
phoneNumbersText | one pasted blob — newline, comma, semicolon or tab separated |
numbersFileUrl | a public TXT / CSV / JSON / JSONL URL; CSV headers are auto-detected |
sourceDatasetId | another Apify dataset — e.g. the output of a lead scraper |
sourceField names the column or JSON key holding the number. Leave it empty and a field called phone, mobile, cell, tel, number or whatsapp is picked automatically.
📤 Output — 65 fields per row
One row per submitted number, in submission order, so the output lines up with your list.
{"input_raw": "+1 415 937 2000","input_index": 0,"e164": "+14159372000","is_valid": true,"is_possible": true,"validation_reason": null,"country": "US","country_name": "United States","country_calling_code": "1","national_number": "4159372000","number_type": "FIXED_LINE_OR_MOBILE","format_national": "(415) 937-2000","format_international": "+1 415 937 2000","format_rfc3966": "tel:+14159372000","format_idd": "1 (415) 937-2000","tel_uri": "tel:+14159372000","extension": null,"carrier_code": null,"is_non_geographic": false,"possible_countries": ["US"],"geo_description": "California","carrier_name_intl": null,"timezones": ["America/Los_Angeles"],"timezone_primary": "America/Los_Angeles","digit_count": 11,"is_mobile": true,"is_landline": false,"is_voip": false,"is_toll_free": false,"is_premium_rate": false,"is_shared_cost": false,"is_pager": false,"is_uan": false,"is_voicemail": false,"is_personal_number": false,"is_duplicate": false,"duplicate_of_index": null,"is_nanp": true,"npa": "415","nxx": "937","line_number": "2000","block_id": "2","npa_is_erc": false,"lerg_carrier_name": "T-MOBILE USA, INC.","lerg_carrier_ocn": "6529","lerg_company_type": "W","line_type_nanp": "wireless","rate_center": "Belvedere","rate_center_region": "CA","rate_center_lat": 37.86717,"rate_center_lon": -122.460023,"rate_center_v": 8476,"rate_center_h": 8730,"lata": "722","switch_clli": "OKLECAZV1MD","switch_name": null,"switch_type": "POI","ilec_ocn": "9740","ilec_name": "PACIFIC BELL","exch": "018620","lir": null,"effective_date": null,"disconnect_date": null,"lerg_updated": "2026-09-01 00:40:05","lerg_status": "ok"}
Field table with measured fill rates
Measured on the real Apify run described under Measured performance: 64 numbers submitted, 64 rows returned, 62 parsed, 50 US/CA prefixes resolved in LERG. Two fill columns, because a field that only applies to a US landline should not be scored against a German mobile:
| Field | Type | What it is | Fill (all 64) | Fill (applicable rows) |
|---|---|---|---|---|
input_raw | string | your original string, echoed back untouched | 100% | — |
input_index | int | 0-based position in the merged input list | 100% | — |
e164 | string | the canonical +CCNNNNNNNNN form | 97% | 100% of 62 parsed |
is_valid | bool | matches an assigned range for its country | 100% | — |
is_possible | bool | right length for its country, range not checked | 100% | — |
validation_reason | string | NOT_A_NUMBER · INVALID_COUNTRY · TOO_SHORT · TOO_LONG · INVALID_LENGTH; null when valid | 3% (only the 2 rejects) | — |
country | string | ISO-3166 alpha-2 of the range, not the dialling country | 97% | 100% of parsed |
country_name | string | English country name | 97% | 100% of parsed |
country_calling_code | string | e.g. 1, 44, 49 | 97% | 100% of parsed |
national_number | string | NSN — the number without the country code | 97% | 100% of parsed |
number_type | string | libphonenumber type (11 values, see below) | 97% | 100% of parsed |
format_national | string | (415) 937-2000 | 97% | 100% of parsed |
format_international | string | +1 415 937 2000 | 97% | 100% of parsed |
format_rfc3966 | string | tel:+14159372000;ext=42 — extension included | 97% | 100% of parsed |
format_idd | string | the digits to dial from your dialFromCountry | 97% | 100% of parsed |
tel_uri | string | bare tel: URI, no extension parameter | 97% | 100% of parsed |
extension | string | parsed extension, e.g. 42 | 2% | 1 of 1 with an extension |
carrier_code | string | national carrier-selection code, where the number carries one | 0% | none in this batch |
is_non_geographic | bool | +800, +870, +882… — a calling code with no country | 97% | 100% of parsed |
possible_countries | array | ambiguity on shared codes (+1, +7, +44) | 97% | 100% of parsed |
geo_description | string | e.g. San Francisco, CA, London, Beijing | 89% | 92% of parsed |
carrier_name_intl | string | non-NANP carrier: Optus, T-Mobile, China Mobile, Airtel, KPN… | 6% | 6% of parsed — see limits |
timezones | array | IANA zones the range can sit in | 97% | 100% of parsed |
timezone_primary | string | first zone, for a flat column | 97% | 100% of parsed |
digit_count | int | digits in the E.164 form, + excluded | 97% | 100% of parsed |
is_mobile | bool | true / false, or null when genuinely unknowable | 94% | — |
is_landline | bool | as above | 80% | — |
is_voip | bool | as above | 83% | — |
is_toll_free | bool | derived from number_type | 97% | 100% of parsed |
is_premium_rate | bool | derived from number_type | 97% | 100% of parsed |
is_shared_cost | bool | derived from number_type | 97% | 100% of parsed |
is_pager | bool | derived from number_type | 97% | 100% of parsed |
is_uan | bool | derived from number_type | 97% | 100% of parsed |
is_voicemail | bool | derived from number_type | 97% | 100% of parsed |
is_personal_number | bool | derived from number_type | 97% | 100% of parsed |
is_duplicate | bool | this exact endpoint already appeared in your list | 100% | — |
duplicate_of_index | int | input_index of the first occurrence | 2% (the 1 duplicate) | — |
is_nanp | bool | US / Canada / Caribbean numbering plan | 100% | — |
npa | string | area code | 81% | 100% of NANP |
nxx | string | exchange | 81% | 100% of NANP |
line_number | string | last 4 digits | 81% | 100% of NANP |
block_id | string | LERG thousands-block, 0–9, or A for a whole-NXX assignment | 78% | 100% of the 50 resolved |
npa_is_erc | bool | easily-recognisable code: 800/888/877/866/855/844/833/822/900 | 81% | 100% of NANP |
lerg_carrier_name | string | assigned block holder — see limits | 78% | 100% of the 50 resolved |
lerg_carrier_ocn | string | Operating Company Number | 78% | 100% of the 50 resolved |
lerg_company_type | string | raw OCN class: W wireless, I ILEC, C CLEC | 78% | 100% of the 50 resolved |
line_type_nanp | string | wireless · landline-ilec · clec-or-voip | 78% | 100% of the 50 resolved |
rate_center | string | e.g. New York City Zone 01, Belvedere | 78% | 100% of the 50 resolved |
rate_center_region | string | US state / Canadian province | 78% | 100% of the 50 resolved |
rate_center_lat | number | rate-centre latitude | 78% | 100% of the 50 resolved |
rate_center_lon | number | rate-centre longitude | 78% | 100% of the 50 resolved |
rate_center_v | number | V coordinate (telco grid) | 78% | 100% of the 50 resolved |
rate_center_h | number | H coordinate (telco grid) | 78% | 100% of the 50 resolved |
lata | string | Local Access and Transport Area | 78% | 100% of the 50 resolved |
switch_clli | string | serving switch CLLI code | 78% | 100% of the 50 resolved |
switch_name | string | switch name, where published | 58% | 74% of the 50 resolved |
switch_type | string | e.g. 5E, POI | 78% | 100% of the 50 resolved |
ilec_ocn | string | incumbent carrier OCN for the rate centre | 78% | 100% of the 50 resolved |
ilec_name | string | incumbent carrier name, e.g. PACIFIC BELL | 78% | 100% of the 50 resolved |
exch | string | exchange identifier | 78% | 100% of the 50 resolved |
lir | string | local interconnection region, where published | 6% | 8% of the 50 resolved |
effective_date | string | block effective date — usually blank in the source | 0% | 0% of the 50 resolved |
disconnect_date | string | block disconnect date — blank unless the block is being returned | 0% | 0% of the 50 resolved |
lerg_updated | string | when the source last refreshed this record | 78% | 100% of the 50 resolved |
lerg_status | string | ok · not_found · not_applicable · unavailable · disabled | 100% | — |
number_type values, all eleven confirmed against a real range while building this actor: FIXED_LINE, MOBILE, FIXED_LINE_OR_MOBILE, TOLL_FREE, PREMIUM_RATE, SHARED_COST, VOIP, PERSONAL_NUMBER, PAGER, UAN, VOICEMAIL.
lerg_status values: ok a block record was found · not_found the prefix is genuinely not in the geographic LERG tables (toll-free NPAs and the reserved 555 exchange both legitimately return nothing) · not_applicable the row is not a geographic NANP number · unavailable the source could not be reached for this row — the offline fields are unaffected · disabled you switched the enrichment off.
📊 Measured performance
Everything here is from real runs on the Apify platform on 2026-09-02, on build 0.1.3 at the actor's declared 1024 MB. No estimates.
| Run | Input | Status | Duration | Rows | Charged events |
|---|---|---|---|---|---|
| Health-test simulation ×4 | prefill-only, 12 numbers | SUCCEEDED | 3.59 – 4.77 s | 12 | number-validated: 12 |
Empty input {} ×3 | none — falls back to the demo batch | SUCCEEDED | 3.45 – 3.63 s | 12 | — |
| Real batch ×2 | 64 numbers, 32 area codes, 13 countries | SUCCEEDED | 7.10 s / 11.87 s | 64 | number-validated: 64 |
On the 64-number batch:
- 64 rows for 64 submitted numbers — nothing is silently dropped; the 2 unparseable strings come back with a
validation_reason. - 62 of 64 parsed, 62 valid. The 2 rejects were
not a phone→NOT_A_NUMBERand+999 12345→INVALID_COUNTRY. - 52 NANP rows; 50 resolved in LERG. The 2 that did not:
+1 800 266 2278(toll-free,not_applicable— never queried) and4155552671(the reserved555exchange,not_found). - Line-type split actually returned: 34
landline-ilec, 9clec-or-voip, 7wireless. - 30 HTTP requests to the carrier feed, for 49 distinct prefixes across 32 area codes (2 were already cached from an earlier run). One area code needed 18 different exchanges and was pulled in a single whole-area-code request that cached all 784 exchanges in it. Zero failures.
- Charged events equalled delivered rows exactly, at both sizes:
{"number-validated": 12}againstitemCount: 12, and{"number-validated": 64}againstitemCount: 64.
Throughput. The offline leg is CPU-bound at roughly 50,000 numbers/minute. The carrier feed is the only latency, and it collapses to about one request per area code once the cache is warm — the cache lives in a named key-value store, so it survives across runs.
💰 Pricing
| Event | Price | Fires |
|---|---|---|
number-validated | $0.001 per number | once for each number delivered to your dataset |
$1.00 per 1,000 numbers, flat. The US/Canada carrier and line-type enrichment is included — it is not a second billable event, because it is the point of the actor.
Rows you filter out with Only valid numbers or Skip duplicates are never delivered, so you are never charged for them. Delivery and billing are atomic (Actor.pushData(rows, 'number-validated')), so if you set a spend cap the run stops at it instead of handing you rows you paid nothing for — or billing you for rows you never got.
⚠️ Honest limits — read this before you buy
1. This does not prove the line rings. Everything here is static numbering-plan metadata plus public block-assignment records. It answers "is this well-formed, where does the range live, and what kind of line is it". It never answers "is this number in service right now". There is no HLR dip, no ping, no live network check. If you need "is it connected", this is not that product.
2. lerg_carrier_name is the ASSIGNED carrier, not necessarily today's carrier. Local number portability means a large share of US mobile numbers have moved carrier at least once, and LERG records who the block was allocated to. We do not do a live LNP dip, so we do not claim current carrier.
Line type is the field to trust. A wireless-to-wireless port keeps the block's W class, so line_type_nanp survives porting far better than carrier identity does. Use line_type_nanp for compliance and channel decisions; treat lerg_carrier_name as "the carrier this range was issued to".
3. clec-or-voip is genuinely ambiguous, and we say so rather than guessing. LERG's C class covers both competitive landline carriers and VoIP/CPaaS providers (Bandwidth, Twilio, Onvoy and friends). We can tell you it is not a wireless block and not an incumbent landline; we cannot split CLEC from VoIP, so on those rows is_landline and is_voip are null, not false. A null here means "unknown", never "no".
4. carrier_name_intl fills on only 6% of rows, and that is the metadata, not a bug. Google's carrier tables only cover mobile ranges in the countries that publish them (DE, AU, IN, NL, NG, CN, GG and others). It is null for landlines everywhere and for all NANP numbers — for NANP the answer lives in lerg_carrier_name instead.
5. country is the range's assigned region, not the dialling country. +44 7911 123456 returns GG (Guernsey), not GB, because that mobile range is allocated to a Crown dependency. The same surprise exists across +1 Caribbean ranges (BB, JM, TT…). This is metadata-correct and it will still look like a bug the first time you see it — hence this note.
6. effective_date and disconnect_date measured 0% fill. The source leaves both blank for blocks in normal service; they populate only around a pending assignment change. They are included because they are real columns, not because they are usually there.
7. Two data sources, so two failure modes — and only one of them can stop a run. The libphonenumber leg is bundled into the image and cannot fail. The carrier feed is a small public reference site, so this actor is deliberately gentle with it: results are cached in a named key-value store across runs, whole area codes are pulled in one request when a run needs 8+ exchanges from one, concurrency is capped at 4 with a stagger between requests, and any error is fail-soft — the row still ships, marked lerg_status: "unavailable", with every offline field intact. A run never fails because the carrier feed was slow, and a carrier-feed outage is never reported to you as "no results found".
🔍 Where the data comes from
| Leg | Source | Cost | Verified |
|---|---|---|---|
| Validation, formats, type, geo, timezone, non-NANP carrier | Google's libphonenumber metadata, bundled into the image (libphonenumber-js/max + libphonenumber-geo-carrier) | offline, no network | 11 of 11 number_type values confirmed on real ranges, 2026-09-02 |
| US/Canada carrier, line class, rate centre, LATA, switch, ILEC | the public LERG / NPA-NXX block tables republished by localcallingguide.com as XML | free, no key, no account | HTTP 200 / 11,478 bytes for a single prefix and HTTP 200 / 3,595,753 bytes with 5,838 records for a whole area code, 2026-09-02 |
The carrier feed answered identically from a home IP, an Apify datacenter IP and an Apify residential IP when this actor was built — same status, same byte count. There is no anti-bot wall, no CAPTCHA and no user-agent gate on this path, and the two real Apify runs above resolved 50 of 52 prefixes with no proxy at all. Proxy support is exposed anyway, for anyone who wants to spread a very large run.
https://localcallingguide.com/robots.txt reads, in full:
User-agent: *Disallow: /cgi-bin/
The path this actor reads (/xmlprefix.php) is not under /cgi-bin/ and is not disallowed.
❓ FAQ
Can it tell me whether a US number is a mobile or a landline?
Yes — that is the reason this actor exists. line_type_nanp returns wireless, landline-ilec or clec-or-voip, and is_mobile / is_landline are set from it. A libphonenumber-only validator returns FIXED_LINE_OR_MOBILE for every geographic US number and cannot answer this.
Can it tell me the carrier?
For US and Canada, it returns the carrier the number's 1,000-block was assigned to, plus their OCN. For mobile numbers in countries that publish carrier ranges (Germany, Australia, India, the Netherlands, Nigeria and others) carrier_name_intl gives the carrier directly. Neither is a live ported-carrier lookup — see limit #2.
Does it check whether the number is active or reachable? No. Nothing here dials, pings or does an HLR lookup. See limit #1 — it is stated plainly rather than buried.
Is it useful for TCPA screening?
The W class identifies numbers issued in a wireless block, which is the distinction TCPA rules turn on, and the field is stable across wireless-to-wireless porting. It is one input to a compliance process, not a compliance product, and it carries no litigator or DNC list. Take your legal advice from your lawyer, not from a scraper's README.
What formats can I feed it?
Anything libphonenumber accepts: +1 415 937 2000, (415) 937-2000, 415.937.2000, +1-415-937-2000 ext. 42, 4159372000. Numbers without a + are read using defaultCountry.
How do I load 50,000 numbers?
Point numbersFileUrl at a public TXT/CSV/JSON file, or set sourceDatasetId to another Apify dataset — the output of a lead scraper, for example. Both stream in and merge with anything typed into the other fields.
Will I be charged for invalid numbers? Only if they are delivered. They are, by default, because a rejection with a reason is usually the useful answer. Switch on Only valid numbers and the invalid ones are dropped before delivery and never billed.
How does duplicate detection work?
Numbers are compared after normalisation, so (415) 937-2000 and +14159372000 are the same endpoint. An extension makes a different endpoint: +1 212 736 5000 and +1 212 736 5000 ext. 42 are not duplicates of each other. is_duplicate and duplicate_of_index are always populated; Skip duplicates additionally drops the repeats.
Why did +44 7911 123456 come back as GG and not GB?
Because that range really is allocated to Guernsey. See limit #5.
Does it need a proxy? No. Both real Apify runs resolved carrier data with no proxy. A proxy field is there if you want it.
How fresh is the carrier data?
The source refreshes daily — a record pulled on 2026-09-02 carried lerg_updated: 2026-09-01 00:40:05. This actor caches each prefix for 30 days by default (lergCacheDays); block assignments move slowly, and the cache is what keeps the load on a small public reference site reasonable.
What happens if the carrier feed goes down mid-run?
The run still succeeds and still delivers every row. Affected rows carry lerg_status: "unavailable" and keep all of their offline fields. A blocked or unreachable source is never reported to you as "no results found".
Can I switch the carrier lookup off?
Yes — set nanpEnrichment: false for a purely offline run with no outbound requests at all.
⚖️ Fair use
Both data sources are public reference data: Google's libphonenumber metadata is open source (Apache 2.0) and bundled into the image, and the NANP block-assignment tables are public numbering-plan records republished by localcallingguide.com, whose robots.txt is quoted verbatim above. This actor caches aggressively, batches whole area codes into single requests, caps concurrency at 4 and staggers its requests, precisely so that scaling it up does not become someone else's problem.
You are responsible for how you use the output — for consent, for TCPA/GDPR/CASL obligations, and for your own do-not-call and marketing compliance. Validating a number is not permission to call it.