Validate a phone number
A phone number validation API takes whatever a user typed, with dashes, spaces, parentheses, a local format or none of it, and turns it into a single reliable E.164 string like +14155552671, or tells you plainly that the number cannot be valid. It is the difference between a database full of guesses and one you can actually dial or text.
Run — free
Runs in your browser. Free, unlimited — your data never leaves this page.
Why phone numbers are messier than they look
A phone number typed into a web form can arrive as (415) 555-2671, 415.555.2671, 04155552671, or 15552671 with the country code assumed and never written down. Numbering plans differ by country in length, area code structure and even whether leading zeros are dropped when dialed internationally, so a regex that works for one country's numbers routinely rejects or mangles another's. Storing that raw mess means every downstream system, SMS provider, dialer, CRM, has to guess at formatting on its own.
What E.164 actually is
E.164 is the international telecommunication numbering standard: a plus sign, the country calling code, then the subscriber number, with no spaces, dashes or parentheses, capped at fifteen digits. It exists specifically so telecom systems worldwide can route calls and messages without ambiguity, and it has become the de facto format every serious SMS and voice API expects. Normalizing to E.164 once, at data entry, means you never have to reconcile formatting differences again.
How the endpoint handles the parsing
Send the raw input along with an optional default country hint, since a number like 555-2671 is meaningless without knowing which country's numbering plan applies, and the endpoint parses it against the numbering rules for that region, validates the length and structure, and returns the normalized E.164 form when the number is structurally valid. It also returns a clear invalid verdict for numbers that cannot be real, catching typos, missing digits and impossible area codes before they ever reach a dialer.
What a response contains
Every call returns a task_id immediately, and the result, delivered by webhook or a signed link valid for 24 hours, includes the normalized number in E.164 format, a validity flag, and the detected or confirmed country. That is enough to store a single clean field in your database and stop juggling multiple raw formats across markets.
Where normalization pays off downstream
SMS and voice platforms bill and route based on E.164, so a badly formatted number can silently fail to send or get billed at the wrong rate. Running every number through this check at signup, checkout or lead capture means your CRM, your two-factor authentication flow and your customer support dialer all read from the same normalized source, with no per-team formatting logic duplicated across your stack.
What you can do with it
Signup form normalization
Convert whatever a user types into a phone field, local format, spaces, parentheses, into clean E.164 before it ever touches your database.
Two-factor authentication setup
Validate a phone number is structurally correct before sending the first SMS code, avoiding a wasted send to an impossible number.
CRM data cleanup
Normalize thousands of legacy contact numbers stored in inconsistent formats so your dialer and SMS tool can use them without manual reformatting.
Checkout delivery confirmation
Confirm a shipping contact number is valid and correctly formatted before an order ships, so delivery text updates actually reach the buyer.
FAQ
What is E.164 and why does the API normalize to it?
E.164 is the international phone numbering standard, a plus sign, country code and subscriber number with no other characters; this phone number validation API normalizes to it because it is what SMS and voice platforms worldwide expect.
Do I need to specify the country?
For numbers without a country code, providing a default country hint improves accuracy, since a local-format number is ambiguous without knowing which numbering plan applies.
Is phone number validation free?
The tool above runs free in your browser. The API is paid — each call draws from your prepaid ForHosting KIT balance: top up from $10.00 (it never expires), pay each request's published price, and a call with no balance returns HTTP 402. No subscription, no tokens, and a failed task is never charged.
How much does each validation cost?
Each request to /verify/phone costs 0.002 dollars, charged only when the task completes successfully.
Does it confirm the number is currently active or reachable?
No, this endpoint validates structure and format against numbering plan rules; for carrier and line-type detail, pair it with the phone carrier lookup endpoint.
Does it work for numbers from any country?
Yes, it parses against numbering plan rules across countries worldwide, not a single region's format.
How do I get the result?
You receive a task_id immediately, and the normalized number and validity flag arrive via your signed webhook or a signed link valid for 24 hours.
Am I charged if validation fails?
No, failed tasks are retried automatically up to three times and are never charged; a clear error is returned only after retries are exhausted.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/verify/phone \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"items":["valor-1","valor-2"]}'const res = await fetch("https://api.kit.forhosting.com/verify/phone", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"items": [
"valor-1",
"valor-2"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/verify/phone",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"items": [
"valor-1",
"valor-2"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/verify/phone", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"items":["valor-1","valor-2"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"items":["valor-1","valor-2"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/verify/phone", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"items": [
"valor-1",
"valor-2"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "verify.phone",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |