Une erreur KSeF FA(3) ne correspond pas à une seule catégorie. Il peut s’agir d’un échec HTTP, d’une exception synchrone de l’API, d’un état asynchrone de facture, d’un état de session ou d’un état d’authentification. Pour établir le diagnostic, associez toujours trois valeurs : l’opération, l’espace de noms de l’état et le code. Corrigez ensuite les échecs déterministes, rapprochez les doublons et les envois dont l’issue reste incertaine, puis ne relancez que les échecs réellement temporaires.

Ce guide couvre le contrat KSeF API 2.6.1 disponible en production au 28 août 2026. TEST expose déjà la version 2.7.1 : consultez donc l’[OpenAPI de production à jour](https://api.ksef.mf.gov.pl/docs/v2/openapi.json) et les limites appliquées à l’exécution avant de considérer un tableau comme définitif. Il s’agit d’un guide technique, pas d’un conseil fiscal ou juridique.

## Qu’est-ce qu’une erreur de validation KSeF FA(3) ?

L’acceptation par KSeF suit une chaîne de traitement, pas un simple contrôle XSD. Une facture envoyée en ligne peut passer par toutes les étapes suivantes :

1. Votre client sérialise la facture métier au format XML FA(3).
2. Il calcule l’empreinte et chiffre les octets exacts qui seront envoyés.
3. KSeF accepte ou rejette la requête API de façon synchrone.
4. Une requête acceptée entre dans le traitement asynchrone de la facture.
5. KSeF vérifie le fichier, le chiffrement, les autorisations, la présence éventuelle d’un doublon et certaines règles sémantiques.
6. Une facture validée reçoit un numéro KSeF et peut donner lieu à un UPO.

La première distinction utile oppose la réussite du transport à celle de la facture. `POST /sessions/online/{referenceNumber}/invoices` renvoie le code HTTP `202 Accepted` accompagné d’une référence de facture. Cela signifie seulement que le traitement a commencé. Rien ne garantit encore que le XML a passé la validation, que la facture a reçu un numéro KSeF ou qu’un UPO existe.

Enregistrez la référence de facture renvoyée avant toute autre opération. Interrogez ensuite le résultat de la facture tout en conservant le résultat de la session comme signal distinct. L’OpenAPI de production montre même une session à l’état `200` contenant dix factures, dont huit ont réussi et deux ont échoué. La réussite d’une session ne prouve pas celle de chacune des factures qu’elle contient.

L’erreur inverse est également fréquente : un code HTTP `200` provenant d’un point de terminaison d’état signifie que la requête d’état a abouti, pas que la facture a été validée. Le corps de la réponse peut contenir l’état de traitement `440`, `450` ou un autre échec.

## Pourquoi un code KSeF ne signifie-t-il rien sans son contexte ?

Supposons qu’une ligne de journal indique seulement `KSeF error 440`. Vous ne savez toujours pas ce qui s’est passé.

- L’état de facture `440` désigne une facture en doublon.
- L’état de session `440` indique que la session a été annulée, par exemple après un délai d’attente dépassé ou parce qu’elle ne contenait aucune facture.
- L’état de facture `450` désigne un échec de validation sémantique.
- L’état d’authentification `450` indique que le jeton n’est pas valide.

Ces valeurs ne constituent pas un registre général des numéros d’erreur. Elles appartiennent à des modèles de réponse. Un historique de diagnostic utile doit conserver le contexte du numéro.

| Champ à conserver | Pourquoi il est important |
|---|---|
| Environnement et version de l’API | TEST et la production peuvent utiliser des contrats différents |
| Opération ou point de terminaison | Identifie l’espace de noms d’état applicable |
| Code HTTP | Distingue le traitement de la requête ou du transport du résultat métier |
| Code d’exception ou de traitement | Classe l’échec au sein de cet espace de noms |
| Description, `details` et `extensions` | Contient les diagnostics exploitables du serveur et les références d’origine |
| Références de session et de facture | Permet de reprendre l’interrogation et de rapprocher les résultats incertains |
| Empreinte et taille exactes du XML | Relie une réponse aux octets que vous vouliez envoyer |

Attribuez un identifiant de corrélation interne à cet historique et joignez-le à chaque nouvelle tentative et à chaque interrogation. Un tableau de bord pourra alors regrouper les échecs sans effacer les preuves nécessaires à la reproduction d’un cas précis.

Séparez également les exceptions synchrones de requête des états asynchrones de facture. Pour un envoi en ligne, les exceptions HTTP `400` actuelles comprennent notamment un état de session non valide (`21180`), une taille incohérente (`21402`), une empreinte incohérente (`21403`) et une erreur de validation de la requête (`21405`). Si vous demandez `X-Error-Format: problem-details`, les erreurs de requête compatibles peuvent adopter la structure Problem Details. Aucun de ces codes ne doit être mélangé à un graphique d’états de facture.

## Quels états de facture imposent d’attendre, de corriger, de rapprocher ou de réessayer ?

Chaque état de facture doit conduire à l’une de ces quatre actions : attendre, corriger, rapprocher ou réessayer. Utilisez l’état renvoyé par le point d’accès dédié à la facture, et non le code HTTP de cette requête GET.

| État de facture | Signification en production 2.6.1 | Action par défaut |
|---|---|---|
| `100`, `150` | Acceptée pour traitement ultérieur / en cours de traitement | Attendre, puis interroger à nouveau avec temporisation progressive |
| `200` | Traitement réussi | Enregistrer le numéro KSeF ; récupérer et stocker l’UPO |
| `405` | Annulée en raison d’une erreur de session | Examiner d’abord l’échec de la session |
| `410` | Périmètre d’autorisation non valide | Corriger l’autorisation ; ne pas modifier le XML au hasard |
| `415` | Impossible d’envoyer une facture avec pièce jointe | Corriger le droit d’utiliser les pièces jointes ou la forme de la facture |
| `430` | Échec de la vérification du fichier de facture | Vérifier les octets, le XML, le schéma, les limites, l’empreinte et les règles associées au fichier |
| `435` | Échec du déchiffrement | Corriger la gestion de la clé et du chiffrement |
| `440` | Facture en doublon | Rapprocher avec la session et le numéro KSeF d’origine |
| `450` | Échec de la validation sémantique | Corriger les données de facture à partir des détails renvoyés |
| `500` | État interne inconnu | Conserver les diagnostics et rapprocher avant toute reprise limitée |
| `550` | Traitement annulé en interne | Rapprocher, puis réessayer selon une politique limitée |

Ce tableau sert à orienter le traitement, pas à remplacer la réponse. Conservez la description brute, tous les éléments de `details` et toutes les `extensions`. Le ministère ne publie pas de catalogue stable et exhaustif qui associe chaque détail possible des états `430` ou `450` à un XPath. Prévoyez les états nouveaux et inconnus au lieu de transformer les descriptions actuelles en analyseur fragile.

L’état de session reste important, mais pour une autre raison. Une erreur d’archive, de déchiffrement, de délai d’attente ou de lot à l’échelle de la session peut annuler ses factures. Une fois la session traitée, examinez le nombre de réussites et d’échecs, puis les états de chaque facture. Le point de terminaison des factures rejetées constitue le chemin le plus court vers les diagnostics d’un lot aux résultats mixtes.

## Comment prévalider le XML FA(3) avant l’envoi ?

Depuis le 1er février 2026, FA(3) est le seul schéma de facture structurée accepté pour les nouveaux envois, y compris pour les corrections de factures initialement émises sous FA(1) ou FA(2). Ouvrez la session avec `systemCode: "FA (3)"`, `schemaVersion: "1-0E"` et `value: "FA"`, puis validez le document par rapport au [XSD FA(3) de référence](https://github.com/CIRFMF/ksef-api/blob/main/faktury/schemy/FA/schemat_FA%283%29_v1-0E.xsd).

Validez les octets exacts dont vous calculerez l’empreinte et que vous chiffrerez. Le [guide de vérification des factures](https://github.com/CIRFMF/ksef-api/blob/main/faktury/weryfikacja-faktury.md) du ministère impose XML 1.0, UTF-8 sans marque d’ordre des octets, le schéma déclaré à l’ouverture de la session, aucune déclaration d’encodage contradictoire, aucune instruction de traitement et aucune des plages Unicode déconseillées indiquées. Dès que vous incluez une structure facultative, ses champs enfants obligatoires le deviennent eux aussi.

Pour une intégration Rails qui utilise Nokogiri, la prévalidation locale peut commencer ainsi :

```ruby
schema = Nokogiri::XML::Schema(File.read("schemat_FA(3)_v1-0E.xsd"))
bytes = File.binread("invoice.xml")

raise "UTF-8 BOM is not allowed" if bytes.start_with?("\xEF\xBB\xBF".b)

document = Nokogiri::XML(bytes) { |config| config.strict.nonet }
errors = schema.validate(document)

raise errors.map(&:message).join("\n") if errors.any?
```

Cette vérification détecte le XML mal formé et les violations du XSD. Elle ne reproduit **pas** toute la validation du serveur. Ajoutez au minimum des contrôles applicatifs sur les éléments suivants :

- l’identité du vendeur et le numéro de facture que votre système de numérotation doit utiliser ;
- les dates, notamment pour vérifier que `P_1` n’est pas postérieur à l’acceptation par KSeF ;
- les structures conditionnelles de FA(3) et les calculs métier ;
- les limites de taille des fichiers et de nombre de factures par session ;
- le droit d’utiliser une pièce jointe, le cas échéant ;
- les tailles en octets et les empreintes SHA-256 du texte en clair et du contenu chiffré transmis dans les métadonnées ;
- le chiffrement avec la clé publique KSeF actuelle et les algorithmes documentés.

Conservez la validation des règles métier même lorsque KSeF renvoie `200`. Les [questions et réponses sur KSeF](https://ksef.podatki.gov.pl/pytania-i-odpowiedzi-ksef-20/) du ministère indiquent que le système peut accepter une facture contenant des erreurs de calcul ou un NIP de contrepartie erroné dont la somme de contrôle est pourtant valide. L’acceptation par le serveur prouve que KSeF a accepté la facture structurée, pas que vos données comptables étaient exactes.

TEST, DEMO et la production n’apportent pas non plus les mêmes garanties. TEST est anonymisé et n’a aucun effet juridique ; DEMO utilise une authentification réelle, mais reste dépourvu d’effet juridique ; la production, elle, en a. Certains contrôles, notamment certaines vérifications de la somme de contrôle du NIP, ne s’appliquent qu’en production. La réussite dans TEST prouve que l’intégration fonctionne dans TEST, pas qu’elle passera en production.

## Comment diagnostiquer l’état 450 sans tâtonner ?

Traitez `450` comme un constat du serveur sur la sémantique de la facture, puis conservez assez de données d’entrée pour le reproduire exactement. Ne commencez pas par modifier des champs au hasard jusqu’à ce que l’erreur disparaisse.

1. Enregistrez l’objet d’état complet, avec tous les détails renvoyés par KSeF.
2. Retrouvez l’instantané immuable de la source qui a servi à produire la facture.
3. Faites correspondre son empreinte XML enregistrée aux octets envoyés sous la référence de facture.
4. Exécutez à nouveau les validateurs XSD et métier locaux sur cet instantané.
5. Associez le détail renvoyé au champ FA(3) et à la valeur du système source qui l’a produit.
6. Corrigez la source ou le convertisseur, générez un nouveau XML et reprenez la validation des nouveaux octets depuis le début.

Une facture XML rejetée n’a pas été émise. Le ministère demande de la corriger et d’envoyer un XML valide ; il ne s’agit pas de corriger une facture acceptée. Cette distinction compte dans la conception des nouvelles tentatives : la correction produit une nouvelle tentative d’envoi, tandis que l’identité métier et la piste d’audit doivent toujours renvoyer à la tentative échouée.

Si la même charge utile passe dans TEST mais échoue en production, examinez les différences propres à chaque environnement en matière d’autorisation, de droits, d’identité et de validation avant d’assouplir un validateur local. N’envoyez jamais une facture sans valeur en production pour observer le résultat : une réussite aurait un effet juridique.

## Pourquoi l’état de doublon 440 impose-t-il un rapprochement ?

KSeF identifie un doublon à partir de trois champs métier : le NIP du vendeur (`Podmiot1:NIP`), le type de facture (`RodzajFaktury`) et le numéro de facture (`P_2`). La période d’unicité documentée couvre dix années civiles complètes après la fin de l’année d’émission de la facture.

L’état `440` ne prouve donc pas que les octets XML sont identiques. Il signifie que KSeF a déjà accepté une facture portant cette identité métier. L’état peut inclure `originalSessionReferenceNumber` et `originalKsefNumber` : utilisez-les.

La reprise suit ces étapes :

1. Retrouvez le numéro KSeF et la référence de session d’origine dans les extensions de l’état.
2. Comparez la facture d’origine à la transaction source prévue.
3. Récupérez et vérifiez l’UPO d’origine.
4. Marquez la tentative locale comme rapprochée de cette facture acceptée.
5. Remontez l’incident si la facture acceptée ne représente pas la transaction métier prévue.

N’incrémentez pas `P_2` dans le seul but de faire disparaître l’erreur. Si la requête d’origine a réussi mais que sa réponse s’est perdue, modifier le numéro peut créer une deuxième facture juridiquement valable. Distinguez la numérotation métier des tentatives de transport : une facture peut comporter plusieurs tentatives, mais une nouvelle tentative ne doit pas inventer discrètement un nouveau document métier.

Cette distinction révèle aussi une importante situation de concurrence. Si des équipes ou unités émettrices distinctes partagent le même NIP vendeur, elles doivent coordonner la numérotation des factures. L’unicité locale au sein de chaque application ne suffit pas pour la clé de doublon globale de KSeF.

## Quels échecs KSeF peut-on réessayer sans risque ?

La politique de nouvelle tentative dépend d’abord de la couche où survient l’échec.

**Ne relancez pas sans modification un échec déterministe des données d’entrée.** Les échecs XML/XSD, les tailles ou empreintes incohérentes, les problèmes d’autorisation, les droits manquants pour les pièces jointes, les échecs de déchiffrement et l’état sémantique `450` imposent une correction. Renvoyer les mêmes octets dans les mêmes conditions ne ferait que produire du bruit, consommer les quotas et n’apporter aucune information nouvelle.

**Ne relancez pas à l’aveugle un doublon à l’état `440`.** Rapprochez-le de la facture acceptée d’origine.

**Pour le code HTTP `429`, respectez toute la durée de `Retry-After`.** Les limites de débit KSeF superposent des fenêtres d’une seconde, d’une minute et d’une heure. Les appels répétés pendant un blocage peuvent le prolonger. Coordonnez les processus qui partagent le même contexte d’authentification et la même adresse IP, ajoutez une variation aléatoire avant de libérer les tâches en attente et consultez `GET /rate-limits` à l’exécution au lieu de supposer que les valeurs publiées s’appliquent à votre compte.

**En cas de délai d’attente dépassé ou de code HTTP `5xx`, le résultat peut rester incertain.** La réponse peut se perdre après que le serveur a enregistré la requête. L’OpenAPI de production ne documente aucune clé d’idempotence fournie par le client pour l’envoi en ligne ou la fermeture d’un lot. Les recommandations qui suivent relèvent donc de l’ingénierie et non d’une garantie KSeF :

- enregistrez la référence de session, l’empreinte et la taille de la facture ainsi que l’horodatage de la tentative avant l’envoi ;
- après un envoi en ligne au résultat incertain, examinez la même session et rapprochez sa liste de factures avant de rejouer l’opération ;
- après une fermeture de lot au résultat incertain, interrogez cette session et ne la refermez que si elle est toujours ouverte ;
- lorsque le rapprochement ne trouve aucun résultat validé, appliquez une temporisation exponentielle limitée avec variation aléatoire ;
- arrêtez-vous lorsque le budget de tentatives est épuisé et transmettez la tentative à une revue humaine, avec toutes ses preuves.

L’état de facture `550` demande explicitement de réessayer, mais « réessayable » ne signifie toujours pas « réessayer indéfiniment ». Conservez les diagnostics, rapprochez la tentative et appliquez la même politique de reprise limitée. Pour l’état `500`, ne confondez pas un code de traitement métier avec le code HTTP `500` ; conservez l’espace de noms et examinez la situation avant de décider.

L’interrogation doit rester tout aussi mesurée. Continuez pour les états `100` et `150`, espacez les interrogations et arrêtez-vous sur un état final. Une boucle fixe d’une seconde utilisée par un client d’exemple ne constitue pas un SLA officiel de traitement.

## Quelles preuves une intégration de production doit-elle conserver ?

Lorsqu’une facture aboutit, les preuves ne se résument pas à un état vert dans un tableau de bord. Conservez un historique durable qui relie la transaction métier à ce que KSeF a accepté :

- l’instantané immuable de la source ainsi que la version du convertisseur et du schéma ;
- l’empreinte exacte du XML en clair et sa taille en octets ;
- les métadonnées de chiffrement ainsi que l’empreinte et la taille du contenu chiffré ;
- l’environnement et la version de l’API observée ;
- les numéros de référence de la session et de la facture ;
- l’historique horodaté des états, avec les descriptions, détails et extensions ;
- le numéro de facture KSeF ;
- le XML de l’UPO et sa valeur d’intégrité SHA-256/Base64 ;
- les liens entre la facture métier, chaque tentative d’envoi et le résultat accepté.

L’UPO d’une facture n’est disponible qu’après la réussite de cette facture. Il peut être récupéré tant que la session reste ouverte. Les UPO de session agrégés apparaissent après la fermeture et contiennent le sous-ensemble accepté : une session aux résultats mixtes peut donc avoir un UPO et des factures rejetées simultanément. N’utilisez pas « la session a un UPO » comme raccourci pour « toutes les factures ont réussi ».

KSeF peut exposer une URL temporaire de téléchargement de l’UPO dans une réponse d’état. Cette URL expire et ne constitue pas l’artefact durable. Téléchargez le XML signé, vérifiez la valeur `x-ms-meta-hash` renvoyée par le point de terminaison authentifié et conservez l’artefact conformément à votre politique de preuve.

## Comment KSeF Kit gère le cycle d’envoi

[KSeF Kit](https://ksef.startupkit.app/) est un produit distinct destiné aux équipes qui transmettent les factures Stripe au KSeF polonais. Son [flux d’envoi documenté](https://ksef.startupkit.app/docs/how-filing-works) sépare les mêmes étapes que celles recommandées dans ce guide : il crée un instantané des données source, les convertit au format FA(3), les envoie au moyen d’une session en ligne chiffrée, interroge le résultat, consigne chaque tentative séparément, reprend l’attente à partir des références enregistrées, stocke l’UPO et enregistre le numéro KSeF dans Stripe.

Cela ne dispense pas de comprendre un rejet. Le rejet trouve ainsi une place durable dans un processus avec suivi d’état, au lieu de rester enfermé dans une seule requête HTTP en échec. Les équipes qui construisent leur propre intégration peuvent reprendre la même conception : entrées immuables, tentatives explicites, références réutilisables, gestion des états selon l’opération et rapprochement avant tout nouvel envoi.

Si Stripe est la source de vos factures et que vous préférez exploiter ce flux de travail plutôt que le construire, consultez [la manière dont KSeF Kit relie les environnements](https://ksef.startupkit.app/docs/connecting-ksef) ainsi que sa [référence de dépannage](https://ksef.startupkit.app/docs/troubleshooting). Dans les deux cas, la règle de production reste la même : un code sans son opération ne constitue pas un diagnostic, et une nouvelle tentative sans rapprochement ne constitue pas un plan de reprise.