# E-invoicing (France)

Issue and receive regulated French e-invoices — EN 16931-validated, delivered as Factur-X, with
the normalized lifecycle (cycle de vie) and e-reporting the 2026 reform requires.

From **1 September 2026**, every French business must be able to receive electronic invoices, and
large/mid-size companies must emit them (SMEs follow on 1 September 2027). BaxPay handles the
formats, the validation, the routing and the tax reporting so your integration is a handful of
REST calls.

> **Authentication**
> All endpoints on this page use your developer API keys — `bx_test_…` or `bx_live_…` — as a
> Bearer token. The key pins both the merchant and the mode: a test key only ever sees sandbox
> invoices. Create keys in the Business dashboard under **Developers**. See
> [Sandbox & test mode](https://developers.baxpay.com/sandbox).

## How it works

1. You **create a draft** invoice with lines. BaxPay computes the totals and VAT breakdown
   server-side and validates everything against **EN 16931** (the European semantic standard) plus
   French rules (SIREN checks, mentions obligatoires).
2. You **send** it. BaxPay builds the **Factur-X** artifact (a human-readable PDF/A-3 with the
   machine-readable XML embedded), looks the buyer up in the **annuaire** (the state directory of
   who is served by which platform), and routes it — internally if the buyer is on BaxPay,
   otherwise through the certified platform network.
3. Both sides then exchange **lifecycle statuses** — the reform's normalized state machine.
   Four statuses are mandatory nationwide: *déposée*, *rejetée*, *refusée*, *encaissée*.
4. Payment collection can be automated with [payment links](https://developers.baxpay.com/einvoicing-paylinks), and the
   *encaissée* events feed your **e-reporting** to the tax authority.

## Enable e-invoicing

Set the business's SIREN once, from the dashboard (**E-invoicing → Settings**) or via the
dashboard API. Every invoice you emit uses your business profile (name, address, VAT number) as
the seller.

## Clients

Invoice counterparties come from your general-purpose **[client book](https://developers.baxpay.com/clients)** — the same
resource used to group [payment links](https://developers.baxpay.com/payment-links) per customer. Create the client once
(`POST /clients`), then invoice by `clientId`. Two e-invoicing specifics:

- a client needs a **Luhn-valid SIREN** (and an address) before you can e-invoice them — a B2C
  or non-French client legitimately has neither;
- every issued invoice carries a **legal snapshot** of the buyer, so editing or deleting a
  client never rewrites an invoice.

## Create a draft invoice

- `POST /einvoices` — Idempotency-Key required

Supply the buyer inline (one-off invoicing, no client record):

```bash
curl -X POST https://sandbox-api.baxpay.com/v1/einvoices \
  -H "Authorization: Bearer $BAXPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "invoiceNumber": "FA-2026-0042",
    "issueDate": "2026-09-01",
    "dueDate": "2026-09-30",
    "buyerName": "ACME SAS",
    "buyerSiren": "552100554",
    "buyerVatId": "FR96552100554",
    "buyerAddress": {
      "line1": "12 rue de la Paix",
      "city": "Paris",
      "postalCode": "75002",
      "countryCode": "FR"
    },
    "paymentTerms": "Paiement à 30 jours.",
    "lines": [
      {
        "lineNumber": 1,
        "description": "Prestation de conseil — septembre 2026",
        "quantity": 3,
        "unitCode": "DAY",
        "unitPrice": 650,
        "vatRate": 20
      }
    ]
  }'
```

```js
const res = await fetch('https://sandbox-api.baxpay.com/v1/einvoices', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.BAXPAY_API_KEY}`,
    'Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    invoiceNumber: 'FA-2026-0043',
    issueDate: '2026-09-01',
    dueDate: '2026-09-30',
    buyerName: 'ACME SAS',
    buyerSiren: '552100554',
    buyerVatId: 'FR96552100554',
    buyerAddress: { line1: '12 rue de la Paix', city: 'Paris', postalCode: '75002', countryCode: 'FR' },
    lines: [
      { lineNumber: 1, description: 'Prestation de conseil — septembre 2026', quantity: 3, unitCode: 'DAY', unitPrice: 650, vatRate: 20 },
    ],
  }),
});
const body = await res.json();
if (!res.ok) throw new Error(JSON.stringify(body));
console.log(JSON.stringify(body, null, 2));
```

```json
{
  "success": true,
  "data": {
    "id": "0d1f9b1e-…",
    "status": "DRAFT",
    "direction": "OUTBOUND",
    "invoiceNumber": "FA-2026-0001",
    "typeCode": "COMMERCIAL_INVOICE",
    "sellerSiren": "732829320",
    "buyerSiren": "552100554",
    "totalNet": "1950",
    "totalVat": "390",
    "totalGross": "2340",
    "amountDue": "2340",
    "vatBreakdown": [{ "rate": 20, "categoryCode": "S", "taxableAmount": 1950, "taxAmount": 390 }],
    "livemode": false
  }
}
```

Or reference a client from your [client book](https://developers.baxpay.com/clients) instead of the inline buyer fields:

```json
{
  "invoiceNumber": "FA-2026-0044",
  "issueDate": "2026-09-01",
  "clientId": "b7e1c2d0-…",
  "lines": [{ "lineNumber": 1, "description": "Conseil", "quantity": 1, "unitCode": "DAY", "unitPrice": 650, "vatRate": 20 }]
}
```

When both are present, inline buyer fields override the client's stored values for that invoice
only. The client needs a SIREN and a postal address.

Things to know about the draft:

- **Numbering is BaxPay's.** French rules require a continuous, gap-free sequence per seller, so with
  French e-invoicing enabled BaxPay assigns the number itself (`FA-YYYY-NNNN`, credit notes
  `AV-YYYY-NNNN`) and the `invoiceNumber` you send is replaced. It is still required by validation.
- **Money is computed for you.** Totals and the VAT breakdown come from the lines. Amounts in the
  response are **decimal strings** (`"1950"`), so parse them as decimals, not floats.
- **Corrections are credit notes.** A deposited invoice is never edited. Send `typeCode:
  "CREDIT_NOTE"` with the original's `precedingInvoiceRef` **and** `precedingInvoiceIssueDate`.
- A draft can still be changed with `PUT /einvoices/{id}` (same body).

If validation fails you get a `400` listing each violated rule with its official EN 16931 identifier.
Here the buyer's VAT number has the wrong check digits:

```bash
curl -X POST https://sandbox-api.baxpay.com/v1/einvoices \
  -H "Authorization: Bearer $BAXPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "invoiceNumber": "FA-2026-0045",
    "issueDate": "2026-09-01",
    "buyerName": "ACME SAS",
    "buyerSiren": "552100554",
    "buyerVatId": "FR60552100554",
    "buyerAddress": { "line1": "12 rue de la Paix", "city": "Paris", "postalCode": "75002", "countryCode": "FR" },
    "lines": [{ "lineNumber": 1, "description": "Conseil", "quantity": 1, "unitCode": "DAY", "unitPrice": 650, "vatRate": 20 }]
  }'
```

```json
{
  "statusCode": 400,
  "message": "The buyer is missing information required for this invoice.",
  "code": "EINVOICE_BUYER_INCOMPLETE",
  "violations": [
    { "rule": "BR-CO-9", "message": "Buyer VAT identifier (BT-48): VAT number is invalid for FR.", "field": "vatId" }
  ]
}
```

Branch on `code`, and show each `violations[].message` to whoever fixes the data.

## Send it

- `POST /einvoices/{id}/send` — Idempotency-Key required

Validates again, generates the Factur-X PDF and XML, deposits the invoice (`DEPOSITED`, the mandatory
first lifecycle step) and emits it towards the buyer's platform (`EMITTED`):

```bash
curl -X POST https://sandbox-api.baxpay.com/v1/einvoices/0d1f9b1e-…/send \
  -H "Authorization: Bearer $BAXPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

```json
{
  "success": true,
  "data": {
    "id": "0d1f9b1e-…",
    "status": "EMITTED",
    "format": "FACTUR_X",
    "pdfUrl": "/einvoicing/invoices/0d1f9b1e-…/artifacts/pdf",
    "lifecycleEvents": [{ "status": "DEPOSITED" }, { "status": "EMITTED" }]
  }
}
```

`pdfUrl` and `xmlUrl` are paths in the Business dashboard: download the Factur-X files there. They
aren't served to API keys.

## List and retrieve

- `GET /einvoices?direction=OUTBOUND&status=EMITTED&page=1&limit=25`
- `GET /einvoices/{id}`

```bash
curl "https://sandbox-api.baxpay.com/v1/einvoices?direction=OUTBOUND&status=EMITTED&limit=25" \
  -H "Authorization: Bearer $BAXPAY_API_KEY"
```

```json
{
  "success": true,
  "data": {
    "data": [{ "id": "0d1f9b1e-…", "direction": "OUTBOUND", "status": "EMITTED", "invoiceNumber": "FA-2026-0001" }],
    "total": 1,
    "page": 1,
    "limit": 25
  }
}
```

`direction` is `OUTBOUND` (you are the seller) or `INBOUND` (you are the buyer: invoices other
platforms delivered to your SIREN).

## Lifecycle statuses

- `POST /einvoices/{id}/lifecycle` — Idempotency-Key required

As the seller, record that the buyer paid outside BaxPay:

```bash
curl -X POST https://sandbox-api.baxpay.com/v1/einvoices/0d1f9b1e-…/lifecycle \
  -H "Authorization: Bearer $BAXPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "action": "PAYMENT_TRANSMITTED" }'
```

```bash
curl -X POST https://sandbox-api.baxpay.com/v1/einvoices/0d1f9b1e-…/lifecycle \
  -H "Authorization: Bearer $BAXPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "action": "COLLECTED" }'
```

```json
{ "success": true, "data": { "id": "0d1f9b1e-…", "status": "COLLECTED" } }
```

As the **buyer** (INBOUND invoices) you may assert `TAKEN_INTO_ACCOUNT`, `APPROVED`,
`PARTIALLY_APPROVED`, `DISPUTED`, `SUSPENDED` or `REFUSED`, for example:

```json
{ "action": "REFUSED", "reasonCode": "GOODS_NOT_RECEIVED", "reason": "Livraison non reçue." }
```

As the **seller** (OUTBOUND): `PAYMENT_TRANSMITTED`, `COLLECTED`. `REFUSED`, `DISPUTED`, `SUSPENDED`
and `REJECTED` require a reason: a legal requirement, not a BaxPay one. An illegal transition
(`COLLECTED → COLLECTED`, say) returns `400`, and a platform-owned status like `DEPOSITED` can never be
asserted by an API client.

The full status vocabulary: `DRAFT`, `DEPOSITED`, `REJECTED`, `EMITTED`, `RECEIVED`,
`MADE_AVAILABLE`, `TAKEN_INTO_ACCOUNT`, `APPROVED`, `PARTIALLY_APPROVED`, `DISPUTED`,
`SUSPENDED`, `REFUSED`, `PAYMENT_TRANSMITTED`, `COLLECTED`, `COMPLETED`.

## E-reporting

Businesses under the reform also transmit transaction and payment data to the tax authority.
BaxPay generates each period's submission from data it already holds: you review, then submit.
E-reporting needs the fiscal settings (VAT regime, default VAT category and rate) filled in under
**E-invoicing → Settings**; until then it answers `400 EINVOICING_SETUP_INCOMPLETE`.

- `GET /einvoices/ereporting/periods?kind=PAYMENT_DATA` — legal periods for your VAT regime
- `POST /einvoices/ereporting/generate` — Idempotency-Key required
- `POST /einvoices/ereporting/{id}/submit` — Idempotency-Key required
- `GET /einvoices/ereporting` — list submissions

Periods are fixed by law and by your VAT regime: `PAYMENT_DATA` is reported per calendar month
(every two months under the VAT exemption regime), and any other window is refused with
`400 EREPORTING_PERIOD_INVALID`. Ask the API for the legal periods instead of computing them:

```bash
# The current legal period for payment data
PERIOD=$(curl -s "https://sandbox-api.baxpay.com/v1/einvoices/ereporting/periods?kind=PAYMENT_DATA" \
  -H "Authorization: Bearer $BAXPAY_API_KEY" | jq -c '.data[0]')

curl -X POST https://sandbox-api.baxpay.com/v1/einvoices/ereporting/generate \
  -H "Authorization: Bearer $BAXPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d "$(echo "$PERIOD" | jq -c '{ kind: "PAYMENT_DATA", periodStart, periodEnd }')"
```

```json
{
  "success": true,
  "data": {
    "id": "a4157f41-…",
    "kind": "PAYMENT_DATA",
    "status": "GENERATED",
    "periodStart": "2026-09-01T00:00:00.000Z",
    "periodEnd": "2026-09-30T00:00:00.000Z",
    "livemode": false
  }
}
```

A period with nothing to report answers `400 EREPORTING_EMPTY_PERIOD`: there are no nil filings.

```bash
curl -X POST https://sandbox-api.baxpay.com/v1/einvoices/ereporting/a4157f41-…/submit \
  -H "Authorization: Bearer $BAXPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

`kind` is `B2C_TRANSACTIONS` (sales to consumers), `INTERNATIONAL_B2B`, or `PAYMENT_DATA`
(encaissements on service invoices, fed automatically by `COLLECTED` lifecycle events, including
[payment-link](https://developers.baxpay.com/einvoicing-paylinks) payments).

## Webhooks

Subscribe to these [webhook](https://developers.baxpay.com/webhooks) events (signed, mode-scoped like every other event):

- `einvoice.status_changed` — any lifecycle transition, with `status`, `source`, and the motif.
- `einvoice.paid` — an invoice was paid through its payment link.

## Sandbox behavior

With a `bx_test_` key everything works end to end with **zero external traffic**. Any 9-digit
SIREN is accepted (live requires a valid checksum), so the PA's test fixtures work: the annuaire
resolves unknown SIRENs to a simulated platform, deposits return simulated tracking references,
and no invoice, status or e-reporting ever reaches a real platform or the tax authority. If the
buyer's SIREN belongs to another BaxPay business, delivery is real and internal — their INBOUND
copy appears instantly, which is the easiest way to test the full buyer flow.
