France
Send and receive domestic French B2B invoices under the 2026 e-invoicing reform.
france declaration takes both legs:
our provider produces the tax record (flow 1) and marks it cleared, then
delivers the invoice over Peppol in the France CIUS format. An accredited French
platform rejected our documents on 2026-09-20; after a correction by our
provider, the same platform has received them since 2026-09-25. Received is not
accepted, and the sandbox cleared is not a verdict from the tax administration:
on 2026-09-27 it also cleared a tax record that breaks a published DGFiP rule. Treat a
sandbox France send as an integration test, never as evidence your document will be
accepted or cleared.
How France works on getpeppr
France's B2B e-invoicing reform routes every domestic invoice through government-accredited platforms (plateformes agréées, PA) that clear documents with the tax authority (DGFiP). getpeppr is not a plateforme agréée: it is the developer layer — API, SDK, dashboard — built on Storecove, which is an accredited platform. You can verify accredited platforms on the official DGFiP list at impots.gouv.fr. Your integration stays the same JSON-in, webhooks-out contract you use for every other country.
| Date | Obligation | Who |
|---|---|---|
| 1 Sept 2026 | Receive electronic invoices | All VAT-subject companies in France |
| 1 Sept 2026 | Issue electronic invoices | Large companies and mid-caps (ETI) |
| 1 Sept 2027 | Issue electronic invoices | SMEs and micro-enterprises |
Set up a French company
A Legal Entity's country is fixed at creation — it cannot be changed later. If your existing organization is registered in another country, create a new organization for your French entity and complete onboarding with country: FR.
French companies are addressed by their SIREN or SIRET. Three identifier schemes are available when you add a Peppol Identifier:
| Scheme | Value | Use it for |
|---|---|---|
0002 SIRENE | 9-digit SIREN or 14-digit SIRET | Your company identity — the recommended default |
0009 SIRET | 14-digit SIRET | Establishment-level addressing |
0225 FR:CTC | 9-digit SIREN | Your electronic address for the French reform (directory registration) |
In production, French identifiers are verified against official registries (VIES and INSEE SIRENE) before sending is enabled, and getpeppr derives your French VAT identifier from your SIREN automatically once it can be proven correct. You never submit a VAT number by hand.
Register in the French directory
Receiving domestic French B2B invoices requires a directory entry: the 0225 FR:CTC identifier is your company's electronic address for the reform, registered with your bare 9-digit SIREN. Adding it from the console (or the Legal Entities API) registers it on the network and advertises it for France-specific documents. In production, registration in the DGFiP directory (annuaire) itself is not open yet; in the sandbox, a platform customer's suffixed address is also registered on our provider's sandbox directory (Legal Entities). If you will also send under the regulated flow, register your SIREN (0002) first and the directory identifier second — see why the order matters.
0002:… when the recipient is
registered under 0225:… does not resolve, even after propagation.
Send an invoice to France
Sending to a French buyer is a standard send() call. Use 0002 (SIRENE) or 0009 (SIRET) as the routing scheme — these are the recommended receiver identifiers for French B2B. French VAT rates: standard 20%, reduced 10% / 5.5% / 2.1%.
Declaring the regulated French flow
A French buyer does not, on its own, put an invoice into the regulated flow. The declaration is yours to make: add a france block carrying the Cadre de Facturation — the use case the document falls under, defined by AFNOR XP Z12-012.
await peppol.invoices.send({
number: "INV-2026-001",
to: { name: "Tricatel", peppolId: "0225:123456782", country: "FR", /* … */ },
france: { cadreDeFacturation: "S1" },
lines: [/* … */],
});Omit the block and the invoice travels as an ordinary Peppol document, whatever the buyer's country — we never infer the regime for you. The Peppol France Solution Architecture puts that choice on the sender, and guessing it would file your document under the wrong set of rules.
0225, settled registry check, published to the network), and a
SIREN under scheme 0002. The SIREN is not about addressing — French
invoicing rule BR-FR-10 puts the seller SIREN on the document
itself. Order matters: the network operator writes the first
non-tax identifier registered on your legal entity as the seller's legal
registration number (measured on 22 September 2026). Register the SIREN
(0002) before the directory identifier (0225);
registered the other way round, the directory identifier is written instead
and the rule fails. Before submitting a declared invoice we read that order,
and refuse the send if the SIREN is not first — to fix it, remove the
identifiers registered ahead of the SIREN and add them back. Any of these
conditions unmet and the send is refused with 422 france_regime_not_eligible — before anything is submitted, and
the message names which one. Register the identifiers first, then declare. A
cadre outside the published list is a 400 france_cadre_invalid.
The buyer's SIREN travels too: the French tax platform records it as the buyer's legal registration number (scheme 0002, nine digits). You rarely need to send it: when to.peppolId is a French address we read it there — the directory address 0225:<SIREN> with or without a suffix such as 0225:123456782_SALES, a SIREN under 0002, or a SIRET under 0009, whose first nine digits are the SIREN. To give it explicitly, send to.companyId with to.companyIdScheme: "0002" (SIREN) or "0009" (SIRET); what you send is always the one used. When none of them gives a valid SIREN (for example a buyer addressed under a non-French scheme), the invoice is refused with 400 france_buyer_siren_missing before anything is submitted.
A declared invoice also constrains its number. French invoicing rule BR-FR-01 allows at most 35 characters, made only of letters, digits and + - _ / with no spaces, and the French tax platform currently accepts at most 20 characters until 1 December 2026. A number outside either limit, or sent as anything but a string, is refused with 400 france_invoice_number_invalid before anything is submitted, and the message names the rule.
A declared credit note must also name the invoice it credits with its issue date: set invoiceReference and invoiceReferenceDate (YYYY-MM-DD). French rule BR-FR-CO-05 ignores a reference without its date, so a credit note missing either is refused with 400 france_credit_note_reference_invalid before anything is submitted.
import { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_live_..." });
// Route with the scheme the buyer is registered under —
// SIRENE (0002) or SIRET (0009) are the recommended routing schemes.
const invoice = await peppol.invoices.send({
number: "INV-2026-042",
to: {
name: "Fournitures Dupont SARL",
peppolId: "0009:73282932000074", // 0009 = SIRET (14 digits)
street: "12 Rue de la Paix",
city: "Paris",
postalCode: "75002",
country: "FR",
},
lines: [
{ description: "Consulting — June", quantity: 1, unitPrice: 1200, vatRate: 20 },
],
});
console.log(invoice.status); // "submitted"Track French invoice statuses
Live today. getpeppr sends French invoices on the regulated profile and reports transmission statuses — accepted, delivered, failed — through the same canonical status vocabulary as every other country, with the native label preserved in detail. Your legal entity is registered to receive lifecycle statuses from the network — every production entity, and every entity created from now on — so when the recipient's accredited platform rejects an invoice it has received (Rejetée, 213), you see status: "failed" with the code in detail.platformFiscal.code set to "213".
Landing next. The statuses issued by your buyer and the administration — Reçue, Refusée, Encaissée — map to the fields below. They travel on the same network capability, but we have not yet observed each of them end to end; until we have, an invoice that your buyer refused may stay delivered on your side. Integrate now: the shape below is final and will not move.
| French status | Meaning | Where it lands |
|---|---|---|
| Reported to the DGFiP | The invoice data (flow 1) reached the administration — code: "F1", and the clearing proof with include=evidence. Not delivery. | detail.platformFiscal |
| Delivered | The buyer's platform acknowledged the invoice on Peppol | detail.delivery |
| Rejetée | Technical rejection (format, directory, authority checks) | detail.platformFiscal |
| Refusée | Business refusal — the buyer declined the invoice | detail.businessDisposition |
| Prise en charge, Approuvée, Approuvée partiellement, En litige | The buyer's response — in hand (204), approved (205), partially approved (206) or disputed (207); 206 and 207 carry the buyer's reason in detail.businessDisposition.reason | detail.businessDisposition |
| Paiement transmis | The buyer says it has sent the payment (211). Not a collection: the invoice is not marked paid | detail.settlement |
| Encaissée | Payment received — reported for VAT on collection. Not yet observed coming back after a report of yours: read your report's outcome on paymentReport | detail.settlement |
const invoice = await peppol.invoices.getStatus(invoiceId);
// Canonical status — the same vocabulary for every country.
// A platform rejection (Rejetée, 213) is "failed": fix and resend.
// A buyer refusal (Refusée, 210) is "rejected": a business decision.
console.log(invoice.status); // e.g. "failed"
// The raw pre-coercion status string is always preserved
console.log(invoice.rawStatus);
// Structured national detail, when the gateway exposes it.
// Native labels are preserved, never over-translated.
const fiscal = invoice.detail?.platformFiscal;
if (fiscal) {
console.log(fiscal.code); // e.g. "213"
console.log(fiscal.label); // e.g. "Rejetée"
console.log(fiscal.jurisdiction); // "FR"
}Report a payment (encaissement)
For services, French VAT is due when the payment is collected, not when the invoice is issued — so the reform requires sellers to report payments received. When a French CTC invoice gets paid, report it with markAs(id, "paid"): getpeppr files the payment report through the accredited platform, and calling again returns the report already filed instead of filing another.
The report declares the whole invoice. There is no amount to pass: the accredited platform takes the VAT breakdown of the invoice as it was sent. A partial payment cannot be reported yet. A credit note is not reported as paid: it is refused with 422 and Getpeppr-Result-Code: invoices.payment_reporting_credit_note, and nothing is filed.
Where the report stands. Read the invoice (invoices.getStatus(id), GET /v1/invoices/{id}): once a report exists it carries paymentReport.status — submitted when the provider accepted it, processed once the provider has processed it, failed when it was refused (call markAs again to retry). If an attempt got no answer, the report may already be filed and reads interrupted: call again within 30 minutes of the first attempt and it is resolved without filing twice; after that it turns into an unconfirmed report. unconfirmed and provider_failed need support, and calling again files nothing. The report is not a status of the invoice: the invoice's own status does not move when you report a payment.
france declaration, the only ones that carry the French mandate profile.
For such an invoice, paid files the report with the accredited platform; its
acceptance by the tax administration is not yet proven end to end — processed does
not say the administration received it. Any other invoice is
refused with 422 and Getpeppr-Result-Code: invoices.payment_reporting_not_applicable,
and nothing is filed. That is the last gate on this route, and an earlier one can refuse
first under its own result code — in production, an unverified identity does — so branch on
the header rather than assuming this refusal.
Platforms. The identity checked is the one that issued the invoice. For an invoice you sent on behalf of a sub-tenant, that is the sub-tenant, not your platform: the call needs your master key, and it is refused with
send_as.sub_tenant_not_sendable while that sub-tenant cannot send (in the
sandbox, send_as.sub_tenant_test_identity_required when the remedy is a test
identity). A standard
key receives 404 for a sub-tenant's invoice, as it does when reading one.
// French VAT on services is due on collection (encaissement):
// when the buyer pays, the seller reports the payment.
// One call files the payment report through the accredited platform;
// calling again returns the report already filed.
const result = await peppol.invoices.markAs(invoiceId, "paid");
// Where the report stands is read on the invoice itself:
// "submitted", then "processed" once the provider has processed it.
// "interrupted": an attempt got no answer — call again within 30 minutes
// of the first one; after that it turns "unconfirmed" (contact support).
const { paymentReport } = await peppol.invoices.getStatus(invoiceId);
console.log(paymentReport?.status);E-reporting FAQ
Do I need to e-report my domestic B2B transactions separately?
No. For domestic B2B invoices sent through the regulated flow, the transaction data reaches the DGFiP through the electronic invoice itself — that is the point of the reform. Do not submit a separate transaction e-report for these invoices.
So e-reporting is fully automatic?
Not entirely — payment reporting stays your responsibility. When a French CTC invoice for services is paid, you report the encaissement with markAs(id, "paid"). Transactions: automatic. Payments: one API call from you.
What about B2C or cross-border transactions?
B2C and international transactions fall under separate e-reporting obligations that getpeppr does not cover today. The current French scope is domestic B2B: send, receive, and payment reporting.
Is getpeppr a plateforme agréée (PA)?
No. getpeppr is a developer layer (API/SDK) built on Storecove, an accredited platform — you can verify accredited platforms on the DGFiP's official list. Storecove carries the regulated role; getpeppr gives you the integration.
Can I test the French flow in sandbox?
Partially, and less than you might expect. Sandbox exercises directory registration, sending, receiving and webhooks over the Peppol network — a real cross-platform exchange was observed on 2026-09-20, in both directions. But on a send to a French recipient our provider applies the France CIUS document type rather than the standard Peppol BIS profile. An accredited French platform rejected it on 2026-09-20 and, after a correction by our provider, has received it since 2026-09-25. So sandbox proves transport and receipt, not acceptance. Since 2026-09-27 the sandbox also exercises the regulated leg: the tax record is produced and marked cleared, and the buyer's French statuses come back. That is still not a verdict from the tax administration, and production is not yet open.