Aller au contenu principal

L'e-reporting

La réforme française ne s'arrête pas aux factures électroniques entre entreprises établies en France : les opérations hors de ce champ - ventes B2C, opérations transfrontalières B2B et B2G - ainsi que les données de paiement des prestations de services B2C et B2B internationales en TVA sur les encaissements doivent être déclarées périodiquement à l'administration fiscale (DGFiP). C'est l'e-reporting. Pour chaque société de vos workspaces soumise à l'obligation, Scribee ouvre les périodes de déclaration à l'avance et y agrège les données issues de votre facturation. Votre intégration consulte ces déclarations et complète les données pour les opérations gérées hors de Scribee.

Ce que couvre l'e-reporting​

Chaque société soumise à l'obligation tient deux types de déclarations, identifiés par le champ kind :

  • Transactions (transactions) : les opérations hors du champ de la facturation électronique. Une déclaration de transactions porte à la fois des agrégats de ventes B2C et des factures unitaires, par exemple les factures transfrontalières.
  • Paiements (payments) : les encaissements reçus sur les prestations de services B2C et B2B internationales dont la TVA est due à l'encaissement, déclarés en euros. L'encaissement d'une vente B2B domestique n'y figure pas : il est déclaré par le statut 212 Encaissée du cycle de vie de la facture.

Les données sont déclarées par société et par rôle déclarant. Le rôle est porté par le champ role, dont les valeurs sont les codes réglementaires SE (déclarant vendeur) et BY (déclarant acheteur). Les déclarations de transactions existent donc en deux séries, une par rôle : pour une même période, deux déclarations distinctes qui partagent les mêmes bornes et la même échéance et ne diffèrent que par id et role. Les déclarations de paiements n'existent qu'en série vendeur : leur role vaut toujours SE.

Ce que Scribee fait pour vous​

  • Les périodes sont créées à l'avance. Vous ne créez jamais de déclaration : pour chaque société soumise à l'obligation, Scribee ouvre les périodes du premier jour du mois en cours jusqu'à la fin du mois situé trois mois plus tard, chacune avec ses bornes (start_date, end_date) et son échéance déclarative (due_date). La période en cours est incluse même lorsqu'elle a commencé avant le mois en cours. Les périodes apparaissent dans la liste avant même qu'une donnée ne s'y rattache.
  • L'ouverture passe par un balayage quotidien. Les périodes d'une société nouvellement créée n'apparaissent pas immédiatement : elles arrivent au balayage suivant, qui a lieu une fois par jour - comptez en général 24 heures, mais ce n'est pas un engagement. Le balayage traite les sociétés une par une ; celle dont l'ouverture échoue ou dont le verrou est déjà pris est simplement passée, sans nouvelle tentative dans le même balayage, et attend le suivant. Une période apparaît en revanche tout de suite lorsque la facturation Scribee en a besoin pour rattacher un document.
  • La cadence découle du régime de TVA. La longueur de chaque période et son échéance sont calculées à partir du régime de TVA renseigné sur la société dans Scribee (voir le tableau plus bas). Ce régime se renseigne dans Scribee, pas par l'API, et l'API ne le retourne pas : déduisez la cadence des bornes start_date et end_date de chaque déclaration.
  • Un changement de régime prend effet au 1er janvier suivant. Un changement de régime enregistré en cours d'année ne modifie rien tout de suite : il prend effet au 1er janvier suivant (ou le jour même si la modification est faite un 1er janvier). Seules les périodes vides commençant à cette date ou après sont recalculées ; celles qui commencent avant gardent la cadence sous laquelle elles ont été ouvertes. Comme l'horizon d'ouverture ne dépasse pas trois mois, la liste ne bouge pas tant que ce 1er janvier n'est pas entré dans l'horizon.
  • La facturation alimente la déclaration de paiements. Chaque paiement enregistré sur une facture de vente dont la TVA est due à l'encaissement est répercuté dans la déclaration de paiements de sa période, sans appel supplémentaire, lorsque l'e-reporting couvre la vente : vente B2B internationale (acheteur identifié sans SIREN, ou porteur d'un SIREN mais établi hors du territoire de TVA français) ou vente B2C (acheteur sans identifiant) - et à condition que le régime de TVA de l'entreprise émettrice porte l'obligation de déclaration des paiements à la date du paiement. Une vente dont l'acheteur porte un SIREN et est établi dans le territoire de TVA français n'y figure pas, ni une vente de biens, ni une vente entièrement en autoliquidation, ni une vente dont toutes les ventilations de TVA sont en catégorie G ou O ; d'une vente qui mêle biens et services, seule la part services est déclarée, une ventilation G ou O ne l'est jamais, et les montants le sont en euros. Hors de ces conditions, rien n'est créé et l'API ne le signale pas (Enregistrer les paiements, Déclarer des paiements).
  • Vous ne créez, ne modifiez ni ne supprimez jamais une déclaration. Vous agissez sur les données qui s'y rattachent, et vous pouvez déclencher sa transmission (voir Transmettre une déclaration). C'est le seul appel qui agit sur la déclaration elle-même.
  • Scribee transmet de lui-même les périodes échues. Une fois votre due_date passée - c'est votre date limite de dépôt des données, pas celle de Scribee vers l'administration - une période close qui porte des données et qui n'a encore rien déposé est transmise automatiquement, dans les huit heures qui suivent la fin de ce délai. Vous gardez donc l'intégralité de votre due_date pour compléter la période, et vous n'avez rien à programmer : l'appel de transmission sert à déposer plus tôt, ou à redéposer après un rejet.

Le champ state​

Chaque déclaration porte un champ state, en lecture seule : aucun appel de l'API ne le modifie. Sa valeur est toujours draft - la période est ouverte et les données s'y accumulent, depuis vos appels d'API comme depuis la facturation Scribee. Ne construisez pas de branche conditionnelle sur ce champ, et ne triez pas dessus : sort_by=state est accepté mais ne classe rien.

Chaque déclaration porte aussi un tableau available_transitions. Ce champ est déprécié - il est marqué deprecated dans la référence OpenAPI - et sa valeur est toujours [] : aucune transition n'est déclenchable sur une déclaration, ni par vous ni par Scribee. Il n'est conservé que pour ne pas casser les clients déjà générés sur /api/v1. Ne construisez rien dessus.

Le verdict du PPF sur le dépôt​

Le champ state ci-dessus est un marqueur local. Ce que l'administration a décidé du dépôt de votre déclaration est publié par trois champs distincts, tous en lecture seule : deposit_outcome, deposit_outcome_at et deposit_receptions. Ils sont retournés par la liste des déclarations et par la réponse de sa transmission. Aucun des trois n'est filtrable ni triable.

deposit_outcome : le code du verdict​

Chaîne de caractères ou null. Lorsque le Portail Public de Facturation (PPF) s'est prononcé, ce champ porte son verdict sous la forme du code de la réforme elle-même (Dossier de spécifications externes FE v3.2, section 3.7.9, Tableau 5) :

ValeurSignification
"300"Déposée : le PPF a accepté le dépôt de la déclaration
"301"Rejetée : le PPF a refusé le dépôt

null signifie que le PPF ne s'est pas encore prononcé sur cette déclaration. Ce n'est ni une erreur, ni une valeur par défaut, ni un troisième statut : lisez-le comme "pas encore de réponse" et relisez la déclaration plus tard.

deposit_outcome_at : l'instant du verdict​

Chaîne de caractères ou null. C'est l'instant où le PPF a horodaté ce verdict (MDT-78), republié tel qu'il l'a envoyé, au format YYYY-MM-DDTHH:MM:SS.

Il ne porte volontairement aucun indicateur de fuseau horaire. La réforme ne déclare pas de référence de temps pour ce champ : lui attacher un décalage reviendrait à publier une précision que le message n'a jamais transportée. Ne le parsez donc pas comme un instant UTC. C'est le seul horodatage de la ressource dans ce cas ; created_at, updated_at et le received_at des réceptions sont, eux, des instants complets avec fuseau.

Ce champ vaut null exactement quand deposit_outcome vaut null.

deposit_receptions : les motifs de rejet, groupés par réception​

Toujours un tableau, jamais null. Un tableau vide signifie qu'aucun motif de rejet n'est au dossier, ce qui est le cas normal d'une déclaration acceptée comme d'une déclaration sans réponse.

Chaque entrée correspond à une réception FFE0624A qui a transporté des motifs de rejet pour cette déclaration, la plus ancienne en premier. Elle porte deux champs :

  • received_at : le moment où Scribee a reçu cette réception.
  • motives : les motifs qu'elle transportait, dans l'ordre du document. Un rejet en porte au moins un.

Chaque motif porte à son tour :

  • code : le contrôle qui a échoué (MDT-113, section 3.7.10, Tableau 6). Quatre valeurs possibles :

    ValeurContrôle en échec
    REJ_SEMANContrôle sémantique ou de format
    REJ_UNIContrôle d'unicité
    REJ_COHContrôle de cohérence des données
    REJ_PERContrôle de période : la date de transmission est incohérente avec la période déclarée
  • anomaly_source : chaîne de caractères ou null (MDT-114). Le texte libre du PPF disant où se situe l'anomalie, là où code dit quel contrôle a échoué. Seul le code est obligatoire sur un rejet : ce champ vaut null quand le PPF n'a envoyé aucun texte.

  • notes : les détails du rejet transmis par le PPF, dans l'ordre du document. Chaque note contient content_code (par exemple G6.26), contents (tous les textes explicatifs, sans troncature) et subject_code (par exemple /Report/ReportDocument/Issuer/Id). Les deux codes peuvent être null et contents peut être vide. Affichez ces détails avec anomaly_source : le motif générique peut simplement indiquer « Contrôle de cohérence de données », alors que la note précise le SIREN en cause. Un tableau vide signifie qu'aucune note n'a été enregistrée. Les réceptions anciennes peuvent nécessiter un retraitement par Scribee pour récupérer leurs notes.

deposit_outcome est le dernier verdict, pas l'historique​

C'est le point à ne pas manquer, et la raison pour laquelle les motifs sont groupés par réception plutôt que listés à plat sur la déclaration. deposit_outcome porte le dernier verdict connu, et rien d'autre. deposit_receptions porte l'historique des reproches. Les deux ne se contredisent jamais : ils ne répondent pas à la même question.

Déroulé d'une déclaration rejetée, corrigée, puis redéposée :

  1. Le PPF rejette le dépôt. deposit_outcome vaut "301", deposit_outcome_at porte l'instant de ce rejet, et deposit_receptions contient une entrée : la réception qui a transporté les motifs.
  2. La déclaration est corrigée puis redéposée, et le PPF l'accepte. deposit_outcome passe à "300" et deposit_outcome_at porte l'instant de cette acceptation. Mais l'entrée du rejet précédent reste dans deposit_receptions, avec son received_at et ses motifs inchangés.

À l'étape 2, la déclaration ressemble à ceci :

{
"id": 287,
"kind": "transactions",
"role": "SE",
"state": "draft",
"start_date": "2025-11-01",
"end_date": "2025-11-30",
"due_date": "2025-12-10",
"available_transitions": [],
"deposit_outcome": "300",
"deposit_outcome_at": "2025-12-18T09:30:00",
"deposit_receptions": [
{
"received_at": "2025-11-03T06:15:00Z",
"motives": [
{
"code": "REJ_PER",
"anomaly_source": "période déclarée hors bornes"
},
{
"code": "REJ_UNI",
"anomaly_source": null
}
]
}
],
"company_id": 7
}

Une intégration qui aplatit deposit_receptions en une simple liste de motifs et les affiche comme "les raisons du rejet de cette déclaration" se trompera donc exactement au moment où le client a déjà corrigé le problème : elle annoncera un rejet sur une déclaration déposée.

La règle est simple. Pour savoir où en est la déclaration, lisez deposit_outcome. Pour savoir ce qui a été reproché et quand, lisez deposit_receptions en gardant chaque groupe de motifs attaché à son received_at. Un deposit_receptions non vide à côté d'un deposit_outcome à "300" n'est pas une incohérence : c'est la trace d'un rejet depuis corrigé.

Le rythme des déclarations par régime de TVA​

La longueur de chaque période et son échéance déclarative découlent du régime de TVA de la société déclarante ; chaque déclaration porte la sienne dans due_date.

Régime de TVA de la sociétéDéclarations de transactionsDéclarations de paiements
Réel normal - mensuelPar décade : du 1 au 10 (échéance le 20 du mois), du 11 au 20 (échéance le dernier jour du mois), du 21 à la fin du mois (échéance le 10 du mois suivant)Mensuelle, échéance le 10 du mois suivant
Réel normal - trimestrielMensuelle, échéance le 10 du mois suivantMensuelle, échéance le 10 du mois suivant
Régime réel simplifiéMensuelle, échéance le dernier jour du mois suivantMensuelle, échéance le dernier jour du mois suivant
Franchise en base de TVAPar bimestre civil (janvier-février, mars-avril, ...), échéance le dernier jour du mois suivant le bimestrePar bimestre civil, même échéance
Non assujetti, Non assujetti et non redevableAucune obligation : aucune déclaration n'est crééeAucune obligation : aucune déclaration n'est créée

Consulter vos déclarations​

Cet appel est une lecture : il ne crée rien et peut être rejoué librement. Le scope read suffit.

GET /api/v1/workspaces/{workspace_id}/e_reportings liste les déclarations d'e-reporting des sociétés du workspace (votre workspace_id).

curl "https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/e_reportings?sort_by=due_date&sort_order=asc" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Extrait de réponse : seules quelques déclarations et quelques champs sont montrés (la réponse complète comporte aussi created_at, updated_at, les trois champs du verdict du PPF décrits plus haut et les deux champs de rectification décrits plus bas).

{
"data": [
{
"id": 311,
"kind": "transactions",
"role": "SE",
"state": "draft",
"start_date": "2026-06-21",
"end_date": "2026-06-30",
"due_date": "2026-07-10",
"available_transitions": [],
"company_id": 7
},
{
"id": 312,
"kind": "transactions",
"role": "BY",
"state": "draft",
"start_date": "2026-06-21",
"end_date": "2026-06-30",
"due_date": "2026-07-10",
"available_transitions": [],
"company_id": 7
},
{
"id": 322,
"kind": "payments",
"role": "SE",
"state": "draft",
"start_date": "2026-07-01",
"end_date": "2026-07-31",
"due_date": "2026-08-10",
"available_transitions": [],
"company_id": 7
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total_pages": 2,
"total_count": 28
}
}

Les deux premières lignes sont la même période de transactions dans ses deux séries de rôle. Le volume monte vite : une société au régime réel normal mensuel ouvre 28 déclarations sur l'horizon, soit 4 mois x 3 décades x 2 rôles pour les transactions, plus 4 périodes de paiements.

L'endpoint n'accepte aucun filtre : ni company_id, ni kind, ni role, ni intervalle de dates. Récupérez la liste et filtrez côté client.

La liste est paginée (page, per_page - 20 par défaut, 100 au maximum) et triable (sort_by : start_date, end_date, due_date, kind, state, created_at ou updated_at ; sort_order : asc ou desc ; tri par défaut : start_date décroissant). À valeurs de tri égales - les deux déclarations d'une même période de transactions partagent start_date, end_date et due_date - les lignes sont départagées par id décroissant. L'ordre est donc total et reproductible : à jeu de données inchangé entre les requêtes de page, parcourir les pages ne saute ni ne répète de déclaration. La pagination reste par décalage : une déclaration ouverte ou modifiée en cours de parcours décale les lignes suivantes, donc sur un jeu de données vivant, prenez per_page=100 pour réduire le nombre de pages et dédoublonnez sur id à la réception.

Le paramètre include (valeurs séparées par des virgules : company, invoices, transactions, payments) ajoute les données rattachées sous forme abrégée, pas les enregistrements complets : company retourne id, name et legal_identifier ; invoices, transactions et payments retournent un résumé de chaque enregistrement rattaché. C'est utile pour vérifier le contenu d'une période avant son échéance, pas pour en récupérer le détail.

Ce qui se passe ensuite​

  • Rien n'est attendu de votre côté pour les déclarations elles-mêmes : les périodes suivantes s'ouvrent au fil du temps et les données s'y accumulent.
  • Ce qui reste à votre charge, c'est la donnée : les opérations gérées hors de Scribee - ventes B2C encaissées dans un autre système, paiements suivis ailleurs - se rattachent à la déclaration de leur période via l'API (Déclarer des transactions, Déclarer des paiements).
  • Les paiements enregistrés sur vos factures de prestations de services B2C et B2B internationales en TVA sur les encaissements sont déjà couverts, quand l'entreprise émettrice porte l'obligation de déclaration des paiements à la date du paiement : ils rejoignent alors la déclaration de paiements de leur période sans appel supplémentaire. Ceux de vos ventes B2B domestiques n'ont pas à y être rattachés : le statut 212 Encaissée de la facture les déclare.

Transmettre une déclaration​

POST /api/v1/workspaces/{workspace_id}/e_reportings/{id}/submit

Cet appel construit la déclaration à partir des données de sa période, la valide contre les règles de conformité de la réforme, puis la dépose. Il exige les portées read et write : votre jeton doit porter les deux.

La transmission est asynchrone. Un 201 signifie que le dépôt est enregistré et pris en charge, pas que l'administration a répondu : son verdict arrive plus tard et se lit sur deposit_outcome (voir plus haut). Le corps de la réponse porte la déclaration à jour et un objet meta :

ChampSignification
transmittedtrue quand cet appel a déposé la déclaration ; false quand elle était déjà entre les mains de l'administration et que rien n'a été déposé à nouveau
transmission_idLe nom du flux déposé. L'administration le renvoie sur son accusé de transport : c'est la référence par laquelle un dépôt se rapproche

Rejouer l'appel est sans danger. Une déclaration déjà déposée et sans réponse répond 200 avec transmitted: false et ne dépose rien : aucun second flux n'est créé, quel que soit le nombre d'appels. Seul le premier appel répond 201.

Un 409 signifie qu'un autre traitement dépose cette déclaration au même instant - votre appel automatique, le nôtre, ou l'écran. Rien n'a été transmis par votre appel et le corps porte code: "submission_contested" ; réessayez dans un instant. Ce n'est pas un verdict sur vos données. Le même 409 répond à l'appel qui transmet une rectificative (voir plus bas) lorsqu'un paiement ou une facture de la période est modifié au même instant : rien n'est transmis, la période doit toujours sa rectificative, et le même appel se rejoue dans un instant.

Une déclaration acceptée est close. Lorsque deposit_outcome vaut "300", les données de la période ne se modifient plus. Un paiement de cette période enregistré, corrigé ou supprimé sur une facture après l'acceptation, ou pendant l'attente du verdict qui l'a prononcée, n'est pas perdu : le changement est mis de côté et la période doit une transmission rectificative (TT-4 = RE) (Déclarer des paiements). Rappeler cet endpoint sur une déclaration qui doit une rectification la transmet, lorsque la transmission rectificative est activée pour votre workspace (voir ci-dessous) : la déclaration est retransmise en entier, changements mis de côté compris, et remplace tout ce que l'administration détenait pour la période au titre de cette déclaration. Un paiement en devise dont la date n'a toujours pas de taux publié reste en attente et n'y figure pas (Déclarer des paiements). La réponse est 201 avec transmitted: true ; rejouer l'appel pendant que la rectificative attend sa réponse répond 200 avec transmitted: false, comme pour tout dépôt. Dans tous les autres cas - la période ne doit aucune rectification, ou la transmission rectificative n'est pas activée pour votre workspace - l'appel est refusé en 422 et rien n'est transmis.

La transmission rectificative s'active par workspace. Elle n'est pas activée par défaut : Scribee l'active, sur demande, pour chaque workspace concerné. Scribee ne transmet jamais une rectificative de lui-même : elle part sur un appel à cet endpoint ou depuis l'écran.

Une rectification due se lit sur la déclaration. Chaque déclaration, dans la liste des déclarations comme dans la réponse de cet endpoint, porte deux champs en lecture seule, toujours présents :

ChampSignification
rectification_owedtrue quand la déclaration doit une transmission rectificative (TT-4 = RE) et que la transmission rectificative est activée pour votre workspace. false quand aucune rectification n'est due, ou quand la transmission rectificative n'est pas activée pour votre workspace : false ne signifie jamais que l'administration détient des données à jour
rectification_owed_atL'instant, avec fuseau, où Scribee a enregistré que la rectification est devenue due. Ce n'est pas une échéance. Vaut null exactement quand rectification_owed vaut false

C'est une obligation, pas un statut. Elle naît lorsqu'une donnée arrive dans une période que l'administration a déjà acceptée (deposit_outcome à "300"). Elle reste due pendant que la rectificative attend son verdict, et après le rejet ("301") de cette rectificative ; seul un "300" sur une rectificative l'éteint. Lisez-la donc avec deposit_outcome et transmitted : à côté d'un deposit_outcome à "300", rectification_owed à true ne dit pas si la rectificative est déjà partie ; c'est transmitted qui le dit (voir plus haut). Pour transmettre une rectification due, rappelez cet endpoint, comme décrit plus haut.

Le verdict d'une rectificative se lit comme celui de tout dépôt. Un 300 referme la période ; un changement arrivé pendant son attente lui fait devoir une nouvelle rectification. Un 301 rend les données de la période modifiables : corrigez, puis rappelez cet endpoint - le nouveau dépôt est de nouveau une rectificative. Entre-temps, deposit_outcome vaut "301", mais l'administration détient toujours la déclaration acceptée auparavant.

Une déclaration rejetée se corrige puis se retransmet. Lorsque deposit_outcome vaut "301", les données de la période redeviennent modifiables : corrigez ce que les motifs de rejet désignent (deposit_receptions), puis rappelez cet endpoint. Hors rectification (voir plus haut), le nouveau dépôt est une transmission initiale - rien n'avait été accepté.

Pendant qu'un dépôt attend sa réponse, la période est close aux modifications. Ajouter, modifier ou supprimer une transaction, une facture ou un encaissement est refusé tant que l'administration ne s'est pas prononcée : ce qui a été transmis et ce que Scribee détient ne peuvent pas diverger sans verdict pour les rapprocher.

Un 422 porte le motif dans details.base, et le champ code sépare deux situations. Avec code: "validation_failed", c'est un verdict sur la déclaration : la période ne porte aucune donnée à déclarer, une partie n'a pas d'identifiant dans un référentiel accepté, une règle de conformité est violée, ou l'administration a déjà accepté la déclaration et aucune transmission rectificative ne peut partir (voir plus haut). Corrigez ce que le motif désigne : rejouer l'appel sans rien changer donnera le même résultat.

Avec code: "schematron_engine_unavailable", rien n'a été jugé. Le moteur de conformité n'a pas pu s'exécuter : ce n'est pas un verdict sur vos données et rien n'a été transmis. Réessayez le même appel, sans modifier la déclaration, une fois le moteur revenu.

Erreurs et cas limites​

401 Unauthorized​

Token absent, expiré ou invalide. Corps vide ; redemandez un token sur /oauth/token (Authentification).

403 Forbidden : accès au workspace​

Votre client OAuth n'est pas rattaché à ce workspace :

{
"error": "forbidden",
"message": "L'application n'a pas accès à cet espace de travail"
}

Vérifiez le workspace_id contre GET /api/v1/workspaces ; s'il est correct, demandez le rattachement à votre contact Scribee.

403 Forbidden : adresse IP refusée​

L'allowlist IPv4 du workspace rejette votre adresse (Votre premier appel). Le message distingue ce cas du précédent :

{
"error": "forbidden",
"message": "Cette adresse IP n'est pas autorisée pour cet espace de travail"
}

404 Not Found​

Aucun workspace n'existe avec cet identifiant :

{
"error": "not_found",
"message": "La ressource demandée est introuvable"
}

400 Bad Request : page au-delà de la dernière​

Demander une page au-delà de la dernière page d'une collection non vide retourne :

{
"error": "bad_request",
"message": "Le numéro de page dépasse le nombre de pages disponibles"
}

Bornez vos requêtes avec meta.total_pages.

Liste vide​

Un tableau data vide n'est pas une erreur. Trois causes possibles : le workspace ne contient aucune société ; toutes ses sociétés sont hors du champ de l'obligation (régime Non assujetti ou Non assujetti et non redevable) ; ou le balayage quotidien n'est pas encore passé pour une société créée récemment. Dans ce dernier cas les périodes apparaissent d'elles-mêmes, en général dès le balayage suivant ; si une société est passée par le balayage (échec ou verrou pris), l'attente peut couvrir un balayage de plus.

Pages liées​