GET /company/peppol
Answers one question: is this company reachable on the Peppol network -
under which participant identifiers, and for which document types?
The answer is served offline from the ingested complete public export
of the Peppol directory: a dated photo (coverage.directory_as_of),
never a live SMP lookup, with zero network at call time.
Use it as the pre-action check before an agent sends an electronic
invoice - a hook that matters with the French e-invoicing reform
(first deadline September 2026) approaching: before emitting an e-invoice,
check that the counterparty can actually receive it on Peppol, and in
which formats. It pairs naturally with
POST /invoice/generate and
POST /invoice/validate (the document), and
with GET /company/resolve and
GET /company/vat (the counterparty). See the
live /catalog for the authoritative endpoint listing
and price.
x402 golden rule: the agent pays for a directory lookup that ran,
not for a “positive” result. A well-formed identifier that is absent
from the directory is a 200 reachable: false, billed - the absence
is the answer, dated by the export (see
the “not registered” answer below).
Requests the service cannot answer - zero or two parameters, a malformed
identifier, a failed Luhn check, or a siret= that does not exist in the
Sirene register (see the one 404)
- leave the 200 range and are not billed.
Parameters
Provide exactly one of siren, siret or participant - zero or two
is a 400 INVALID_INPUT. There is no search by name.
| Parameter | Type | Required | Description |
|---|---|---|---|
siren | string | one of | 9-digit French SIREN (Luhn-checked); queries the French index |
siret | string | one of | 14-digit French SIRET (Luhn-checked); its existence is checked in the Sirene register first (unknown → 404, not billed), then matched by its SIREN prefix |
participant | string | one of | Explicit Peppol participant identifier <icd>:<value> (4-digit ISO 6523 ICD scheme + value as-is), any country |
GET /company/peppol?siren=333285633
GET /company/peppol?siret=33328563300033
GET /company/peppol?participant=0002:000126010
siren=/siret=interrogate the French index: one SIREN returns every identifier the company appears under in the directory (see below). The SIRET form is matched by its SIREN prefix.participant=is an exact lookup on the identifier (leading zeros count), for any country. It is also the only way to query an identifier whose embedded number would fail a Luhn check - a real case: the Monegasque SIREN000126010(ENERTECH MONACO) is only reachable throughparticipant=0002:000126010.
The French index - one SIREN, all its identifiers
A French company can be registered in the Peppol directory under several identifiers at once, each with its own declared document types:
- the head-office SIRET (ICD
0009), - the bare SIREN (ICD
0002or0225), - the CTC routing sub-addresses of the 2026 French e-invoicing reform
(ICD
0225,FR:CTC) - in the current export, this scheme carries the vast majority of French reachability.
A siren= query returns them all, so the agent sees the complete
routing picture in one call - which identifier to address, and which
document types each one accepts.
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "peppol-directory",
"fetched_at": "2026-07-12T20:44:13Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-12T00:00:00Z" }
}
}
freshness.kind:snapshot-as_ofis the date of the ingested directory export, the same date ascoverage.directory_as_of.provenance.source:peppol-directory- the public Peppol directory export, nothing else was consulted.
Fields of data
| Field | Type | Description |
|---|---|---|
reachable | bool | At least one directory record was found for the queried identifier(s) |
participants | array | One entry per directory record - empty when reachable is false |
company | object | Minimal Sirene identity echo: { name, natural_person, status }; present for siren=/siret= input only - see below |
coverage | object | directory_as_of (date of the export behind the answer) + note (“not a live SMP lookup”) - on every response; identity when the Sirene echo is unavailable |
Each entry of participants[]:
| Field | Type | Description |
|---|---|---|
id | string | Full Peppol participant identifier, scheme:value |
scheme | string | 4-digit ISO 6523 ICD scheme (0009, 0002, 0225, …) |
value | string | Identifier value under that scheme |
country | string | Country declared in the directory |
names | array | Names as declared by the participant in the export - relayed as-is, never corrected |
registered_on | string | Registration date as declared - a 0001-01-01 is the participant’s own declaration, relayed as-is |
document_types | array | Declared document types for this identifier: identifier (full Peppol document type identifier), display_name (as declared), label (short label derived at ingestion - invoice, credit-note, … - null when it cannot be derived, never invented) |
A dated photo of the directory, not a live lookup
The central point of this endpoint’s contract: the answer reflects the public Peppol directory export ingested into the local store - millions of participants, tens of millions of declared document types - at the date of that export, not the state of the network at call time. Every response says so twice:
coverage.directory_as_ofdates the export behind the answer;coverage.notespells it out: “reachability reflects the public Peppol directory export dated directory_as_of, not a live SMP lookup”.
A registration or deregistration that happened after the export is not visible. The answer is never presented as real-time - the agent always knows exactly how fresh the photo is.
”Not registered” is a paid answer
A well-formed identifier that is absent from the directory gets a
200 reachable: false, billed - never a 404: not being registered on
Peppol is not the same thing as not existing. Real example (production
store, export of 2026-07-12):
GET /company/peppol?siren=902120096 - a well-formed SIREN (Luhn passes),
a real sole trader, absent from the Peppol directory:
{
"data": {
"reachable": false,
"participants": [],
"company": {
"name": null,
"natural_person": { "last_name": "DEMAILLY", "first_name": "LOLITA" },
"status": "active"
},
"coverage": {
"directory_as_of": "2026-07-12",
"note": "reachability reflects the public Peppol directory export dated directory_as_of, not a live SMP lookup"
}
},
"provenance": {
"source": "peppol-directory",
"fetched_at": "2026-07-12T20:44:29.010422662Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-12T00:00:00Z" }
}
}
The question “is this company reachable on Peppol?” received its answer:
no, as of the export date (coverage.directory_as_of). The agent knows
it cannot send a Peppol invoice to this counterparty today. And the Sirene
echo shows the company does exist (a natural person, active): “not
registered on Peppol” is not “nonexistent” - which is exactly why an
absence from the directory is never a 404. The 404 below answers the other
question entirely: an identifier that does not exist at all.
The one 404: a SIRET that does not exist
Absence from the directory is a paid answer. Absence from reality is
not. A siret= is checked against the Sirene register before the
directory is queried: a 14-digit, Luhn-valid SIRET whose establishment was
never assigned by INSEE is a 404 NOT_FOUND, not billed, with the
very same message as GET /company/resolve:
{ "error": "siret not found in the Sirene register", "code": "NOT_FOUND" }
The existence of an identifier is one single truth across the service: one route cannot answer “unknown” while another bills an answer for the same input - least of all the one that charges. The two SIRETs of the same company, on the production store:
| Request | Answer |
|---|---|
?siret=39440693800008 - Luhn-valid, NIC never assigned by INSEE | 404 NOT_FOUND, not billed - word for word the answer of /company/resolve |
?siret=39440693800149 - the real head office | 200 reachable: true, billed - 0225:394406938_0073_replyto, strictly identical to ?siren=394406938 |
Two boundaries keep this rule narrow:
- Only
siret=can 404.siren=andparticipant=have no establishment existence to verify: a SIREN unknown to the Sirene stock is stated incoverage.identity(see the Sirene identity echo) and still gets a billed200, and aparticipant=is an explicit directory key rather than a French register entry. - A 404 is never invented. Once the SIRET exists, the lookup is the
one described above, unchanged (matched by its SIREN prefix, same
response as the equivalent
siren=). And if the service cannot consult the Sirene stock at all, reachability is served as before with its stated coverage - the answer is never downgraded to a 404 out of ignorance.
Example - reachable, multi-identifier French index
GET /company/peppol?siren=333285633 (EQUIPTEC - the French index returns
two identifiers: the head-office SIRET under ICD 0009, and the bare
SIREN under FR:CTC 0225 from the French reform, each with its own
document types; production store, export of 2026-07-12, document types
abridged):
{
"data": {
"reachable": true,
"participants": [
{
"id": "0009:33328563300033",
"scheme": "0009",
"value": "33328563300033",
"country": "FR",
"names": ["EQUIPTEC"],
"registered_on": "2023-03-27",
"document_types": [
{
"identifier": "urn:oasis:names:specification:ubl:schema:xsd:ApplicationResponse-2::ApplicationResponse##urn:fdc:peppol.eu:poacc:trns:invoice_response:3::2.1",
"display_name": "Peppol Invoice Response transaction 3.0",
"label": "application-response"
}
]
},
{
"id": "0225:333285633",
"scheme": "0225",
"value": "333285633",
"country": "FR",
"names": ["equiptec_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:un:unece:uncefact:data:standard:CrossIndustryInvoice:100::CrossIndustryInvoice##urn:cen.eu:en16931:2017#compliant#urn:peppol:france:billing:cius:1.0::D22B",
"display_name": "France CII Invoice CIUS",
"label": "cross-industry-invoice"
},
{
"identifier": "urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2::CreditNote##urn:cen.eu:en16931:2017#compliant#urn:peppol:france:billing:cius:1.0::2.1",
"display_name": "France UBL Credit Note CIUS",
"label": "credit-note"
}
]
}
],
"company": {
"name": "EQUIP'TEC",
"natural_person": null,
"status": "active"
},
"coverage": {
"directory_as_of": "2026-07-12",
"note": "reachability reflects the public Peppol directory export dated directory_as_of, not a live SMP lookup"
}
},
"provenance": {
"source": "peppol-directory",
"fetched_at": "2026-07-12T20:44:13.847400075Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-12T00:00:00Z" }
}
}
Note the fidelity to the export: the two records carry different declared
names ("EQUIPTEC" vs "equiptec_Sage_Company_FUll_Name") and the second
declares registered_on: "0001-01-01" - relayed as-is, never corrected.
The Sirene echo company, by contrast, carries the official legal name
("EQUIP'TEC"). Each identifier lists its own document types: here the
reform-era 0225 identifier is the one accepting invoices and credit notes.
Example - explicit participant identifier
GET /company/peppol?participant=0002:000126010 (ENERTECH MONACO - a SIREN
with leading zeros whose Luhn check does not pass: only queryable through
the explicit Peppol identifier; no Sirene echo is attempted on this input
form; production store, export of 2026-07-12):
{
"data": {
"reachable": true,
"participants": [
{
"id": "0002:000126010",
"scheme": "0002",
"value": "000126010",
"country": "FR",
"names": ["ENERTECH MONACO"],
"registered_on": "2021-11-10",
"document_types": [
{
"identifier": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
"display_name": "Peppol BIS Billing UBL Invoice V3",
"label": "invoice"
},
{
"identifier": "urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2::CreditNote##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
"display_name": "Peppol BIS Billing UBL Credit Note V3",
"label": "credit-note"
},
{
"identifier": "urn:oasis:names:specification:ubl:schema:xsd:OrderResponse-2::OrderResponse##urn:fdc:peppol.eu:poacc:trns:order_response:3::2.1",
"display_name": "Peppol Order Response transaction 3.0",
"label": "order-response"
}
]
}
],
"coverage": {
"directory_as_of": "2026-07-12",
"note": "reachability reflects the public Peppol directory export dated directory_as_of, not a live SMP lookup"
}
},
"provenance": {
"source": "peppol-directory",
"fetched_at": "2026-07-12T20:44:20.679729822Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-12T00:00:00Z" }
}
}
Sirene identity echo
For siren= / siret= input, data.company echoes the counterparty’s
Sirene identity - { name, natural_person, status } - so the agent can
cross-check who it is addressing without a second call (for the full
identity card, use GET /company/resolve).
- The echo is never attempted for
participant=input: an explicit Peppol identifier can belong to any country. - A SIREN unknown to the Sirene register is never an error:
coverage.identitysays why the echo is missing ("siren_unknown_to_sirene", or"sirene_dataset_unavailable"when the Sirene store itself cannot be read).
Freshness
Answers are served from a snapshot: freshness.as_of is the date of
the ingested Peppol directory export, the same date carried by
coverage.directory_as_of on every response. If a refresh is missed, the
service keeps serving the last good export - honestly dated - and never
turns staleness into a 5xx.
Limits
Stated plainly, so the agent knows what the answer does and does not claim:
- The answer is a photo at the export date - a registration or deregistration after the export is not visible. There is no live SMP lookup in v1.
namesandregistered_onare the participants’ own declarations in the public export - quality varies, and they are relayed as-is, never corrected.- Reachable does not mean compliant or obliged: the endpoint states a directory fact, not a regulatory opinion - being absent from the directory says nothing about a company’s legal e-invoicing obligations.
- An identifier whose embedded SIREN fails the Luhn check (real case:
leading-zero Monegasque SIREN
000126010) is only queryable through an explicitparticipant=identifier. - The Sirene identity echo covers French companies present in the Sirene stock.
Errors
Only requests the service cannot answer leave the 200 range - none of
them are billed. “Absent from the directory” is not one of them: it is a
paid 200 reachable: false. The only business 404 is a siret= that
does not exist in the Sirene register - there, what is missing is the
input, not the answer.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_INPUT | Zero or two of siren/siret/participant; malformed SIREN/SIRET (wrong length, non-digits); participant not of the form <icd>:<value> |
| 400 | INVALID_CHECKSUM | siren=/siret= fails its Luhn check - a probable typo the agent should fix |
| 404 | NOT_FOUND | A well-formed siret= absent from the Sirene register (an establishment number never assigned - same message as /company/resolve); also unknown routes. Never on siren=/participant=, and never for an absence from the Peppol directory |
| 503 | DATA_UNAVAILABLE | The Peppol directory dataset has not been ingested yet - there is no last good export to serve |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "provide exactly one of 'siren', 'siret' or 'participant'", "code": "INVALID_INPUT" }
{ "error": "siret not found in the Sirene register", "code": "NOT_FOUND" }
Attribution
Reachability is answered from the Peppol directory (Peppol Directory,
directory.peppol.eu) - data published by the network’s participants,
attribution OpenPeppol. The identity echo comes from the Sirene
register (INSEE).
See also
GET /company/resolve- the official identity card behind a SIREN/SIRET: legal name, status, legal form, NAF activity, head office.GET /company/vat- live intra-EU VAT number validation against VIES: the other pre-invoicing check on the same counterparty.POST /invoice/generate- generate the EN 16931 e-invoice once the counterparty’s reachability is confirmed.POST /invoice/validate- deterministic EN 16931 validation of the invoice document itself, including the?ruleset=peppolpack (Peppol BIS Billing 3.0): validate the BIS invoice, then check the Peppol participant here - the verdict judges the document, this route judges the participant’s reachability.- For agents - discovery surfaces, the live
/catalogand how settlement works.