Tutoriels en français

Vérifier un numéro SIRET ou SIREN en Python : clé de Luhn, La Poste et TVA

Tutoriels en français · · Publié le 2026-11-09

Un tampon encreur en bois à poignée noire, posé de biais sur un fond blanc
Avant les formulaires en ligne, le numéro d'établissement se tamponnait à l'encre.

Un numéro SIREN compte 9 chiffres, un SIRET 14, et le dernier chiffre de chacun est une clé de contrôle calculée avec l'algorithme de Luhn. Quelques lignes de Python suffisent pour rejeter les fautes de frappe dans un formulaire ou un fichier de fournisseurs, à condition de connaître la seule exception notable : les établissements de La Poste. Pour savoir si l'entreprise existe vraiment, il faut ensuite interroger une source officielle, ce que permet l'API publique Recherche d'entreprises, sans clé.

Tous les exemples ont été exécutés avec Python 3.13, pandas 3.0 et requests 2.34 ; les sorties affichées sont les sorties réelles. Les blocs s'enchaînent : chacun réutilise les fonctions définies plus haut.

SIREN, SIRET, NIC : de quoi parle-t-on ?

L'Insee attribue un numéro SIREN à chaque unité légale (une société, une association, un entrepreneur individuel) lors de son inscription au répertoire Sirene. Chaque établissement de cette unité légale (le siège, une agence, un magasin) reçoit ensuite un numéro SIRET de 14 chiffres. D'après la définition de l'Insee, le SIRET se compose du SIREN suivi du NIC (numéro interne de classement), lui-même formé d'un numéro d'ordre à quatre chiffres et d'un chiffre de contrôle qui permet de vérifier la validité de l'ensemble.

Les numéros s'écrivent souvent avec des espaces (552 120 222 00013), parfois avec des points, et les tableurs y glissent volontiers des espaces insécables. La première étape consiste donc à normaliser la saisie avant tout contrôle.

La clé de Luhn appliquée au SIREN

L'algorithme de Luhn, le même que pour les numéros de carte bancaire, parcourt les chiffres de droite à gauche et double un chiffre sur deux en commençant par le deuxième. Quand un double dépasse 9, on lui retire 9 (ce qui revient à additionner ses deux chiffres). Le numéro est valide si la somme totale est un multiple de 10.

import re

def nettoyer(numero: str) -> str:
    """Retire les espaces (y compris insécables) et les points."""
    return re.sub(r"[\s.\u00a0\u202f]", "", numero)

def luhn_valide(chiffres: str) -> bool:
    """Contrôle de Luhn sur une chaîne composée uniquement de chiffres."""
    total = 0
    for rang, caractere in enumerate(reversed(chiffres)):
        n = int(caractere)
        if rang % 2 == 1:      # un chiffre sur deux, en partant de la droite
            n *= 2
            if n > 9:
                n -= 9
        total += n
    return total % 10 == 0

def siren_valide(numero: str) -> bool:
    s = nettoyer(numero)
    return len(s) == 9 and s.isdigit() and luhn_valide(s)

for n in ["552 120 222", "552 120 223", "552.120.222", "55212022", "55212022A"]:
    print(f"{n!r:16} {siren_valide(n)}")
'552 120 222'    True
'552 120 223'    False
'552.120.222'    True
'55212022'       False
'55212022A'      False

Le contrôle refuse un chiffre modifié (552 120 223), un numéro trop court et un caractère non numérique. Il accepte les points et espaces parce que nettoyer() les retire avant le calcul. Notez qu'on garde le numéro sous forme de chaîne : converti en entier, un SIREN commençant par 0 perdrait ses zéros de tête, et le contrôle de longueur n'aurait plus de sens.

Une clé valide prouve seulement que le numéro est bien formé. Elle ne dit rien de l'existence de l'entreprise, de son activité ni de sa situation (active ou cessée). Une faute qui permute deux chiffres non adjacents peut aussi produire un numéro valide par hasard.

Le SIRET et l'exception de La Poste

Pour un SIRET, on applique Luhn aux 14 chiffres, après avoir vérifié que les 9 premiers forment un SIREN valide. Il existe pourtant une exception bien connue des développeurs de logiciels de gestion : La Poste (SIREN 356 000 000) compte 13 136 établissements, fermés compris, selon l'API Recherche d'entreprises, alors qu'un numéro d'ordre de quatre chiffres suivi d'une clé de Luhn n'en permet que 10 000. La plupart de ses SIRET suivent une autre règle : la somme des 14 chiffres est un multiple de 5.

Nous avons vérifié les deux comportements sur des numéros réels publiés par l'API Recherche d'entreprises : le SIRET du siège de La Poste (356 000 000 00048) passe le contrôle de Luhn mais pas celui du multiple de 5, alors que ceux de ses bureaux (par exemple 356 000 000 38497) échouent à Luhn et respectent la règle du multiple de 5. La fonction accepte donc l'une ou l'autre règle pour ce seul SIREN.

SIREN_LA_POSTE = "356000000"

def siret_valide(numero: str) -> bool:
    s = nettoyer(numero)
    if len(s) != 14 or not s.isdigit():
        return False
    if not siren_valide(s[:9]):
        return False
    if luhn_valide(s):
        return True
    # Exception : les établissements de La Poste (somme des chiffres multiple de 5)
    return s[:9] == SIREN_LA_POSTE and sum(int(c) for c in s) % 5 == 0

tests = {
    "siège de la Société Générale": "552 120 222 00013",
    "même SIRET, dernier chiffre faux": "552 120 222 00014",
    "siège de La Poste": "356 000 000 00048",
    "établissement de La Poste": "356 000 000 38497",
    "La Poste, chiffre faux": "356 000 000 38498",
}
for libelle, n in tests.items():
    print(f"{n}  {siret_valide(n)!s:5}  {libelle}")
552 120 222 00013  True   siège de la Société Générale
552 120 222 00014  False  même SIRET, dernier chiffre faux
356 000 000 00048  True   siège de La Poste
356 000 000 38497  True   établissement de La Poste
356 000 000 38498  False  La Poste, chiffre faux

Si vous validez des SIRET dans un formulaire, cette exception évite de bloquer un fournisseur légitime : un bureau de poste, un centre de tri ou une plateforme Colissimo peuvent apparaître dans une base clients ou fournisseurs.

Calculer le numéro de TVA intracommunautaire

Pour une entreprise française, le numéro de TVA intracommunautaire se déduit du SIREN : la clé vaut (12 + 3 × (SIREN mod 97)) mod 97, écrite sur deux chiffres. C'est pratique pour préremplir un champ de facture ou repérer une incohérence entre le SIREN et le numéro de TVA saisis par un client.

def numero_tva(siren: str) -> str:
    s = nettoyer(siren)
    if not siren_valide(s):
        raise ValueError(f"SIREN invalide : {siren!r}")
    cle = (12 + 3 * (int(s) % 97)) % 97
    return f"FR{cle:02d}{s}"

print(numero_tva("552 120 222"))
FR27552120222

Le résultat correspond au numéro de TVA que renvoie l'API officielle dans l'exemple suivant. Attention : le calcul donne le numéro théorique. Seule une entreprise assujettie à la TVA en possède un en pratique, et la validité d'un numéro de TVA d'un autre pays de l'Union se contrôle avec le service VIES de la Commission européenne.

Vérifier que l'entreprise existe avec l'API Recherche d'entreprises

L'État publie l'API Recherche d'entreprises, ouverte et sans authentification, qui interroge les données du répertoire Sirene et du Registre national des entreprises. Elle accepte au maximum 7 requêtes par seconde et par adresse IP, d'après sa documentation : suffisant pour un contrôle à la saisie, pas pour revérifier un fichier de 100 000 lignes en boucle serrée.

import requests

API = "https://recherche-entreprises.api.gouv.fr/search"

def chercher_siren(siren: str) -> dict | None:
    s = nettoyer(siren)
    if not siren_valide(s):
        raise ValueError(f"SIREN invalide : {siren!r}")
    r = requests.get(API, params={"q": s, "per_page": 1}, timeout=10)
    r.raise_for_status()
    for resultat in r.json()["results"]:
        if resultat["siren"] == s:      # la recherche est plein texte : on vérifie
            return resultat
    return None

ent = chercher_siren("552 120 222")
print(ent["nom_complet"])
print("SIRET du siège :", ent["siege"]["siret"])
print("État :", ent["etat_administratif"], "| TVA :", ent["tva"])
SOCIETE GENERALE (SG)
SIRET du siège : 55212022200013
État : A | TVA : ['FR27552120222']

Le champ etat_administratif vaut A pour une unité active et C pour une unité cessée. La recherche étant plein texte, la fonction compare le SIREN de chaque résultat à celui demandé au lieu de faire confiance au premier résultat. Pour un usage intensif ou des données détaillées sur les établissements, l'Insee propose aussi l'API Sirene, qui nécessite une inscription sur son portail.

Quelques précautions avant d'appeler l'API dans une application :

Contrôler une colonne de SIRET avec pandas

Le cas le plus courant reste le nettoyage d'un export comptable ou d'un fichier de fournisseurs. Les mêmes fonctions s'appliquent à une colonne entière avec Series.map() :

import pandas as pd

fournisseurs = pd.DataFrame({
    "nom": ["Fournisseur A", "Fournisseur B", "Fournisseur C"],
    "siret": ["552 120 222 00013", "55212022200014", "356000000 38497"],
})
fournisseurs["siret"] = fournisseurs["siret"].map(nettoyer)
fournisseurs["siret_ok"] = fournisseurs["siret"].map(siret_valide)
print(fournisseurs)
print(fournisseurs.loc[~fournisseurs["siret_ok"], "nom"].tolist())
             nom           siret  siret_ok
0  Fournisseur A  55212022200013      True
1  Fournisseur B  55212022200014     False
2  Fournisseur C  35600000038497      True
['Fournisseur B']

Si ce fichier vient d'un tableur français, lisez la colonne en texte (dtype="string") : sinon pandas convertit les numéros en entiers et peut les afficher en notation scientifique. Notre guide pour lire un CSV « à la française » avec pandas détaille ce point, ainsi que le point-virgule et la virgule décimale. Et si vous avez aussi besoin de localiser l'adresse de chaque établissement, l'article sur le géocodage avec l'API de la Géoplateforme prend le relais.

En résumé

Sources