Skip to main content
Un document est soit une facture / note de crédit générée par l’entreprise, soit un document reçu de l’extérieur (Peppol, mail, dépôt manuel, Odoo, Billit…). Les deux passent par la même ressource ; origin dit d’où vient un document (fidly pour les documents générés dans Fidly, api pour les fichiers importés via cette API, sinon le canal d’import).
origin: api ne concerne que les fichiers importés via l’API, pas les documents créés via l’API. Un document créé porte toujours origin: fidly, comme s’il avait été émis dans l’interface.

Lister les documents

GET /documents — liste paginée, du plus récent au plus ancien. Filtres : origin, type, accounting_status, payment_status, issue_date_from, issue_date_to. Les filtres d’énumération acceptent des listes séparées par des virgules (origin=manual,peppol). Les devis ne sont pas des documents et n’apparaissent jamais ; les proformas, si.

Récupérer un document

GET /documents/{id} — le document complet, avec ses lines, ses allowance_charges et ses attachments. GET /documents/{id}/file?format=pdf|xml — les octets du fichier, servis en pièce jointe. format=pdf par défaut ; format=xml renvoie la version UBL/Peppol quand elle existe. 404 si la version demandée n’existe pas. Chaque document porte ses lignes : sequence, item_name, item_description, quantity, unit_price, unit_code, vat_code, vat_rate, subtotal, total (tous deux hors TVA), tax_amount, section_name, item_properties, item_identification, product_code et les allowance_charges de ligne. Les allowance_charges du document (remises et majorations appliquées après les lignes) sont à la racine.

Statuts d’envoi

Un document émis porte deux statuts d’envoi, un par canal :
  • peppol_status : not_sent, sending, sent (remis au réseau), accepted (acquitté par le destinataire), rejected (refusé par le destinataire) ou failed. Un document rejected ou failed peut être renvoyé.
  • mail_status : not_sent, sent ou failed.
Les deux valent null sur un document reçu : il n’a jamais été envoyé par l’entreprise.

Créer un document

POST /documents — droit requis : création de documents. Types créables : sale_invoice, sale_credit_note, purchase_invoice, purchase_credit_note, proforma_invoice. La réponse est le document complet (201), tel qu’un GET ultérieur le renverra. Vous fournissez ce qui identifie le document et ce qui est facturé ; tout le reste est calculé. Envoyer un champ calculé est refusé par un 422 qui le nomme : Entrées optionnelles : journal_id, delivery_location_id, issue_date, delivery_date, tax_point_date, period_start/period_end, payment_terms, order_number, despatch_reference, buyer_reference, legal_notice, currency (défaut EUR, validée contre la liste des devises), paid_amount (défaut 0) et les allowance_charges de document.
Deux exceptions, acceptées sans erreur mais sans effet : due_date, toujours recalculée depuis les conditions de paiement, et, sur chaque ligne, sequence, subtotal, total et tax_amount, toujours recalculés. L’ordre des lignes est celui du tableau lines.

Lignes

De 1 à 500 lignes, au plus 50 remises/majorations de document. Par ligne, item_name, quantity, unit_price et vat_code sont obligatoires.
  • unit_price n’est jamais négatif ; quantity peut l’être. Le total du document doit rester positif (422 invalid_amount sinon) — une note de crédit totalise aussi un montant positif, son type porte le sens.
  • unit_code (unité par défaut — C62) est validé contre la liste des unités, currency contre celle des devises — voir Listes de référence.
  • Les montants sont calculés : subtotal = quantity × unit_price, total = subtotal + majorations − remises (hors TVA), et tax_amount = total × vat_rate / 100. La TVA du document est totalisée par couple (code, taux), avec un seul arrondi par taux.
  • item_properties est un objet plat clé/valeur ; item_identification un GTIN.

Codes TVA

vat_code nomme le régime, vat_rate porte le pourcentage :
Les codes combinables (✅) sont des régimes de ligne : ils se mélangent librement sur une même facture domestique. Les trois autres (❌) sont des régimes de facture entière — une vente intracommunautaire, une exportation ou un vendeur non assujetti — qui déterminent les mentions légales du document, sa catégorie de TVA dans le XML Peppol et la façon dont votre comptable la déclare. Une ligne domestique n’a pas sa place sur une telle facture : un code ❌ doit être porté par toutes les lignes, et jamais avec un autre code ❌ (422 sinon). Émettez un document distinct par régime.
Tout vat_rate strictement positif est accepté avec standard, mais les taux légaux belges sont 6, 12 et 21 %.

Remises et majorations

Chaque entrée porte un type obligatoire (allowance = remise, charge = majoration) et un montant exprimé soit en amount fixe, soit en rate (pourcentage). Si les deux sont présents, rate l’emporte et le montant est recalculé. Sur une ligne, le rate s’applique au subtotal de la ligne ; sur le document, au total des lignes. Un reason libre nomme l’entrée : facultatif sur une ligne, obligatoire sur le document. Une remise ou majoration de document est répartie entre les taux de TVA du document, au prorata de ce que les lignes de chaque taux totalisent, et la TVA de chaque taux est calculée sur cette base corrigée. Exemple : 1 000 € de lignes à 21 % et 500 € à 6 %, avec une remise de 150 € → 100 € sont déduits de la base à 21 % et 50 € de celle à 6 %. Deux conditions en découlent ; si l’une des deux n’est pas respectée, 422 invalid_allowance_charge :
  • Le total du document, remise ou majoration comprise, doit rester positif ou nul (422 invalid_amount sinon). En particulier, un montant fixe ne peut pas s’appliquer à des lignes qui totalisent 0.
  • Aucun taux de TVA ne peut totaliser un montant négatif (par exemple une ligne à quantité négative qui dépasse les autres lignes du même taux) : la remise n’aurait plus de base cohérente à répartir. Dans ce cas, portez la remise ou la majoration sur les lignes plutôt que sur le document.

Conditions de paiement et dates

  • payment_terms s’écrit <jours>-<base> avec df (date du document), fm (1er du mois suivant) ou mf (fin du mois où tombe le délai) : 30-df, 0-df… Résolution : valeur du corps → conditions du tiers (sociétés uniquement) → 30-df. due_date en dérive toujours.
  • issue_date n’est jamais dans le futur et suit l’ordre de numérotation du journal :
    • à la création, le jour du dernier document numéroté du journal, ou après ;
    • à la modification, entre le document numéroté juste avant et celui numéroté juste après, leurs dates incluses (sans document suivant, la borne haute est la date du jour).
    Pour un journal dont le compteur redémarre chaque mois ou chaque année, la date doit en plus rester dans la période dont le numéro est tiré. Sinon 422 invalid_issue_date, le message donne la borne attendue.
  • delivery_date est la date où la livraison a effectivement eu lieu : aujourd’hui ou antérieure.

Journal et tiers

  • journal_id est optionnel : à défaut, le document prend le journal par défaut correspondant à son type (facture de vente, note de crédit de vente, facture d’achat…). Un journal d’une autre catégorie que celle du type est refusé (422 invalid_journal).
  • relation_id doit désigner un tiers de l’entreprise, et ce tiers doit avoir une adresse (422 invalid_relation). Sa nature (société ou contact) est lue sur le tiers et renvoyée dans relation_type.
  • purchase_invoice et purchase_credit_note relèvent de l’auto-facturation : ils exigent le paramètre d’auto-facturation de la société (422 self_billing_disabled sinon).

Après la création

Le PDF (et, quand le type le permet, le XML Peppol) est rendu de façon asynchrone : pdf_available / xml_available valent false dans la réponse 201 et passent à true peu après — les fichiers sont généralement disponibles dans les 5 à 7 secondes. Suivez GET /documents/{id}. Rien n’est envoyé au client à la création — voir Envoyer ci-dessous.

Envoyer un document

L’envoi par e-mail n’est pas encore disponible via l’API : seul l’envoi par Peppol l’est.
Droit requis : modification de documents. Deux voies :
  • POST /documents/{id}/send avec le corps {"peppol": true} — envoyer un document existant.
  • POST /documents?send_peppol=true — créer et envoyer en un appel (exige aussi le droit de création).
La réponse est un 202 : l’envoi est asynchrone, l’API accuse la prise en charge, pas la livraison. Le document est re-rendu avant de partir : les pièces jointes ajoutées depuis la création entrent dans le XML.
  • Types transportables : sale_invoice, sale_credit_note, purchase_invoice, purchase_credit_note. Un proforma est refusé (422 not_sendable), un document reçu aussi (409).
  • Un document déjà parti renvoie 409 already_sent ; un envoi en échec (peppol_status à failed ou rejected) se relance avec le même appel.
  • Un corps qui ne demande aucun canal (vide ou tout à false) est refusé (422).
  • Si le workflow d’envoi ne peut pas démarrer, 422 send_failed. Avec ?send_peppol=true le document est créé et numéroté — le message donne son id, et la reprise est POST /documents/{id}/send, jamais une seconde création.
Les exigences du réseau (société enregistrée sur Peppol, client société avec numéro de TVA ou identifiant) sont vérifiées par le workflow d’envoi lui-même ; l’issue se lit ensuite dans le peppol_status du document, pas dans la réponse 202 : sending, puis sent une fois remis au réseau, puis accepted ou rejected selon la réponse du destinataire — ou failed si l’envoi n’a pas abouti.

Mettre à jour un document

PATCH /documents/{id} — droit requis : modification de documents. Seuls les champs présents dans le corps sont appliqués ; la réponse est le document complet. Possible tant que le document n’est parti nulle part :
  • pas envoyé au client (mail_status et peppol_status à not_sent),
  • pas transmis à la comptabilité.
Sinon 409 document_not_editable, en nommant la condition qui bloque. Seuls les documents d’origin: fidly peuvent être modifiés.

Champs en lecture seule

type et journal_id ne changent plus après la création (422 field_not_editable au changement). Pour changer le type ou le journal, il faut supprimer et recréer le document.

Effacer un champ

Seuls les champs optionnels acceptent null : cela les efface (ou détache l’adresse de livraison pour delivery_location_id). Sur un champ obligatoire, null est refusé (422 validation_error) — omettez-le pour ne pas y toucher.

Changer la date ou les conditions de paiement

La modification d’issue_date obéit aux mêmes règles de numérotation qu’à la création ; la changer, ou changer payment_terms, recalcule la due_date.

Recalcul et régénération

Toute modification recalcule le document (TVA, totaux, statut de paiement) et régénère son PDF/XML.

Modifier les lignes, remises et majorations

lines et allowance_charges remplacent entièrement les listes stockées ; les omettre laisse les listes stockées intactes.
Pour des raisons techniques, lines et allowance_charges se renvoient toujours en entier : il n’est pas possible de modifier une seule ligne ou une seule remise. Reprenez le tableau du document lu, éditez-le et renvoyez-le complet — ses champs calculés (sequence, subtotal, total, tax_amount) peuvent y rester : ils sont recalculés, la valeur envoyée est ignorée.
Il n’y a pas de jeton de version : le dernier écrivain gagne. Un tableau lines construit sur une lecture périmée écrase les modifications faites entre-temps.

Enregistrer un paiement

paid_amount enregistre un encaissement que Fidly ne voit pas de lui-même (espèces, compte bancaire non suivi). Un PATCH ne portant que paid_amount est accepté quel que soit l’état du document — déjà envoyé, déjà exporté — et ne régénère rien. Il pilote payment_status et remaining_amount. Déclarer plus que le total est refusé (422 invalid_paid_amount). Ajouter tout autre champ éditable fait de la requête une modification ordinaire, soumise au verrou ci-dessus.

Pièces jointes

POST /documents/{id}/attachments — droit requis : modification de documents. Ajouter des fichiers à un document émis par l’entreprise : multipart/form-data, le champ files répété, 10 fichiers max par appel, 10 Mo pour l’appel entier. La réponse est le document complet avec sa liste attachments.
  • Tout ou rien : un fichier refusé fait échouer l’appel entier et rien n’est attaché.
  • Un nom déjà attaché est refusé (409 duplicate_attachment).
  • Strictement additif : une pièce jointe ne peut jamais être retirée ni remplacée via l’API.
  • Attacher ne régénère pas le PDF/XML stockés. Les pièces jointes entrent dans le XML Peppol au moment de l’envoi, qui re-rend le document. Attacher reste possible sur un document déjà envoyé ou exporté.
  • Un document reçu ne prend pas de pièce jointe (409).

Importer un fichier

POST /documents/import — droit requis : création de documents. L’équivalent API du glisser-déposer de l’interface : multipart/form-data avec un seul champ file.

Fichiers autorisés

Extensions acceptées : .pdf, .xml, .jpg, .jpeg, .png. Toute autre extension est refusée (415).

Taille des fichiers

10 Mo maximum par fichier ; au-delà, 413.

Traitement

?type= (query) pilote le traitement : purchase (défaut), sale ou other. Renvoie 201 avec le seul id du document créé : à la création, le document est volontairement vide (type: unknown, montants null) tant que la chaîne asynchrone ne l’a pas analysé (parsing Peppol pour les XML, analyse IA pour les PDF/images). Suivez l’enrichissement via GET /documents/{id}. Seul type=other est final dès la création (other_document, archivé sans analyse). Erreurs : 415 extension refusée, 413 fichier trop gros, 400 type inconnu, 409 fichier déjà importé.

Supprimer un document

DELETE /documents/{id} — droit requis : suppression de documents. Renvoie 204. La suppression est définitive, et possible seulement quand :
  • le document porte le plus haut numéro de sa séquence (journal + type) dans la fenêtre de numérotation courante — sinon 409 document_not_deletable, en nommant les deux numéros. Supprimer le dernier rend son numéro à la séquence. Un proforma n’a pas de numéro : cette règle ne s’applique pas à lui ;
  • il n’est parti nulle part (mêmes conditions que le PATCH) : pas envoyé, pas transmis à la comptabilité, pas un document reçu ;
  • rien n’y est rattaché (409 document_in_use) : ni rapprochement bancaire, ni ligne de prestation facturée, ni intention de paiement.