Phone Number Validator - Bulk E.164, Mobile vs Landline
Pricing
$1.00 / 1,000 valid phone numbers
Phone Number Validator - Bulk E.164, Mobile vs Landline
Offline phone number validator and E.164 formatter for bulk lists. Valid or not, E.164, national and international format, country and mobile or landline type, from libphonenumber. No API key, no lookups. $0.001 per valid number; invalid and duplicate numbers are free.
Pricing
$1.00 / 1,000 valid phone numbers
Rating
0.0
(0)
Developer
Luke Hunter
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
A bulk phone number validator and E.164 formatter for sales, marketing and CRM teams, running entirely offline. Paste a list of phone numbers in any common format, or hand it the dataset of a contact scraper, and get one clean row per number: valid or not, E.164, international and national format, country, calling code and line type (mobile or landline, toll-free, premium rate, VoIP and more). It uses Google's libphonenumber metadata, the same data behind Android and many CRMs.
$0.001 per valid number. Invalid, unparseable and duplicate numbers are free. 1,000 numbers that are all valid = $1.
There is no network call, no carrier lookup, no API key and no third-party service, so it is instant (100,000 numbers take seconds), has no upstream that can break, and no phone number ever leaves the run.
Read this before you rely on it. Offline validation proves a number is valid in its country's numbering plan: right length, an allocated range, a known line type. It does not prove the number is currently connected, that anyone answers it, or who owns it. There is no live "is this phone reachable" check and no carrier or ported-number lookup. If you need that, you need a paid HLR service; this Actor is the cheap first pass that removes the numbers that can never work.
What you get
| Field | Meaning |
|---|---|
input | The number exactly as you gave it (cut at 200 characters). |
index | Position in the run's list, from 1. Rows are written in batches of 1,000, valid first, so sort by index to restore your order. |
status | valid, invalid (readable but not a real number) or unparseable (not a phone number, or no country known). |
valid | true when the number is valid in its national numbering plan. |
possible | true when the length is plausible for the country, even if the number is not valid. |
e164 | International E.164 form, e.g. +442079460000. Valid numbers only. |
international, national | Formatted as +44 20 7946 0000 and 020 7946 0000. Valid numbers only. |
countryCode, callingCode | ISO-2 country (GB, US, DE, IM...) and calling code (44). |
numberType | MOBILE, FIXED_LINE, FIXED_LINE_OR_MOBILE, TOLL_FREE, PREMIUM_RATE, SHARED_COST, VOIP, PERSONAL_NUMBER, PAGER, UAN, VOICEMAIL. |
extension | An extension written after the number (x123, ext. 5), kept out of e164. |
duplicateOf | When the same E.164 and extension appeared earlier in the run, the index of the first. Duplicates are free. |
reason | For invalid and unparseable rows: why (too short, no country, not numbered in this country). |
source | For numbers read from rows or a dataset: {row, field, domain}, to join results back. |
Quick start
{"phoneNumbers": ["+44 20 7946 0000", "(202) 555-0123", "0151 496 0123"],"defaultCountry": "GB","maxItems": 10}
Numbers that start with + (or 00) carry their own country. Numbers without it, such as 0151 496 0123, use defaultCountry; without a default they come back unparseable with a reason that says so. Spaces, dots, dashes, brackets, (0), tel: prefixes and extensions are all fine.
Recipes
Clean phone numbers from scraped leads before importing into a CRM. Chain the dataset of Website Contact Scraper, Google Maps Email Extractor & Lead Enricher or Impressum Scraper straight in, with no export step:
{"datasetId": "<DEFAULT_DATASET_ID_OF_THE_CONTACT_SCRAPER_RUN>","defaultCountry": "GB","maxItems": 5000}
The Actor reads phones (a list of texts or of {phone} objects, as the contact scraper and lead enricher write them), or phone (Impressum Scraper, the contact scraper's contacts output, Google Maps rows). A row's own countryCode (for example DE on a Google Maps row) overrides defaultCountry for that row's numbers. Import only rows with status = valid and use e164 as the phone column; source.row and source.domain tell you which lead each number came from. Rows without a phone are skipped and counted.
Split mobile vs landline for SMS campaigns. Run the list, then filter the dataset on numberType: send SMS to MOBILE, call or skip FIXED_LINE, and drop PREMIUM_RATE, SHARED_COST and TOLL_FREE from outreach lists. In the United States, Canada and some other countries the numbering plan cannot tell mobile from landline: those come back as FIXED_LINE_OR_MOBILE, so treat them as unknown, not as mobile.
Normalise to E.164. E.164 (+ country code and number, no spaces, no extension) is what Twilio, WhatsApp, Google Contacts and most CRMs want. Give any mix of formats and a defaultCountry, and take the e164 column:
{"phoneNumbers": ["020 7946 0000", "+44 (0)20 7946 0018", "0044 20 7946 0000", "(202) 555-0123"],"defaultCountry": "GB"}
The three UK forms give +442079460000 (twice) and +442079460018; the repeat is marked duplicateOf and not charged, so the list de-duplicates itself. The US number is invalid under GB: write it as +1 202 555 0123 or give it its own countryCode in rows.
Check a column from a spreadsheet. Paste the column into phoneNumbers (one per line, or separated by commas or semicolons) and download the dataset as CSV or Excel.
Input
| Field | Default | What it does |
|---|---|---|
phoneNumbers | none | Phone numbers in any common format. |
defaultCountry | none | ISO-2 country for numbers without a +, e.g. GB, US, DE. UK is accepted for GB. |
rows | none | Objects with a phone field, merged with phoneNumbers. |
datasetId | none | An Apify dataset whose items have a phone field (up to 100,000 items are read). |
phoneField | auto | Field name holding the number(s). Empty tries phones, phone, phoneNumber, phoneNumbers, phone_number, telephone, tel, mobile, phoneUnformatted. |
maxItems | 1000 | Hard cap on numbers checked per run, 1 to 100,000. |
At least one of phoneNumbers, rows or datasetId is required. Numbers over maxItems are not checked, and the run's status message says how many.
Example output
{"input": "020 7946 0000","index": 1,"status": "valid","valid": true,"possible": true,"e164": "+442079460000","international": "+44 20 7946 0000","national": "020 7946 0000","countryCode": "GB","callingCode": "44","numberType": "FIXED_LINE","extension": null,"duplicateOf": null,"reason": null,"source": { "row": 3, "field": "phones", "domain": "example.co.uk" }}
A number that is the right length but not allocated is invalid and free:
{"input": "+91 22 1234 5678","index": 2,"status": "invalid","valid": false,"possible": true,"e164": null,"international": null,"national": null,"countryCode": "IN","callingCode": "91","numberType": null,"extension": null,"duplicateOf": null,"reason": "Right length, but not a number that exists in the national numbering plan.","source": null}
The example numbers are from public documentation and reserved ranges (Ofcom's drama range 020 7946 0xxx, the US 555-01xx range), not real subscribers. Status meanings:
valid: a number that exists in its national numbering plan. Charged, unless it is a duplicate.invalid: readable as a number but not valid (wrong length, or an unallocated range). Free.unparseable: not a phone number, empty, too long, or no country known. Free.
Use with AI agents (MCP)
This Actor works as a tool for Claude, ChatGPT, Cursor and other MCP clients through Apify's MCP server. Every input property description is short and carries an example, so agents fill it in correctly. Give it phoneNumbers, rows or a datasetId.
Claude Desktop (claude_desktop_config.json), with your own token from Apify Console > Settings > API & Integrations:
{"mcpServers": {"apify-phone-number-validator": {"command": "npx","args": ["-y", "mcp-remote","https://mcp.apify.com?tools=lukehunter/phone-number-validator","--header", "Authorization: Bearer ${APIFY_TOKEN}"],"env": { "APIFY_TOKEN": "<YOUR_APIFY_TOKEN>" }}}}
Clients that support OAuth (Claude.ai, VS Code) can connect with just the URL https://mcp.apify.com?tools=lukehunter/phone-number-validator and sign in to Apify. Never paste a real token into a shared file or chat.
Example. You ask: "Is +44 20 7946 0000 a valid UK number, and is it a mobile or a landline?" The agent calls lukehunter--phone-number-validator with:
{"phoneNumbers": ["+44 20 7946 0000"],"maxItems": 1}
It answers valid, FIXED_LINE (a landline), and is charged $0.001 for the one valid number.
Pricing
Pay-per-event: $0.001 per valid number (event number). Invalid numbers, unparseable numbers and duplicates of an earlier number in the same run are free, and nothing is charged for reading a dataset.
| Valid numbers | Cost |
|---|---|
| 10 | $0.01 |
| 1,000 (default cap) | $1.00 |
| 10,000 | $10.00 |
| 100,000 (maximum) | $100.00 |
For comparison, other phone validation Actors on the Apify Store were listed in October 2026 at between $0.001 and $0.06 per number. The ones at about $0.015 and above generally bundle live carrier or reachability lookups, which this Actor does not do; the one at the same $0.001 returns no line type, region or national format. maxItems and your Apify per-run spending limit both stop delivery before the cap is passed: a result is delivered and charged together, and never past your limit. Price changes follow Apify's notice rules; the Pricing tab always shows the live price.
Limitations
- Valid is not reachable. The check is against the numbering plan, so a valid number may be unassigned, disconnected, a landline nobody answers, or ported to another carrier. For live reachability or carrier lookups you need a different, paid service.
- Line type comes from the numbering plan.
numberTypeis the type the country's plan assigns to that range. It is not live: a mobile number ported to a VoIP provider still showsMOBILE. Countries whose plan does not separate mobile from fixed (the United States and Canada among them) returnFIXED_LINE_OR_MOBILE, and some ranges have no type at all (numberTypeisnull). - Country is where the range was allocated.
countryCodeis the country or territory the range belongs to; for shared codes it picks the territory (for example+44 7624isIM, Isle of Man). Non-geographic numbers (such as+800) have no country. - Needs a country to read a national number. Without a
+and withoutdefaultCountry(or a rowcountryCode), a number like020 7946 0000isunparseable. The Actor never guesses a country from the digits. - Numbering plans change. The metadata is Google's libphonenumber data bundled at build time; new ranges allocated since the last release show as
invaliduntil the Actor is rebuilt with newer data. - Personal data. Phone numbers can identify a person. The Actor processes them only for the run, writes them only to your dataset, never contacts anyone, and logs only counts. Handle the data in line with the GDPR and your local rules, including any consent rules for calls and SMS.
- Not legal advice. A valid, mobile number is not permission to message it.
FAQ
Does it call or text the numbers? No. It never contacts anyone and never touches the network. It only reads the number's digits against libphonenumber's rules.
Is it a phone validation API? Yes, in the way Apify Actors are: call it from code, Make, Zapier or n8n with the API snippets above and get JSON back. See "Use it from your code" below.
How can I tell mobile or landline? Read numberType: MOBILE or FIXED_LINE. Where it says FIXED_LINE_OR_MOBILE, the country's plan cannot say.
Why was a number I know is real marked invalid? Usually a missing digit, a stray digit, a trunk 0 in the wrong place, or a very new range. reason says which kind of problem it is. If you are sure the number works and it is not a brand-new range, open an issue with the number's country and format.
Are duplicates charged? No. The same E.164 (with the same extension) appearing again later in the run is returned with duplicateOf set to the first row's index and is free.
What about extensions? +44 20 7946 0000 x123 gives e164 +442079460000 and extension 123. The base number is what is validated and charged.
Which countries are covered? Every country and territory with a calling code in libphonenumber, about 240.
Use it from your code, Make, Zapier or n8n
Built for automated pipelines: small runs, stable field names, one row per item, and you pay only for rows actually delivered.
One API call that runs the Actor and returns the data (replace $APIFY_TOKEN with your token from Apify Console → Settings → Integrations):
curl -s -X POST "https://api.apify.com/v2/acts/lukehunter~phone-number-validator/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H 'Content-Type: application/json' \-d '{"phoneNumbers": ["+44 20 7946 0000", "+1 (202) 555-0123", "+1 800 555 0199"], "maxItems": 10}'
JavaScript (npm i apify-client):
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('lukehunter~phone-number-validator').call({"phoneNumbers": ["+44 20 7946 0000","+1 (202) 555-0123","+1 800 555 0199"],"maxItems": 10});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Python (pip install apify-client):
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ['APIFY_TOKEN'])run = client.actor('lukehunter~phone-number-validator').call(run_input={"phoneNumbers": ["+44 20 7946 0000", "+1 (202) 555-0123", "+1 800 555 0199"], "maxItems": 10})items = client.dataset(run['defaultDatasetId']).list_items().items
Schedules, webhooks and no-code tools: save your input as a Task, add a Schedule (e.g. hourly or daily), and attach a webhook or one of the Integrations tab's connectors (Make, Zapier, n8n, Google Sheets, Slack) to push each run's results into your own system.
Feedback: an honest rating on the Store page, good or bad, helps others decide if this Actor fits their needs. Missing a field or found a problem? Open an issue on the Actor page.
Related Actors
Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.
- Domain Email Provider Lookup: the email provider (Google Workspace, Microsoft 365, Zoho and more), MX, SPF and DMARC setup of any domain list, with provider-change alerts.
- French Company Leads: French companies (entreprises) from the official Sirene-based API, filtered by NAF code, department, headcount and creation date, with SIREN/SIRET, officers and new-company alerts.
- Poland KRS Company Lookup: Polish companies from the official KRS API by KRS number: NIP, REGON, address, share capital, PKD and the board as published, with optional VAT status.
- EU VAT Number Validator: bulk EU VAT number checks against the official VIES service: valid or invalid, registered name and address, with optional consultation numbers.
- Website Contact Scraper: emails, phone numbers and social profile links from any list of company domains, with the page each came from and a changed-contacts-only mode for scheduled runs.
- Impressum Scraper: legal entity data from the imprint of any company website in Germany, Austria and Switzerland: legal name and form, managing directors, HRB/HRA or FN register, VAT id and address, with a changed-data-only mode for scheduled runs.
- Website Tech Stack Detector: the technologies a website runs (CMS, ecommerce platform, analytics, ad pixels, chat, payments, CDN, frameworks) from its home page, with the evidence for each match and a changed-technologies-only mode for scheduled runs.
- Google Maps Email Extractor & Lead Enricher: emails, phones, social links, named people and the tech stack of every business in your Google Maps Scraper results, one enriched row per business, with filters such as only Shopify stores with an email.