Submit a Flux-10 e-reporting file
This guide explains how to deposit a Flux 10 (e-reporting) file with the PPF when your own system already generates it, without the underlying invoices going through B2Brouter’s invoicing. B2Brouter validates the file against the official v3.2 XSD, normalizes it for deposit, and transmits it to the DGFiP as an Accredited Platform (Plateforme Agréée), returning the CDV lifecycle states to you via webhook and API. This is an alternative path to the native pipeline, where you send invoice data as JSON and B2Brouter builds the F10, and both share the same output channel to the PPF.
Who this guide is for
Section titled “Who this guide is for”For integrators and software vendors who already produce their own F10 <Report> and only
need an accredited channel to deposit it. If what you want is for B2Brouter to generate the F10
from your invoices, don’t use this guide: see the e-Reporting (Flux 10) section of the
DGFiP guide.
Prerequisites
Section titled “Prerequisites”- A French account on B2Brouter with the
dgfipTax Report Setting enabled. See steps 1 and 2 of the DGFiP guide. X-B2B-API-Version: 2026-03-02or later.- API permissions for the ledger endpoints enabled on your plan. If you get a
403, open a support ticket to have them enabled. - An F10 file that validates against the official v3.2 XSD package.
The examples use
https://api-staging.b2brouter.net. For production, replace it withhttps://api.b2brouter.net.
Deposit the file
Section titled “Deposit the file”POST /accounts/{ACCOUNT_ID}/ledgers/importThe request body is the raw XML, with Content-Type: application/octet-stream (any other
content type is rejected, so Rails doesn’t try to parse the body as parameters).
curl --request POST \ --url https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/ledgers/import \ --header 'X-B2B-API-Key: {YOUR_API_KEY}' \ --header 'X-B2B-API-Version: 2026-03-02' \ --header 'Content-Type: application/octet-stream' \ --header 'Idempotency-Key: f10-2026-01-transactions-001' \ --data-binary @flux10-transactions.xmlResponse — 201 Created:
{ "ledger": { "id": 68916, "type": "dgfip", "state": "new" }}The ledger id is the identifier you’ll use to track the whole lifecycle. Save it.
Minimum file structure
Section titled “Minimum file structure”The file is a <Report> with no namespace. B2Brouter reads three things from it to decide
whether it can be deposited: the <Issuer>, the declared sub-flow, and XSD validity.
<?xml version="1.0" encoding="UTF-8"?><Report xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ReportDocument> <Id>your-identifier</Id> <Name>Déclaration Transactions Janvier 2026</Name> <IssueDateTime> <DateTimeString>202602110400</DateTimeString> </IssueDateTime> <TypeCode>IN</TypeCode> <Sender> <!-- Replaced with B2Brouter's PA identity when deposited --> <Id schemeId="0238">0426</Id> <Name>PDP426</Name> <RoleCode>WK</RoleCode> <URIUniversalCommunication> <URIID>contact@exemple.fr</URIID> </URIUniversalCommunication> </Sender> <Issuer> <!-- Must match the SIREN of the destination account's company --> <Id schemeId="0002">832987911</Id> <Name>METACORTEX</Name> <RoleCode>SE</RoleCode> <URIUniversalCommunication> <URIID>contact@metacortex.fr</URIID> </URIUniversalCommunication> </Issuer> </ReportDocument> <TransactionsReport> <!-- … or <PaymentsReport>, but not both at once --> </TransactionsReport></Report>One sub-flow per deposit. The PPF transmits transactions and payments separately (spec
v3.2 §3.7.7): a <Report> must carry <TransactionsReport> or <PaymentsReport>. The
declared sub-flow determines the ledger’s mode. A report carrying both, or neither, is
rejected.
Validations, in order
Section titled “Validations, in order”| # | Check | On failure |
|---|---|---|
| 1 | Content-Type: application/octet-stream | 406 — Content-Type must be application/octet-stream |
| 2 | Body size ≤ 100 MB (PPF’s IRR_TAILLE limit, v3.2 §3.4.2) | 422 — The payload exceeds the PPF file limit of 100 Mo |
| 3 | XSD validation against the official v3.2 package | 422 with schema error details |
| 4 | API version ≥ 2026-03-02 | 422 — DGFiP F10 import requires API version 2026-03-02 or later |
| 5 | Account has the dgfip setting enabled | 422 — The account has no enabled DGFiP e-reporting configuration |
| 6 | <Issuer><Id> SIREN matches the account’s company | 422 — The <Issuer> SIREN (…) does not match the account's company |
| 7 | Exactly one sub-flow (transactions or payments) | 422 — The report carries both transactions and payments; split it into two deposits or The report carries neither transactions nor payments |
The root element decides the authority: a <Report> with no namespace is interpreted as a
DGFiP F10; any other root keeps being treated as a Verifactu ledger, so Verifactu import
continues to work across all API versions.
Idempotency
Section titled “Idempotency”Send an Idempotency-Key header with every deposit. If you don’t send one, we use the SHA256
of the payload, so resubmitting an identical file is also idempotent.
| Scenario | Response |
|---|---|
| New key | 201 with the created ledger |
| Same key, same payload | 200 with the already-existing ledger — no duplicate is created |
| Same key, different payload | 409 — Idempotency-Key already used with a different payload |
The key is unique per account. Keys longer than 64 characters or containing non-ASCII characters are stored as a SHA256 digest; the behavior is the same.
Deposit normalization
Section titled “Deposit normalization”Before transmitting, B2Brouter rewrites two nodes of your file:
<ReportDocument><Id>becomes the ledger id. It’s the key the PPF uses to correlate returning CDVs; if a client-chosen value were kept, a deposit could claim the responses of a ledger belonging to another account.<ReportDocument><Sender>becomes B2Brouter’s Accredited Platform identity (matricule withschemeId="0238",RoleCodeWK), since we’re the one depositing with the PPF. The AIFE has confirmed in writing that this deposit normalization is acceptable.
The rest of the file is left untouched, and the original submission is kept exactly as we received it as an audit attachment on the ledger.
Submission to the PPF
Section titled “Submission to the PPF”The imported ledger stays in the new state, and the sending cron picks it up on its first
run after midnight of the day of deposit: in practice, it’s deposited with the PPF the next
day. The transport is an SFTP deposit to the PPF (tar.gz with the F10 envelope).
The VAT regime doesn’t come into play for imported files: periodization by regime is a function of the native pipeline. Here the cadence is that of the deposit, and the declared period is whatever you write into the file.
Tracking the lifecycle
Section titled “Tracking the lifecycle”Ledger states
Section titled “Ledger states”| State | PPF CDV | Meaning |
|---|---|---|
new | — | Imported and validated, pending deposit |
sent | — | Deposited with the PPF |
acknowledged | 500 — Reçue | The PPF has received the file |
deposited | — | Deposit accepted by the PPF |
registered | 300 | ✅ Terminal — declaration registered by the DGFiP |
refused | 301 | ❌ Terminal — rejected; error_code and error_description carry the reason |
error | — | Transmission error; error_description carries the detail |
Webhooks (recommended)
Section titled “Webhooks (recommended)”Subscribe to the ledger.state_change event to receive every transition without polling.
It fires when entering sent, error, acknowledged, deposited, registered and
refused.
{ "web_hook": { "url": "https://example.com/hooks", "events": ["ledger.state_change"] }}The body you receive carries the account id and the full ledger:
{ "account_id": 42, "object": { "object": "ledger", "id": 68916, "state": "registered", "mode": null, "error_code": null, "error_description": null, "document_type_code": "xml.ledger.dgfip.transactions", "sent_at": "2026-07-25T02:04:11.000Z", "created_at": "2026-07-24T10:12:03.000Z" }}If two CDVs arrive close together, the already-superseded state isn’t delivered: you always receive the current state, not necessarily a delivery per transition. Check the ledger if you need to confirm the current state. For webhook signature verification, see the webhooks guide.
Checking the state
Section titled “Checking the state”curl --request GET \ --url https://api-staging.b2brouter.net/ledgers/{LEDGER_ID} \ --header 'X-B2B-API-Key: {YOUR_API_KEY}' \ --header 'X-B2B-API-Version: 2026-03-02'{ "ledger": { "id": 68916, "type": "dgfip", "state": "refused", "error_code": "303", "error_description": null, "created_at": "2026-07-24T10:12:03.000Z", "sent_at": "2026-07-25T02:04:11.000Z" }}Downloading the deposited file
Section titled “Downloading the deposited file”GET /ledgers/{LEDGER_ID}/downloadReturns the normalized XML we deposited (with <Id> and <Sender> rewritten), available
from the moment of import. It’s the document to keep as proof of what was declared.
Downloading the PPF response
Section titled “Downloading the PPF response”GET /ledgers/{LEDGER_ID}/download_responseReturns the CDV received from the PPF. While none has arrived, it returns 404.
In Sandbox mode,
downloadanddownload_responsereturn 503: there’s no real deposit or PPF responses.