# Phone Number Validator: Format, Type, Country & Carrier (`private_image_hg9/phone-number-validator`) Actor

Validate and format phone numbers in bulk, offline. E.164, international and national formats, mobile or landline, country, original carrier, city and time zone for up to 1,000,000 numbers per run. Finds numbers in pasted text. $0.20 per 1,000.

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

## Pricing

$0.20 / 1,000 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: Format, Type, Country & Carrier

Clean a phone number list in one run. Give it numbers in any format (`(202) 456-1414`, `+44 20 7219 3000`, `0049 30 227-0`, `1-800-FLOWERS`) and get one row per number back: **valid or not, E.164 and other standard formats, mobile or landline, country, city or area, original carrier and time zone**. Up to **1,000,000 numbers per run** for **$0.20 per 1,000**.

It works **fully offline**. Every check uses the numbering-plan data inside [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers), the Python port of Google's libphonenumber (the library Android and Google use to read phone numbers, Apache-2.0). No website or API is called, so runs are fast, cheap and never rate limited, and your numbers never leave the run.

### Who it's for

- **CRM and marketing teams** cleaning a contact list before an import or an SMS campaign: drop invalid numbers, split mobiles from landlines, store everything in E.164.
- **Developers** who need E.164 for Twilio, WhatsApp Business or a database column, without adding a library to their own code.
- **Lead and data teams** who scraped or bought a list and want to know which numbers can be real, from which country, and in which time zone to call.
- **Anyone with a pile of text** (emails, notes, web page text): turn on **Find numbers in text** and it pulls out every phone number.

### What it does and doesn't do

| | This tool | Live lookup tools (HLR or carrier APIs) | WhatsApp checkers |
|---|---|---|---|
| Valid number for its country, possible length | Yes | Yes | Some |
| E.164, international, national, `tel:` formats | Yes | Varies | No |
| Mobile, landline, toll free, VoIP, premium... | Yes, from the number range | Yes | No |
| Country, city or area, time zones | Yes | Country, sometimes more | No |
| Carrier | **Original** carrier of the number range (no porting) | Current carrier | No |
| Is the line switched on right now / has WhatsApp | **No** | Yes (line status) | Yes (needs a logged-in account) |
| Speed and price | Thousands per second, $0.20 per 1,000 | Slower (one network query per number), usually priced higher | Slow, one number at a time |

So use this to **format, validate and classify** numbers cheaply, and use a live lookup only for the numbers where you need to know the line is active today.

### Input

| Field | What it does |
|---|---|
| **Phone numbers** | One per line, any common format. Letters on vanity numbers are converted (`1-800-FLOWERS`). Extensions are read (`ext. 123`, `x123`). |
| **Default country** | Two-letter code (`US`, `GB`, `DE`, `IN`...) for numbers written without a `+` country code. Without it, such numbers come back as `unparseable` with the reason `missing_country` (free). `UK` is accepted for `GB`. International dialing prefixes are read for this country (`011` for US, `00` for most of Europe). |
| **Text** | Paste numbers here instead, one per line (commas, semicolons and tabs also separate them), e.g. a column copied from a spreadsheet. |
| **Find numbers in text** | Read **Text** as free text and extract every valid phone number in it. Each row gets the matched text (`input`) and where it starts (`textOffset`). Dates, order numbers and invalid numbers are skipped. Numbers without `+` are found only when valid for the default country. |
| **Numbers with their own country (JSON)** | For mixed lists: `[{"number": "020 7219 3000", "country": "GB"}, {"number": "030 227-0", "country": "DE"}]`. The country overrides the default for that number. |
| **Remove repeats** | On by default. Each number is checked once per run by its E.164 form, so `+1 202-456-1111` and `(202) 456-1111` count as one. Repeats are skipped and not charged. |

Example input:

```json
{
  "numbers": ["+1 202-456-1111", "(202) 456-1414", "+44 20 7219 3000", "1-800-FLOWERS", "202-555-01", "not a number"],
  "defaultCountry": "US",
  "dedupe": true
}
```

### Output

One row per number, in this shape (a UK mobile from libphonenumber's own example range):

```json
{
  "position": 4,
  "source": "numbers",
  "input": "+44 7400 123456",
  "countryHint": "US",
  "status": "valid",
  "valid": true,
  "possible": true,
  "notPossibleReason": null,
  "e164": "+447400123456",
  "international": "+44 7400 123456",
  "national": "07400 123456",
  "rfc3966": "tel:+44-7400-123456",
  "countryCode": 44,
  "region": "GB",
  "countryName": "United Kingdom",
  "numberType": "mobile",
  "originalCarrier": "Three",
  "location": null,
  "timeZones": ["Europe/Guernsey", "Europe/Isle_of_Man", "Europe/Jersey", "Europe/London"],
  "extension": null,
  "error": null
}
```

- `status`: `valid` (a real, assigned number range for that country), `invalid` (parsed, but not a valid number: wrong length or an unused range), or `unparseable` (not a phone number at all, or the country can't be known; `error` says why). Only `valid` and `invalid` are charged.
- `possible` / `notPossibleReason`: whether the length could work at all. Reasons: `too_short`, `too_long`, `invalid_length`, `invalid_country_code`, and for unparseable input also `not_a_number` and `missing_country`. A number can be possible but still invalid (right length, unused range).
- `numberType`: `mobile`, `fixed_line`, `fixed_line_or_mobile` (the US, Canada and a few others don't separate them), `toll_free`, `premium_rate`, `shared_cost`, `voip`, `personal_number`, `pager`, `uan`, `voicemail`, or `unknown`.
- `originalCarrier`: the operator the number range was **originally allocated to**, when the library knows it (mostly mobiles). Numbers may since have been **ported** to another operator, so this is not necessarily today's carrier. Many countries (including the US and Canada) have no carrier data.
- `location`: city, area or state in English for geographic numbers (`London`, `Washington D.C.`, `Berlin`); `null` for mobiles and non-geographic numbers, or when only the country is known.
- `region` is the ISO country code; `null` for global numbers such as `+800` international freephone.
- `position` is the place in your input (1 = first); `source` says which field it came from. Rows from the same batch can be saved slightly out of order, so sort by `position` if order matters.
- For `invalid` numbers the formats show how the digits read with that country code; filter on `valid` before dialing.

Download results as JSON, CSV or Excel, or send them to Google Sheets, Zapier, Make or n8n. The run also saves a `SUMMARY` record: counts of valid, invalid, unparseable and repeats, numbers per type and per country, numbers charged, speed, and whether the spending cap stopped the run.

### Pricing

Pay per event: **$0.0002 per number checked ($0.20 per 1,000)**. You pay for every number that parsed, valid or invalid, because that verdict is the result. Text that isn't a phone number (no digits, garbage, an unknown `+` code, a national number with no country to read it by) is saved with the reason **for free**, and repeats skipped by **Remove repeats** are free too. No start fee. Set a maximum charge per run in the run options and the Actor stops cleanly when it's reached (checked before every batch of 500 numbers).

### Speed and memory

- About **14,000 numbers per second on one full CPU core** (measured locally on 1,000,000 generated numbers from 20 countries, dataset writes not counted).
- Apify gives an Actor CPU in proportion to its memory (a full core per 4 GB). At the default **256 MB** expect roughly 1,000 numbers per second, so a million numbers take around 20 minutes. Give the run **1 to 4 GB** to go several times faster; the price per number stays the same.
- 1,000,000 numbers sent as plain text peaked at about 200 MB of memory locally, so the 256 MB default is enough. If you send hundreds of thousands of numbers as `{"number", "country"}` objects, use 512 MB, because each object takes more memory to read.
- Very large lists make a large input. If the Console refuses to save it, split the list over a few runs or start the run through the API.

### Good to know

- **Valid doesn't mean in service.** `valid` means the number belongs to a range the country's numbering plan has assigned. It can't tell whether a particular line is connected, who owns it, or whether it has WhatsApp. That needs a live network lookup, which this tool deliberately doesn't do.
- **Data freshness.** Numbering plans change. The data is as fresh as the phonenumbers release in the build (the version is in `SUMMARY.phonenumbersVersion`); rebuilding the Actor picks up new releases.
- **No web requests at all.** There's nothing to rate limit and no robots.txt to respect, because nothing is fetched. Your numbers are only processed inside the run and stored in your own dataset.
- **Your data, your responsibility.** Phone numbers of people are personal data. Validate only lists you're allowed to process, and follow the rules that apply to you (for example GDPR, TCPA and do-not-call rules) before you call or text anyone.

This Actor isn't affiliated with or endorsed by Google, the libphonenumber project or any telephone operator.

### More tools from the same developer

- [Website Contact Details Scraper: Emails, Phones & Socials](https://apify.com/private_image_hg9/website-contact-details-scraper)
- [Email Verifier: Syntax, MX, Disposable & Role Check](https://apify.com/private_image_hg9/email-verifier-mx-check)
- [Company Job Boards Scraper: Greenhouse, Lever & Ashby](https://apify.com/private_image_hg9/company-job-boards-scraper)
- [Domain WHOIS, DNS & SSL Checker: Expiry, SPF & DMARC](https://apify.com/private_image_hg9/domain-whois-dns-ssl-checker)
- [Federal Contracts Search: SAM.gov Opportunities & Award History](https://apify.com/private_image_hg9/federal-contracts-search)

# Actor input Schema

## `numbers` (type: `array`):

One number per line, in any common format: +1 202-456-1111, (202) 456-1414, 0044 20 7219 3000, 1-800-FLOWERS. Numbers without a + country code use the default country below. Up to 1,000,000 numbers per run across all fields.

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

Two-letter country code (US, GB, DE, IN...) for numbers written without a + country code. Leave empty if every number starts with + or 00; numbers without one then come back as unparseable (free).

## `text` (type: `string`):

Paste numbers here instead, one per line (commas, semicolons and tabs also separate them), for example a column copied from a spreadsheet. Turn on "Find numbers in text" to paste any text (emails, web page text, notes) and pull out every phone number written in it.

## `findInText` (type: `boolean`):

Read "Text" as free text and extract every valid phone number in it (each row gets the matched text and its position). Numbers without a + country code are found only when they are valid for the default country.

## `numbersWithCountry` (type: `array`):

For mixed lists where the country differs per number: \[{"number": "020 7219 3000", "country": "GB"}, {"number": "030 2270", "country": "DE"}]. The country here overrides the default country for that number.

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

Check each number once per run, by its E.164 form (and extension), so +1 202-456-1111 and (202) 456-1111 count as one. Repeats are skipped and not charged.

## Actor input object example

```json
{
  "numbers": [
    "+1 202-456-1111",
    "(202) 456-1414",
    "+44 20 7219 3000",
    "1-800-FLOWERS",
    "+49 30 227-0",
    "202-555-01",
    "not a number"
  ],
  "defaultCountry": "US",
  "findInText": false,
  "dedupe": true
}
```

# Actor output Schema

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

One row per number: valid or not, formats, type, country, area, original carrier and time zones.

## `summary` (type: `string`):

Counts for the run: valid, invalid, unparseable, repeats skipped, charged, numbers per type and country, and whether the spending cap was reached.

# 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 = {
    "numbers": [
        "+1 202-456-1111",
        "(202) 456-1414",
        "+44 20 7219 3000",
        "1-800-FLOWERS",
        "+49 30 227-0",
        "202-555-01",
        "not a number"
    ],
    "defaultCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("private_image_hg9/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 = {
    "numbers": [
        "+1 202-456-1111",
        "(202) 456-1414",
        "+44 20 7219 3000",
        "1-800-FLOWERS",
        "+49 30 227-0",
        "202-555-01",
        "not a number",
    ],
    "defaultCountry": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("private_image_hg9/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 '{
  "numbers": [
    "+1 202-456-1111",
    "(202) 456-1414",
    "+44 20 7219 3000",
    "1-800-FLOWERS",
    "+49 30 227-0",
    "202-555-01",
    "not a number"
  ],
  "defaultCountry": "US"
}' |
apify call private_image_hg9/phone-number-validator --silent --output-dataset

```

## MCP server setup

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