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= or siren= 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.

ParameterTypeRequiredDescription
sirenstringone of9-digit French SIREN (Luhn-checked); its existence is checked in the Sirene register first (unknown -> 404, not billed), then it queries the French index
siretstringone of14-digit French SIRET (Luhn-checked); its existence is checked in the Sirene register first (unknown → 404, not billed), then matched by its SIREN prefix
participantstringone ofExplicit 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 SIREN 000126010 (ENERTECH MONACO) is only reachable through participant=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 0002 or 0225),
  • 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_of is the date of the ingested directory export, the same date as coverage.directory_as_of.
  • provenance.source: peppol-directory - the public Peppol directory export, nothing else was consulted.

Fields of data

FieldTypeDescription
reachableboolAt least one directory record was found for the queried identifier(s)
participantsarrayOne entry per directory record - empty when reachable is false
companyobjectMinimal Sirene identity echo: { name, natural_person, status }; present for siren=/siret= input only - see below
coverageobjectdirectory_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[]:

FieldTypeDescription
idstringFull Peppol participant identifier, scheme:value
schemestring4-digit ISO 6523 ICD scheme (0009, 0002, 0225, …)
valuestringIdentifier value under that scheme
countrystringCountry declared in the directory
namesarrayNames as declared by the participant in the export - relayed as-is, never corrected
registered_onstringRegistration date as declared - a 0001-01-01 is the participant’s own declaration, relayed as-is
document_typesarrayDeclared 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_of dates the export behind the answer;
  • coverage.note spells 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. Note that this very SIREN is the demonstration: it exists, so it is billed, list empty. The 404 below answers the other question entirely: an identifier that does not exist at all.

The one 404: an identifier that does not exist

Absence from the directory is a paid answer. Absence from reality is not. Both French input forms are checked against the Sirene register before the directory is queried: a Luhn-valid siret= whose establishment was never assigned by INSEE, and a Luhn-valid siren= naming a legal unit INSEE never assigned, are 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" }

{ "error": "siren 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:

RequestAnswer
?siret=39440693800008 - Luhn-valid, NIC never assigned by INSEE404 NOT_FOUND, not billed - word for word the answer of /company/resolve
?siret=39440693800149 - the real head office200 reachable: true, billed - 0225:394406938_0073_replyto, strictly identical to ?siren=394406938

The siren= form works the same way: ?siren=000000000 passes the Luhn check (its digits sum to zero) but names no company, so it is a 404, not billed - while ?siren=902120096, a real sole trader simply absent from the Peppol directory, remains a billed 200 reachable: false.

Three boundaries keep this rule narrow:

  • participant= can never 404. An explicit Peppol identifier can name a participant of any country - there is no French register entry to verify, so this input form is entirely outside the check. It is an explicit directory key: the only possible answers are a record, or reachable: false.
  • The 404 is about the input, never about the directory. An identifier that exists but is not registered on Peppol stays a billed 200 reachable: false, dated by coverage.directory_as_of - that is the answer this route sells, and nothing above narrows it.
  • A 404 is never invented. Once the identifier exists, the lookup is the one described above, unchanged (a SIRET is matched by its SIREN prefix, same response as the equivalent siren=). The check is the Sirene identity lookup this route already performed, simply moved ahead of the directory read - no new source, no added cost. 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.
  • On siren= input, the echo is always there when the answer is: an unknown SIREN never reaches this point, it is the 404 above.
  • A missing echo is never an error: coverage.identity says why ("sirene_dataset_unavailable" when the Sirene stock itself cannot be read; "siren_unknown_to_sirene" on siret= input, where the establishment exists but its legal unit is absent from the stock), and reachability is served on its own authority.

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.
  • names and registered_on are 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 explicit participant= 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= or a siren= that does not exist in the Sirene register - there, what is missing is the input, not the answer.

StatuscodeCase
400INVALID_INPUTZero or two of siren/siret/participant; malformed SIREN/SIRET (wrong length, non-digits); participant not of the form <icd>:<value>
400INVALID_INPUTUnknown query parameter name - the message names it, suggests the nearest expected one when there is a close match, and lists what the route accepts (siren, siret, participant). Not charged.
400INVALID_CHECKSUMsiren=/siret= fails its Luhn check - a probable typo the agent should fix
404NOT_FOUNDA well-formed siret= or siren= absent from the Sirene register (an establishment or legal unit never assigned by INSEE - same message as /company/resolve); also unknown routes. Never on participant= (any country, no French existence to verify), never for an absence from the Peppol directory, and never invented when the Sirene stock cannot be consulted
503DATA_UNAVAILABLEThe Peppol directory dataset has not been ingested yet - there is no last good export to serve
500INTERNALInternal 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" }

{ "error": "siren 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=peppol pack (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 /catalog and how settlement works.