Receiving Peppol documents in France for the companies you onboard

Getting started with Peppol e-invoicing for French companies you onboard from your own software platform: register and verify the company, pull incoming documents through webhooks, report invoice lifecycle statuses, go live. French UBL, CII and Factur-X on a French-accredited access point, with a signed mandate and its own regulated process.

This guide walks through everything needed to exchange Peppol documents for a company registered in France, assuming you are integrating Recommand into your own product and onboarding French companies as your users, and that the company only receives documents over Peppol. Change any of the three answers at the top of the page to get the guide for a different situation.

What you are building

You are onboarding companies that are not your own: one Recommand team, and a company inside it for every customer you put on the network. Your customers never need a Recommand account โ€” they see your interface, and Recommand stays behind your API calls.

That shape has a few consequences worth knowing before you write code:

  • One team, many companies. There is no limit on companies per team, and billing is per team with the document volume of all companies pooled together, so the more companies you onboard the lower your price per document. See pricing per team.
  • Every company is registered and verified individually. Peppol identifies companies, not platforms. Each company gets its own Peppol address and its own authorisation record.
  • Verification is taken care of. Recommand hands you a URL that the company's authorised representative opens to confirm their identity. You present or forward that link; you never need to handle identity documents yourself. If you prefer to handle verification yourself, reach out to us at support@recommand.eu, we have a few other flows we can set up for you.
  • You can run the whole flow under your own brand. The API is designed for white-label use, see can I whitelabel Recommand.

The Recommand dashboard shows the same teams, companies and documents your API calls produce, which is the quickest way to see what a customer is looking at while you are debugging.

Only setting up your own company?

If the only company you will register is your own, switch the first answer above to One company for the shorter version of this guide. The endpoints are the same; there is simply less to organise.

Peppol in France

French domestic e-invoicing follows the French e-invoicing reform rather than plain Peppol BIS 3. Recommand covers the French specifics for you, but they do change what you send and how a company is onboarded.

What is specific to France:

  • A French-accredited access point and SMP. Companies you register with country FR are automatically published on a French-accredited SMP and exchange documents through the matching access point. You do not choose or configure this: it follows from the company's country.
  • A signed mandate. Before a French company can operate, its authorised representative signs a mandate that lets that accredited platform act for the company, and the file is reviewed before the company goes live. This is the one step in this guide that is not instant, so start it early.
  • French document formats. Invoices and credit notes travel as French CIUS or Extended UBL, CII D22B (CIUS or Extended), or Factur-X (a PDF/A-3 with the CII XML embedded), next to plain Peppol BIS 3 UBL.
  • Two processes. The same document types are published for a regulated process (urn:peppol:france:billing:regulated, transactions inside the French e-invoicing perimeter) and a non-regulated one (urn:peppol:france:billing:non-regulated, transactions outside it).
  • Mandatory content. French invoices carry a billing mode and three statements (recovery costs, late-payment penalties, early-payment discount) that plain EN 16931 does not require. They go in a countrySpecific block.
  • Lifecycle statuses and e-reporting. Inside the perimeter, receivers report back on the invoices they receive, and B2C or international B2B transactions have to be reported.
  • Identifiers are SIREN-based. French companies are published under scheme 0225.

Dates and legal scope

The reform phases the obligations in over time: from 1 September 2026 every company must be able to receive electronic invoices, with issuance starting for large and mid-size companies, and from 1 September 2027 issuance applies to small and micro companies as well. Confirm the schedule and what falls inside the perimeter with your accountant or legal advisor โ€” this documentation describes what the API does, not what your obligations are.

Create your team and API credentials

  1. Sign up at app.recommand.eu/signup. The team you get is the container for every company you will onboard.
  2. Create an API key on the API keys page. Note the key, the secret and your team ID.
  3. Check the credentials with a request that needs no data of its own:
curl -X GET https://app.recommand.eu/api/v1/companies \
  -u key_xxx:secret_xxx

All endpoints in this guide live under https://app.recommand.eu/api/v1 and accept HTTP Basic authentication with the key as username and the secret as password. If you would rather not store a long-lived secret, JWT API keys and OAuth2 with a JWT assertion are available too, see the authentication guide.

Try it safely first

Build the whole flow against a playground team before you touch production. Playgrounds look and behave like production teams, but nothing is delivered over the real Peppol network, there are no SMP registrations, no subscription checks and no billing.

Create one from the team switcher at the top of the dashboard: Add playground, give it a name, and you are switched into it. There is no limit on how many you create.

Everything that follows in this guide is identical there: same endpoints, same validation, same webhooks (triggered by simulated inbound delivery). Register a company in the playground and use it as both sender and recipient to see a document arrive.

Three things worth knowing before you start:

  • POST /:companyId/generate takes the same body as the send endpoint and returns the exact XML that sending would produce, fully validated, without transmitting, storing or billing it. It also returns the resolved documentType, doctypeId and processId, which is the quickest way to check that your country-specific fields map to the format and process you expect.
  • Failure addresses. In a playground that is not connected to the Peppol Test Network, sending to 404:404 or 0208:1234567894 always fails, so you can exercise your error handling. Any other unregistered recipient is skipped without an error.
  • The Peppol Test Network. For genuine end-to-end tests with real counterparties, tick Use Peppol Test Network when you create the playground. It then uses dedicated test access point and SMP endpoints while staying fully separated from production. The setting cannot be changed after creation, so make a second playground if you want both.

More detail in the getting started guide and how do I use the playground environment.

Register the company

Create one company per customer with the create company endpoint. Registration on the Peppol network happens as part of this call: identifiers and document types are set up for you, based on the company's country.

const auth =
  "Basic " + Buffer.from("key_xxx:secret_xxx").toString("base64");

const response = await fetch("https://app.recommand.eu/api/v1/companies", {
  method: "POST",
  headers: { Authorization: auth, "Content-Type": "application/json" },
  body: JSON.stringify(company),
});

const result = await response.json();
if (!result.success) throw new Error(JSON.stringify(result.errors));

const companyId = result.company.id;
const verificationUrl = result.verificationUrl; // hand this to your user
curl -X POST https://app.recommand.eu/api/v1/companies \
  -u key_xxx:secret_xxx \
  -H "Content-Type: application/json" \
  -d @company.json

The response carries a verificationUrl straight away. Keep it: the next step is to put it in front of the company's representative.

Only register companies you are allowed to act for

Register the companies that use your platform, not the companies they invoice. Customers and suppliers manage their own Peppol registration; adding them causes registration conflicts. See managing companies.

The exact identifier fields to send depend on the country, which is what the next section covers. If you would rather create identifiers and document types yourself instead of accepting the country defaults, pass skipDefaultCompanySetup: true and use the company identifiers and company document types endpoints.

French identifiers and Peppol address

In France the identifier you register decides whether your documents will pass validation later, so it is worth getting exactly right.

FieldFrench value
country"FR"
enterpriseNumberThe nine-digit SIREN of the company
enterpriseNumberScheme"0002"
vatNumberFR + the French VAT number
company.json
{
  "name": "Sociรฉtรฉ de Test SAS",
  "address": "10 rue de la Paix",
  "postalCode": "75002",
  "city": "Paris",
  "country": "FR",
  "enterpriseNumber": "133512194",
  "enterpriseNumberScheme": "0002",
  "vatNumber": "FR23133512194"
}

Use the SIREN with scheme 0002

French regulated invoices must carry the seller's nine-digit SIREN as enterpriseNumber with enterpriseNumberScheme "0002". Because the seller block of a document defaults to the company's own details, a company registered with a SIRET or without the scheme produces invoices that are rejected at validation time. Register the SIREN, and name a specific establishment through the document's delivery.locationIdentifier (scheme 0009) when you need to.

One Peppol identifier is registered for the company:

  • 0225:133512194 โ€” the French electronic address

The company's Peppol address is therefore 0225: followed by the SIREN. French addresses may also carry a routing suffix, as in 0225:987654321_STATUTS; treat the whole string after the scheme as the identifier when a recipient gives you one.

Both SIREN (9 digits) and SIRET (14 digits) are checked with the Luhn algorithm before they are filed, and numbers that disagree with each other are refused rather than guessed at.

Registering as a recipient

To receive documents, the company must be published as a recipient on an SMP (Service Metadata Publisher). That is what isSmpRecipient does, and it is the default:

{
  "isSmpRecipient": true
}

What that means:

  • The company becomes findable on the Peppol network: any sender can look it up and deliver to it via the Peppol network.
  • Recipient registration is exclusive. If the company is already registered for receiving through another Peppol provider, registration fails until it is deregistered there.
  • The document types the company accepts are registered along with it, based on its country. Which ones those are is covered further down.
  • The company is only published once it is verified.

Where a French company is published

A French company is published on the French-accredited SMP, and it exchanges documents through the matching access point. This follows from the company's country: there is nothing to choose or configure.

Two consequences for the order of your onboarding:

  • The mandate gates the go-live. The accredited platform only acts for the company once the signed mandate has been accepted, so the company is not operational the minute it is created. The verification section below covers that step; start it early.
  • Deregister elsewhere first. Recipient registration is exclusive here as well, and France has no automatic migration path โ€” a company that currently receives through another platform has to be deregistered there before it can be registered with Recommand.

Sign the mandate and verify the company

French companies go through the same identity check as everyone else, plus two steps that are specific to France: the representative signs a mandate, and the resulting file is reviewed before the company goes live.

  1. Open the verification URL (from the create-company response, the dashboard, or a fresh one from the verify company endpoint).
  2. Read and sign the mandate. The verification page shows the mandate that authorises the French-accredited platform to act for the company on the Peppol network, naming the company by its SIREN and the establishment it is filed under. The representative accepts it before the identity check starts.
  3. Complete the identity check. The identity verification is what signs the mandate: the proof reference is recorded on it.
  4. Wait for the review. The signed mandate and the company's details are filed with the accredited platform, and the verification sits in review until that file is accepted. Only then does isVerified become true and the company start operating.

In some cases manual verification by our team will be required. If that's the case, isVerified will remain false until this manual verification is completed. The user is informed of this in the verification process.

Plan the review in

This is the one step of French onboarding that is not immediate. Start verification as soon as the company is created, and do not promise your users a same-minute go-live for France. If a file seems stuck, mail support@recommand.eu with the company ID.

Playgrounds skip the mandate

Companies in a playground team are never filed with the accredited platform, so there is no mandate and no review. Test the French flow in a playground first, then run the real thing once.

Building verification into your onboarding

With one company you would click through this once. With many, verification is part of the flow you build: every company you register needs its own, and it is the step most likely to leave a customer stuck halfway.

Show the URL immediately. The create-company response already carries verificationUrl โ€” no extra call needed. Put it in front of the user while they are still in your onboarding.

Ask for a fresh one when the moment has passed. Links get lost, and companies you created earlier never had one shown. The verify company endpoint starts a new verification session:

curl -X POST https://app.recommand.eu/api/v1/companies/{companyId}/verify \
  -u key_xxx:secret_xxx

The company.verification webhook fires when verification reaches a final state: verified, rejected or error. A company that comes back rejected or error and is not surfaced anywhere sits silently unusable. See working with webhooks.

Respect isVerified in your own UI. Do not let a user press send for a company that is not verified yet; this will result in an error. You should inform the user what is missing instead.

Re-verify after identifier changes. Updating a company's vatNumber or enterpriseNumber resets isVerified to false. Check the field after an update and present a new verificationUrl if it flipped.

The full mechanics are in the company verification guide.

Document types registered for you

A French company is published for the whole French set, all on the regulated process, so any sender inside the perimeter can reach it in the format they prefer:

Document typeRegistered process
Invoice + credit note (Peppol BIS 3 UBL)urn:peppol:france:billing:regulated
Invoice + credit note (France UBL CIUS)urn:peppol:france:billing:regulated
Invoice + credit note (France UBL Extended)urn:peppol:france:billing:regulated
Invoice + credit note (France CII CIUS)urn:peppol:france:billing:regulated
Invoice + credit note (France CII Extended)urn:peppol:france:billing:regulated
Factur-X invoice + credit noteurn:peppol:france:billing:regulated
Invoice lifecycle status (CDAR)urn:peppol:france:billing:regulated

Whichever format arrives, you read the same parsed document out of the API. For Factur-X, the CII XML is extracted from the PDF/A-3 and parsed like any other document, and the original PDF is kept and included in the document's download package.

Trading outside the perimeter

The defaults cover the regulated process. If counterparties will send you documents over urn:peppol:france:billing:non-regulated, register the same document types for that process as well with the create company document type endpoint.

Get incoming documents into your product

Recommand receives, validates and stores incoming documents for every company in your team. You pick them up in one of two ways.

Register an endpoint once with the create webhook endpoint or through the dashboard and events are pushed to you as they happen, document.received among them:

await fetch("https://app.recommand.eu/api/v1/webhooks", {
  method: "POST",
  headers: { Authorization: auth, "Content-Type": "application/json" },
  body: JSON.stringify(webhook), // the body below
});
curl -X POST https://app.recommand.eu/api/v1/webhooks \
  -u key_xxx:secret_xxx \
  -H "Content-Type: application/json" \
  -d @create-webhook.json
create-webhook.json
{
  "url": "https://your-app.example/webhooks/recommand",
  "companyId": null,
  "secret": "your_webhook_signing_secret"
}

companyId: null covers every company in the team.

Pass a secret and verify the HMAC SHA-256 signature on every delivery before you trust the payload. Switch on event.eventType, and acknowledge with a 200 before doing the heavy processing. Both are covered in working with webhooks.

Polling the inbox

If you would rather pull, the inbox endpoint lists unread documents:

const inbox = await fetch("https://app.recommand.eu/api/v1/inbox", {
  headers: { Authorization: auth },
}).then((r) => r.json());

Mark each document as read with the mark as read endpoint once your system has it, so it drops off the list.

After you have the document

For the full picture, including retries and idempotency, see receiving documents.

Report back on the invoices you receive

Inside the French perimeter, receiving an invoice comes with an obligation the other two countries do not have: you report its lifecycle back to the sender. Recommand models those status messages as a document type of their own, frenchInvoicingCdar, so you send them the same way you send anything else.

Received and made available are sent for you

When an invoice arrives, Recommand automatically sends the transmission statuses back to the sender: 202 (received) when the document reaches the access point, and 203 (made available) when it is delivered to you. You only send the later processing statuses.

await fetch(`https://app.recommand.eu/api/v1/${companyId}/send`, {
  method: "POST",
  headers: { Authorization: auth, "Content-Type": "application/json" },
  body: JSON.stringify({
    recipient: "0225:987654321",
    documentType: "frenchInvoicingCdar",
    document: {
      businessProcess: "REGULATED",
      senderRole: "WK",
      issuerRole: "BY",
      issuerLegalId: "123456789",
      issuerLegalIdScheme: "0002",
      recipientRole: "SE",
      statusCode: "205",
      statusDate: "2026-08-17T14:05:09",
      invoiceId: "INV-2026-001",
      invoiceTypeCode: "380",
      invoiceIssueDate: "2026-08-17",
      sellerLegalId: "987654321",
      sellerLegalIdScheme: "0002",
    },
  }),
});

The processing statuses that matter most on the receiving side:

StatusMeaning
204Taken in charge (processing started)
205Approved
206Partially approved
207In dispute
210Refused
211Payment sent

A refusal, partial approval or dispute carries a coded reason (DOUBLON, TX_TVA_ERR, NON_CONFORME, โ€ฆ) and an optional free-text note, so the sender knows what to fix. The full status and reason lists are on the send document endpoint.

Incoming CDAR messages are parsed, stored and shown next to your other documents, and delivered through your existing webhooks and notifications โ€” so this is also how you learn what your customers did with the invoices you sent them, including the 202 and 203 their access point sent when they received yours.

Going live

Before you switch your first real customer over, walk this list:

  • A valid subscription in production. Playgrounds skip the subscription check; production does not.
  • Verification handled in your UI. Show the verificationUrl at the right moment, make it forwardable, and handle the company.verification webhook so a company that comes back rejected or error does not sit silently unusable.
  • isVerified respected. Do not let a user press send for a company that is not verified yet; explain what is missing instead.
  • Webhook endpoint hardened. Signature verification, a fast 200, retries and idempotency on your side.
  • Errors surfaced, not swallowed. Validation errors name the field that is wrong; put that in front of the person who can fix it.
  • One real document, end to end. Send an invoice between two companies you control on production before letting customers in.

Volume and pricing

Documents are counted per team, with the volume of all your companies pooled, so onboarding more companies lowers your price per document rather than adding per-company fees. Generated XML through generate is not billed; emails and submitted reports are.

Before you go live in France

France adds a few checks to the list above, all of them things that only show up once real documents move:

  • The mandate is accepted. isVerified is true, which for a French company means the signed mandate cleared its review. Plan this in: it is the one step that is not instant.
  • The company carries the SIREN under scheme 0002. Both the documents you send and the mandate itself are built from the company's own identifiers, so a SIRET or a missing scheme surfaces as rejected documents rather than as a registration error.
  • You know which side of the perimeter you are on. Regulated is the default; transactions outside it have to say so explicitly, and the counterparty has to be registered for the process you use.

If the company sends documents, two more:

  • The format and process are what you meant. Check one document before the first real send: the preview in the dashboard, or generate from the API, shows the resolved doctypeId and processId.
  • Invoices are in EUR, with the billing mode and the three mandatory statements filled in.

Selling to consumers as well?

B2B invoices are only half of the French obligation. If the company also sells to private individuals, its daily B2C totals (and international B2B totals) have to be reported separately, through the French B2C reporting endpoint.

Where to get help

  • Troubleshooting. Common rejections, delivery failures and validation errors are collected in the troubleshooting guide.
  • Questions. The FAQ covers Peppol, addressing, VAT and billing.
  • Email. support@recommand.eu:include the company ID and, for a delivery problem, the document ID.
  • Discord. Join the server for release announcements and quick questions.
  • Keep track of changes. New endpoints and behaviour are recorded in the changelog.