Valider une adresse francaise en Python avec l'API GEOREFER
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 :
- Python 3.7+ installe sur votre machine
- La bibliotheque
requestspour les appels HTTP - Une cle API GEOREFER (gratuite, sans carte bancaire)
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
| Composant | Poids | Description |
|---|---|---|
| Confidence | 35% | Score de confiance du geocodage |
| Geo Consistency | 25% | Coherence des couches administratives (postal/commune/dept/INSEE) |
| Postal Match | 20% | Precision du match sur le code postal |
| Country Risk | 20% | 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 :
| Ligne | Contenu | Exemple |
|---|---|---|
| Ligne 1 | Identite du destinataire | MONSIEUR DUPONT JEAN |
| Ligne 2 | Complement (apt, etage, batiment) | APPARTEMENT 42 |
| Ligne 3 | Complement (entree, residence) | RESIDENCE LES LILAS |
| Ligne 4 | Numero et libelle de voie | 15 RUE DE LA PAIX |
| Ligne 5 | Lieu-dit ou boite postale | |
| Ligne 6 | Code postal et commune | 75002 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
| Code | Signification | Action recommandee |
|---|---|---|
| 401 | Cle API invalide ou absente | Verifier la cle dans le header |
| 403 | Feature non disponible sur le plan | Upgrader le plan tarifaire |
| 429 | Quota depasse ou rate limit | Attendre et reessayer (backoff) |
| 400 | Requete invalide (champs manquants) | Verifier le format de la requete |
| 500 | Erreur serveur interne | Reessayer 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.
| Plan | Requetes / jour | Requetes / mois | Rate / min | Prix |
|---|---|---|---|---|
| DEMO | 50 | 1 500 | 10 | Gratuit |
| FREE | 100 | 3 000 | 10 | Gratuit |
| STARTER | 5 000 | 150 000 | 30 | 49 EUR / mois |
| PRO | 50 000 | 1 500 000 | 60 | 199 EUR / mois |
| ENTERPRISE | Illimite | Illimite | 200 | Sur 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