Phone Number Validator
Validate phone numbers in bulk, offline. Get E.164 and national formats, country, mobile or landline, region, original carrier and time zones for each number.
How it works
- 1Open it on Apify
Hit Run on Apify — it opens the tool in the cloud, no install.
- 2Set the inputs
Adjust
phoneNumbers,defaultCountry,datasetId(sensible defaults are pre-filled). - 3Click Run
The tool runs on Apify’s cloud and collects the data for you.
- 4Export the results
Download as JSON, CSV or Excel, or pipe straight into your app, Google Sheets, or an AI agent.
Pricing
$0.00026 per number checked = $0.26 per 1,000
| You are charged for | When | Price |
|---|---|---|
| Number checked | One distinct phone number checked and returned in the dataset, valid or not. Blank entries and repeats in the same run are never charged. | $0.00026 |
Pay-per-event pricing: you are billed per result, not per subscription. Billing is handled by Apify on your own account. These are the live Apify store prices, in effect since 2026-09-14, and they are what you are actually charged.
Inputs
| Field | What it does | Type |
|---|---|---|
phoneNumbers | One number per line, written any way: +44 20 7946 0018, (201) 555-0123, 0033 6 12 34 56 78 or tel:+81-3-1234-5678. Numbers without + or 00 in front need the default country below. Leave this empty to check a dataset instead. | array |
defaultCountry | The country for numbers written without + or 00, the way people write local numbers. (201) 555-0123 checks out as a US number with United States here and fails with most other countries. Numbers that start with + or 00 carry their own country and ignore this. | string |
datasetId | Check the numbers in one of your Apify datasets, for example the results of a business-listing or lead scraper. Pick it here, or pass its ID as datasetId through the API. Works together with the list above. | string |
datasetField | The field in each dataset item that holds the number: phone unless you change it. Use dots for a nested field (contact.phone). A field holding a list checks every number in it. | string |
addRegionAndCarrier | For valid numbers, add the place the number range belongs to (for landlines usually a town or city), the carrier the range was first given to, and its time zones. Switch it off for just the check and the formats. | boolean |
language | Names that don't exist in the chosen language come in English. | string |
onlyValid | Return only the numbers that pass the check. The others are left out and aren't charged. | boolean |
numberTypes | Return only valid numbers of these types, mobile for example. Leave it empty for every type. Numbers left out aren't charged. In the US and Canada most numbers come back as "landline or mobile", so pick that too for those. | array |
countries | Return only numbers that belong to these countries. Leave it empty for every country. Numbers left out aren't charged. | array |
maxNumbers | The most numbers checked in one run. Only distinct, non-blank entries count, and each one checked is charged. | integer |
What you get
A structured dataset — each result includes fields like:
inputvalidreasone164nationalcountrynumberTypesmsCapableregioncarriertimeZonesExport every run as JSON, CSV or Excel, or send it to your app, a database, Google Sheets, or an AI agent.
Related tools in Developer & Research Tools
Other ready-to-run tools in the same category — all pay-per-use on the Apify cloud.
GitHub Scraper
Search GitHub repos and users: stars, forks, language, topics, licence, plus user bio, company and followers. No token needed. $0.90 per 1,000 rows.
Stack Overflow / Stack Exchange Scraper
Search Stack Overflow and Stack Exchange by keyword or tag. Score, answer count, views, reputation and body text. $2 per 1,000 questions.
Package Registry Scraper (npm + PyPI)
Get npm and PyPI package metadata as JSON. Version, license, author, repo, keywords and npm monthly downloads. $2 per 1,000 packages.
arXiv Scraper
Search arXiv papers by title, author, abstract or category. Get full abstracts, authors, categories, DOI, dates and PDF links. $2 per 1,000 papers.
OpenAlex Scholarly Works Scraper
Search 250M+ OpenAlex papers with no API key. Get titles, authors, venue, year, citations, DOI, OA links and full abstracts. $2.00 per 1,000 papers.
Crossref Scholarly Works Scraper
Search 150M+ papers on Crossref: DOI, title, authors, journal, publisher, date, citations and abstract. No API key. $1.00 per 1,000 works.
Where this tool sits
- Categories
- Developer & Research Tools
Phone Number Validator: bulk check, format, line type and carrier, offline
Paste a list of numbers, or point it at a field in one of your own Apify datasets. Every distinct number comes back as a row: valid or not and why not, the E.164, international, national and tel: forms, the country, the calling code, and whether it is a mobile or a landline.
Nobody is called and nothing is texted. This is a check against the world's numbering plans, so it tells you whether a number can be right at all, not whether it is switched on today.
| Input | A list of numbers, a dataset field, or both |
| Output | One row per distinct number |
| Ceiling | 500,000 numbers per run |
| Account needed | None |
| Price | $0.26 per 1,000 numbers checked, flat on every plan |
📞 What Phone Number Validator does
Write the numbers however you have them. +44 20 7946 0018, (201) 555-0123, 0033 6 12 34 56 78 and tel:+81-3-1234-5678 all parse. A leading 00 becomes +, a tel: prefix is dropped, and spaces, brackets and dashes do not matter.
A number without + or 00 has no country in it, so the run uses your Default country to read it. (201) 555-0123 is a valid United States number and is nonsense as a French one, which is the whole reason that field exists.
For the numbers that pass, you also get the region the range belongs to, which for a landline is usually a town or city, the carrier the range was first handed to, and the time zones it covers. Region and carrier names come back in any of 33 languages.
The datasetId field is the part that saves real work. Point it at the output of a business-listing or lead scraper, name the field that holds the number, and the two run back to back without you exporting anything.
📥 What you give it
{
"phoneNumbers": ["+44 20 7946 0018", "(201) 555-0123", "06 12 34 56 78"],
"defaultCountry": "US",
"onlyValid": true,
"numberTypes": ["mobile"],
"maxNumbers": 5000
}
| Field | Default | What it is |
|---|---|---|
phoneNumbers | none | One per line, up to 500,000. Leave it empty to check a dataset instead. |
defaultCountry | none | Two-letter code, from a list of 245. Used only for numbers written without + or 00. |
datasetId | none | One of your own datasets, picked from a list in the form or passed as an ID through the API. Works alongside the list. |
datasetField | phone | Which field holds the number. Dots reach a nested one, like contact.phone. A field holding a list checks every number in it. |
addRegionAndCarrier | true | Adds region, carrier and time zones to valid numbers. Off leaves those three empty. |
language | en | For region and carrier names, from 33 languages. Names missing in your language come back in English. |
onlyValid | false | Leave out numbers that fail the check. |
numberTypes | none | Keep only these line types: mobile, landline, landline or mobile, toll-free, VoIP and six more. |
countries | none | Keep only numbers belonging to these countries. |
maxNumbers | 10000 | 1 to 500,000. Only distinct numbers that get past your filters count towards it. |
Anything a filter leaves out is not charged, and neither are blanks or repeats. So onlyValid: true on a list that is half rubbish costs you roughly half.
In the United States and Canada, pick landline or mobile too if you are filtering by line type. Those two countries share number ranges between mobiles and landlines, so most numbers there honestly cannot be told apart and come back under that label.
📤 What you get back
A real row, from run fV91ws4Xze6pNDOfZ:
{
"input": "+44 20 7946 0018",
"valid": true,
"possible": true,
"reason": null,
"e164": "+442079460018",
"international": "+44 20 7946 0018",
"national": "020 7946 0018",
"rfc3966": "tel:+442079460018",
"extension": null,
"countryCode": "GB",
"country": "United Kingdom",
"callingCode": "+44",
"numberType": "landline",
"smsCapable": false,
"region": "London",
"carrier": null,
"timeZones": ["Europe/London"],
"source": "list",
"sourceIndex": 1
}
carrier is empty on that row because it is a landline: carrier data covers mobile ranges.
| Field | What it is |
|---|---|
valid | The number fits its country's numbering plan. This is the one to filter on. |
possible | A length check only, and it is set for anything that parses at all. A number can be possible and not valid. |
reason | Why a number failed, in plain words: "too short", "wrong length for France", "not a number in use in Germany", "unknown country code", "no country code: start it with + or set a default country". null when it passed. |
e164 | The clean +441234567890 form. Store this one. |
smsCapable | true for mobiles, false for landlines and other fixed types, and null where the plan genuinely cannot say, which includes "landline or mobile" and VoIP. |
region, carrier, timeZones | Valid numbers only, and only with addRegionAndCarrier on. |
source, sourceIndex | list with the line number, or dataset with the item's offset, so you can join the row back to where it came from. |
Formats, region, carrier and time zones are filled in only for numbers that pass. An invalid number gets its verdict, its reason and nothing invented around it.
🧾 Reading the output
One row per distinct number, and that is the whole dataset. No refusal rows and no blank lines.
A sample row carries _sample: true and turns up only when you run with nothing to check.
Everything else about the run is in a key-value record called RUN_REPORT:
| In the report | What it tells you |
|---|---|
| Counts by verdict, reason, type and country | The shape of your list in one place. |
| Entries skipped | How many were blank, how many were repeats, how many your filters removed. |
| The filters used | Written out, including any value that was not recognised and dropped. |
| The dataset read | Items seen, items with no number in the named field, and any error reading it. |
| Stop reason and seconds taken | Whether it finished the list or hit maxNumbers, the deadline or your budget. |
A run whose filters match nothing finishes normally and says so in the status message. A run with nothing to check at all fails.
▶️ How to run it
1. Open Phone Number Validator and click Try for free. 2. Paste numbers into Phone numbers, or pick a dataset under Dataset and name the field. 3. Set Default country if your list has local numbers in it. 4. Add filters under Only valid numbers, Only these line types or Only these countries. 5. Click Start, then download the dataset as JSON, CSV or Excel, or read it from the Apify API.
💰 How much does it cost?
$0.26 per 1,000 numbers checked. The same rate on every Apify plan, with no volume tiers.
One charge per row delivered. Blanks, repeats of a number already in the run, and anything your filters remove are not charged, and neither is the sample row. Region, carrier and time zones cost nothing extra.
💡 What people use it for
- Cleaning a scraped lead list down to the numbers that can actually be dialled.
- Splitting a list into mobiles and landlines before a texting campaign.
- Normalising a CRM export to E.164 so two systems finally agree on a format.
- Working out which countries a list is really from, by counting
countryCode. - Cutting a scraper's output straight into a clean sheet, using
datasetIdinstead of a CSV.
🚧 What it does not do
- It does not call or text anything. No answer test, no ring test, no HLR lookup. Nothing tells
you whether a number is live, disconnected or answered today.
- No names, no owners, no WhatsApp check.
carrieris the carrier the range was first given to. A ported number shows the old one. Take
it as a hint about the range, not as fact about the handset.
- United States and Canada numbers come back as "landline or mobile" most of the time, because
the ranges are genuinely shared. smsCapable is null for those.
- The numbering data has a date. A range that opened after the last update reads as not in use.
- A number not tied to one place lists every time zone of its country. A US toll-free number
names 42 of them.
rfc3966has no separators, sotel:+442079460018. That is how the standard form is written.- 500,000 numbers in a run, and a large run wants the memory to match.
🧭 Which list-cleaning tool do you need?
| If you want | Use |
|---|---|
| Phone numbers checked and formatted | This one |
| Email addresses checked | Email Verification |
| Phones and emails pulled off a list of websites | Contact Details Scraper |
| Business listings with phone numbers to clean | Google Maps Scraper |
| Someone's social profiles from a name or domain | Social Profile Finder |
❓ Questions people ask
Does it ring the number? No. Everything is worked out from the numbering plans, so nobody's phone lights up and nothing leaves the run.
What is the difference between valid and possible? possible says the length could be right. valid says the whole number matches a range that country actually uses. Filter on valid.
Why did a number I know is fine come back invalid? Nearly always a missing country. Set Default country to match where the local numbers were written, or put them in + form.
Can I check the numbers from another actor's run? Yes. Pick that run's dataset under Dataset and set the field name. Nothing needs exporting.
Is the carrier current? No. It is the carrier the range was originally allocated to. Porting is invisible to an offline check.
Do I pay for numbers my filters removed? No. Only rows that reach the dataset are charged.
🆘 If something breaks
Open the Issues tab on the actor page. Send the run ID and a number that came back wrong. The RUN_REPORT record in the run's key-value store carries the counts, the filters and the stop reason, which usually explains a short result on its own.