# Phone Number Validator - Bulk E.164, Line Type & Country (`neverempty/phone-number-validator`) Actor

For CRM hygiene, lead qualification, SMS and voice routing and fraud checks: one row per number with valid and possible, line type (mobile, fixed, toll-free, VoIP and more), country and calling code, E.164, international, national and tel: formats, and time zones. No API key, no outside service.

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

## Pricing

from $1.83 / 1,000 phone number checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 E.164, Line Type & Country

**For CRM and lead-list cleanup, SMS and call-centre routing, signup validation and deduplicating a customer database**: check a list of phone numbers in bulk and get one row per number — whether it is **valid** and **possible**, its **line type** (mobile, fixed line, toll-free, premium rate, shared cost, VoIP, personal number, pager, UAN, voicemail), the **country** and calling code, the number in **E.164**, international, national and RFC 3966 (`tel:`) format, the **region** for geographic numbers, the **time zones** it can be in, and the carrier its range was **originally assigned** to.

**Checked against Google's own reference set, not against a claim.** Measured on 2026-09-11 with the versions pinned in this Actor: all **1,377** of Google's example numbers (**245** regions) were run through this check. `valid` agreed on **1,377 of 1,377**; E.164, calling code and national number on **1,377 of 1,377**; the line type on **1,376** — the single difference being Tristan da Cunha `+290 8999`, where the two sources disagree about one label.

**The same number written five ways is charged once.** `+1 201-555-0123`, `(201) 555-0123` (with `US`) and `+1 (201) 555 0123` are one number: checked and charged once, with the repeats returned as uncharged `duplicate-input` rows naming the first input. Full-width digits, `＋`, brackets and dash characters of any kind are normalised first, so a pasted `＋81 90-1234-5678` is read as `+81 90-1234-5678`.

**A number with no country code is not silently assigned one.** `020 7946 0000` is a valid London number with `GB` and an **uncharged** row with `US` that shows the E.164 it was read as and tells you to add the country code. International dialling prefixes work too (`011 44 20 7946 0000` with `US`, `0044 20 7946 0000` with `GB`).

Numbers are checked against Google's libphonenumber numbering plan data (the `libphonenumber-js` port with its full "max" metadata). **No API key, no proxy, no outside service** — nothing leaves the run. Export as JSON, CSV or Excel.

```json
{
  "phones": ["+1 201-555-0123", "(201) 555-0123", "+44 7400 123456", "＋81 90-1234-5678"],
  "defaultCountry": "US"
}
```

### What this checks, and what it cannot

This Actor checks the **format** of a number against each country's numbering plan. It does not dial or query
anything. So:

| It tells you | It cannot tell you |
|---|---|
| Whether the digits fall in a number range the country has assigned (`valid`) | Whether the number is in service or switched off today |
| Whether the length is plausible for the country (`possible`) | Who uses the number |
| The line type the range is assigned to (mobile, fixed line, toll-free...) | The carrier the number uses **now** - numbers that were ported keep the original carrier in `originalCarrier` |
| The region for geographic numbers, the time zones, the country | Whether it can receive SMS or has WhatsApp |

`valid: true` means "this is a correctly formed number from an assigned range", not "this number works". If you
need live reachability or the current carrier, you need a service that queries the phone network; this is not
one.

### What this does that a plain validator does not

Everything below was measured on 2026-09-11 with the versions pinned in this Actor. UK numbers in the examples are
from the 020 7946 0xxx range Ofcom reserves for drama; the others are example numbers from Google's metadata.

#### 1. Checked against Google's own examples

Google's libphonenumber (the Java/C++ original, via `google-libphonenumber` 3.2.46) publishes an example number for
every country and line type. All 1,377 of them (245 regions) were run through this Actor's check: `valid` agreed
on 1,377 of 1,377, E.164, calling code and national number on 1,377 of 1,377, and the line type on 1,376. The one
difference is Tristan da Cunha `+290 8999`, which Google calls fixed line and this library calls fixed line or
mobile. These comparisons are part of the Actor's tests.

It uses the full metadata. With the small default metadata of the same library, `isValid()` only checks length:
`+49 10123456` is "valid" there and not valid here (Google agrees it is not valid).

#### 2. US and Canadian numbers are not called "mobile"

In the North American Numbering Plan (+1), mobile and landline numbers share the same area codes, so the number
alone cannot tell them apart. Every valid US and Canadian geographic number comes back as `fixed-line-or-mobile`,
as in Google's library. A validator that labels US numbers "mobile" or "landline" from the digits is guessing.

#### 3. No country is guessed, and a wrong assumption is not charged

A number written without a + country code, such as `020 7946 0000`, can only be read as a number of some country.

- If you set `defaultCountry`, it is read as a number of that country, and the row says so in `countryCodeFrom`
  (`default-country`) and `assumedCountry`.
- If it is **not valid** for that country, the row comes back with `status` = `not-valid-in-default-country` and
  is **not charged**: the country was an assumption, so an assumption that turns out wrong is not billed. The row
  still shows the E.164 it was read as and why it is not valid, and says to add + and the country code, or to give
  its country with it. `020 7946 0000` is a valid London number with `GB` and an uncharged row with `US`.
- The international dialling prefix of the default country works: `011 44 20 7946 0000` with `US`, or
  `0044 20 7946 0000` with `GB` or `DE`, becomes `+44 20 7946 0000` (`countryCodeFrom`: `international-prefix`).
  `0044 ...` with `US` is not a US way of dialling and comes back as an uncharged row.
- If `defaultCountry` is empty, such numbers are **not read at all**. They come back as uncharged `bad-input` rows
  saying the country is unknown. The Actor never picks a country for you.

#### 4. The same number is charged once

`+1 201-555-0123`, `(201) 555-0123` (with `US`) and `+1 (201) 555 0123` are the same number. It is checked and
charged once; the repeats come back as uncharged `duplicate-input` rows that name the first input and its E.164.
Numbers are compared by E.164 plus extension, so `+1 201-555-0123 ext. 7` is a different number.

#### 5. Full-width characters and bracketed country codes

Numbers typed on Japanese, Chinese or Korean keyboards often use full-width characters: `＋８１ ９０－１２３４－５６７８`
is read as `+81 90-1234-5678`. Full-width digits, ＋ and brackets, and a country code in brackets such as
`(+44) 20 7946 0000` or `+(44) 20 7946 0000`, are converted before the number is read; dash characters of any kind
(`–`, `−`, `ー`, `－`) are accepted as separators. The `input` column keeps exactly what you sent.

#### 6. Region, time zones and carrier follow Google's rules

- `region` is filled only for number types Google treats as geographic: fixed line, fixed line or mobile, and
  mobile numbers in Mexico, Argentina and Brazil (for example `+55 11 96123-4567` is São Paulo). A UK mobile or a
  toll-free number has no region, rather than a guessed one. The region comes from Google's area-code tables, in
  English.
- `timeZones` follows Google's time zone mapper: for geographic numbers, the time zones of the number's prefix
  (`+61 2 9123 4567` is `Australia/Sydney`); for mobile, toll-free and other non-geographic numbers, every time zone
  of the country code, because the number is not tied to a place. An Australian mobile gets 8 time zones, a UK
  mobile 4, a US toll-free number 42.
- `originalCarrier` is filled only for mobile, fixed-line-or-mobile and pager numbers, and only where Google has a
  table. For +1, Google's table (696 ranges) is almost all Caribbean and US-territory numbers (for example Jamaica `+1 876`
  and Puerto Rico); it has only 15 mainland US ranges, all in area code 659, and none in Canada, so most US numbers and
  all Canadian numbers have no carrier.

### Charging

| What you give | Row | Charged |
|---|---|---|
| A number that can be read and is valid | `ok`, `valid: true` | Yes |
| A number with a + country code (or with its own `country`) that can be read but is not valid - wrong length, or digits outside any assigned range | `ok`, `valid: false`, with `validationDetail` and a note saying why | Yes - it is a checked result |
| A number without a + country code, read with `defaultCountry`, that is not valid for that country | `not-valid-in-default-country`, with what it was read as and why | No |
| Something that is not written as a phone number (letters only, a label such as `Tel:` in the entry, two numbers in one entry, 1-2 digits, over 17 digits) | `bad-input`, with the reason | No |
| A number without a + country code when `defaultCountry` is empty | `bad-input` | No |
| A country calling code not in use (`+999 ...`), or an entry whose own `country` is not a known country code | `bad-input` | No |
| A number sent as a JSON number too large to be exact (over 2^53) | `bad-input`, asking to send it as text | No |
| The same number again, written any way | `duplicate-input` | No |
| Inputs left over when the run reached its maximum total charge, or beyond 100,000 | `not-checked`, saying how many | No |

Only rows with `status` = `ok` are charged.

### Input

| Field | What it does |
|---|---|
| `phones` | Phone numbers, one per line, in any common format: `+1 201-555-0123`, `(201) 555-0123`, `+44 (0) 20 7946 0000`, `(+44) 20 7946 0000`, full-width `＋81 90-1234-5678`, with an extension such as `ext. 12`. Several per line are split on commas, semicolons and line breaks. A number that starts with an international dialling prefix instead of + (`011 44 ...`, `0044 ...`) is read only when that prefix is the one used in `defaultCountry`. Through the API an entry can also be `{"phone": "020 7946 0000", "country": "GB"}` to give that number its own country. Send numbers as text: a JSON number over 2^53 has already lost digits and is not read. Up to 100,000 per run. If left empty, seven example numbers from Google's metadata are checked, and the log says so. |
| `defaultCountry` | Two-letter ISO 3166 code (`US`, `GB`, `DE`, `JP`, `BR`, `IN`...) used to read numbers written without a + country code. Numbers read this way that are not valid for that country are returned but not charged. The form pre-fills `US`. Through the API it has no default: if you leave it out, numbers without a country code are not read. |

Neither field has a default value in the input schema, so nothing is ever added to your input that you did not
send.

Entries are read strictly as phone numbers: text around the number is not searched, so an entry such as
`Call +44 20 7946 0000` is a `bad-input` row rather than a guess at which digits you meant. Vanity numbers with
letters (`1-800-FLOWERS`) are not converted.

### What you get

Verdict: `valid`, `possible`, `validationDetail` (`valid`, `too-short`, `too-long`, `invalid-length`,
`not-in-numbering-plan`), `type` (`mobile`, `fixed-line`, `fixed-line-or-mobile`, `toll-free`, `premium-rate`,
`shared-cost`, `voip`, `personal-number`, `pager`, `uan`, `voicemail`, or `unknown` when the number is not valid)
and `typeCode` (the library's own name, such as `FIXED_LINE_OR_MOBILE`).

Formats: `e164`, `international`, `national`, `rfc3966`. `e164` is given for every number that could be read, so
you can spot repeats; for a number that is not valid it is just your digits after the calling code, not a
dialable number. The other three formats are given only for valid numbers.

Country: `country` (ISO code), `countryName`, `callingCode`, `nationalNumber`, `extension`, `isNonGeographic`
(for example `+800` international freephone). For a number that is not valid, `country` is empty when the calling
code is shared by several countries (+1 covers more than 20).

How it was read: `input` (exactly what you sent, trimmed), `inputIndex` (its position in your list after
entries are split on commas, semicolons and line breaks, from 1; empty entries are skipped), `inputCountry` (a
country you gave with that entry), `countryCodeFrom` (`plus-sign`, `international-prefix`, `row-country`,
`default-country`), `assumedCountry`.

Location: `region`, `timeZones`, `originalCarrier`.

Every row also has `status`, `note`, `library` (the library and version the verdict comes from) and `checkedAt`.

### Speed and limits

- One run checks up to 100,000 numbers. On Apify, a run of 100,000 different numbers finished in 92 seconds with
  512 MB of memory (peak 220 MB). Checking itself takes a few seconds; most of the run time is writing rows to the
  dataset. Anything beyond 100,000 is not checked and is listed in one uncharged row.
- Rows are written in blocks of 1,000 inputs. Within a block the charged rows come first and the uncharged rows
  after them; sort by `inputIndex` to get your original order back.
- Numbering plans change. The verdict is as current as the pinned library version shown in `library`
  (libphonenumber-js 1.13.13, published 2026-09-10). A range a country opened after that can come back as not
  valid.

### Source and licence

- Validation, line type and formats: [libphonenumber-js](https://gitlab.com/catamphetamine/libphonenumber-js)
  (MIT), a port of Google's [libphonenumber](https://github.com/google/libphonenumber) with its metadata (Apache
  License 2.0).
- Region, time zones and original carrier: Google libphonenumber's geocoding, carrier and time zone tables
  (Apache License 2.0), in the files packaged by `libphonenumber-geo-carrier` (MIT).

This Actor is not affiliated with Google.

### Support

Found a number that comes back wrong? Open an issue in the Issues tab with the number (or one of the same
shape), the country, and what you expected.

# Actor input Schema

## `phones` (type: `array`):

One phone number per line, in any common format: +1 201-555-0123, (201) 555-0123, +44 (0) 20 7946 0000, (+44) 20 7946 0000, full-width ＋81 90-1234-5678, with an extension such as ext. 12. Several per line are split on commas and semicolons. Numbers with a + country code are read as written. Numbers without one are read as numbers of the default country below; an international dialling prefix works only if it is the one used in that country (011 44 ... with US, 0044 ... with GB or DE). A number read with the default country that is not valid there is returned but not charged. The same number written two ways is checked and charged once. Up to 100,000 per run.

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

Two-letter ISO 3166 country code (US, GB, DE, JP, BR, IN...) used to read numbers written without a + country code, as they are dialled inside that country. Each such row says which country it was read as. Numbers read this way that are not valid for that country are returned as uncharged rows. If this is left empty, numbers without a + country code are not guessed: they come back as uncharged bad-input rows.

## Actor input object example

```json
{
  "phones": [
    "+1 201-555-0123",
    "+44 7400 123456",
    "(201) 555-0123",
    "+81 90-1234-5678",
    "+49 30 123456",
    "+1 800-234-5678",
    "+44 20 7946 000"
  ],
  "defaultCountry": "US"
}
```

# Actor output Schema

## `results` (type: `string`):

One row per phone number: whether it is valid and possible, its line type, country, E.164 and other formats, region and time zones, or an uncharged row saying why it could not be checked (not a phone number, no country code, repeated input).

# 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 = {
    "phones": [
        "+1 201-555-0123",
        "+44 7400 123456",
        "(201) 555-0123",
        "+81 90-1234-5678",
        "+49 30 123456",
        "+1 800-234-5678",
        "+44 20 7946 000"
    ],
    "defaultCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/phone-number-validator").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 = {
    "phones": [
        "+1 201-555-0123",
        "+44 7400 123456",
        "(201) 555-0123",
        "+81 90-1234-5678",
        "+49 30 123456",
        "+1 800-234-5678",
        "+44 20 7946 000",
    ],
    "defaultCountry": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/phone-number-validator").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 '{
  "phones": [
    "+1 201-555-0123",
    "+44 7400 123456",
    "(201) 555-0123",
    "+81 90-1234-5678",
    "+49 30 123456",
    "+1 800-234-5678",
    "+44 20 7946 000"
  ],
  "defaultCountry": "US"
}' |
apify call neverempty/phone-number-validator --silent --output-dataset

```

## MCP server setup

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

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/xpjPhODbWRxlHrL7J/builds/E9haHmfurpaoG6bta/openapi.json
