DEV Community

Cover image for I Built a Phone Number Validator & Formatter API for 50+ Countries — Day 6 of 21
Ruan Muller
Ruan Muller

Posted on

I Built a Phone Number Validator & Formatter API for 50+ Countries — Day 6 of 21

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:


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"
}
Enter fullscreen mode Exit fullscreen mode

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"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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";
}
Enter fullscreen mode Exit fullscreen mode

Three Output Formats

Every valid number gets returned in three formats:

E.164 — the international standard used by databases and APIs:

+27821234567
Enter fullscreen mode Exit fullscreen mode

International — human-readable with spaces:

+27 821 234 567
Enter fullscreen mode Exit fullscreen mode

National — local format with leading zero:

082 123 4567
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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)