Guide · France · Facturation électronique
Valider le SIREN d'un client avant d'émettre une facture électronique
Le numéro SIREN du client fait partie des nouvelles mentions obligatoires. Trois vérifications, dans cet ordre : la clé de Luhn en local, l'existence et l'état au registre, puis le numéro de TVA. Code en JavaScript et en Python.
Ce que la réforme change pour la fiche client
La réforme a commencé : depuis le 1er septembre 2026, toute entreprise assujettie à la TVA en France doit pouvoir recevoir des factures électroniques, et les grandes entreprises et les ETI doivent les émettre ; les PME et micro-entreprises suivront le 1er septembre 2027. Parmi les nouvelles mentions obligatoires figure le numéro SIREN du client.
Un SIREN saisi avec un chiffre inversé, ou celui d'une entreprise qui a cessé son activité, est une erreur facile à créer et coûteuse à corriger une fois la facture émise. Ce guide vérifie le tiers avant l'émission. Il ne remplace ni votre plateforme agréée (PDP/PA) ni l'annuaire officiel : voir la page vérification de contrepartie pour la facturation électronique.
Les trois vérifications, dans cet ordre
| Étape | Appel | Ce qu'elle établit | Ce qu'elle n'établit pas |
|---|---|---|---|
| 1. Clé de Luhn | Aucun, en local | Le SIREN est bien formé : neuf chiffres, clé correcte. | Que l'entreprise existe. |
| 2. Registre | GET /companies/search?siren=…&country=FR | Le SIREN existe, avec sa dénomination, sa forme juridique, son adresse et son état (actif, cessé…). | Que le client qui saisit est bien cette entreprise. |
| 3. TVA | GET /vat/{vatNumber} | Le numéro de TVA est actuellement valide au VIES, avec le nom et l'adresse enregistrés quand l'État membre les communique. | L'état administratif de l'entreprise. |
Étape 1 : la clé de Luhn du SIREN
Le SIREN compte neuf chiffres, et le dernier est une clé de contrôle selon l'algorithme de Luhn : en partant de la droite, on double un chiffre sur deux, on retire 9 quand le résultat dépasse 9, et la somme doit être un multiple de 10. Un chiffre erroné, ou deux chiffres voisins inversés dans la plupart des cas, fait échouer le contrôle sans aucun appel réseau.
Cette étape passe en premier pour une raison précise : la recherche par SIREN compare les neuf chiffres à l'identique, donc un SIREN mal saisi revient comme une liste vide, indiscernable d'une entreprise absente du registre. Sans le contrôle local, une faute de frappe ressemble à un client inconnu.
// SIREN : neuf chiffres, dont le dernier est une clé de Luhn
export function sirenValide(saisie) {
const siren = String(saisie).replace(/[\s.]/g, '')
if (!/^\d{9}$/.test(siren)) return null
let somme = 0
for (let i = 0; i < 9; i++) {
let chiffre = Number(siren[8 - i]) // on part de la droite
if (i % 2 === 1) { // un chiffre sur deux est doublé
chiffre *= 2
if (chiffre > 9) chiffre -= 9
}
somme += chiffre
}
return somme % 10 === 0 ? siren : null
}
sirenValide('552 032 534') // '552032534'
sirenValide('552032543') // null : deux chiffres voisins inversés
sirenValide('552032535') // null : la clé devrait être 4Un résultat correct dit que le numéro est bien formé, jamais que l'entreprise existe ou qu'elle est active. Si le client saisit un SIRET, ses neuf premiers chiffres sont le SIREN : voir SIREN ou SIRET (English version). Le même contrôle tourne dans le navigateur avec le validateur d'identifiants. Pour un client étranger, la référence des identifiants d'entreprise donne le format et la clé de contrôle de l'identifiant de chaque pays.
Étape 2 : l'existence et l'état au registre
curl -H "Authorization: Bearer $PROMETIAM_API_KEY" \ "https://api.prometiam.com/functions/v1/risk-api/companies/search?siren=552032534&country=FR"
data est vide quand le SIREN est inconnu. Sinon data[0] porte company_name, legal_form, status, local_status, address, postal_code, city et last_seen_date. Le local_status brut, dans le vocabulaire propre au registre français, reste sur la fiche pour l'audit ; pour décider, lisez status, qui a le même sens dans tous les pays.
status | Ce que cela veut dire | Décision courante |
|---|---|---|
active | Actif au registre. | Émettre la facture. |
deregistered | Cessé ou radié : fin d'activité ou radiation au registre. | Ne pas émettre sans confirmer auprès du client. |
withheld | Non diffusible : l'INSEE restreint la publication de l'identité de l'entité, le nom revient [ND] et l'état n'est pas communiqué. | Traiter comme inconnu, pas comme inactif : revue manuelle. |
in_dissolution, in_liquidation_insolvent, in_restructuring, insolvent, merged | Dissolution, liquidation, procédure collective ou fusion. | À vous de fixer la règle ; revue manuelle par défaut. |
L'enregistrement porte la date de sa dernière lecture (last_seen_date) : une société immatriculée cette semaine peut mettre quelques jours à apparaître, comme dans le registre source. Pour aller plus loin, le SIREN est aussi la clé des avis du BODACC, où sont publiées les procédures collectives : voir Qu'est-ce que le BODACC ?. Les avis d'insolvabilité sont un endpoint à part (GET /insolvency/search?company_number=…&country=FR), pour les entreprises uniquement, dans seize marchés (FR, DE, GB, AT, CH, NO, FI, US, NL, DK, HR, SE, ES, LV, EE, SG).
Étape 3 : le numéro de TVA (VIES)
Le numéro de TVA intracommunautaire français est FR, une clé de deux caractères, puis le SIREN. Pour une clé numérique, la clé se déduit du SIREN : (12 + 3 × (SIREN modulo 97)) modulo 97. C'est un contrôle de cohérence local entre le SIREN et le numéro de TVA que le client a saisi ; il ne dit pas que le numéro est actif, c'est VIES qui le dit.
// Numéro de TVA français attendu pour un SIREN (clé numérique) : FR + clé + SIREN
export function tvaFrancaise(siren) {
const cle = (12 + 3 * (Number(siren) % 97)) % 97
return 'FR' + String(cle).padStart(2, '0') + siren
}
tvaFrancaise('552032534') // 'FR27552032534'curl -H "Authorization: Bearer $PROMETIAM_API_KEY" \ "https://api.prometiam.com/functions/v1/risk-api/vat/FR27552032534"
La réponse porte is_valid, vat_number, number, name, address et request_date. Trois précisions :
- Une erreur 503 n'est pas un numéro invalide. Elle veut dire que VIES ou le registre de l'État membre n'a pas répondu : le numéro n'a pas été vérifié. Réessayez. Seule une réponse 200 avec
is_validàfalsedit que le numéro n'est pas valide. - Le nom et l'adresse dépendent de l'État membre. Certains ne les communiquent pas, même pour un numéro valide. Comparez le nom au registre avec tolérance plutôt qu'à l'identique.
- Gardez
request_date. VIES n'a pas d'historique : c'est la trace de votre contrôle.
Un client non assujetti à la TVA n'a pas de numéro à vérifier : l'étape 3 est facultative, les étapes 1 et 2 restent. Le détail de l'endpoint est dans le guide de validation de TVA (VIES), en anglais.
Les trois étapes en une fonction
Node.js (18 ou plus), avec la clé dans PROMETIAM_API_KEY. La fonction s'appuie sur sirenValide ci-dessus et renvoie ok: true, ok: false, ou ok: null quand il faut réessayer ou faire relire à quelqu'un :
const API = 'https://api.prometiam.com/functions/v1/risk-api'
const HEAD = { Authorization: 'Bearer ' + process.env.PROMETIAM_API_KEY }
export async function verifierClient(saisie, tva) {
// 1. Forme : clé de Luhn, sans appel réseau
const siren = sirenValide(saisie)
if (!siren) return { ok: false, etape: 'forme', motif: 'SIREN mal formé' }
// 2. Registre : le SIREN existe-t-il, et dans quel état ?
const r = await fetch(API + '/companies/search?country=FR&siren=' + siren, { headers: HEAD })
if (!r.ok) throw new Error('registre : HTTP ' + r.status)
const societe = (await r.json()).data[0]
if (!societe) return { ok: false, etape: 'registre', motif: 'SIREN absent du registre' }
if (societe.status === 'withheld') {
return { ok: null, etape: 'registre', motif: 'non diffusible : état inconnu, revue manuelle' }
}
if (societe.status !== 'active') {
return { ok: false, etape: 'registre', motif: 'entreprise non active', etat: societe.status, statut: societe.local_status }
}
const resultat = {
ok: true, siren, nom: societe.company_name, statut: societe.status,
adresse: [societe.address, societe.postal_code, societe.city].filter(Boolean).join(', '),
lu_le: societe.last_seen_date,
}
// 3. TVA (facultatif) : VIES, en direct
if (tva) {
const numero = tva.replace(/\s/g, '').toUpperCase()
const v = await fetch(API + '/vat/' + encodeURIComponent(numero), { headers: HEAD })
if (v.status === 503) return { ...resultat, ok: null, etape: 'tva', motif: 'VIES indisponible : à réessayer' }
if (!v.ok) throw new Error('TVA : HTTP ' + v.status)
const t = (await v.json()).data
if (!t.is_valid) return { ...resultat, ok: false, etape: 'tva', motif: 'numéro de TVA non valide' }
if (!String(t.number).endsWith(siren)) {
return { ...resultat, ok: false, etape: 'tva', motif: 'ce numéro de TVA ne correspond pas à ce SIREN' }
}
resultat.tva = { numero: t.vat_number, nom: t.name, verifie_le: t.request_date }
}
return resultat
}La même chose en Python, avec requests :
import os
import re
import requests
API = 'https://api.prometiam.com/functions/v1/risk-api'
HEAD = {'Authorization': 'Bearer ' + os.environ['PROMETIAM_API_KEY']}
def siren_valide(saisie):
"""Neuf chiffres, dont le dernier est une clé de Luhn. Renvoie le SIREN nettoyé ou None."""
siren = re.sub(r'[\s.]', '', str(saisie))
if not re.fullmatch(r'\d{9}', siren):
return None
somme = 0
for i, c in enumerate(reversed(siren)): # on part de la droite
chiffre = int(c)
if i % 2 == 1: # un chiffre sur deux est doublé
chiffre *= 2
if chiffre > 9:
chiffre -= 9
somme += chiffre
return siren if somme % 10 == 0 else None
def verifier_client(saisie, tva=None):
# 1. Forme : clé de Luhn, sans appel réseau
siren = siren_valide(saisie)
if not siren:
return {'ok': False, 'etape': 'forme', 'motif': 'SIREN mal formé'}
# 2. Registre : le SIREN existe-t-il, et dans quel état ?
r = requests.get(API + '/companies/search', params={'country': 'FR', 'siren': siren},
headers=HEAD, timeout=30)
r.raise_for_status()
lignes = r.json()['data']
if not lignes:
return {'ok': False, 'etape': 'registre', 'motif': 'SIREN absent du registre'}
societe = lignes[0]
if societe['status'] == 'withheld':
return {'ok': None, 'etape': 'registre', 'motif': 'non diffusible : état inconnu, revue manuelle'}
if societe['status'] != 'active':
return {'ok': False, 'etape': 'registre', 'motif': 'entreprise non active',
'etat': societe['status'], 'statut': societe['local_status']}
resultat = {'ok': True, 'siren': siren, 'nom': societe['company_name'],
'statut': societe['status'], 'lu_le': societe['last_seen_date']}
# 3. TVA (facultatif) : VIES, en direct
if tva:
numero = re.sub(r'\s', '', tva).upper()
v = requests.get(API + '/vat/' + numero, headers=HEAD, timeout=30)
if v.status_code == 503:
return {**resultat, 'ok': None, 'etape': 'tva', 'motif': 'VIES indisponible : à réessayer'}
v.raise_for_status()
t = v.json()['data']
if not t['is_valid']:
return {**resultat, 'ok': False, 'etape': 'tva', 'motif': 'numéro de TVA non valide'}
if not str(t['number']).endswith(siren):
return {**resultat, 'ok': False, 'etape': 'tva', 'motif': 'ce numéro de TVA ne correspond pas à ce SIREN'}
resultat['tva'] = {'numero': t['vat_number'], 'nom': t['name'], 'verifie_le': t['request_date']}
return resultatLes paramètres sont dans les sections recherche d'entreprises et TVA de la documentation. La recherche SIREN gratuite fait le même appel dans le navigateur, sans clé.
Revérifier tout un fichier client
Avant une échéance, POST /companies/lookup résout jusqu'à cent contreparties en un seul appel. Chaque élément compte pour une requête, et un lot qui ne tient pas dans le quota est refusé d'emblée avec un 429 batch_exceeds_quota. Contrôlez la clé de Luhn de chaque SIREN avant de l'envoyer : un SIREN mal saisi reviendrait not_found.
curl -X POST "https://api.prometiam.com/functions/v1/risk-api/companies/lookup" \
-H "Authorization: Bearer $PROMETIAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"items":[{"country":"FR","siren":"552032534"}]}'Chaque résultat, dans l'ordre de la demande, porte un status (found, not_found, error ou timeout) et, pour found, le même objet company que la recherche. Les éléments timeout se renvoient. La TVA ne passe pas par le lot : elle se contrôle numéro par numéro.
Ce que cette vérification ne fait pas
- Ce n'est pas un opérateur de facturation électronique. C'est une source de données que vous appelez avant ou en parallèle de l'outil qui émet ou transmet la facture. Elle ne remplace ni une plateforme agréée (PDP/PA), ni l'annuaire officiel, ni votre logiciel de conformité.
- Elle ne contrôle pas la facture. Ni son contenu, ni les taux de TVA, ni le calcul de la taxe.
- Elle ne dit pas qui vous avez en face. Le registre confirme qu'un SIREN existe et dans quel état ; comparez la dénomination à celle que le client a indiquée.
- Elle est aussi fraîche que le registre. Chaque registre est lu quotidiennement ;
last_seen_datedonne la date de la dernière lecture. Le contrôle de TVA est un instantané VIES, daté parrequest_date.
Tarifs
Le contrôle de Luhn est local et gratuit. La recherche au registre et la validation de TVA sont un appel chacune. Starter commence par un essai de 14 jours : 500 crédits, 10 requêtes par minute, sans débit avant la fin de l’essai ; la même clé continue de fonctionner à l'abonnement. Puis Starter à 9,99 €/mois (1 000 crédits). Professional à 29,99 €/mois (10 000 crédits) et Scale à 99,99 €/mois (50 000 crédits), mensuel ou annuel. Voir les tarifs.
Questions fréquentes
Comment valider un SIREN avant d'émettre une facture électronique ?
En trois temps : contrôler la clé de Luhn du SIREN en local, vérifier l'existence et l'état de l'entreprise au registre avec GET /companies/search?siren=…&country=FR, puis, si le client a un numéro de TVA, le valider avec GET /vat/{vatNumber} (VIES).
La clé de Luhn suffit-elle à valider un SIREN ?
Non. Elle prouve que le SIREN est bien formé et attrape la plupart des fautes de frappe, pas que l'entreprise existe ni qu'elle est active. C'est la recherche au registre qui le dit.
Que faire d'un SIREN non diffusible ?
Le traiter comme inconnu, pas comme inactif. L'INSEE restreint la publication de l'identité de l'entité : le nom revient [ND] et son état n'est pas communiqué (status vaut withheld). Une revue manuelle est le choix le plus prudent.
Une erreur 503 sur la TVA veut-elle dire que le numéro est invalide ?
Non. Une erreur 503 veut dire que VIES ou le registre de l'État membre n'a pas répondu : le numéro n'a pas été vérifié. Réessayez plus tard. Seule une réponse 200 avec is_valid à false dit que le numéro n'est pas valide.
Cette vérification remplace-t-elle ma plateforme de facturation agréée ?
Non. Prometiam est une source de données que vous appelez avant ou en parallèle de l'outil qui émet ou transmet la facture. Ce n'est pas un opérateur certifié (PDP/PA) et cela ne remplace ni l'annuaire officiel, ni votre logiciel de conformité de référence.
Combien coûte la vérification d'un client ?
Le contrôle de Luhn est local et gratuit. La recherche au registre et la validation de TVA sont un appel chacune. Le forfait Starter commence par un essai de 14 jours avec 500 crédits, sans débit avant la fin de l’essai ; les offres payantes vont de 9,99 € à 99,99 € par mois.