# Phone Number Validator & Formatter – E.164, Bulk, Offline (`shephouse/phone-number-validator-offline`) Actor

Bulk phone validator and phone number formatter: validate, format to E.164 and deduplicate phone numbers offline with Google's libphonenumber. Country, line type and time zone per number. Checks plausibility, not whether a line is active. $0.0008 per phone number + $0.00005 per run.

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

## Pricing

from $0.80 / 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

### Phone number validator & formatter: what a full run gives you

Paste a list of phone numbers (or send a CSV column through the API) and get back, for **every** number, one row like this (real output from the Actor's code for `(650) 253-0000` with default country `US`, shortened):

```json
{"input": "(650) 253-0000", "isValid": true, "e164": "+16502530000", "international": "+1 650-253-0000",
 "national": "(650) 253-0000", "regionCode": "US", "numberType": "FIXED_LINE_OR_MOBILE",
 "timeZones": ["America/Los_Angeles"], "billable": true, "error": null}
```

- **Phone validator:** valid / invalid against the country's numbering plan (plausibility, not whether the line is active).
- **Phone number formatter:** E.164, international and national format, ready for HubSpot, Salesforce, Twilio.
- **Deduplication:** spelling variants of one number count once. Unparseable inputs come back with an error code and are **not charged**.
- **Price:** $0.0008 per analysed phone number + $0.00005 per run. Set "Max cost per run" to cap spend.
- **Free Apify plan:** the first 100 numbers of each run are processed so you can check the output on your own data; the rest are skipped and not charged. A paid Apify plan processes the whole list (up to 100,000 numbers per run) at the same price per number.

### What does this phone number validator & E.164 formatter do?

Clean and standardise phone-number lists in bulk. This phone number validator and formatter gives you, for every number:

- **valid / invalid** against the official numbering plan
- **E.164**, international and national formats
- **country**, **line type** (mobile, fixed line, toll-free, VoIP…)
- original carrier (where known), location and **time zones**

The Actor runs **entirely offline** using Google's open-source **libphonenumber** metadata. It calls no third-party API and visits no website.

> **What "valid" means:** this Actor checks that a number is **correctly formatted and plausible** under the numbering plan of its country. It does **not** check whether a line is **active, in service, owned by a particular person, or reachable**. It makes no network or carrier (HLR) lookups.

### Typical uses of a bulk phone number validator

- Normalise CRM, lead or customer lists to E.164 before importing them into HubSpot, Salesforce, Twilio and similar tools.
- Remove malformed and impossible numbers before an SMS or calling campaign (you're responsible for consent and compliance).
- Segment by country, line type or time zone, for example to call mobiles only during local business hours.
- Deduplicate numbers written in different formats: `(650) 253-0000`, `+1 650 253 0000` and `6502530000` count as one.

### How to validate and format phone numbers in bulk

1. Paste numbers into **Phone numbers**, one per line, or send them as a JSON array through the API.
2. Set **Default country** for numbers written without a `+` prefix (for example `US`, `GB`, `DE`).
3. Optionally set **Max numbers to process** and a **Max cost per run** to cap your spend.
4. Run the Actor, then download the results as CSV, Excel or JSON, or fetch them through the API.

#### Example input

```json
{
  "phoneNumbers": ["+1 650-253-0000", "020 7031 3000", "0412 345 678", "not a number"],
  "defaultCountry": "GB",
  "maxItems": 1000,
  "deduplicate": true,
  "includeCarrierAndLocation": true
}
```

#### Example output (one item per number)

```json
{"input":"+1 650-253-0000","billable":true,"isValid":true,"isPossible":true,
 "e164":"+16502530000","international":"+1 650-253-0000","national":"(650) 253-0000",
 "countryCallingCode":1,"regionCode":"US","numberType":"FIXED_LINE_OR_MOBILE",
 "carrier":null,"location":"Mountain View, CA","timeZones":["America/Los_Angeles"],"error":null}
```

Values that can appear in `error`:

- **Analysed but failed:** `INVALID_NUMBER`. The number parsed but doesn't match its country's numbering plan.
- **Rejected:** `NOT_A_NUMBER`, `INVALID_COUNTRY_CODE`, `TOO_SHORT_NSN`, `TOO_SHORT_AFTER_IDD`, `TOO_LONG`, `EMPTY`, `INPUT_TOO_LONG`, `NOT_A_STRING`.
- **Status row (free):** `MAX_COST_TOO_LOW` or `TIMEOUT_GUARD` appears as a single non-billable row when a run could deliver nothing, for example when "Max cost per run" is below the price of one number.

A bad row never stops the run. A `SUMMARY` record in the run's key-value store shows the totals: processed, billable, non-billable, duplicates skipped, truncated, and the stop reason.

### Pricing (pay per event)

- **$0.0008 per billable number** ($0.80 per 1,000) **plus $0.00005 per run** (Apify's standard "Actor Start" event, $0.05 per 1,000 runs). Platform usage (compute, storage) is included: you pay only these two events.
- **Billable:** any number that was successfully parsed and analysed, whether it turns out **valid or invalid**. You get a real numbering-plan verdict for it.
- **Never billable:**
  - rejected or unparseable inputs (`error` is one of the "Rejected" codes above; `billable: false`)
  - duplicates that were skipped
  - numbers cut off by `maxItems`, the free-plan limit or your max-cost limit
  - anything not delivered to your dataset
- **Spending limit:** the Actor respects your **Max cost per run**. The $0.01 minimum covers the start fee plus **12 numbers** ((0.01 − 0.00005) / 0.0008 = 12.4, rounded down). The run then stops cleanly, and everything already delivered is kept.
- **Free Apify plan:** up to **100 numbers per run**.

Examples: 1,000 numbers = $0.80005; 10,000 numbers = $8.00005; a 10-number run = $0.00805.

### How it compares

Checked on the Apify Store on 2026-10-23 (prices change; verify before you decide):

| | **This Actor** | Live lookup / messaging-app validators |
|---|---|---|
| Price per number | **$0.0008** (+ $0.00005 per run) | about $0.001 to $0.06, depending on the data returned |
| Checks format and plausibility (E.164, country, line type) | yes | usually yes |
| Tells whether a line is active, or registered on WhatsApp/Telegram | **no** | yes |
| Names the owner of a number (reverse lookup) | **no** | some do |
| Numbers leave the Actor run | **no**: fully offline | usually yes: they query external services |

**Good combination:** clean and deduplicate your list here first ($0.80 per 1,000), then send only the plausible numbers to a paid live-lookup service, so you do not pay for impossible numbers. **Choose something else if** you need to know whether a line is live or who owns it.

### Use it in your workflow

- **Spreadsheets and automations**: copy a phone column into the input, or connect the Actor through Apify's integrations (for example Make, Zapier, n8n or Google Sheets).
- **Schedule** it to clean new leads on a timetable, and filter on `isValid`, `numberType` or `billable`.
- **AI agents**: MCP-compatible assistants can call the Actor through the Apify MCP server. The input is a plain list of numbers and the output is flat JSON.

### Recipes: Python, curl, n8n, Make, Zapier, Google Sheets

**curl (synchronous, waits up to 300 s; good for lists up to a few thousand numbers):**

```bash
curl -X POST "https://api.apify.com/v2/actors/shephouse~phone-number-validator-offline/run-sync-get-dataset-items?maxTotalChargeUsd=1" \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d '{"phoneNumbers":["(650) 253-0000","020 7946 0018"],"defaultCountry":"US"}'
```

`maxTotalChargeUsd` is a hard cap on what the call can charge. Keep your token in an environment variable, never in a shared file.

**n8n, Make, Zapier:** use an HTTP request step with the URL, header and JSON body above, then filter the returned array on `isValid` (n8n: *HTTP Request* node + *Code* node; Make: *HTTP > Make a request* + *Iterator* + *Filter*; Zapier: *Webhooks by Zapier > Custom Request*). Apify also offers official n8n, Make and Zapier integrations that run an Actor and read its dataset.

**Pick the right default country.** Numbers without a `+` are interpreted with `defaultCountry`, so the same text can be valid or invalid:

| input | defaultCountry | result |
|---|---|---|
| `020 7946 0018` | `GB` | valid, `+442079460018` |
| `020 7946 0018` | `US` | invalid |
| `(650) 253-0000` | `US` | valid, `+16502530000` |
| `(650) 253-0000` | `GB` | invalid |

If your list mixes countries, run one batch per country or ask the source for numbers with a `+` prefix. A second spelling of a number you already submitted (for example `+1 650 253 0000` after `(650) 253-0000`) is skipped as a duplicate and not charged, so map results back by `e164`, not by row position.

### Privacy, retention and data processing

- Numbers are processed only inside your own Actor run on the Apify platform. They are never sent to any third-party service, and the developer does not copy them elsewhere.
- Results are stored in **your** run's default dataset and key-value store and follow **your Apify account's data-retention settings**. You can delete them at any time.
- The run log shows only counts, never the numbers themselves.
- Phone numbers can be personal data. You are responsible for having a lawful basis to process them (for example under GDPR or CCPA).
- Metadata comes from [libphonenumber](https://github.com/google/libphonenumber) (Apache-2.0) via the [`phonenumbers`](https://pypi.org/project/phonenumbers/) Python package.

### Limitations

- The Actor can't confirm that a line is live, ported, disconnected or reachable.
- Carrier is the operator the number range was **originally allocated** to. It doesn't reflect porting, and it is often empty (including for most US numbers).
- Location is approximate (region or city level).
- Accuracy depends on the bundled libphonenumber metadata release, which is updated periodically.
- Up to 100,000 numbers per run, and each input must be 64 characters or fewer.

### FAQ

#### How do I convert phone numbers to E.164 format?

Paste your numbers, set the **Default country** for numbers written without a `+`, and run. Every parsed number comes back as `e164`, for example `+16502530000`. E.164 is the ITU-T international numbering format (`+`, country code, subscriber number, max. 15 digits) expected by Twilio, HubSpot, Salesforce and most SMS/CRM APIs.

#### Does it check whether a phone number is active or reachable?

No. It validates **format and plausibility** against the numbering plan of the country (the same rules as Google's libphonenumber). It does not call carriers, send SMS or do HLR lookups, so it cannot tell whether a line is live, ported or owned by a person.

#### Can I use it from Python, JavaScript or the Apify API?

Yes. Python:

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("shephouse/phone-number-validator-offline").call(
    run_input={"phoneNumbers": ["+1 650-253-0000", "020 7031 3000"], "defaultCountry": "GB"})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["e164"], item["isValid"], item["numberType"])
```

JavaScript:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('shephouse/phone-number-validator-offline').call({ phoneNumbers: ['+1 650-253-0000'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Why is a number that looks right marked invalid?

The check follows the numbering plan of the number's country: a wrong length, an unassigned prefix or a missing country code makes a number invalid or only "possible". For local numbers without a `+`, set **Default country** correctly (for example `GB` for UK numbers).

#### Is it worth paying for this if live lookups exist?

Live lookups answer a different question (is the line active, or who owns it) and usually cost more per number. This Actor is for the cheap first pass: normalise to E.164, drop impossible numbers and duplicates, and see country and line type, before you spend on anything else.

#### Can I validate a column from a spreadsheet or CSV?

Yes. Paste the column into **Phone numbers** (one per line), or send the values as a JSON array through the API, and download the results as CSV, Excel or JSON.

#### How many numbers can I validate per run and what does it cost?

Up to 100,000 numbers per run at $0.80 per 1,000 (see **Pricing** below). Use **Max cost per run** to cap spend.

#### Does it work for every country?

It supports all regions covered by libphonenumber (over 200). Line-type and carrier detail varies by country; see **Limitations**.

#### Is my data sent to third parties?

No. The Actor runs offline and calls no external service (see **Privacy**).

### Changelog

- **0.4.6:** listing and documentation: clearer title and description, a "what a full run gives you" section, clearer input help and free-plan message. No change to validation, billing or pricing.
- **0.4.4:** documentation only: recipes (curl, n8n, Make, Zapier), default-country table, duplicate-handling note. No change to behavior or pricing.
- **0.4.3:** documentation only: comparison table, workflow and AI-agent section, more FAQ answers. No change to behavior or pricing.
- **0.4.1:** listing and documentation update only (README structure, FAQ, API examples, SEO fields). No change to behaviour or pricing.
- **0.4:** a run that can deliver nothing (cost limit below one number, or timeout) now returns one free status row instead of an empty dataset; verified locally with the default (prefill) input: Succeeded and non-empty dataset.
- **0.3:** pricing documentation updated for the per-run start event ($0.00005); stricter guard against unexpected priced events.
- **0.2:** pay-per-event billing only for analysed numbers; deduplication by E.164; free-plan limit; spending-limit handling; `billable` field; safeguard against pricing misconfiguration.
- **0.1:** first private version.

### Support

Open an **Issue** on this Actor's page with your run ID, but **do not paste real phone numbers**. We aim to reply within 2 business days.

# Actor input Schema

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

One number per line, in any common format: +1 650-253-0000, (650) 253-0000, 020 7946 0018, 0044 20 7946 0018. Numbers without a + are read with the Default country below. Unparseable text is returned free (not charged). Free Apify plan: first 100 numbers per run.

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

Two-letter ISO country code used for numbers written without +, e.g. US, GB, DE, FR, IN, AU. Wrong default = valid numbers reported as invalid, so set it to where your list comes from. Mixed list? Run one batch per country.

## `maxItems` (type: `integer`):

Stop after this many numbers (max 100,000 per run). Together with 'Max cost per run' in Run options it caps your spend: $0.0008 per analysed number + $0.00005 per run.

## `deduplicate` (type: `boolean`):

Recommended. Spelling variants of the same number, e.g. (650) 253-0000 and +1 650 253 0000, are analysed and charged once.

## `includeCarrierAndLocation` (type: `boolean`):

Adds original carrier (where known), approximate location and time zones from libphonenumber. Carrier reflects original allocation, not porting. Turn off for a leaner output.

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

Language code for geocoder/carrier names, e.g. en, de, fr.

## Actor input object example

```json
{
  "phoneNumbers": [
    "+1 650-253-0000",
    "020 7946 0018",
    "not a number"
  ],
  "defaultCountry": "US",
  "maxItems": 1000,
  "deduplicate": true,
  "includeCarrierAndLocation": true,
  "language": "en"
}
```

# Actor output Schema

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

No description

## `summary` (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",
        "020 7946 0018",
        "not a number"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("shephouse/phone-number-validator-offline").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",
        "020 7946 0018",
        "not a number",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("shephouse/phone-number-validator-offline").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",
    "020 7946 0018",
    "not a number"
  ]
}' |
apify call shephouse/phone-number-validator-offline --silent --output-dataset

```

## MCP server setup

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

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/xePFyZzUr59AQ4cdz/builds/eDxGnmQ5TrqZyacqJ/openapi.json
