DEV Community

Dave Sng
Dave Sng

Posted on

How to Validate Phone Numbers, Emails, and IBANs in One API Call

Every fintech or e-commerce app needs to validate user data at registration: phone numbers, email addresses, and often bank details like IBANs. The traditional approach means subscribing to three different APIs, managing three API keys, and writing three different integration patterns.

What if you could validate all of them with a single API key and a unified response format?

This tutorial shows you how to build a complete user data validation layer using DataForge — a single API that handles phone numbers, emails, IBANs, credit cards, VAT numbers, postal codes, dates, passwords, and crypto wallets.


Prerequisites

  • Python 3.8+
  • A free RapidAPI account
  • DataForge API subscription (free tier available): Subscribe here

Step 1: Install Dependencies

pip install httpx pydantic
Enter fullscreen mode Exit fullscreen mode

We'll use httpx for async HTTP requests and pydantic for response models.

Step 2: Create Your Validation Client

# validator.py
import httpx
from pydantic import BaseModel
from typing import Optional

DATAFORGE_BASE = "https://dataforge.p.rapidapi.com"

class ValidationClient:
    def __init__(self, api_key: str):
        self.headers = {
            "X-RapidAPI-Key": api_key,
            "X-RapidAPI-Host": "dataforge.p.rapidapi.com"
        }
        self.client = httpx.AsyncClient(
            base_url=DATAFORGE_BASE,
            headers=self.headers,
            timeout=10.0
        )

    async def validate_phone(self, number: str) -> dict:
        r = await self.client.get(
            "/phone/validate",
            params={"number": number}
        )
        return r.json()

    async def validate_email(self, email: str) -> dict:
        r = await self.client.get(
            "/email/validate",
            params={"email": email}
        )
        return r.json()

    async def validate_iban(self, iban: str) -> dict:
        r = await self.client.get(
            "/iban/validate",
            params={"iban": iban}
        )
        return r.json()

    async def validate_all(
        self, phone: str, email: str, iban: str
    ) -> dict:
        """Validate all three in parallel."""
        import asyncio
        phone_task = self.validate_phone(phone)
        email_task = self.validate_email(email)
        iban_task = self.validate_iban(iban)

        results = await asyncio.gather(
            phone_task, email_task, iban_task,
            return_exceptions=True
        )

        return {
            "phone": results[0] if not isinstance(results[0], Exception) else {"error": str(results[0])},
            "email": results[1] if not isinstance(results[1], Exception) else {"error": str(results[1])},
            "iban": results[2] if not isinstance(results[2], Exception) else {"error": str(results[2])},
        }

    async def close(self):
        await self.client.aclose()
Enter fullscreen mode Exit fullscreen mode

The key technique here is asyncio.gather() — it fires all three validation requests simultaneously, so the total time is the slowest single request (typically under 50ms), not the sum of all three.

Step 3: Build a FastAPI Registration Endpoint

# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from validator import ValidationClient
import os

app = FastAPI()
validator = ValidationClient(os.getenv("RAPIDAPI_KEY"))

class RegistrationRequest(BaseModel):
    name: str
    phone: str
    email: str
    iban: str

class ValidationError(BaseModel):
    field: str
    message: str

@app.post("/register")
async def register(req: RegistrationRequest):
    # Validate all fields in parallel
    results = await validator.validate_all(
        phone=req.phone,
        email=req.email,
        iban=req.iban
    )

    errors = []

    # Check phone
    phone = results["phone"]
    if "error" in phone or not phone.get("valid"):
        errors.append(ValidationError(
            field="phone",
            message=f"Invalid phone number: {req.phone}"
        ))

    # Check email
    email = results["email"]
    if "error" in email or not email.get("valid"):
        errors.append(ValidationError(
            field="email",
            message=f"Invalid email address: {req.email}"
        ))
    elif email.get("is_disposable"):
        errors.append(ValidationError(
            field="email",
            message="Disposable email addresses are not allowed"
        ))

    # Check IBAN
    iban = results["iban"]
    if "error" in iban or not iban.get("valid"):
        errors.append(ValidationError(
            field="iban",
            message=f"Invalid IBAN: {req.iban}"
        ))

    if errors:
        raise HTTPException(
            status_code=422,
            detail=[e.dict() for e in errors]
        )

    # All valid — create user
    return {
        "status": "registered",
        "user": {
            "name": req.name,
            "phone_country": phone.get("country"),
            "phone_formatted": phone.get("format", {}).get("international"),
            "email_mx_valid": email.get("mx_check"),
            "iban_country": iban.get("country"),
            "iban_bank": iban.get("bank_code"),
        }
    }
Enter fullscreen mode Exit fullscreen mode

Step 4: Test It

# Start the server
uvicorn main:app --reload

# Test with valid data
curl -X POST http://localhost:8000/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Alice",
    "phone": "+60123456789",
    "email": "alice@gmail.com",
    "iban": "DE89370400440532013000"
  }'
Enter fullscreen mode Exit fullscreen mode

Expected response:

{
  "status": "registered",
  "user": {
    "name": "Alice",
    "phone_country": "MY",
    "phone_formatted": "+60 12-345 6789",
    "email_mx_valid": true,
    "iban_country": "DE",
    "iban_bank": "37040044"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now test with a disposable email:

curl -X POST http://localhost:8000/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Bob",
    "phone": "+1234",
    "email": "test@tempmail.com",
    "iban": "INVALID"
  }'
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "detail": [
    {"field": "phone", "message": "Invalid phone number: +1234"},
    {"field": "email", "message": "Disposable email addresses are not allowed"},
    {"field": "iban", "message": "Invalid IBAN: INVALID"}
  ]
}
Enter fullscreen mode Exit fullscreen mode

All three validation errors returned in a single response — no separate API calls needed from the client.

Step 5: Add Bulk Phone Validation

Need to validate a batch of phone numbers during a data import? DataForge supports bulk operations:

async def validate_phones_bulk(
    client: ValidationClient,
    numbers: list[str]
) -> list[dict]:
    """Validate up to 100 phone numbers in one API call."""
    r = await client.client.post(
        "/phone/validate/bulk",
        json={"numbers": numbers}
    )
    return r.json()

# Usage:
numbers = ["+60123456789", "+44207946000", "+1415555678"]
results = await validate_phones_bulk(validator, numbers)
for r in results:
    print(f"{r['number']}: {'valid' if r['valid'] else 'INVALID'}")
Enter fullscreen mode Exit fullscreen mode

Additional Validators

Once your DataForge client is set up, adding more validators is trivial. Here are a few extras you might need:

# Credit card pre-validation (Luhn check)
async def validate_card(self, number: str) -> dict:
    r = await self.client.get(
        "/creditcard/validate",
        params={"number": number}
    )
    return r.json()
    # Returns: valid, card_type (Visa/MC/Amex), issuer

# Password strength check
async def check_password(self, password: str) -> dict:
    r = await self.client.post(
        "/password/analyze",
        json={"password": password}
    )
    return r.json()
    # Returns: strength_score, entropy, is_common, suggestions

# Crypto wallet validation
async def validate_wallet(self, address: str) -> dict:
    r = await self.client.get(
        "/crypto/validate",
        params={"address": address}
    )
    return r.json()
    # Returns: valid, type (legacy/segwit/taproot), blockchain
Enter fullscreen mode Exit fullscreen mode

Performance Tips

  1. Use asyncio.gather() to validate multiple fields in parallel
  2. DataForge caches responses for 5 minutes — repeated validations of the same input are instant
  3. Bulk endpoints reduce HTTP overhead for batch operations
  4. Connection pooling with httpx.AsyncClient reuses TCP connections

Endpoint Summary

Endpoint Method Description
/phone/validate GET Phone number validation + carrier/timezone
/phone/validate/bulk POST Bulk phone validation (up to 100)
/email/validate GET Email syntax + MX + disposable check
/iban/validate GET IBAN validation + BIC lookup
/creditcard/validate GET Luhn check + card type detection
/vat/validate GET EU VAT number format validation
/postalcode/validate GET Postal code validation (30+ countries)
/date/convert POST Date format conversion (15+ formats)
/password/analyze POST Password strength + entropy analysis
/crypto/validate GET BTC + ETH address validation

All endpoints use the same API key and return consistent JSON response formats.

Pricing

Plan Requests/Month Price
Basic 500 Free
Pro 10,000 $9.99/mo
Ultra 100,000 $49.99/mo
Mega 1,000,000 $199.99/mo

The free tier is enough to build and test your entire integration. Scale up when you launch.

Get started: DataForge on RapidAPI


Building something with DataForge? I'd love to see it — drop a link in the comments.

Top comments (0)