Validate phone numbers and convert to E.164 with one API call
Your signup form collects phone numbers from 40 countries. Users type them in every format imaginable: with dashes, spaces, parentheses, country codes, without country codes. You need to normalize these into E.164 format before storing them in your database. Otherwise, you end up with five different representations of the same number, and your SMS gateway rejects half of them.
The botoi phone validation API parses any international phone number into a structured response: a validity flag, E.164 format, national number, country code, and country name. One POST request, no libphonenumber install, no 300KB bundle hit.
Validate a phone number in one request
Send a phone number to /v1/phone and get back a structured result.
curl -X POST https://api.botoi.com/v1/phone \
-H "Content-Type: application/json" \
-d '{"phone": "+14155552671"}' Response:
{
"success": true,
"data": {
"phone": "+14155552671",
"valid": true,
"country_code": "+1",
"country": "United States / Canada",
"e164_format": "+14155552671",
"national_format": "4155552671"
}
}
The API strips spaces, dashes, and parentheses from the input, identifies the country from the
dialing prefix, and returns both the E.164 format (for storage) and the national format (for display).
The valid flag checks that the national number has between 7 and 15 digits, which
covers every country's numbering plan.
International format examples
The API handles numbers from 30+ countries. Spaces, dashes, and parentheses in the input are all stripped before parsing.
Country Input E.164 Country code
────────────────── ─────────────────────── ────────────────── ────────────
United States +1 (415) 555-2671 +14155552671 +1
United Kingdom +44 20 7946 0958 +442079460958 +44
India +91 98765 43210 +919876543210 +91
Germany +49 30 1234567 +49301234567 +49
Japan +81 3-1234-5678 +81312345678 +81
Brazil +55 11 91234-5678 +5511912345678 +55
Australia +61 2 1234 5678 +61212345678 +61
Singapore +65 6123 4567 +6561234567 +65 UK number with spaces
curl -X POST https://api.botoi.com/v1/phone \
-H "Content-Type: application/json" \
-d '{"phone": "+44 20 7946 0958"}' Response:
{
"success": true,
"data": {
"phone": "+44 20 7946 0958",
"valid": true,
"country_code": "+44",
"country": "United Kingdom",
"e164_format": "+442079460958",
"national_format": "2079460958"
}
} What happens with invalid input
Numbers without a + prefix return valid: false with a note explaining
what the API expects.
curl -X POST https://api.botoi.com/v1/phone \
-H "Content-Type: application/json" \
-d '{"phone": "555-1234"}' Response:
{
"success": true,
"data": {
"phone": "555-1234",
"valid": false,
"country_code": null,
"country": null,
"e164_format": null,
"national_format": null,
"note": "Phone number should start with \"+\" followed by a country code for reliable detection."
}
}
No exceptions, no cryptic error codes. Check data.valid and show the user a
clear message.
Signup form validation
The most common use case: validate a phone number on form submission, then store the E.164 version instead of whatever the user typed. This keeps your database clean and your SMS provider happy.
async function validatePhone(phone) {
const res = await fetch("https://api.botoi.com/v1/phone", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone }),
});
return res.json();
}
// Signup form handler
document.querySelector("#signup-form").addEventListener("submit", async (e) => {
e.preventDefault();
const phoneInput = document.querySelector("#phone").value.trim();
const { data } = await validatePhone(phoneInput);
if (!data.valid) {
showError("Enter a valid phone number with country code (e.g. +1 415 555 2671)");
return;
}
// Store the E.164 version, not the raw input
await createAccount({
phone: data.e164_format,
country: data.country,
});
});
The raw input goes to the API. The E.164 format comes back. You store +14155552671
instead of (415) 555-2671 or 415.555.2671 or any other variation.
Every downstream system (Twilio, AWS SNS, Vonage) expects E.164, so you avoid conversion
headaches later.
Normalize a CSV of phone numbers
You have a CSV export from a CRM with 5,000 contacts. The phone column is a mess: some numbers have country codes, some don't, some have dashes, some have dots. This script reads the CSV, validates each number, and writes a clean version with E.164 format and country info.
import { readFileSync, writeFileSync } from "fs";
import { parse } from "csv-parse/sync";
import { stringify } from "csv-stringify/sync";
const records = parse(readFileSync("contacts.csv"), { columns: true });
async function normalizePhone(phone) {
const res = await fetch("https://api.botoi.com/v1/phone", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone }),
});
const { data } = await res.json();
return data;
}
async function processContacts() {
const results = [];
for (const row of records) {
const data = await normalizePhone(row.phone);
results.push({
name: row.name,
original_phone: row.phone,
e164: data.valid ? data.e164_format : "INVALID",
country: data.country || "Unknown",
valid: data.valid,
});
}
writeFileSync("contacts_normalized.csv", stringify(results, { header: true }));
const invalid = results.filter((r) => !r.valid);
console.log(`Processed \${results.length} contacts. \${invalid.length} invalid.`);
}
processContacts(); The output CSV has four columns: the original phone, the E.164 version, the detected country, and a validity flag. You can filter for invalid rows and fix them manually, then import the clean data into your system.
For large files, add a small delay between requests or batch them with Promise.all
in groups of 5 to stay within rate limits. Paid plans support higher throughput.
Express middleware for phone validation
This middleware validates the phone number before your route handler runs. If the number is invalid, the request gets a 422 response. If it's valid, the middleware replaces the raw input with the normalized E.164 format so your handler always receives clean data.
import express from "express";
const app = express();
app.use(express.json());
async function validatePhoneMiddleware(req, res, next) {
const phone = req.body.phone;
if (!phone) {
return res.status(400).json({ error: "Phone number is required" });
}
const apiRes = await fetch("https://api.botoi.com/v1/phone", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone }),
});
const { data } = await apiRes.json();
if (!data.valid) {
return res.status(422).json({
error: "Invalid phone number",
detail: "Provide an international number starting with + and a country code",
});
}
// Replace raw input with normalized E.164 format
req.body.phone = data.e164_format;
req.body.phoneCountry = data.country;
next();
}
app.post("/users", validatePhoneMiddleware, async (req, res) => {
// req.body.phone is now in E.164 format
const user = await db.users.create({
email: req.body.email,
phone: req.body.phone, // "+14155552671"
phoneCountry: req.body.phoneCountry, // "United States / Canada"
});
res.status(201).json({ id: user.id });
}); Every route that accepts a phone number gets the same validation logic. Your database always stores E.164. Your route handlers never deal with parsing or formatting; they receive a normalized number and a country name.
Why E.164 matters
E.164 is the ITU-T standard for international phone number formatting. The format is simple:
a + sign, the country code, and the subscriber number, with no spaces or punctuation.
Example: +14155552671.
- Deduplication. Without a canonical format, the same number shows up as
(415) 555-2671,415-555-2671,+1 415 555 2671, and14155552671. E.164 collapses all four into one string. - SMS delivery. Twilio, AWS SNS, Vonage, MessageBird, and every major SMS gateway require E.164. If you store numbers in a different format, you need a conversion step before every send.
- Database indexing. A single format means your unique constraint on the phone column works. Mixed formats let duplicates slip through.
- International support. E.164 includes the country code, so your system handles US, UK, Indian, and Brazilian numbers without special-case logic.
Key points
- One endpoint covers validation and formatting.
POST /v1/phonereturns validity, E.164, national format, country code, and country name in a single response. - No library required. Skip the 300KB libphonenumber bundle. One HTTP call replaces the dependency.
- Store E.164, display national. Write
e164_formatto your database. Shownational_formatin the UI with the country flag. - Validate at the boundary. Add the middleware to your API routes and every downstream system receives clean data.
- Free tier available. 5 requests per minute with no API key. Paid plans for production workloads start at $9/month.
Frequently asked questions
- How do I validate an international phone number via API?
- Send a POST request to https://api.botoi.com/v1/phone with a JSON body containing the phone number in international format (starting with +). The API returns a valid flag, E.164 format, national format, country code, and country name.
- Does the phone validation API support numbers without a + prefix?
- The API requires the + prefix for reliable country detection. If you send a number without it, the response will return valid: false with a note explaining that the number should start with + followed by a country code.
- Is the phone validation API free?
- Yes. Anonymous access is available at 5 requests per minute with IP-based rate limiting. No API key, no account, no credit card required. Paid plans start at $9/month for higher rate limits.
- What is E.164 format and why should I store phone numbers in it?
- E.164 is the international phone number standard defined by ITU-T. It starts with a + sign, followed by the country code and subscriber number, with no spaces or dashes. Example: +14155552671. Storing numbers in E.164 gives you a single canonical format that works with Twilio, AWS SNS, and every SMS gateway.
- Which countries does the phone validation API support?
- The API supports 30+ countries including the US, Canada, UK, India, Japan, Germany, France, China, Australia, Brazil, Mexico, South Korea, Indonesia, Singapore, and more. Country detection uses the international dialing prefix from the phone number.
Try this API
Phone Validation API — interactive playground and code examples
More tutorial posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.