Facturation électronique
Intégrer l'API d'une Plateforme Agréée : architecture, pièges et retour d'expérience
Dernière mise à jour :
par Paul Mantez, Co-fondateur & Développeur

On a connecté un CRM (Salesforce) à une Plateforme Agréée, Sage Network, pour un groupe d'événementiel parisien, deux entités légales distinctes. Émission et réception, du sandbox à la production. Cet article raconte l'intérieur du chantier : le modèle de données de l'API, ce qu'il faut produire pour qu'une facture parte, comment les statuts reviennent, et la liste des choses qu'on aurait aimé savoir avant de commencer.
L'essentiel de ce qu'on trouve en ligne sur le sujet vient d'éditeurs qui vendent leur propre API. Voici le point de vue de l'intégrateur, celui qui doit faire marcher la chose pour de vrai.
Le décor en deux minutes
La réforme impose de passer par une Plateforme Agréée (PA), l'opérateur privé habilité par la DGFiP à émettre, recevoir et transmettre les factures électroniques. Le PPF (Portail Public de Facturation) tient l'annuaire central qui dit, pour chaque SIREN, quelle PA dessert l'entreprise destinataire ; les factures, elles, circulent de PA à PA. La DGFiP a immatriculé plus de 150 plateformes. Si vous voulez le cadre réglementaire complet (les dates, les profils de stack, les formats du socle), on l'a détaillé dans le guide quand vos factures sortent d'un CRM ou d'un outil métier. Ici, on entre dans le code.
Le cadre de ce retour d'expérience : les factures naissaient dans Salesforce, sur un modèle de données qui n'avait rien de standard, pour deux sociétés du même groupe. Une passerelle externe lit les données dans le CRM, fabrique le format conforme, dépose sur Sage Network, suit les statuts et reçoit les factures fournisseurs par le même canal. La partie « lire dans Salesforce » est un sujet en soi ; ce qui suit concerne le dialogue avec la PA, transposable quel que soit l'outil source.
Le modèle d'identité : Account → Dataset → Connection → E-Invoice
La première chose à comprendre dans une API de PA, c'est sa façon de représenter qui émet quoi. Chez Sage Network, quatre objets s'emboîtent.
L'Account est l'identité du partenaire intégrateur : vous, ou l'agence qui construit la passerelle. On s'y authentifie en OAuth2 client_credentials, ce qui renvoie un JWT à présenter sur chaque appel. C'est le niveau technique, pas le niveau fiscal.
Le Dataset représente une entité légale : un SIREN, une raison sociale, un type. C'est l'objet pivot. Pour le groupe événementiel, on a deux Datasets, un par société, sous un seul Account. Toute la séparation comptable et fiscale entre les deux entités se joue à ce niveau.
La Connection relie un Dataset au point d'accès de la plateforme : c'est elle qui « branche » l'entité sur le réseau. Et l'E-Invoice est le document qui circule, rattaché à son Dataset.
La leçon dépasse Sage Network. Quel que soit l'éditeur de PA que vous intégrez, cherchez en premier l'objet qui représente l'entité légale dans son modèle de données. Il porte un nom différent d'une API à l'autre, mais c'est toujours lui qui structure le multi-entités : un Dataset par SIREN, et le routage, les mentions, les statuts s'organisent autour de cet axe. Se tromper de granularité ici, c'est se condamner à refactorer plus tard. On a détaillé les conséquences côté architecture dans notre article sur la facturation électronique en multi-entités.
L'envoi : UBL 2.1 + PDF, le couple exigé en pratique
Sur la plateforme qu'on a intégrée, une facture voyage en couple : l'UBL 2.1 (le XML structuré que la machine lit) accompagné du PDF lisible par un humain. Nuance utile : le socle réglementaire admet un UBL seul (et Factur-X embarque son PDF par construction), mais Sage Network exige les deux fichiers. On l'apprend vite quand un envoi qui semble complet est refusé parce qu'il manque la représentation visuelle.
Pour générer l'UBL, on n'a pas écrit le XML à la main depuis la spec. On est partis d'exemples valides (des « golden fixtures », des factures de référence dont on sait qu'elles passent) et on a construit le mapping en s'y comparant. On valide tôt, avant même d'avoir branché quoi que ce soit sur la plateforme : un UBL invalide se détecte localement, sans aller-retour réseau.
Dernier point à cadrer dès le départ : une facture d'avoir (credit note) est un type de document UBL distinct de la facture, avec sa propre structure, pas une facture qu'on passe en montant négatif. Si vous découvrez ça au moment où le client émet son premier avoir, vous reprenez le mapping. On l'a prévu dès le début, et c'était le bon réflexe.
Le genre de détail qui coûte une journée : dans l'UBL, l'unité de mesure d'une ligne n'est pas un libellé libre mais un code normalisé.
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<!-- C62 = « unité ». Pas « UNIT », pas « U », pas vide. -->
<cbc:InvoicedQuantity unitCode="C62">3</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">1249.99</cbc:LineExtensionAmount>
</cac:InvoiceLine>
Et les arrondis suivent la même logique implacable : la TVA se calcule par taux sur les totaux, pas en sommant des montants de ligne déjà arrondis. Un centime d'écart entre les deux méthodes et la facture est rejetée : pas un avertissement, un rejet.
La réception et le cycle de vie des statuts
L'émission n'est que la moitié du travail. Une fois une facture déposée, son statut évolue côté réseau (acceptée, rejetée, prise en charge par la PA destinataire, encaissée) et ces transitions reviennent par webhooks. Pas de polling : la plateforme appelle votre endpoint quand quelque chose bouge.
Les subscriptions aux événements se provisionnent par dataset. Chaque entité légale a ses propres abonnements ; on ne s'abonne pas globalement. Côté passerelle, il faut un récepteur HTTP authentifié (un point d'entrée public mais protégé, qui vérifie que l'appel vient bien de la plateforme), une déduplication parce que le même événement peut arriver deux fois, et une persistance des événements reçus. On stocke chaque événement avant de le traiter ; comme ça, si le traitement échoue, on rejoue depuis le journal plutôt que de perdre l'information.
Le flux de réception des factures fournisseurs double l'architecture. Quand une facture entrante arrive, la passerelle la télécharge (XML et PDF), parse l'UBL pour en extraire les données, et écrit dans le système cible. C'est, en miroir, le même travail que l'émission : on a deux fois le couple format + dialogue API, une fois dans chaque sens. Le sous-estimer fait dérailler un planning : on couvre l'émission, on se croit au bout, et la réception reste entière.
Les pièges qu'on a rencontrés
Aucun de ces points n'est dur à comprendre. Chacun nous a quand même coûté des jours, faute de l'avoir anticipé.
L'idempotency-key sur chaque appel. On envoie un idempotency-key unique par requête mutante. C'est ce qui rend les retries sûrs : si le réseau coupe après l'envoi mais avant la réponse, rejouer l'appel avec la même clé ne crée pas un doublon. Sans ça, un timeout malheureux émet deux fois la même facture.
L'onboarding d'un dataset en sandbox est one-shot. Une connection, une fois créée, ne peut pas être supprimée, même en sandbox. On a appris ça en jouant manuellement, puis en se retrouvant avec un bac à sable encombré d'essais irréversibles. La parade : scripter la création d'un dataset et de sa connection avant de toucher quoi que ce soit à la main. On exécute le script propre, on ne bricole pas.
Le cleanup des subscriptions échoue parfois. Un DELETE renvoie un 403 : on code le ménage en best-effort, on tente, on log, on tolère les orphelines.
Le traçage de bout en bout. Quand une facture se perd entre quatre systèmes, le traceparent W3C est la seule façon de savoir où. On propage cet identifiant de corrélation sur toute la chaîne (CRM, passerelle, PA, comptable) dès le premier appel, sinon c'est une investigation à l'aveugle à chaque incident.
Secrets dans les logs : le jour un, pas plus tard. Tokens OAuth, tokens opaques de webhook : on les masque à la source. Un secret qui fuite dans un fichier de log est compromis.
La mise en production : ce que le sandbox ne vous apprend pas
Mise à jour du 21 septembre 2026. La passerelle décrite plus haut a été déployée en production mi-septembre, pour les deux entités du groupe, sur le point d'accès de production de Sage Network ; au moment où on écrit, aucune facture réelle n'a encore été envoyée, le premier envoi attend le feu vert de la comptabilité. Elle tourne sur AWS (Fargate) dans le compte du client, avec une image versionnée par date et par commit, un endpoint de santé et un tableau de bord CloudWatch. Voici ce que la bascule nous a appris, et qu'aucun sandbox ne montre.
Le SIRET, pour de vrai. En sandbox, on déclarait chaque entité avec une convention de siège fictive (le SIREN suivi de 00010). Sur le point d'accès de production, ça revient à déclarer un établissement qui n'existe pas dans le répertoire INSEE. On a rendu le vrai SIRET obligatoire dès que l'environnement est la production, validé avant tout appel, et demandé au client de nous confirmer les deux numéros avant d'enregistrer quoi que ce soit. Une identité fiscale se déclare une fois ; on ne la corrige pas après coup.
La première lecture des vraies factures. Le filtre hérité du sandbox (« tout ce qui n'est pas en brouillon ») remontait 26 factures sur trente jours, et dedans de tout : des factures annulées, des avoirs pas encore émis, des particuliers sans numéro de TVA, des acheteurs étrangers. Toutes avec un PDF attaché, donc toutes prêtes à partir. Au premier tour de boucle, la passerelle aurait envoyé les deux avoirs pour de bon et marqué les autres en échec, puis réessayé à chaque passage. Le filtre de production ne retient que les factures au statut « émise », avec un numéro de TVA français (le SIREN de l'acheteur en découle) et pas encore transmises : dix-huit factures, et pas une de trop. Le sandbox contenait nos données de test ; la production contient les données du client, avec tous les cas qu'on n'avait pas imaginés.
Un plafond de tentatives. Une facture qui échoue trois fois sort de la boucle et passe dans un compteur « épuisées », visible dans le résumé de chaque passage. Sans ça, une facture mal renseignée serait renvoyée toutes les quelques minutes, indéfiniment, avec un journal illisible. L'envoi manuel d'une facture précise, lui, ignore le plafond : c'est l'outil de l'opérateur, pas celui de l'automate.
L'annuaire, on ne le consulte pas. L'API qu'on utilise n'expose pas l'annuaire des destinataires. Le seul test de joignabilité d'un acheteur, c'est l'envoi lui-même : si son SIREN n'est desservi par aucune plateforme, la facture revient en échec et rien ne circule. Ça change la façon de raconter la chose au service comptable, qui s'attend à pouvoir vérifier avant d'envoyer.
La bascule en quatre temps. On n'allume pas tout d'un coup. D'abord le récepteur de webhooks, seul, sur un nom de domaine du client avec son certificat : il écoute, il ne fait rien. Ensuite l'enregistrement des deux entités sur le point d'accès de production, avec leurs abonnements aux événements (ils se recréent, ceux du sandbox ne suivent pas). Puis un premier envoi réel, à la main, d'une facture choisie avec la comptabilité, depuis un poste de travail et pas depuis la boucle. La boucle automatique ne démarre qu'une fois le statut terminal de cette première facture revenu par le webhook. Au moment où on écrit, les deux premières étapes sont faites, la troisième attend que la comptabilité ait choisi sa facture. Entre chaque étape, on lit, on ne pousse pas.
Deux détails pour finir. Un récepteur de webhooks public reçoit des requêtes de scanners dès sa mise en ligne, avec des en-têtes forgés : le code qui parse l'URL entrante doit le supporter sans tomber. Et le secret qui signe les jetons de webhook ne se change jamais après le premier enregistrement : les jetons remis à la plateforme en dérivent, le changer casserait tout le flux entrant.
Checklist avant de signer le chantier
Avant de s'engager sur une PA et un budget, on pose ces huit questions. Les réponses changent radicalement la charge.
- Le sandbox laisse-t-il rejouer les cas tordus ? Pas juste « existe-t-il » : peut-on y émettre, recevoir, provoquer des rejets ? Et nettoyer derrière soi : on a vu plus haut que certaines créations sont définitives.
- La doc API est-elle publique ? Si vous devez signer pour seulement lire la documentation, c'est déjà un signal sur la suite.
- L'émission ET la réception sont-elles couvertes ? Sur la même plateforme, idéalement. Sinon vous montez deux intégrations.
- Le multi-entités est-il un paramètre ou une config globale ? Plusieurs SIREN sous un même compte technique, proprement isolés, ou un compte par société, avec la duplication que ça implique.
- Webhooks ou polling pour les statuts ? Les webhooks demandent un récepteur authentifié ; le polling, une boucle et une gestion de fréquence. À savoir avant de chiffrer.
- Quels formats sont acceptés en entrée ? UBL, Factur-X, CII : vérifiez que celui que produit votre outil est accepté. Et sinon, qui fait la conversion.
- Existe-t-il un environnement de test de l'annuaire ? Pour vérifier le routage vers un destinataire sans envoyer une vraie facture à une vraie entreprise.
- Quelle réversibilité ? Comment on sort de cette PA si on en change dans deux ans. Une intégration qui enferme est une dette déguisée.
Le chantier d'émission complet, code, sandbox et certification, nous a pris 6 à 10 semaines (le code lui-même a tenu en une semaine), pour deux entités légales et une seule passerelle. La réception s'ajoute ensuite sur la même base. Ce qui se rejoue d'un client à l'autre, c'est surtout le mapping vers le modèle source ; le dialogue avec la PA, lui, est désormais écrit, éprouvé, et en production depuis septembre 2026.