GEOREFER
Retour au blog

Valider une adresse francaise en Python avec l'API GEOREFER

Python Tutoriel AZMORIS Group 20 mars 2026 Temps de lecture : 12 minutes

La validation d'adresse est un composant essentiel de tout projet Python qui traite des donnees geographiques francaises. Que vous construisiez une plateforme e-commerce, un outil de conformite KYC/AML ou un systeme de facturation, vous avez besoin de verifier que les adresses saisies par vos utilisateurs sont reelles, completes et correctement formatees.

L'API GEOREFER offre une solution REST complete pour valider, normaliser et enrichir les adresses francaises. Dans ce tutoriel, nous allons construire pas a pas un module Python complet de validation d'adresse, du simple appel unitaire jusqu'au workflow KYC en production.

Vous apprendrez a valider une adresse avec un score de confiance, exploiter le GeoTrust Score pour evaluer la fiabilite geographique, normaliser selon la norme AFNOR NF Z 10-011, et traiter des lots d'adresses en batch.

1. Prerequis

Avant de commencer, assurez-vous d'avoir les elements suivants :

Installer requests

Si vous n'avez pas encore la bibliotheque requests, installez-la via pip :

pip install requests

Obtenir votre cle API

Rendez-vous sur georefer.io/#signup et inscrivez-vous avec votre adresse email. Vous recevrez votre cle API instantanement. Le plan DEMO offre 50 requetes par jour, suffisant pour suivre ce tutoriel et tester l'integration.

2. Configuration initiale

Commencez par creer un fichier georefer_client.py avec la configuration de base. Toutes les requetes vers l'API GEOREFER utilisent le header X-Georefer-API-Key pour l'authentification.

import requests

# Configuration GEOREFER
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://georefer.io/geographical_repository/v1"
HEADERS = {
    "X-Georefer-API-Key": API_KEY,
    "Content-Type": "application/json"
}

Bonne pratique : ne stockez jamais votre cle API en dur dans le code source. Utilisez une variable d'environnement ou un fichier .env avec python-dotenv.

import os
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv("GEOREFER_API_KEY")
if not API_KEY:
    raise ValueError("GEOREFER_API_KEY non definie")

3. Valider une adresse unitaire

L'endpoint POST /addresses/validate est le point d'entree principal pour la validation d'adresse. Il accepte une adresse en entree et retourne un score de confiance, un GeoTrust Score composite et des informations geographiques enrichies.

Fonction de validation

def validate_address(street, postal_code, city, country_code="FR"):
    """Valide une adresse francaise via l'API GEOREFER.

    Args:
        street: Numero et nom de rue (ex: '15 Rue de la Paix')
        postal_code: Code postal a 5 chiffres (ex: '75002')
        city: Nom de la commune (ex: 'Paris')
        country_code: Code ISO pays, defaut 'FR'

    Returns:
        dict: Reponse JSON de l'API avec score de confiance
    """
    response = requests.post(
        f"{BASE_URL}/addresses/validate",
        headers=HEADERS,
        json={
            "street_line": street,
            "postal_code": postal_code,
            "city": city,
            "country_code": country_code
        }
    )
    response.raise_for_status()
    return response.json()

Appel et affichage du resultat

# Valider une adresse parisienne
result = validate_address("15 Rue de la Paix", "75002", "Paris")

if result["success"]:
    data = result["data"]
    print(f"Score de confiance : {data['confidence_score']}")
    print(f"GeoTrust Score    : {data['geotrust_score']['overall']}")
    print(f"Niveau de risque  : {data['geotrust_score']['level']}")
    print(f"Code INSEE        : {data.get('insee_code', 'N/A')}")
else:
    print(f"Erreur : {result.get('error', 'Inconnue')}")

L'API retourne un objet JSON complet avec le score de confiance (0 a 100), le GeoTrust Score avec ses 4 sous-scores, et les informations INSEE de la commune identifiee.

4. Exploiter le GeoTrust Score

Le GeoTrust Score est un indicateur composite de fiabilite geographique. Il combine 4 sous-scores ponderes pour produire une note finale de 0 a 100, particulierement utile pour les workflows de conformite.

Les 4 composants du GeoTrust Score

ComposantPoidsDescription
Confidence35%Score de confiance du geocodage
Geo Consistency25%Coherence des couches administratives (postal/commune/dept/INSEE)
Postal Match20%Precision du match sur le code postal
Country Risk20%Score inverse du risque pays FATF/GAFI

Logique de decision basee sur le score

Voici une fonction utilitaire qui traduit le GeoTrust Score en une decision metier exploitable :

def evaluate_address(result):
    """Evalue une adresse validee et retourne une decision.

    Returns:
        tuple: (decision, message, details)
    """
    data = result["data"]
    geotrust = data["geotrust_score"]
    score = geotrust["overall"]
    level = geotrust["level"]

    details = {
        "score": score,
        "level": level,
        "confidence": geotrust.get("confidence"),
        "geo_consistency": geotrust.get("geo_consistency"),
        "postal_match": geotrust.get("postal_match"),
        "country_risk": geotrust.get("country_risk"),
    }

    if score >= 80:
        return "APPROVED", "Adresse verifiee avec haute confiance", details
    elif score >= 60:
        return "REVIEW", "Revue manuelle recommandee", details
    else:
        return "REJECTED", "Adresse non verifiable", details


# Utilisation
result = validate_address("15 Rue de la Paix", "75002", "Paris")
decision, message, details = evaluate_address(result)

print(f"Decision   : {decision}")
print(f"Message    : {message}")
print(f"GeoTrust   : {details['score']}/100 ({details['level']})")

Les quatre niveaux de risque permettent d'adapter votre logique metier : LOW (score 80+) pour une approbation automatique, MEDIUM (60-79) pour une revue manuelle, HIGH (40-59) pour un rejet avec possibilite de correction, et VERY_HIGH (sous 40) pour un rejet definitif.

5. Normaliser une adresse (AFNOR NF Z 10-011)

La normalisation AFNOR reformate une adresse selon la norme postale francaise NF Z 10-011. Le resultat est structure en 6 lignes de 38 caracteres maximum, directement utilisable pour l'envoi de courrier ou la conformite reglementaire.

Fonction de normalisation

def normalize_address(street, postal_code, city, country_code="FR"):
    """Normalise une adresse selon AFNOR NF Z 10-011.

    Returns:
        dict: Adresse normalisee en 6 lignes AFNOR
    """
    response = requests.post(
        f"{BASE_URL}/addresses/normalize",
        headers=HEADERS,
        json={
            "street_line": street,
            "postal_code": postal_code,
            "city": city,
            "country_code": country_code
        }
    )
    response.raise_for_status()
    return response.json()


# Normaliser une adresse
result = normalize_address("15 rue de la paix", "75002", "paris")

if result["success"]:
    normalized = result["data"]
    print("Adresse normalisee AFNOR :")
    for i in range(1, 7):
        line = normalized.get(f"line{i}", "")
        if line:
            print(f"  Ligne {i} : {line}")

Les 6 lignes AFNOR

La norme AFNOR definit 6 lignes avec un role precis :

LigneContenuExemple
Ligne 1Identite du destinataireMONSIEUR DUPONT JEAN
Ligne 2Complement (apt, etage, batiment)APPARTEMENT 42
Ligne 3Complement (entree, residence)RESIDENCE LES LILAS
Ligne 4Numero et libelle de voie15 RUE DE LA PAIX
Ligne 5Lieu-dit ou boite postale
Ligne 6Code postal et commune75002 PARIS

Chaque ligne est automatiquement convertie en majuscules, les accents sont retires et la longueur est limitee a 38 caracteres conformement au standard postal.

6. Validation par lot (batch)

Pour traiter plusieurs adresses, iterez sur votre liste en respectant les limites de debit de votre plan. Ajoutez un delai entre chaque requete pour eviter le code HTTP 429 (Too Many Requests).

Traitement batch simple

import time

addresses = [
    {"street": "15 Rue de la Paix", "postal_code": "75002", "city": "Paris"},
    {"street": "1 Place Bellecour", "postal_code": "69002", "city": "Lyon"},
    {"street": "25 Quai des Chartrons", "postal_code": "33000", "city": "Bordeaux"},
    {"street": "10 Rue du Vieux Port", "postal_code": "13001", "city": "Marseille"},
    {"street": "3 Place du Capitole", "postal_code": "31000", "city": "Toulouse"},
]

results = []
for addr in addresses:
    result = validate_address(addr["street"], addr["postal_code"], addr["city"])
    results.append(result)
    time.sleep(0.1)  # Respecter les limites de debit

# Afficher les resultats
print(f"{'Ville':<15} {'GeoTrust':>10} {'Niveau':<12} {'Decision'}")
print("-" * 55)

for addr, result in zip(addresses, results):
    if result["success"]:
        score = result["data"]["geotrust_score"]["overall"]
        level = result["data"]["geotrust_score"]["level"]
        decision, _, _ = evaluate_address(result)
        print(f"{addr['city']:15} {score:>10} {level:12} {decision}")

Batch avec rapport CSV

Pour des traitements plus importants, exportez les resultats en CSV :

import csv

def batch_validate_to_csv(addresses, output_file="validation_results.csv"):
    """Valide un lot d'adresses et exporte en CSV."""
    with open(output_file, "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow([
            "rue", "code_postal", "ville",
            "geotrust_score", "niveau", "decision"
        ])

        for addr in addresses:
            try:
                result = validate_address(
                    addr["street"], addr["postal_code"], addr["city"]
                )
                decision, msg, details = evaluate_address(result)
                writer.writerow([
                    addr["street"], addr["postal_code"], addr["city"],
                    details["score"], details["level"], decision
                ])
            except Exception as e:
                writer.writerow([
                    addr["street"], addr["postal_code"], addr["city"],
                    "", "", f"ERREUR: {e}"
                ])
            time.sleep(0.1)

    print(f"Resultats exportes dans {output_file}")

7. Gestion des erreurs

En production, votre code doit gerer proprement les differents cas d'erreur HTTP retournes par l'API. Voici les codes a surveiller et un wrapper robuste pour vos appels.

Codes d'erreur HTTP

CodeSignificationAction recommandee
401Cle API invalide ou absenteVerifier la cle dans le header
403Feature non disponible sur le planUpgrader le plan tarifaire
429Quota depasse ou rate limitAttendre et reessayer (backoff)
400Requete invalide (champs manquants)Verifier le format de la requete
500Erreur serveur interneReessayer apres un delai

Fonction de validation robuste

def safe_validate(street, postal_code, city, max_retries=3):
    """Valide une adresse avec gestion complete des erreurs.

    Gere les timeouts, les erreurs de connexion, les limites
    de debit (429) avec retry exponentiel, et les erreurs metier.
    """
    for attempt in range(max_retries):
        try:
            response = requests.post(
                f"{BASE_URL}/addresses/validate",
                headers=HEADERS,
                json={
                    "street_line": street,
                    "postal_code": postal_code,
                    "city": city,
                    "country_code": "FR"
                },
                timeout=10
            )

            if response.status_code == 401:
                raise Exception("Cle API invalide ou absente")

            elif response.status_code == 429:
                retry_after = int(response.headers.get("Retry-After", 60))
                if attempt < max_retries - 1:
                    print(f"Rate limit atteint. Retry dans {retry_after}s...")
                    time.sleep(retry_after)
                    continue
                raise Exception(f"Rate limit. Reessayer apres {retry_after}s")

            elif response.status_code == 403:
                raise Exception("Feature non disponible sur votre plan")

            response.raise_for_status()
            return response.json()

        except requests.exceptions.Timeout:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)  # Backoff exponentiel
                continue
            raise Exception("Timeout apres 3 tentatives")

        except requests.exceptions.ConnectionError:
            raise Exception("Impossible de se connecter a l'API GEOREFER")

Cette fonction gere automatiquement le retry avec backoff exponentiel en cas de timeout ou de rate limiting, tout en remontant immediatement les erreurs non recoverables (cle invalide, plan insuffisant).

8. Recherche de villes (autocomplete)

L'endpoint GET /cities/autocomplete fournit une autocompletion ultra-rapide (moins de 50 ms) alimentee par Elasticsearch. Ideal pour integrer un champ de saisie avec suggestions en temps reel.

Fonction d'autocompletion

def autocomplete_city(query, limit=5):
    """Recherche de villes par autocompletion.

    Args:
        query: Debut du nom de ville (ex: 'marseil')
        limit: Nombre max de suggestions (defaut: 5)

    Returns:
        list: Liste des villes correspondantes
    """
    response = requests.get(
        f"{BASE_URL}/cities/autocomplete",
        headers=HEADERS,
        params={"q": query, "limit": limit}
    )
    response.raise_for_status()
    return response.json()


# Recherche par prefixe
cities = autocomplete_city("marseil")
for city in cities["data"]:
    print(f"{city['name']} ({city['postal_code']}) - {city['department_code']}")

Recherche fuzzy (tolerant aux fautes)

L'endpoint GET /cities/search utilise le fuzzy matching pour tolerer les fautes de frappe et les variantes orthographiques :

def fuzzy_search_city(query, country_code="FR", limit=10):
    """Recherche fuzzy tolerante aux fautes de frappe."""
    response = requests.get(
        f"{BASE_URL}/cities/search",
        headers=HEADERS,
        params={
            "q": query,
            "country_code": country_code,
            "limit": limit
        }
    )
    response.raise_for_status()
    return response.json()


# Meme avec une faute de frappe, la recherche trouve "Marseille"
results = fuzzy_search_city("marsseile")
for city in results["data"]:
    print(f"{city['name']} (score: {city.get('score', 'N/A')})")

La recherche fuzzy est disponible sur les plans DEMO, STARTER, PRO et ENTERPRISE (feature gate search). Pour en savoir plus sur les fonctionnalites par plan, consultez la documentation API.

9. Rechercher des entreprises (plan PRO)

Avec le plan PRO, vous accedez aux 16,8 millions d'etablissements du repertoire SIRENE. L'endpoint GET /companies permet de rechercher par SIREN, SIRET ou nom d'entreprise, avec des filtres geographiques.

Recherche par SIREN ou nom

def search_company(siren=None, name=None, department=None):
    """Recherche d'entreprise par SIREN, nom ou departement.

    Necessite le plan PRO ou ENTERPRISE (feature: companies).
    """
    params = {}
    if siren:
        params["siren"] = siren
    if name:
        params["name"] = name
    if department:
        params["department_code"] = department

    endpoint = f"{BASE_URL}/companies" if siren else f"{BASE_URL}/companies/search"
    response = requests.get(endpoint, headers=HEADERS, params=params)
    response.raise_for_status()
    return response.json()


# Recherche par nom dans le departement 75
results = search_company(name="boulangerie", department="75")
for company in results["data"][:5]:
    print(f"{company['name']}")
    print(f"  SIRET : {company['siret']}")
    print(f"  NAF   : {company['naf_code']}")
    print(f"  Ville : {company['city']}")
    print()

Detail d'un etablissement par SIRET

def get_company_detail(siret):
    """Recupere le detail complet d'un etablissement par SIRET."""
    response = requests.get(
        f"{BASE_URL}/companies/{siret}",
        headers=HEADERS
    )
    response.raise_for_status()
    return response.json()


# Detail d'un etablissement
detail = get_company_detail("55212022200013")
if detail["success"]:
    company = detail["data"]
    print(f"Raison sociale : {company['name']}")
    print(f"SIREN          : {company['siren']}")
    print(f"Code NAF       : {company['naf_code']}")
    print(f"Adresse        : {company['address']}")

10. Exemple complet : workflow KYC

Voici un script complet qui combine validation d'adresse, analyse du GeoTrust Score, normalisation AFNOR et verification d'entreprise dans un workflow KYC automatise.

import requests
import json
import time
import os

# --- Configuration ---
API_KEY = os.getenv("GEOREFER_API_KEY", "YOUR_API_KEY")
BASE_URL = "https://georefer.io/geographical_repository/v1"
HEADERS = {
    "X-Georefer-API-Key": API_KEY,
    "Content-Type": "application/json"
}


def kyc_check(street, postal_code, city, siret=None):
    """Execute un workflow KYC complet.

    1. Valide l'adresse et obtient le GeoTrust Score
    2. Normalise l'adresse selon AFNOR
    3. Verifie l'entreprise par SIRET (si fourni)
    4. Produit un rapport de conformite
    """
    report = {"status": "pending", "checks": []}

    # Etape 1 : validation d'adresse
    try:
        validation = requests.post(
            f"{BASE_URL}/addresses/validate",
            headers=HEADERS,
            json={
                "street_line": street,
                "postal_code": postal_code,
                "city": city,
                "country_code": "FR"
            },
            timeout=10
        ).json()

        geotrust = validation["data"]["geotrust_score"]
        report["geotrust_score"] = geotrust["overall"]
        report["risk_level"] = geotrust["level"]
        report["checks"].append({
            "name": "address_validation",
            "passed": geotrust["overall"] >= 60,
            "score": geotrust["overall"]
        })
    except Exception as e:
        report["checks"].append({
            "name": "address_validation",
            "passed": False,
            "error": str(e)
        })

    # Etape 2 : normalisation AFNOR
    try:
        normalized = requests.post(
            f"{BASE_URL}/addresses/normalize",
            headers=HEADERS,
            json={
                "street_line": street,
                "postal_code": postal_code,
                "city": city,
                "country_code": "FR"
            },
            timeout=10
        ).json()

        report["normalized_address"] = normalized.get("data", {})
        report["checks"].append({
            "name": "afnor_normalization",
            "passed": normalized.get("success", False)
        })
    except Exception as e:
        report["checks"].append({
            "name": "afnor_normalization",
            "passed": False,
            "error": str(e)
        })

    # Etape 3 : verification SIRENE (si SIRET fourni)
    if siret:
        try:
            company = requests.get(
                f"{BASE_URL}/companies/{siret}",
                headers=HEADERS,
                timeout=10
            ).json()

            report["company"] = company.get("data", {})
            report["checks"].append({
                "name": "sirene_verification",
                "passed": company.get("success", False)
            })
        except Exception as e:
            report["checks"].append({
                "name": "sirene_verification",
                "passed": False,
                "error": str(e)
            })

    # Decision finale
    all_passed = all(c["passed"] for c in report["checks"])
    report["status"] = "APPROVED" if all_passed else "REVIEW_REQUIRED"

    return report


# --- Execution ---
report = kyc_check(
    street="15 Rue de la Paix",
    postal_code="75002",
    city="Paris",
    siret="55212022200013"
)

print(json.dumps(report, indent=2, ensure_ascii=False))

Ce script produit un rapport JSON complet avec le statut final de chaque verification, exploitable directement dans votre pipeline de conformite.

11. Limites de debit et plans tarifaires

Chaque plan GEOREFER a ses propres limites de debit et fonctionnalites. Choisissez le plan adapte a votre volume de requetes.

PlanRequetes / jourRequetes / moisRate / minPrix
DEMO501 50010Gratuit
FREE1003 00010Gratuit
STARTER5 000150 0003049 EUR / mois
PRO50 0001 500 00060199 EUR / mois
ENTERPRISEIllimiteIllimite200Sur devis

Conseil : le plan DEMO est ideal pour le prototypage et les tests. Pour la production avec validation d'adresse et normalisation AFNOR, le plan STARTER (5 000 requetes/jour) couvre la plupart des cas d'usage. Le plan PRO ajoute l'acces aux donnees SIRENE et au GeoTrust Score complet.

Gerer le header Retry-After

Quand le quota est depasse, l'API retourne un code 429 avec un header Retry-After. Votre code Python doit lire ce header pour savoir quand reessayer :

def check_remaining_quota():
    """Verifie le quota restant via /api/info."""
    response = requests.get(
        f"{BASE_URL}/api/info",
        headers=HEADERS
    )
    info = response.json()
    if info.get("success"):
        data = info["data"]
        print(f"Plan            : {data['plan']}")
        print(f"Requetes today  : {data.get('daily_usage', 0)} / {data.get('daily_limit', '?')}")
        print(f"Features        : {', '.join(data.get('features', []))}")

12. Questions frequentes

Comment valider une adresse francaise en Python ?

Utilisez la bibliotheque requests pour envoyer une requete POST a l'endpoint /addresses/validate de l'API GEOREFER. Passez les champs street_line, postal_code, city et country_code en JSON. L'API retourne un score de confiance de 0 a 100 et un GeoTrust Score composite qui evalue la fiabilite geographique de l'adresse.

Quelle est la difference entre validation et normalisation d'adresse ?

La validation verifie qu'une adresse existe reellement et retourne un score de confiance. La normalisation reformate l'adresse selon la norme AFNOR NF Z 10-011 en 6 lignes de 38 caracteres maximum, en majuscules sans accents. La validation repond a la question "cette adresse est-elle fiable ?", la normalisation repond a "comment formater cette adresse pour un courrier postal ?".

Combien coute l'API GEOREFER pour un projet Python ?

GEOREFER propose un plan DEMO gratuit avec 50 requetes par jour, suffisant pour le developpement et les tests. Le plan STARTER a 49 EUR/mois offre 5 000 requetes/jour et convient a la plupart des applications en production. Le plan PRO a 199 EUR/mois ajoute les donnees SIRENE (16,8 millions d'etablissements) et 50 000 requetes/jour. Inscrivez-vous sur georefer.io pour commencer gratuitement.

Peut-on valider des adresses en batch avec Python ?

Oui. Iterez sur votre liste d'adresses et appelez l'endpoint /addresses/validate pour chacune. Ajoutez un delai entre les requetes avec time.sleep() pour respecter les limites de debit de votre plan. Pour des volumes importants, le plan PRO offre 60 requetes par minute et le plan ENTERPRISE est illimite.

Commencez a valider des adresses en Python

Obtenez votre cle API gratuite et testez la validation d'adresse, le GeoTrust Score et la normalisation AFNOR en moins de 5 minutes.

Obtenir ma cle API gratuite

Consulter la documentation API

© 2026 AZMORIS Group — georefer.io