# Phone Number Validator: Bulk E.164 Format, Line Type, Carrier (`latamdatatools/phone-number-validator`) Actor

Validate, clean and format phone numbers offline with Google's libphonenumber: valid/possible, E.164, international and national format, country, line type (mobile, landline, VoIP, toll-free), time zone, carrier and duplicates. Lists, CSV or datasets.

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

## Pricing

from $2.00 / 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.

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

## Phone Number Validator: Bulk E.164 Format, Line Type, Carrier

Validate, clean and format **thousands of phone numbers in seconds**. Paste a list, point it at a CSV file or at the dataset of another Actor (Google Maps, contact or lead scrapers), and get one clean row per number:

- **Valid / possible / invalid**, with the reason (too short, too long, unknown country code, not an assigned range…)
- **E.164** (`+16502530000`), **international** (`+1 650-253-0000`), **national** (`(650) 253-0000`) and `tel:` URI format
- **Country** (ISO code and name), **calling code** and national number, plus the **extension** if there is one
- **Line type**: mobile, fixed line, fixed line or mobile, VoIP, toll-free, premium rate, shared cost, personal number, pager, UAN, voicemail
- **Time zones** of the number (great for call scheduling)
- **Location**: city or region of the number range (e.g. "Mountain View, CA", "Berlin", "Buenos Aires")
- **Carrier**: the network the number range was originally assigned to, where available
- **Duplicate detection**: "(650) 253-0000" and "+1 650 253 0000" are the same number. Flag duplicates or remove them

It runs **100% offline** with Google's **libphonenumber** metadata (the same library used by Android and many CRMs). No external API, no HLR lookup, no calls, no SMS: your contact list never leaves the run. That is also why it is fast and cheap.

### What does Phone Number Validator do?

Phone Number Validator checks and formats phone numbers in bulk with Google's libphonenumber, offline: valid / possible / invalid with a reason, E.164, international and national format, country, line type (mobile, landline, VoIP, toll-free), time zone, original carrier and duplicates. Use it to clean CRM and lead lists before calling, SMS or WhatsApp campaigns.

### What it does NOT do

This Actor checks that a number is **correctly written and belongs to a real numbering plan** (right length, assigned prefix, right country). It **does not check whether the line is active**, who owns it, or whether it has WhatsApp. A valid number can still be disconnected. The carrier is the *original* network of the number range: if the user ported the number to another operator, that is not reflected.

### Use cases

- **Clean CRM lists** before importing them into HubSpot, Salesforce, Pipedrive or Zoho: one E.164 format, no duplicates, broken numbers flagged
- **Normalise web form and checkout submissions** that arrive as "011 15 2345-6789", "(212) 555 0123" or "+44 (0) 20…"
- **Prepare SMS, WhatsApp or call-center campaigns**: keep only valid mobile numbers, group them by country and time zone, avoid paying for messages to invalid numbers
- **Enrich scraped leads**: run it on the output of a Google Maps or business directory scraper and keep the `title`, `website` etc. next to the clean number
- **Data quality checks** in ETL pipelines through the Apify API, Make, Zapier or n8n

### How to use it

1. Paste numbers (one per line, any format), **or** enter the **dataset ID** of another run, **or** a **CSV URL** (Google Sheets links work if the sheet is shared as "Anyone with the link").
2. Set the **default country** for numbers written without `+` (e.g. `US`, `GB`, `DE`, `BR`, `MX`). Numbers that start with `+`, `00` or `011` keep their own country. If your data has a country column, put its name in **Country field** and it is used per record.
3. For datasets and CSV files, set **Phone field** to the column with the number (`phone`, `phoneNumber`, `Phone`, `contact.phone`…). Lists of numbers or numbers separated by `;` or `|` in one cell give one row each.
4. Run. Export as CSV, Excel, JSON or use the API.

#### Input examples

A pasted list:

```json
{
  "phoneNumbers": ["+1 650-253-0000", "(212) 555-0123", "+44 7400 123456", "12345"],
  "defaultCountry": "US"
}
```

The output of another Actor, keeping the business name and website, without duplicates and only valid numbers:

```json
{
  "datasetId": "aBcDeFgHiJkLmNoPq",
  "phoneField": "phone",
  "countryField": "countryCode",
  "defaultCountry": "US",
  "keepFields": ["title", "website"],
  "duplicates": "skip",
  "includeInvalid": false
}
```

A CSV file or Google Sheet:

```json
{
  "csvUrl": "https://docs.google.com/spreadsheets/d/<sheet-id>/edit#gid=0",
  "phoneField": "Phone",
  "defaultCountry": "GB"
}
```

### Output example

```json
{
  "position": 3,
  "input": "+44 7400 123456",
  "status": "valid",
  "isValid": true,
  "isPossible": true,
  "possibleReason": "IS_POSSIBLE",
  "e164": "+447400123456",
  "international": "+44 7400 123456",
  "national": "07400 123456",
  "rfc3966": "tel:+44-7400-123456",
  "countryCode": "GB",
  "countryName": "United Kingdom",
  "countryCallingCode": 44,
  "nationalNumber": "7400123456",
  "extension": null,
  "numberType": "MOBILE",
  "location": "United Kingdom",
  "timeZones": ["Europe/Guernsey", "Europe/Isle_of_Man", "Europe/Jersey", "Europe/London"],
  "carrier": "Three",
  "defaultCountryUsed": "US",
  "isDuplicate": false,
  "duplicateOf": null,
  "error": null,
  "source": null
}
```

Every row has exactly the same keys, so CSV and Excel exports have stable columns.

| `status` | Meaning |
|---|---|
| `valid` | Right length and an assigned range for that country. Formats, type, time zone, location and carrier are filled |
| `possible` | Right length, but the prefix is not an assigned range (often a typo or a fictional number). Formats are filled, type is not |
| `invalid` | Wrong length or unknown country code. `error` says why |
| `unparseable` | Not a phone number, no country could be found, or the value was mangled by a spreadsheet (e.g. `5.49E+12`) |

The dataset has three views: **Validated numbers**, **Clean list (E.164 only)** and **Problems**. The `RUN_SUMMARY` record in the key-value store gives totals: valid, possible, invalid, unparseable, duplicates, numbers by country and by line type, and speed.

### Pricing

Pay per event: **USD 2 per 1,000 numbers** (USD 0.002 per number in the results). Numbers you choose to leave out (duplicates with "Remove duplicates", invalid ones with "Include invalid numbers" off) and empty cells are **not charged**. The run respects your *max charge per run*. Platform usage is included.

### Speed and limits

- Thousands of numbers per second in memory; a list of 100,000 numbers finishes in a few minutes, most of it writing the results.
- Default memory 256 MB is enough for large lists. Up to 1,000,000 numbers per run (`maxNumbers`).
- CSV files up to 50 MB with a header row; `,` `;` tab or `|` separators are detected automatically.
- Location and carrier names can be returned in other languages where libphonenumber has them (`language`: es, pt, de, fr, zh, ja…); English otherwise.
- If no row is produced (empty input, wrong field name, wrong dataset ID), the run is marked as **failed** with a clear message, never a silent empty success.

### Why use Phone Number Validator

- **Offline and private.** Validation runs inside the Actor with Google's libphonenumber metadata; numbers are not sent to any third-party API.
- **CRM-ready output:** E.164 (`+14155550123`), international and national formats, `tel:` URI, country, line type (mobile, landline, VoIP, toll-free), time zone, and carrier where the numbering plan has it.
- **Works on messy columns:** paste a list, point to a CSV or a Google Sheets link, or read a dataset from another Actor; keep your other columns with `keepFields`; per-row country with `countryField`.
- **Duplicates and invalid rows** can be flagged, removed (not charged) or left out (not charged).

### Cost examples

Prices at the current pay-per-event rates (see Pricing). You can set a maximum charge per run in the run options.

| Job | Approximate cost |
|---|---|
| 1,000 numbers | about $2 |
| 10,000 numbers | about $20 |
| A 500-row CSV export from your CRM | about $1 |

### Use it from your code, an integration or an AI agent

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("latamdatatools/phone-number-validator").call(run_input={
    "phoneNumbers": [
        "+1 650-253-0000",
        "(212) 555-0123",
        "+44 7400 123456",
        "+49 30 901820"
    ],
    "defaultCountry": "US"
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

**JavaScript / Node.js** (`npm install apify-client`):

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('latamdatatools/phone-number-validator').call({
  "phoneNumbers": [
    "+1 650-253-0000",
    "(212) 555-0123",
    "+44 7400 123456",
    "+49 30 901820"
  ],
  "defaultCountry": "US"
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**No code:**

- **Schedules:** run it daily or weekly from Apify Console (Schedules), with the same input saved as a Task.
- **Integrations:** send results to Google Sheets, Slack, email, Make, Zapier, n8n or Airbyte from the Integrations tab, or call your own webhook when a run finishes.
- **AI agents:** add it as a tool in Claude, Cursor, VS Code and other MCP clients through the Apify MCP server (`https://mcp.apify.com`), so the agent can run Phone Number Validator and read the results.

### FAQ

**Is my data sent anywhere?** No. Validation runs inside the Actor with libphonenumber's built-in metadata. The only network access is reading the CSV URL or dataset you give it.

**Why is a real number marked `possible` or `invalid`?** Usually the default country is wrong (a UK number without `+44` read as US), a digit is missing, or the number belongs to a range that was assigned after the library's last update. Check `defaultCountryUsed` and `error`.

**Why is `carrier` empty?** libphonenumber only ships carrier data for mobile ranges in some countries, and never for fixed lines.

**Can it tell me if the number is on WhatsApp or still connected?** No, that needs live network lookups, which this Actor deliberately does not do.

**How is this different from a phone lookup API?** Lookup APIs query telecom networks live (HLR, current carrier, porting), which costs more per number. This Actor checks numbering-plan validity and formats numbers. Use it to clean a list first, and send only the valid mobiles to a paid lookup if you need live status.

**Can I read a Google Sheet?** Yes. Paste the sheet link in `csvUrl`; it must be shared as "Anyone with the link".

Questions or a missing field? Open an issue in the Issues tab.

# Actor input Schema

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

One number per line, in any format: +44 20 7946 0958, (212) 555-0123, 011 15-2345-6789, tel:+33..., with or without spaces, dashes or dots. Numbers without a leading + are read with the default country below.

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

2-letter ISO code (US, GB, DE, FR, BR, MX, AR, IN...) used for numbers written without the international +code. Numbers that start with + (or 00 / 011) keep their own country.

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

Optional: validate the phone numbers stored in an Apify dataset, for example the output of a Google Maps or contact scraper. Pick the dataset or paste its ID. Use "Phone field" to say which field holds the number.

## `csvUrl` (type: `string`):

Optional: a public URL of a CSV file with a header row (comma, semicolon, tab or | separated, up to 50 MB). Google Sheets links work too if the sheet is shared as "Anyone with the link".

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

Name of the field / CSV column that holds the phone number, e.g. phone, phoneNumber, Phone, contact.phone (dot notation for nested fields). A field with a list of numbers, or numbers separated by ; or |, gives one row per number.

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

Optional field / column with a 2-letter country code per record (e.g. countryCode). When it has a valid code it is used instead of the default country for that record.

## `keepFields` (type: `array`):

Optional: copy these fields of each dataset record / CSV row into the output (under "source"), e.g. name, email, url, so you can join the results back.

## `duplicates` (type: `string`):

Two inputs are duplicates when they are the same number after normalisation (e.g. "(650) 253-0000" and "+1 650 253 0000"). Flag them (isDuplicate = true, duplicateOf = first input) or leave them out of the results (not charged).

## `includeInvalid` (type: `boolean`):

When off, only valid numbers are returned: invalid, unreadable and "possible but not valid" numbers are left out of the results (and not charged). They are still counted in RUN_SUMMARY.

## `includeCarrier` (type: `boolean`):

Name of the mobile network the number range was originally assigned to, when libphonenumber has it for that country. It does not reflect number porting.

## `includeLocation` (type: `boolean`):

City, region or country of the number range, when available.

## `language` (type: `string`):

2-letter language code, e.g. en, es, pt, de, fr. Falls back to English when there is no translation.

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

Upper limit of numbers to process in one run. 0 = no limit (up to 1,000,000). Your max charge per run is respected too.

## Actor input object example

```json
{
  "phoneNumbers": [
    "+1 650-253-0000",
    "(212) 555-0123",
    "+44 7400 123456",
    "+49 30 901820",
    "+1 650 253 0000",
    "12345"
  ],
  "defaultCountry": "US",
  "phoneField": "phone",
  "duplicates": "flag",
  "includeInvalid": true,
  "includeCarrier": true,
  "includeLocation": true,
  "language": "en",
  "maxNumbers": 0
}
```

# Actor output Schema

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

No description

## `runSummary` (type: `string`):

No description

# 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 650-253-0000",
        "(212) 555-0123",
        "+44 7400 123456",
        "+49 30 901820",
        "+1 650 253 0000",
        "12345"
    ],
    "defaultCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("latamdatatools/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 650-253-0000",
        "(212) 555-0123",
        "+44 7400 123456",
        "+49 30 901820",
        "+1 650 253 0000",
        "12345",
    ],
    "defaultCountry": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("latamdatatools/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 650-253-0000",
    "(212) 555-0123",
    "+44 7400 123456",
    "+49 30 901820",
    "+1 650 253 0000",
    "12345"
  ],
  "defaultCountry": "US"
}' |
apify call latamdatatools/phone-number-validator --silent --output-dataset

```

## MCP server setup

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