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
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()
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"),
}
}
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"
}'
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"
}
}
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"
}'
Response:
{
"detail": [
{"field": "phone", "message": "Invalid phone number: +1234"},
{"field": "email", "message": "Disposable email addresses are not allowed"},
{"field": "iban", "message": "Invalid IBAN: INVALID"}
]
}
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'}")
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
Performance Tips
-
Use
asyncio.gather()to validate multiple fields in parallel - DataForge caches responses for 5 minutes — repeated validations of the same input are instant
- Bulk endpoints reduce HTTP overhead for batch operations
-
Connection pooling with
httpx.AsyncClientreuses 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)