Day 6 of my 21-day API challenge is done.
Yesterday I built an Email Validator. Today I built a Phone Number Formatter & Validator API — validate any international phone number, detect the country, classify the number type and format it in E.164, international and national formats.
Previous days:
- Day 1 — Invoice & Receipt Parser API
- Day 2 — Password Strength & Security Scorer API
- Day 3 — VAT Number Validator API
- Day 4 — Text Readability Scorer API
- Day 5 — Email Validator & Disposable Checker API
The Problem
Every CRM, contact form and SMS platform needs phone validation. The challenge is that phone numbers are wildly inconsistent:
-
+27 82 123 4567— South African mobile with spaces -
(212) 555-1234— US format with brackets -
07911 123456— UK local format with leading zero -
+49 151 234 56789— German mobile international format
Every developer ends up writing their own messy regex that handles their home country fine but breaks on international numbers. I packaged a proper solution into a clean API.
What I Built
The Phone Number Formatter & Validator API — send it any phone number, get back a full analysis.
Input:
{
"phone": "+27821234567"
}
Output:
{
"success": true,
"data": {
"is_valid": true,
"original": "+27821234567",
"country_code": "ZA",
"country": "South Africa",
"dial_code": "+27",
"number_type": "mobile",
"local_number": "821234567",
"formats": {
"e164": "+27821234567",
"international": "+27 821 234 567",
"national": "082 123 4567"
}
}
}
The 6 Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| POST | /validate |
Full phone analysis |
| POST | /validate/batch |
Validate up to 50 numbers at once |
| POST | /format |
Format in all three formats |
| POST | /extract |
Extract phone numbers from text |
| GET | /countries |
List all 50+ supported countries |
How Country Detection Works
The core of the API is a country database mapping dial codes to country info:
const COUNTRIES = {
"27": { code: "ZA", name: "South Africa", min: 9, max: 9 },
"1": { code: "US", name: "United States", min: 10, max: 10 },
"44": { code: "GB", name: "United Kingdom", min: 10, max: 10 },
"49": { code: "DE", name: "Germany", min: 10, max: 11 },
"61": { code: "AU", name: "Australia", min: 9, max: 9 },
// ... 45+ more countries
};
Detection tries the longest prefix first — 3 digits, then 2, then 1:
function detectCountry(digits) {
for (const len of [3, 2, 1]) {
const prefix = digits.slice(0, len);
if (COUNTRIES[prefix]) {
return { dialCode: prefix, country: COUNTRIES[prefix] };
}
}
return null;
}
This matters because 1 (US/Canada), 44 (UK) and 353 (Ireland) all start differently and need to be checked longest-first to avoid mismatches.
Number Type Classification
The API classifies numbers as mobile, landline, toll-free or premium:
const mobilePatterns = {
"27": /^[6-8]/, // ZA: 06x, 07x, 08x
"44": /^7/, // UK: 07x
"1": /^\d{10}$/, // US: all 10-digit
"49": /^1[5-7]/, // DE: 015x, 016x, 017x
"61": /^4/, // AU: 04x
"91": /^[6-9]/, // IN: 6-9
};
function classifyNumberType(digits, dialCode) {
const local = digits.slice(dialCode.length);
const pattern = mobilePatterns[dialCode];
if (pattern && pattern.test(local)) return "mobile";
// Toll-free detection
if (dialCode === "1" && /^(800|888|877|866)/.test(local)) return "toll_free";
return "landline";
}
Three Output Formats
Every valid number gets returned in three formats:
E.164 — the international standard used by databases and APIs:
+27821234567
International — human-readable with spaces:
+27 821 234 567
National — local format with leading zero:
082 123 4567
The default_country Parameter
If a user types a local number without a country code, you can pass their country:
{
"phone": "0821234567",
"default_country": "ZA"
}
The API strips the leading zero and prepends the South African dial code automatically.
Batch Validation
The /validate/batch endpoint validates up to 50 numbers and returns a summary:
{
"summary": {
"total": 3,
"valid": 3,
"invalid": 0,
"mobile": 2,
"landline": 1,
"countries_found": {
"ZA": 1,
"US": 1,
"GB": 1
}
}
}
Useful for importing contact lists, auditing CRM data or validating bulk SMS recipient lists.
Why I Included South Africa
I'm a self-taught developer from South Africa. South African numbers follow the format +27 followed by 9 digits, with mobile numbers starting with 6, 7 or 8.
Most phone APIs either don't support ZA at all or get the mobile detection wrong. I made sure it works correctly.
Tech Stack
- Runtime: Node.js + Express
- Hosting: Railway (free tier)
- Marketplace: RapidAPI
- Dependencies: express, cors, helmet, morgan, express-rate-limit
Zero paid APIs. Zero external calls. Pure in-memory validation.
Try It
Live on RapidAPI — search Phone Number Formatter Validator or find me at https://rapidapi.com/user/ruanmul04.
Free tier: 10 requests/month. No credit card required.
What's Next
Day 7 tomorrow — URL Safety & Metadata Checker API.
Send it any URL and get back safety analysis, metadata extraction, redirect detection and domain info. Perfect for link shorteners, security tools and browser extensions.
Follow me here on dev.to to catch every day of the challenge. 🇿🇦
21 APIs in 21 days. Built in South Africa. Sold globally.
Top comments (0)