Can this company receive your e-invoice? A Peppol participant lookup
Before an agent sends an e-invoice, GET /company/peppol returns the participant identifiers and accepted document types from a dated Peppol directory export.
By Matthias Begot · · Updated
An agent that has just produced a conformant e-invoice still has one question
left before it sends anything: can the counterparty actually receive it, at
which address, and in which format?
GET /company/peppol answers it from the ingested
public export of the Peppol directory — every participant identifier a company
is known under, each with its own declared document types. “Not registered” is
a paid, dated 200, because the agent pays for a directory lookup that
ran, not for a “yes”.
This article walks the endpoint and its boundaries, with captures taken from production on 12 August 2026, against a directory export dated 11 August 2026.
Why this became a pre-send check in 2026
The French e-invoicing reform makes the question operational rather than academic. Per impots.gouv.fr, the calendar is:
| Date | Obligation | Who |
|---|---|---|
| 1 September 2026 | receive e-invoices through an approved platform | every VAT-liable business, whatever its size |
| 1 September 2026 | issue e-invoices | large and mid-cap companies |
| 1 September 2027 | issue e-invoices | SMEs, small businesses, micro-enterprises |
The asymmetry is the interesting part. From September 2026 the reception side is universal while the issuing side is not, so an agent issuing invoices on behalf of a large company spends a year sending documents to counterparties who are obliged to receive them but not yet to send any. Whether a given counterparty is addressable, and under which identifier, stops being a one-time onboarding detail and becomes a per-send question.
That is visible in the data. In the export we serve, the French reform identifiers registered in June and July 2026 are everywhere: companies were still declaring their routing addresses weeks before the deadline. A reachability answer cached at onboarding time would already be wrong.
What the endpoint reads — and what it does not
The answer is served offline, from an ingested copy of the complete
public Peppol directory export: over six million participants and tens of
millions of declared document types, at the date of that export. Every
response carries the date twice, in coverage.directory_as_of and in
provenance.freshness.as_of.
Three things it is deliberately not:
- Not a live SMP lookup. A registration or deregistration that happened
after the export is invisible. The response says so in
coverage.noterather than leaving you to assume real time. - Not the French annuaire central. France’s own directory of VAT-liable businesses and their invoicing addresses, operated on the public portal, is a separate register. Peppol reachability is a strong, checkable signal about addressability on the Peppol network — it is not a statement about a company’s compliance with the French mandate.
- Not a regulatory opinion. Absent from the directory says nothing about whether a company is legally obliged to do anything.
One SIREN, several addresses
A French company is routinely registered under several Peppol identifiers
at once, each with its own declared document types. A siren= query returns
them all — that is the point of the French index.
GET /company/peppol?siren=380129866 (Orange; document types abridged):
{
"data": {
"reachable": true,
"participants": [
{
"id": "0225:380129866",
"scheme": "0225",
"value": "380129866",
"country": "FR",
"names": ["Dickinson LLC_Sage_Company_FUll_Name"],
"registered_on": "0001-01-01",
"document_types": [
{
"identifier": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:peppol:france:billing:cius:1.0::2.1",
"display_name": "France UBL Invoice CIUS",
"label": "invoice"
},
{
"identifier": "urn:peppol:doctype:pdf+xml##urn:cen.eu:en16931:2017#conformant#urn:peppol:france:billing:Factur-X:1.0::D22B",
"display_name": "France Factur-X",
"label": null
}
]
},
{
"id": "0225:380129866_achatpub_507807949",
"scheme": "0225",
"value": "380129866_achatpub_507807949",
"country": "FR",
"names": ["ORANGE"],
"registered_on": "0001-01-01",
"document_types": ["…"]
},
{
"id": "0225:380129866_reserve_pilote",
"scheme": "0225",
"value": "380129866_reserve_pilote",
"country": "FR",
"names": ["Orange SA"],
"registered_on": "2026-07-05",
"document_types": ["…"]
}
],
"company": {
"name": "ORANGE",
"natural_person": null,
"status": "active"
},
"coverage": {
"directory_as_of": "2026-08-11",
"note": "reachability reflects the public Peppol directory export dated directory_as_of, not a live SMP lookup"
}
},
"provenance": {
"source": "peppol-directory",
"fetched_at": "2026-08-12T06:49:52.426422654Z",
"freshness": { "kind": "snapshot", "as_of": "2026-08-11T00:00:00Z" }
}
}
Scheme 0225 is FR:CTC, “FRCTC Electronic Address”, issued by AIFE and
defined in the
Peppol participant identifier scheme code list
as an alphanumeric value of up to 130 characters. That length allowance is
exactly what the suffixes above use: a single legal entity exposes routing
sub-addresses for distinct flows. In the same export, Michelin publishes
_fact, _autofact and _ignoauto; Dassault Systèmes publishes _ndf. These
are the counterparty’s own internal routing, declared publicly.
Note also the fidelity of the relay. The bare-SIREN record declares its name as
"Dickinson LLC_Sage_Company_FUll_Name" — a software template left in a real
production registration — and registered_on: "0001-01-01". Both are relayed
as-is, never corrected, while the Sirene echo in data.company carries the
official legal name. An agent gets the declaration and the register side by
side, and can see they disagree.
You cannot construct a CTC address — you have to read it
The suffix is not derivable from the SIREN. BNP Paribas, in the same export, is reachable under two identifiers that differ only by a repeated prefix:
0225:662042449_bnpp1capp
0225:662042449_662042449_bnpp1capp
Both resolve, both carry the same declared name and the same eight document types. Any agent that “builds” a CTC identifier by concatenating a SIREN with a guessed suffix is inventing an address; any agent that only ever tries the bare SIREN will miss the sub-address a counterparty actually reads. Look it up, then address what the directory returned.
reachable: true does not mean “will accept your invoice”
This is the trap worth stating loudly: reachability is about the participant, acceptance is about the document type. The two answers live in different fields, and they disagree more often than you would expect.
EQUIPTEC is registered twice. Its SIRET identifier under scheme 0009 accepts
only Peppol Invoice Response messages — no invoice at all:
GET /company/peppol?participant=0009:33328563300033
→ reachable: true, 2 document types, all "application-response"
Its 0225 identifier accepts UBL and CII invoices plus credit notes. Same
company, same call, two very different answers to “can I send an invoice here?”.
The pattern is not French-specific: the Norwegian public agency
0192:991825827 is reachable with eleven declared document types, nine of them
innbyggerpost (digital post to citizens) and only one an invoice.
So the decision rule for an agent is: reachable selects candidate
addresses, document_types selects the one to send to. Each entry carries
its full Peppol document type identifier, the display_name as declared, and
a short label derived at ingestion (invoice, credit-note, …) —
null when it cannot be derived, as with "France Factur-X" above. A label is
never invented: if you need certainty, match on identifier, which is the
normative string.
”Not registered” is a paid, dated answer
A well-formed identifier absent from the directory returns
200 reachable: false, billed. Carrefour, on the export of 11 August 2026:
{
"data": {
"reachable": false,
"participants": [],
"company": {
"name": "CARREFOUR",
"natural_person": null,
"status": "active"
},
"coverage": {
"directory_as_of": "2026-08-11",
"note": "reachability reflects the public Peppol directory export dated directory_as_of, not a live SMP lookup"
}
},
"provenance": {
"source": "peppol-directory",
"fetched_at": "2026-08-12T06:50:06.181287944Z",
"freshness": { "kind": "snapshot", "as_of": "2026-08-11T00:00:00Z" }
}
}
That is a complete answer to the question asked: no, as of the export date.
The agent now knows to fall back to another channel instead of emitting a
document into a void. And the Sirene echo makes the distinction explicit — a
large, active company that simply is not in this directory. Absence from a
directory is not absence from reality, which is why it is never a 404.
The one 404: unknown input, not a missing answer
Both French input forms are checked against the Sirene register before the
directory is read. A Luhn-valid SIREN that INSEE never assigned is a
404 NOT_FOUND, not billed, with the same message as
GET /company/resolve:
| Request | Result |
|---|---|
?siren=380129866 | 200 reachable: true, billed |
?siret=38012986648625 (head office) | 200, same three identifiers — a SIRET is matched by its SIREN prefix |
?siren=652014051 | 200 reachable: false, billed — real company, absent from the directory |
?siren=123456782 | 404 NOT_FOUND, unbilled — Luhn passes, names nobody |
?siren=000000000 | 404 NOT_FOUND, unbilled |
?siren=…&participant=… | 400 INVALID_INPUT — exactly one of the three |
?participant=0225380129866 | 400 INVALID_INPUT — must be scheme:value |
?participant=022:380129866 | 400 INVALID_INPUT — scheme must be a 4-digit ISO 6523 ICD |
?siret=38012986600628 | 400 INVALID_CHECKSUM — fails Luhn, probably a typo |
The rule behind the table: “unknown” and “known but unregistered” are different answers, and only the second one is worth paying for. Existence is one single truth across the service — no route can bill an answer for an input another route calls unknown.
Cross-border: participant= names any country
siren= and siret= query the French index. participant= is an exact lookup
on a full Peppol identifier, in any country, and is never subject to the
Sirene existence check — there is no French register entry to verify. In a
sweep across the export it resolved participants in Norway, Belgium, Denmark,
Germany, the Netherlands and Austria as readily as French ones.
It is also the only way to reach an identifier whose embedded number would fail
a French checksum: the Monegasque SIREN 000126010 (ENERTECH MONACO) has
leading zeros and does not pass Luhn, so it is queryable only as
participant=0002:000126010.
Where it sits in an agent’s invoicing loop
Discovery and settlement are the standard x402 loop from
the quickstart and for agents: call,
receive a 402 carrying the terms, pay, replay. The sequencing around it:
- Check the counterparty is addressable. This endpoint. Read
participants[], pick the identifier whosedocument_typesinclude what you are about to send, and keepcoverage.directory_as_ofwith the decision. - Check the tax identity.
GET /company/vatvalidates the intra-EU VAT number live against VIES — the other pre-invoicing question about the same counterparty, and the one that decides the VAT treatment. - Produce the document.
POST /invoice/generateemits a guaranteed EN 16931-conformant invoice, withruleset=peppolwhen the target is a Peppol BIS Billing 3.0 flow. If the document came from elsewhere,POST /invoice/validategives the deterministic verdict on it first — that judges the document, this endpoint judges the participant.
If the agent wants identity, legal events, VAT and reachability in a single
settlement rather than several,
GET /company/report composes them by SIREN, each
block carrying its own provenance. The live /catalog is
authoritative for the endpoint listing and pricing — never hardcode either.
Limits, stated plainly
- The answer is a photo at the export date. No live SMP lookup in v1. Read
coverage.directory_as_ofand decide whether that is fresh enough for what you are about to send. namesandregistered_onare the participants’ own declarations, relayed as-is.0001-01-01and templated company names are real values in the public export, not errors on our side.- Reachable is not compliant, and not obliged. The endpoint states a directory fact. It is not the French annuaire central and carries no view on anyone’s legal obligations.
labelmay benull. The normative value is the full document typeidentifier; the short label is a convenience derived at ingestion and never guessed.- The Sirene identity echo covers French companies present in the Sirene
stock, and is never attempted on
participant=input. A missing echo is explained incoverage.identity, never an error.
See also
GET /company/peppol— full parameters, response fields and error codes.GET /company/vat— live VIES validation of the counterparty’s intra-EU VAT number.GET /company/resolve— the identity card behind a SIREN or SIRET.POST /invoice/generate— produce the EN 16931 document once the address is confirmed.POST /invoice/validate— deterministic EN 16931 verdict, including the Peppol BIS Billing 3.0 pack.- Quickstart — the
402→ pay → replay loop end to end.