api.
Real-time VerificationFrameworks

Flask

Getting Started

Flask's simplicity makes integrating user.cleaning straightforward. We'll use the requests library to communicate with our API, which you're likely already familiar with if you've worked with Flask before.

First, install the requests library:

pip install requests

Next, store your API key securely in your environment variables. Never hardcode API keys directly in your source code:

.env
USER_CLEANING_API_KEY = "your-api-key"

Next, load it in your application:

config.py
import os
USER_CLEANING_API_KEY = os.environ.get("USER_CLEANING_API_KEY", "")

If you don't have an API key yet, check out our Quickstart guide to generate one.

IP Forwarding for Rate Limiting

For user.cleaning's rate limiting to work correctly, you need to forward the real client IP with each request. Add this helper function to extract the client IP:

utils.py
from flask import request

def get_client_ip():
    x_forwarded_for = request.headers.get("X-Forwarded-For")
    if x_forwarded_for:
        return x_forwarded_for.split(",")[0].strip()
    return request.headers.get("X-Real-IP") or request.remote_addr or "unknown"

Production: If you're behind nginx, Cloudflare, or a load balancer, X-Forwarded-For is set automatically.

Development/testing: Without a reverse proxy forwarding X-Forwarded-For, the integration will see your server's IP instead of actual client IPs. This means all requests appear to come from the same IP, and after 5 disposable email attempts, all registrations will be blocked until the IP is unbanned.

If you experience unexpected registration blocks, verify your reverse proxy is forwarding client IPs correctly. You can unban blocked IPs at API Settings and configure number of registration attempts before block or disable it completely at Color Configuration.

Creating the Validation Function

Let's create a helper function that handles communication with the user.cleaning API:

utils.py
import os
import requests
from config import USER_CLEANING_API_KEY

def check_email(email: str, client_ip: str) -> dict:
    try:
        response = requests.post(
            "https://api.user.cleaning/v1/external-api-requests/check-email",
            params={"email": email},
            headers={
                "X-API-Key": USER_CLEANING_API_KEY,
                "X-Forwarded-For": client_ip,
                "X-Real-IP": client_ip,
            },
            timeout=5
        )
        if response.status_code == 403:
            data = response.json()
            return {"category": "banned", "message": data.get("detail", "Access denied")}
        response.raise_for_status()
        return response.json()
    except requests.RequestException:
        return {"category": "white"}

The API responds with a JSON object containing a category field that tells you how to handle the email address.

Integration Approaches

Validating in Route Handlers

The most straightforward approach is to call the validation function directly in your route handlers and use Flask's abort() function to reject disposable emails:

routes.py
from flask import Flask, request, jsonify, abort

app = Flask(__name__)

@app.route("/register", methods=["POST"])
def register():
    email = request.form.get("email")
    client_ip = get_client_ip()
    result = check_email(email, client_ip)
    
    if result["category"] == "banned":
        abort(403, description=result["message"])
    
    if result["category"] == "black":
        abort(400, description="Disposable emails are not allowed")
    
    if result["category"] == "grey":
        # Suspicious email — let them in but require phone verification
        user = create_user(email, requires_phone_verification=True)
        return jsonify({"message": "Please enter your phone number for verification"}), 201
    
    # 'white' — all good
    user = create_user(email, requires_phone_verification=False)
    return jsonify({"user_id": user.id}), 201

This approach works well when you have just a few routes that need email validation and want direct control over the logic.

Creating Reusable Middleware

If you need to validate emails across multiple routes, creating a decorator function keeps your code DRY and consistent:

middleware.py
from flask import request, jsonify
from functools import wraps

def validate_email_middleware(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        email = request.form.get("email") or request.json.get("email")
        
        if email:
            client_ip = get_client_ip()
            result = check_email(email, client_ip)
            
            if result["category"] == "banned":
                return jsonify({"error": result["message"]}), 403
            
            if result["category"] == "black":
                return jsonify({"error": "Disposable emails are not allowed"}), 400
            
            # Attach result for use in route handler
            request.email_validation = result
        
        return f(*args, **kwargs)
    return decorated

Now you can easily apply this validation to any route:

routes.py
@app.route("/register", methods=["POST"])
@validate_email_middleware
def register():
    requires_phone_verification = request.email_validation["category"] == "grey"
    user = create_user(email, requires_phone_verification=requires_phone_verification)
    return jsonify({"user_id": user.id}), 201

Integration with WTForms

If you're using Flask-WTF for form handling, you can create a custom validator. Note that WTForms validators don't have direct access to the request object, so we access it from Flask's context:

validators.py
from flask import request
from wtforms import ValidationError

def validate_not_disposable(form, field):
    client_ip = get_client_ip()
    result = check_email(field.data, client_ip)
    
    if result["category"] == "banned":
        raise ValidationError(result["message"])
    
    if result["category"] == "black":
        raise ValidationError("Please use a permanent email address")

Then add this validator to your form definition:

forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import Email, DataRequired

class RegistrationForm(FlaskForm):
    email = StringField("Email", validators=[DataRequired(), Email(), validate_not_disposable])
    password = PasswordField("Password", validators=[DataRequired()])

When a user submits the form with a disposable email, they'll see your custom error message, and the form will remain on the page so they can correct their email address.

Understanding the Results

The category field can have three possible values:

  • black — This is a disposable or temporary email address that should be blocked
  • grey — Suspicious patterns were detected (like alias chains or plus addressing), consider requiring phone verification
  • white — The email looks legitimate and can be accepted without restrictions

For a complete description of the output data structure and all available fields, see our Categories Reference.

On this page