# Phone Number Validator - Bulk Carrier & Line Type Lookup (`scrapersdelight/phone-number-validator-scraper`) Actor

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.

- **URL**: https://apify.com/scrapersdelight/phone-number-validator-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Automation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 per number validateds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## ☎️ 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

```jsonc
{
  "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.

```json
{
  "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](#-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_NUMBER` and `+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) and `4155552671` (the reserved `555` exchange, `not_found`).
- Line-type split actually returned: **34 `landline-ilec`, 9 `clec-or-voip`, 7 `wireless`.**
- **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}` against `itemCount: 12`, and `{"number-validated": 64}` against `itemCount: 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](https://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.

# Actor input Schema

## `phoneNumbers` (type: `array`):

The numbers to validate. Any format works — `+1 415 937 2000`, `(415) 937-2000`, `415.937.2000`. Numbers written without a `+` are read using the default country below. Leave this empty and add a list URL or source dataset instead if you have thousands.

## `phoneNumbersText` (type: `string`):

Paste numbers here instead of adding them one by one — one per line, or comma / semicolon / tab separated. Combined with anything in the field above.

## `numbersFileUrl` (type: `string`):

A publicly reachable TXT, CSV, JSON or JSONL file holding the numbers. CSV headers are detected automatically; a column named phone / mobile / cell / tel / number is picked unless you name one below.

## `sourceDatasetId` (type: `string`):

Read the numbers out of another Apify dataset — for example the output of a lead scraper. Paste the dataset ID.

## `sourceField` (type: `string`):

Which column / JSON key holds the number in the file or dataset above, e.g. `phone` or `contactNumber`. Leave empty to auto-detect.

## `defaultCountry` (type: `string`):

ISO-3166 alpha-2 code used to interpret numbers written without a `+` country code, e.g. `US`, `GB`, `DE`, `AU`. Numbers that already start with `+` ignore this.

## `dialFromCountry` (type: `string`):

ISO-3166 alpha-2 code the `format_idd` column dials from — the exact digits a phone in that country would need, including the international prefix. `US` gives `011 44 20 7183 8750` for a London number.

## `nanpEnrichment` (type: `boolean`):

Look up the assigned carrier, wireless/landline/VoIP class, rate centre and LATA for US and Canada numbers from the free public LERG feed. This is the data libphonenumber cannot give you — it types every geographic US number as FIXED\_LINE\_OR\_MOBILE. Adds no per-lookup fee; results are cached across runs. Switch off for a pure offline run.

## `lergCacheDays` (type: `integer`):

How long a cached NPA-NXX record stays fresh before it is refetched. The source feed updates daily but block assignments move slowly, so 30 days is a sensible balance.

## `onlyValid` (type: `boolean`):

Drop numbers that fail validation instead of returning them with a rejection reason. You are not charged for rows that are not delivered.

## `skipDuplicates` (type: `boolean`):

Deliver each distinct number once. Off by default, so the output lines up row-for-row with your input list and the `is_duplicate` / `duplicate_of_index` columns tell you what repeated.

## `maxNumbers` (type: `integer`):

Stop after this many numbers from the merged input. 0 = no limit.

## `proxyConfiguration` (type: `object`):

Optional. The carrier feed has no anti-bot and needs no proxy — it answered identically from a home IP, an Apify datacenter IP and an Apify residential IP when this actor was built. Add one only if you want to spread very large runs.

## Actor input object example

```json
{
  "phoneNumbers": [
    "+1 415 937 2000",
    "+1 415 937 1234",
    "+1 212 736 5000",
    "+1 604 685 3300",
    "+1 800 266 2278",
    "+44 20 7183 8750",
    "+49 1511 2345678",
    "+61 412 345 678",
    "+86 138 0013 8000",
    "+91 98765 43210",
    "4155552671",
    "not a phone"
  ],
  "phoneNumbersText": "",
  "numbersFileUrl": "",
  "sourceDatasetId": "",
  "sourceField": "",
  "defaultCountry": "US",
  "dialFromCountry": "US",
  "nanpEnrichment": true,
  "lergCacheDays": 30,
  "onlyValid": false,
  "skipDuplicates": false,
  "maxNumbers": 0,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `validatedNumbers` (type: `string`):

The dataset of validated and enriched phone numbers (one item per submitted number).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "phoneNumbers": [
        "+1 415 937 2000",
        "+1 415 937 1234",
        "+1 212 736 5000",
        "+1 604 685 3300",
        "+1 800 266 2278",
        "+44 20 7183 8750",
        "+49 1511 2345678",
        "+61 412 345 678",
        "+86 138 0013 8000",
        "+91 98765 43210",
        "4155552671",
        "not a phone"
    ],
    "defaultCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/phone-number-validator-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "phoneNumbers": [
        "+1 415 937 2000",
        "+1 415 937 1234",
        "+1 212 736 5000",
        "+1 604 685 3300",
        "+1 800 266 2278",
        "+44 20 7183 8750",
        "+49 1511 2345678",
        "+61 412 345 678",
        "+86 138 0013 8000",
        "+91 98765 43210",
        "4155552671",
        "not a phone",
    ],
    "defaultCountry": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/phone-number-validator-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "phoneNumbers": [
    "+1 415 937 2000",
    "+1 415 937 1234",
    "+1 212 736 5000",
    "+1 604 685 3300",
    "+1 800 266 2278",
    "+44 20 7183 8750",
    "+49 1511 2345678",
    "+61 412 345 678",
    "+86 138 0013 8000",
    "+91 98765 43210",
    "4155552671",
    "not a phone"
  ],
  "defaultCountry": "US"
}' |
apify call scrapersdelight/phone-number-validator-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/phone-number-validator-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/v58JszdWq1OJgdN8Z/builds/tGWOwSTUlcq4WqelJ/openapi.json
