# Phone Number Validator: Bulk Check, Format, Number Type (`fernandosord/phone-number-validator`) Actor

Validate and format phone numbers in bulk, offline with Google's libphonenumber data: valid or not and why, E.164, international and national formats, country and number type (mobile, fixed line, toll free, VoIP). Paste a list, a CSV link or a dataset from a lead scraper.

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

## Pricing

from $0.50 / 1,000 phone numbers

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

This phone number validator checks and formats phone numbers in bulk. For each number you get whether it is valid and, if not, why, plus the E.164, international and national formats, the country and the number type (mobile, fixed line, toll free, VoIP and more). It runs fully offline with Google's libphonenumber data (the complete "max" metadata through libphonenumber-js), so your numbers are never sent to a third-party lookup service and 20,000 numbers take about half a minute.

- **Three ways to give it numbers.** Paste a list, link a CSV or text file (a published Google Sheet works too), or point it at the dataset of another Apify run, such as a Google Maps or lead scraper, and say which field holds the phone number.
- **Any format in, clean format out.** `(213) 373-4253`, `0044 20 7946 0958`, `+33 6 12 34 56 78 ext. 12` and `tel:+14165550123` all come back as E.164 (`+12133734253`) and as the format people dial.
- **Keep your data.** Turn on **Keep all fields of the source row** and every result carries the original row (name, address, website) next to the validation fields.
- **Country per row.** Numbers without a `+country` code are read with a default country, or with each row's own country field when your data has one.

### What data you get

| Field | What it holds |
|---|---|
| `input` | The number as you gave it |
| `valid` | True when the length is right and the number is in a range assigned to a phone network in its country |
| `possible` | True when the digit count is possible for the country (a weaker check) |
| `e164`, `international`, `national`, `rfc3966` | `+12133734253`, `+1 213 373 4253`, `(213) 373-4253`, `tel:+12133734253` |
| `extension` | The extension, when the input had one |
| `countryCallingCode`, `country`, `countryName` | `+1`, `CA`, `Canada` |
| `type` | `mobile`, `fixed_line`, `fixed_line_or_mobile`, `toll_free`, `premium_rate`, `shared_cost`, `voip`, `personal_number`, `pager`, `uan` or `voicemail` |
| `invalidReason`, `invalidReasonText` | `NOT_A_NUMBER`, `INVALID_COUNTRY`, `TOO_SHORT`, `TOO_LONG`, `INVALID_LENGTH` or `NOT_IN_ASSIGNED_RANGE`, and the same in plain English |
| `defaultCountryUsed` | The country used to read a number that had no `+country` code |
| `source`, `sourceRow` | `list`, `dataset:<id>` or the file URL, and the row number in it |

The run's key-value store also gets a `SUMMARY` record: how many numbers were read, valid, invalid, skipped as duplicates, counts by type, country and invalid reason, and any input that could not be loaded.

### How to use it

1. Paste numbers into **Phone numbers**, or fill in **Dataset ID** or **CSV or text file URL**. You can combine all three in one run.
2. Pick the **Default country** for numbers written without a `+country` code.
3. For a dataset or CSV: set **Phone field or column name** (or leave it empty to use the first column with "phone" in its name), and optionally **Country field** and **Keep all fields of the source row**.
4. Run it and download the results as CSV, Excel or JSON, or call it from the API, Make, Zapier, n8n or an AI agent through the Apify MCP server.

To clean the output of a lead scraper: open that run, copy the dataset ID from its Storage tab, and paste it here. A field that holds a list of numbers (like `phones`) gives one result per number.

#### Example input

```json
{
  "phoneNumbers": [
    "+1 213 373 4253",
    "(416) 555-0123",
    "+44 20 7946 0958",
    "+44 7400 123456",
    "+1 800 555 0199",
    "12345",
    "not a phone"
  ],
  "defaultCountry": "US"
}
```

#### Example output (from a real run: 4 of 9 items, some fields left out after the first)

```json
[
  {
    "input": "(416) 555-0123",
    "valid": true,
    "possible": true,
    "e164": "+14165550123",
    "international": "+1 416 555 0123",
    "national": "(416) 555-0123",
    "rfc3966": "tel:+14165550123",
    "extension": null,
    "countryCallingCode": "+1",
    "country": "CA",
    "countryName": "Canada",
    "type": "fixed_line_or_mobile",
    "invalidReason": null,
    "invalidReasonText": null,
    "defaultCountryUsed": "US",
    "source": "list",
    "sourceRow": 2
  },
  {
    "input": "+44 7400 123456",
    "valid": true,
    "possible": true,
    "e164": "+447400123456",
    "international": "+44 7400 123456",
    "national": "07400 123456",
    "country": "GB",
    "countryName": "United Kingdom",
    "type": "mobile",
    "invalidReason": null
  },
  {
    "input": "+1 800 555 0199",
    "valid": true,
    "e164": "+18005550199",
    "national": "(800) 555-0199",
    "country": "US",
    "type": "toll_free"
  },
  {
    "input": "12345",
    "valid": false,
    "possible": false,
    "e164": null,
    "countryCallingCode": "+1",
    "country": "US",
    "type": null,
    "invalidReason": "TOO_SHORT",
    "invalidReasonText": "Too few digits for a number in this country.",
    "defaultCountryUsed": "US",
    "source": "list",
    "sourceRow": 8
  }
]
```

Note how `(416) 555-0123` was read with the default country US but came back as Canada: 416 is a Toronto area code, and the country is taken from the number itself.

### How much does it cost?

You pay $0.0005 per phone number saved, plus $0.0005 once per run. Valid and invalid numbers cost the same, because both are results. Duplicates removed by **Remove duplicates**, invalid numbers skipped by **Only save valid numbers**, empty rows and inputs that fail to load are not saved and cost nothing.

1,000 numbers cost $0.50, and 10,000 numbers cost $5.00. Use **Max numbers** to cap a run on a big file or dataset.

### Use cases

- **Clean lead lists** from Google Maps, directory or CRM exports before they go into a dialer, an SMS tool or a CRM that wants E.164.
- **Drop junk numbers** (too short, wrong country, not assigned) before you pay for SMS sends or a live HLR lookup.
- **Split mobiles from landlines** to decide which leads get an SMS and which get a call.
- **Normalise for dedupe and matching:** two lists with `(213) 373-4253` and `+1-213-373-4253` match once both are in E.164.

### FAQ

**Does "valid" mean the phone line is live?**
No. This Actor checks that the number has the right length and falls in a range that the country's numbering plan has assigned to a network, using Google's libphonenumber metadata. It does not call the number, query a carrier or run an HLR lookup, so it cannot tell you whether the line is switched on, who the carrier is today, or whether the number was ported. A valid number can still be disconnected. For that you need a paid carrier or HLR service.

**Why is the type `fixed_line_or_mobile` for US and Canadian numbers?**
The North American numbering plan does not separate mobile and landline ranges, so libphonenumber cannot tell them apart from the number alone. Most other countries have separate ranges, so you get `mobile` or `fixed_line`.

**What happens to numbers without a +country code?**
They are read as numbers of the **Default country** (or of the row's **Country field**). A number that starts with `+` or with the international prefix `00` is always read with its own country code. If no default country is set, such numbers come back with `invalidReason: INVALID_COUNTRY`.

**Which fictional or test numbers count as valid?**
The ones Google's metadata lists as assigned. For example, US numbers in 555-01XX are inside assigned area code ranges and pass, while `+1 555 555 5555` fails with `NOT_IN_ASSIGNED_RANGE`.

**How current is the numbering data?**
It comes from the libphonenumber-js package bundled at build time, which follows Google's releases. Countries add new ranges over time, so a very recently opened range might show as invalid until the next update.

**Can it read Excel files?**
Not directly. Save the sheet as CSV, or publish a Google Sheet as CSV (File > Share > Publish to web) and paste that link.

**Is my data sent anywhere?**
No. Numbers are checked inside the Actor's container. Only the file download (if you give a URL) leaves it.

### Other Actors by this developer

- [Business Leads List](https://apify.com/fernandosord/open-places-leads): business lists by city or country from Overture Maps open data, with phone, website and email. Paste its dataset ID here to validate the phones.
- [Google Sheets Export](https://apify.com/fernandosord/google-sheets-export): write the validated numbers to a Google Sheets tab, appending or upserting by key.
- [Email Security Check](https://apify.com/fernandosord/email-security-audit-spf-dkim-dmarc): SPF, DKIM, DMARC and MX checks with a grade for the domains in your lead list.
- [WHOIS and DNS Lookup](https://apify.com/fernandosord/domain-profile-whois-dns): domain age, registrar, DNS, mail provider and SSL for the websites of your leads.
- [Companies House Search](https://apify.com/fernandosord/uk-company-lookup-companies-house): status, address, SIC codes and filing dates for UK companies.

# Actor input Schema

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

One number per line, in any format: +44 20 7946 0958, (213) 373-4253, 0044 7400 123456, tel:+33142685300. Numbers without a +country code are read with the Default country below.

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

Country used for numbers written without a +country code, such as (213) 373-4253 or 020 7946 0958. Numbers that start with + or 00 are always read with their own country code.

## `datasetId` (type: `string`):

Optional. The ID of an Apify dataset (or username~dataset-name), for example the output of a Google Maps or lead scraper run. It is shown on the run's Storage tab. Every row is read and the number is taken from the field named below.

## `fileUrl` (type: `string`):

Optional. A link to a CSV file (comma, semicolon or tab separated, with a header row) or a plain text file with one number per line. A Google Sheet works too: use File > Share > Publish to web > CSV and paste that link.

## `phoneField` (type: `string`):

For a dataset or CSV file: the field or column that holds the number, for example phone, phoneNumber or contact.phone (dots reach inside nested objects). A field that holds a list of numbers gives one result per number. Leave empty to use the first column whose name contains phone, tel, mobile or number.

## `countryField` (type: `string`):

Optional. For a dataset or CSV file: a field holding each row's 2-letter country code (US, GB, DE). When it is filled, it is used instead of Default country for that row's numbers.

## `keepSourceFields` (type: `boolean`):

For a dataset or CSV file: copy every field of the source row into the result, so the output is your original data plus the validation fields. If a source field has the same name as a result field, it is kept as source\_<name>.

## `dedupe` (type: `boolean`):

Return each number once, even when it appears in different formats (+1 213 373 4253 and (213) 373-4253 count as the same number). Duplicates are not saved and cost nothing. Turn off to get one result for every input row.

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

Skip numbers that fail validation. They are not saved, not charged, and are counted in the SUMMARY record.

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

The run stops after saving this many results. Use it to cap the cost of a large file or dataset.

## Actor input object example

```json
{
  "phoneNumbers": [
    "+1 213 373 4253",
    "(416) 555-0123",
    "+44 20 7946 0958",
    "+44 7400 123456",
    "+49 30 901820",
    "+61 2 9374 4000",
    "+1 800 555 0199",
    "12345",
    "not a phone"
  ],
  "defaultCountry": "US",
  "keepSourceFields": false,
  "dedupe": true,
  "onlyValid": false,
  "maxNumbers": 100000
}
```

# Actor output Schema

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

One item per input number, in input order.

# 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 213 373 4253",
        "(416) 555-0123",
        "+44 20 7946 0958",
        "+44 7400 123456",
        "+49 30 901820",
        "+61 2 9374 4000",
        "+1 800 555 0199",
        "12345",
        "not a phone"
    ],
    "defaultCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("fernandosord/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 = {
    "phoneNumbers": [
        "+1 213 373 4253",
        "(416) 555-0123",
        "+44 20 7946 0958",
        "+44 7400 123456",
        "+49 30 901820",
        "+61 2 9374 4000",
        "+1 800 555 0199",
        "12345",
        "not a phone",
    ],
    "defaultCountry": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("fernandosord/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 '{
  "phoneNumbers": [
    "+1 213 373 4253",
    "(416) 555-0123",
    "+44 20 7946 0958",
    "+44 7400 123456",
    "+49 30 901820",
    "+61 2 9374 4000",
    "+1 800 555 0199",
    "12345",
    "not a phone"
  ],
  "defaultCountry": "US"
}' |
apify call fernandosord/phone-number-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fernandosord/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/BelbRjbb1I24UwofT/builds/PeTI2XQx31uGRU3AP/openapi.json
