Skip to content

Peppol and French E-Invoicing

Peppol sending lets an eligible entity deliver outgoing invoices and credit notes to a recipient’s Peppol address. It is separate from local e-invoice file exports such as German XRechnung/ZUGFeRD or Slovenian e-SLOG.

Use this guide when you want Space Invoices to build the UBL XML from your document and submit it over Peppol.

How It Works

  1. Create or select the sender entity in sandbox or live.
  2. Enable e-invoicing for that entity.
  3. Complete verification if the enrollment state requires it.
  4. Add a Peppol identifier for the sender when requested.
  5. Store or pass the recipient Peppol address.
  6. Check the recipient before sending.
  7. Send an invoice or credit note.
  8. Track the submission by API or webhook.

Peppol sending is entity-level. Platforms and marketplaces should enroll the legal seller entity that sends the invoice, not a global platform entity.

For French entities, the same setting connects the approved platform used for the September 2026 reform. Space Invoices automatically chooses the legally relevant path: domestic French B2B invoice delivery, B2C transaction reporting, or international B2B transaction reporting. Service collections are reported when the entity is not configured for VAT on debits.

Requirements

RequirementNotes
Supported sender countryThe entity country must support Peppol sending in Space Invoices.
Complete entity detailsLegal name, address, city, postal code, and country must be present.
Verified enrollmentenrollment.state must be verified and sending_allowed must be true.
Sender identifierThe sender must have a Peppol-compatible identifier on the enrollment.
Recipient addressBusiness recipients need peppol_id and, when not using scheme:value, peppol_scheme_id.
Sendable documentOnly invoices and credit notes are supported.

Enable Sending

Enable e-invoicing on the entity. The response includes a normalized enrollment state.

Enable Peppol sendingtypescript
const settings = await sdk.eInvoicing.update(
  {
    enabled: true,
    auto_send: false,
  },
  { entity_id: entity.id },
);

if (settings.enrollment.state === "verification_required") {
  const nextStep = await sdk.eInvoicing.create({ entity_id: entity.id });
  console.log(nextStep.enrollment.verification_url);
}

Important enrollment states:

StateMeaning
missing_entity_dataUpdate the entity details before enrollment can continue.
verification_requiredCreate a verification link and send the user through the verification step.
verification_in_progressVerification was started but is not complete yet.
verifiedThe entity can send when sending_allowed is true.
reverification_requiredThe enrollment needs a fresh verification step.
rejectedThe entity cannot send until the issue is resolved.
provider_errorEnrollment state could not be resolved; retry sync or contact support.

Use POST /e-invoicing/enrollment/sync after returning from verification or when your backend wants to refresh state. Verification updates are also delivered through e-invoicing enrollment webhooks when configured.

French rollout settings

Every French business must be able to receive electronic invoices from September 1, 2026. Large companies and ETIs must also issue electronic invoices and e-report from that date. SMEs and micro-enterprises start issuing and e-reporting on September 1, 2027, but can join the outgoing flow early.

In Settings → E-invoicing, select September 2026 outgoing obligation when the entity is a large company, an ETI, or is voluntarily joining the 2026 wave. The option is unchecked by default. Selecting it forces e-invoicing and automatic delivery/reporting on; leaving it unchecked delays outgoing enforcement until September 1, 2027. The API equivalent is:

{
"enabled": true,
"france_2026_emission_applicable": true
}

GET /e-invoicing/settings returns france_emission_required and france_receiving_required so API clients do not need to reproduce the date logic. Once French reception is mandatory and the entity has activated it, the setting cannot be disabled.

Recipient Addresses

Peppol addresses use a scheme and identifier. You can send them as a single value:

{
"peppol_id": "0192:991825827"
}

or split them:

{
"scheme_id": "0192",
"peppol_id": "991825827"
}

For saved customers, store the address as:

  • customer.peppol_scheme_id
  • customer.peppol_id

The buyer reference used in some e-invoice profiles stays separate at customer.e_invoicing.buyer_reference. French invoice and credit-note forms show this as Buyer reference without exposing Peppol routing fields.

When goods are delivered somewhere other than the billing address, snapshot the legal delivery address on the document:

{
"customer": {
"e_invoicing": { "buyer_reference": "PO-2026-001" },
"delivery_address": {
"address": "10 rue de la Livraison",
"post_code": "75008",
"city": "Paris",
"country_code": "FR"
}
}
}

The delivery address belongs to the invoice snapshot and is not written back to the saved customer.

Sandbox Test Data

Use sandbox API keys and sandbox entities for these values.

ValueUse
0192:991825827Public sandbox recipient that currently verifies for invoice and credit-note support. Use this as the recipient address in sandbox send tests.
Scheme 9959, identifier 87-2450541Space Invoices Inc sandbox sender identifier used for our verified US sandbox entity. This is sender setup/test data, not a recipient target.

French sandbox

French sandbox testing is available before the September 1, 2026 rollout date. Every French Space Invoices sandbox entity automatically gets its own tenant-isolated simulation; no external platform account, credentials, or pre-existing company is required. If the entity does not have a company number, the simulation derives a stable sandbox-only identity for it.

The simulation covers the full user journey: activation, Annuaire lookup, domestic invoice and credit-note delivery, B2C and international reporting, service-payment reporting, lifecycle actions, receiving, notifications, download, idempotent acceptance into Expenses, retry, and disconnection/reconnection. Mock company and inbox identifiers are derived per entity, and an entity cannot read or accept another entity’s received documents. Sandbox actions do not register a legal address or transmit a legal invoice.

Space Invoices also validates the integration against the approved platform’s own fictional sandbox companies. That shared mailbox is never used as the product sandbox for customer tenants. Sanitized fixtures cover invoice, credit-note, B2C, international, payment, lifecycle, inbox, outbox, and e-reporting calls without storing OAuth secrets. The generated French UBL profile is checked against the platform validator so an unsupported document profile cannot be mistaken for a sandbox-address outage.

In the web app, connect the sandbox entity under Settings → E-invoicing, then review simulated incoming invoices under Expenses → Received e-invoices. Accepting one creates a draft expense and removes it from the pending inbox. Through the API, use the same enrollment, directory, send, submission, received-document, acceptance, and disconnect endpoints documented below with a sandbox entity key.

Check the recipient before you create a send attempt:

Check a sandbox recipienttypescript
const recipient = await sdk.eInvoicing.checkEInvoicingRecipient(
  {
    peppol_id: "0192:991825827",
    document_type: "invoice",
  },
  { entity_id: entity.id },
);

if (!recipient.registered || !recipient.document_supported) {
  throw new Error("Recipient cannot receive this document type over Peppol");
}

Send Or Report A Document

The send endpoint derives the document type from the document ID prefix, so you do not pass a separate document type. For a French entity it also derives the compliance route from the saved customer:

  • French business customer: deliver a domestic B2B e-invoice through the Annuaire route.
  • End consumer: store a B2C transaction for scheduled tax-administration reporting.
  • Business customer outside France: store an international B2B transaction for scheduled reporting.

The response remains one provider-neutral submission. Its network is france_e_invoicing for domestic delivery and france_e_reporting for a reported transaction.

Before any platform call, Space Invoices performs a local preflight check. Domestic UBL is checked for required EN 16931 source data, well-formed XML, French profile identifiers, routing identifiers, totals, and billable lines. B2C, international, payment, and lifecycle payloads are checked for valid identifiers, dates, currencies, amounts, VAT treatment, and reconciled tax totals. This check is local and does not add another network round trip; the approved platform’s validation remains the authoritative acceptance result.

French electronic credit notes must be linked to at least one original invoice. Space Invoices includes each linked invoice number and date in the UBL credit-note references required by the French rules.

If the document customer already has a Peppol address, the request body can be omitted. Pass recipient overrides only when you need to send to a different address for this attempt.

Send an invoicetypescript
const submission = await sdk.eInvoicing.sendEInvoicingDocument(
  invoice.id,
  {
    recipient_peppol_id: "0192:991825827",
    recipient_name: "Sandbox Peppol Recipient",
  },
  { entity_id: entity.id },
);

console.log(submission.status);

Automatic Sending

When entity settings have auto_send: true, finalized invoices and credit notes can be delivered or reported automatically if:

  • entity enrollment is verified
  • the document is sendable
  • a domestic French B2B recipient has an active Annuaire address
  • the document did not opt out

B2C and international documents do not need a recipient network address. Their line items must still be classified as goods or services so the legally required transaction category is unambiguous.

Set e_invoicing.send_enabled: true to send one finalized document even when entity auto-send is off. Pass false to opt out when sending is not legally required. When the field is omitted, the entity auto-send setting applies.

Draft creation ignores this per-document choice. Choose electronic sending when the draft is finalized, either in the issue dialog or by passing e_invoicing.send_enabled to POST /documents/{id}/finalize. Deterministic customer, address, tax, unit, classification, and buyer-reference checks run before the document is numbered or issued.

Disable auto-send for one documenttypescript
const draft = await sdk.invoices.create(
  {
    customer_id: customer.id,
    e_invoicing: {
      send_enabled: false,
    },
    items: [
      {
        name: "Implementation services",
        quantity: 1,
        price: 1000,
        taxes: [{ rate: 0 }],
      },
    ],
  },
  { entity_id: entity.id },
);

console.log(draft.id);

Payment Reporting

When a payment is recorded against a French invoice, Space Invoices reports the service share automatically if VAT is due on collection. Goods are excluded, mixed invoices are allocated proportionally by line and VAT rate, and entities configured for VAT on debits are skipped. The API blocks deletion or mutation after the provider accepts the report; record a correcting payment instead.

  • Domestic B2B collections use the French payment received invoice lifecycle event.
  • B2C collections use B2C payment reporting.
  • International B2B collections use international payment reporting.

The five-minute reporting worker retries definitive failures. It never automatically repeats a timed-out command whose provider outcome is unknown.

Received Invoice Lifecycle

Received invoices appear under Expenses → Received e-invoices and trigger a notification. Acknowledge confirms receipt to the supplier. Accept as expense creates the draft expense and sends the provider acceptance status. Advanced workflows can call POST /e-invoicing/received-documents/{id}/lifecycle for partial acceptance, dispute, hold, completion, refusal, or payment sent.

Partial acceptance, dispute, hold, and refusal require the applicable AFNOR MDT-113 reason_code; a human-readable reason can be included as the optional MDT-114 explanation. The web refusal dialog collects both fields and limits the code list to reasons accepted for refusal.

Use GET /e-invoicing/reporting-records to inspect transaction, payment, and lifecycle status. Failed records can be retried with POST /e-invoicing/reporting-records/{id}/retry.

Submission Status

Every send attempt creates an e-invoicing submission record.

  • GET /e-invoicing/submissions lists submissions for the entity.
  • GET /e-invoicing/submissions?document_id={id} limits the list to one document for a detail/status view.
  • GET /e-invoicing/submissions/{id} returns one submission and, when available, generated UBL XML.
  • POST /e-invoicing/submissions/{id}/retry retries failed or rejected submissions.

French reporting status can likewise be limited with GET /e-invoicing/reporting-records?source_document_id={id}.

For production integrations, subscribe to webhooks:

  • e_invoicing.enrollment.verified
  • e_invoicing.enrollment.rejected
  • e_invoicing.submission.created
  • e_invoicing.submission.delivered
  • e_invoicing.submission.failed

Once a real Peppol submission is active, legal/output-changing document edits are blocked. Issue a credit note or a replacement document instead of mutating a sent document.