Le code commune INSEE marche bien sur des villes moyennes. Sur Paris 15e (~230 000 habitants), Marseille ou Lyon, il devient inutilisable pour qui veut croiser une adresse avec autre chose qu'une moyenne municipale. Le passage à l'IRIS (environ 2 000 habitants par zone) débloque les usages réels : scoring géographique en assurance, ciblage retail, études de zone de chalandise. Sur les vingt-cinq dernières années passées dans la data quality, c'est la demande que j'ai vue revenir le plus souvent dès qu'un projet dépassait la simple normalisation d'adresse.
Cet article décrit comment obtenir le code IRIS d'une adresse française en Python via l'API TrustyData, avec le code qui marche et les nuances à connaître avant la prod.
IRIS, mode d'emploi rapide
L'IRIS (Îlot Regroupé pour l'Indication Statistique) est le maillage infra-communal de l'INSEE. Toutes les communes d'au moins 10 000 habitants sont systématiquement découpées, et la plupart des communes entre 5 000 et 10 000 le sont aussi. Les communes plus petites sont assimilées à un IRIS unique dont le code vaut <code commune>0000 et désigne la commune entière.
Chaque IRIS pèse environ 2 000 habitants. La France en compte près de 15 500, dont plus de 750 dans les DOM. Le code IRIS fait 9 chiffres : les 5 premiers reprennent le code commune INSEE, les 4 suivants identifient l'IRIS dans la commune. 751010101 désigne par exemple un IRIS du 1er arrondissement de Paris.
Chaque IRIS porte aussi un type sur un caractère : H (habitat), A (activité), D (divers), Z (commune non découpée). Si vous faites du scoring grand public, ce type sert à exclure les zones non résidentielles.
Pourquoi ne pas faire l'intersection soi-même
C'est faisable. On télécharge les contours IRIS de l'IGN (shapefile, ~150 Mo), on géocode l'adresse via la BAN, on charge le tout dans GeoPandas et on enchaîne avec un sjoin point-polygone. Quelques heures pour un prototype.
Le coût caché commence après. Les contours bougent au fil des recensements, il faut tracer le millésime utilisé pour qualifier chaque adresse. Le géocodage BAN doit être maintenu en parallèle (snapshot mensuel, voies créées ou fusionnées). Les jointures spatiales sur 15 500 polygones et plusieurs millions de points demandent un index spatial correctement tuné. J'ai vu plusieurs équipes monter ce pipeline puis l'abandonner deux ans plus tard, quand l'ingénieur qui le maîtrisait est parti et que personne n'a repris le flambeau.
Pour un projet où l'IRIS n'est pas le cœur de métier, l'arbitrage est rapide : un appel HTTP renvoie le code et personne n'a à entretenir d'infra SIG.
Le code minimal
L'endpoint est POST /address/verify. Il géocode l'adresse via la BAN et retourne le bloc geocoding (contenant le code IRIS) dans la même réponse que l'adresse normalisée.
import requests
API_URL = "https://api.trustydata.app/services/v1/address/verify"
def obtenir_iris(adresse: str) -> dict | None:
"""Retourne les infos IRIS pour une adresse française, ou None si pas de match."""
response = requests.post(
API_URL,
json={"q": adresse, "max_results": 1},
headers={"Authorization": "Bearer VOTRE_CLE_API"},
timeout=5,
)
response.raise_for_status()
matches = response.json().get("matches") or []
if not matches:
return None
top = matches[0]
geo = top.get("geocoding") or {}
return {
"adresse": top.get("adresse"),
"code_insee": top.get("code_insee"),
"code_iris": geo.get("code_iris"),
"nom_iris": geo.get("nom_iris"),
"type_iris": geo.get("type_iris"),
"verdict": top.get("verdict"),
}
resultat = obtenir_iris("10 rue de la paix 75002 paris")
# {'adresse': '10 Rue de la Paix, 75002 Paris 2e Arrondissement',
# 'code_insee': '75102', 'code_iris': '751020502', 'nom_iris': 'Gaillon 2',
# 'type_iris': 'H', 'verdict': 'match_exact'}
Le verbe est POST avec un corps JSON, pas GET en query string. Le paramètre s'appelle max_results, pas limit. Et la réponse vient toujours sous matches[], même quand on demande un seul résultat. Penser à indexer matches[0] plutôt que lire la racine.
Prêt à intégrer l'API TrustyData ?
Enrichir avec le carreau INSEE 200m (plan Business)
Le bloc geocoding (IRIS) est ouvert dès le plan Growth. Le plan Business ajoute un bloc statistical_grid qui décrit le carreau INSEE 200m dans lequel tombe l'adresse. Le carreau est une grille régulière de 200 m × 200 m, indépendante du découpage IRIS, qui sert de maille de référence pour les indicateurs Filosofi.
Les champs gardent le nom du fichier source INSEE, qui n'est pas franchement parlant. Mapping rapide :
def enrichir_avec_carreau(adresse: str) -> dict | None:
"""Retourne IRIS + indicateurs du carreau 200m INSEE (plan Business)."""
response = requests.post(
API_URL,
json={"q": adresse, "max_results": 1},
headers={"Authorization": "Bearer VOTRE_CLE_API_BUSINESS"},
timeout=5,
)
response.raise_for_status()
matches = response.json().get("matches") or []
if not matches:
return None
top = matches[0]
geo = top.get("geocoding") or {}
grid = top.get("statistical_grid") or {}
return {
"adresse": top.get("adresse"),
"code_iris": geo.get("code_iris"),
"carreau_id": grid.get("id_inspire"),
"individus": grid.get("ind"), # nb d'individus dans le carreau
"menages": grid.get("men"), # nb de ménages
"menages_pauvres": grid.get("men_pauv"), # ménages sous le seuil de pauvreté
"menages_proprietaires": grid.get("men_prop"),
"snv": grid.get("ind_snv"), # somme des niveaux de vie
"ind_25_39": grid.get("ind_25_39"), # tranches d'âge (ind_0_3 → ind_80p)
"ind_40_54": grid.get("ind_40_54"),
}
Chaque carreau a un id_inspire au format CRS3035RES200mN...E... (standard européen INSPIRE). statistical_grid contient plus de 30 indicateurs : tranches d'âge de ind_0_3 à ind_80p, époques de construction des logements (log_av45, log_45_70, log_70_90, log_ap90), logement social (log_soc), composition des ménages (men_1ind, men_5ind, men_fmp familles monoparentales), surfaces. Le schéma complet est sur la page géocodage statistique IRIS.
IRIS et carreau 200m ne font pas la même chose. L'IRIS suit les limites administratives et la trame urbaine. Le carreau est une grille de 200 m × 200 m, posée par-dessus le territoire sans tenir compte des communes. Les indicateurs Filosofi vivent au niveau du carreau, pas de l'IRIS. Pour croiser avec d'autres sources INSEE publiées à la maille IRIS (recensement, enquêtes), la jointure se fait côté client à partir du code_iris retourné.
À savoir avant de partir en prod
Le code_iris retourné par l'API est dérivé de la position dans matches[0].position. Si type_position == "commune" (autrement dit, adresse pas trouvée à la rue, position au centroïde communal), l'IRIS retourné n'a aucune valeur statistique : c'est juste celui qui contient le centroïde, choisi presque au hasard. Avant d'exploiter l'IRIS pour du scoring, filtrer sur type_position in ("parcelle", "entree", "numero", "voie"). Même logique pour les petites communes assimilées à un IRIS unique (code_iris = <code commune>0000) : si votre segmentation suppose des IRIS fins, il faut un branchement explicite, sinon elles finissent toutes dans le même cluster et plombent l'analyse.
Côté format, le type_iris arrive sur un caractère brut (H, A, D, Z) : c'est le format INSEE source, à mapper côté script avant tout affichage utilisateur. Et les contours IRIS évoluent au fil des recensements : l'API utilise toujours les contours en vigueur, donc pour une étude longitudinale qui doit rester comparable d'une année à l'autre, persistez le code_iris à date plutôt que de le recalculer plus tard.
Enfin, pas d'endpoint batch côté serveur : l'API traite une adresse par requête. Pour enrichir un fichier, on boucle côté script avec un ThreadPoolExecutor calibré sur le quota du plan. Le pattern est détaillé dans normaliser une adresse française en Python.
Pour résumer
Le code IRIS débloque ce que le code commune ne permet pas : scoring fin et analyse socio-démographique de quartier. Avec l'API TrustyData, ça tient en un appel POST /address/verify et un coup d'œil au bloc matches[0].geocoding. Le plan Growth ouvre le bloc IRIS, le plan Business ajoute le carreau INSEE 200m et ses ~30 indicateurs Filosofi.
Le détail des plans est sur la page tarifs. Pour tester sans intégration, la démo /address/verify fait le job depuis le navigateur.