Peppol Identifiers
Every participant on the Peppol network has a unique identifier. Understanding how these identifiers work is essential for successful document exchange.
Identifier Structureโ
A Peppol identifier consists of two parts:
{scheme}:{identifier}
For example:
0106:12345678โ Netherlands Chamber of Commerce (KVK) number9930:DE123456789โ German VAT number0009:12345678900017โ French SIRET number
Common Identifier Schemesโ
The scheme code decides what the value means, so the wrong code makes an otherwise correct number unusable. Every name below is taken verbatim from OpenPeppol's published Electronic Address Scheme (EAS) code list, which is the only authority for what a code means. The tables cover the codes you are most likely to meet in Europe; the published list has 83.
The format of the value is set by whichever register or tax authority issues it, not by Peppol. This page states a format only where GoRoute itself enforces one โ see Format Rules below โ rather than guessing the rest.
European Schemesโ
| Scheme | Official name | Identifies |
|---|---|---|
0007 | Organisationsnummer (Swedish legal entities) | ๐ธ๐ช Sweden |
0088 | Global Location Number (EAN/GLN) | ๐ Global |
0096 | The Danish Business Authority โ P-number (DK:P) | ๐ฉ๐ฐ Denmark, a production unit |
0106 | Association of Chambers of Commerce and Industry in the Netherlands (KVK) | ๐ณ๐ฑ Netherlands |
0130 | Directorates of the European Commission | ๐ช๐บ EU institutions |
0184 | The Danish Business Authority โ CVR-number (DK:CVR) | ๐ฉ๐ฐ Denmark, the company itself |
0190 | Organisatie-identificatienummer (OIN) | ๐ณ๐ฑ Netherlands, public bodies |
0192 | Organisasjonsnummer | ๐ณ๐ด Norway |
0195 | Singapore Nationwide E-Invoice Framework (UEN) | ๐ธ๐ฌ Singapore |
0196 | Icelandic identifier (kennitala) | ๐ฎ๐ธ Iceland |
0198 | The Danish Business Authority โ SE-number (DK:SE) | ๐ฉ๐ฐ Denmark, a VAT registration |
0200 | Legal entity code (Lithuania) | ๐ฑ๐น Lithuania |
0201 | Codice Univoco Unitร Organizzativa iPA | ๐ฎ๐น Italy, public bodies |
0204 | Peppol-Leitweg-ID | ๐ฉ๐ช Germany, public bodies |
0208 | Numero d'entreprise / ondernemingsnummer (CBE/KBO) | ๐ง๐ช Belgium |
0209 | GS1 identification keys | ๐ Global |
0210 | Codice Fiscale | ๐ฎ๐น Italy |
0211 | Partita IVA | ๐ฎ๐น Italy |
0216 | OVTcode | ๐ซ๐ฎ Finland |
0221 | The registered number of the qualified invoice issuer | ๐ฏ๐ต Japan |
0230 | National e-Invoicing Framework (Malaysia) | ๐ฒ๐พ Malaysia |
0204 is the German Leitweg-ID, not a German VAT number โ German VAT is 9930. 0209
is GS1, not the Leitweg-ID. 0211 is the Italian Partita IVA, not an IPA code โ the
Italian public-administration code is 0201.
VAT-based Schemesโ
These are the 99xx codes. Most of them are VAT numbers, but not all: three that this page
used to list as national VAT schemes are something else entirely, and are named here as what
they are.
| Scheme | Official name | Identifies |
|---|---|---|
9910 | Hungary VAT number | ๐ญ๐บ Hungary |
9914 | รsterreichische Umsatzsteuer-Identifikationsnummer | ๐ฆ๐น Austria |
9918 | S.W.I.F.T. โ a bank identifier, not a VAT scheme | ๐ Global |
9919 | Kennziffer des Unternehmensregisters โ the Austrian company-register number, not a VAT scheme | ๐ฆ๐น Austria |
9920 | Agencia Espaรฑola de Administraciรณn Tributaria | ๐ช๐ธ Spain |
9922 | Andorra VAT number | ๐ฆ๐ฉ Andorra |
9923 | Albania VAT number | ๐ฆ๐ฑ Albania |
9924 | Bosnia and Herzegovina VAT number | ๐ง๐ฆ Bosnia and Herzegovina |
9925 | Belgium VAT number | ๐ง๐ช Belgium |
9926 | Bulgaria VAT number | ๐ง๐ฌ Bulgaria |
9930 | Germany VAT number | ๐ฉ๐ช Germany |
9931 | Estonia VAT number | ๐ช๐ช Estonia |
9944 | Netherlands VAT number | ๐ณ๐ฑ Netherlands |
9957 | French VAT number | ๐ซ๐ท France |
9959 | US Employer ID Number โ not a VAT scheme | ๐บ๐ธ United States |
Codes this page used to list that the EAS list no longer carries. Six codes that earlier revisions of this page presented as current have been withdrawn from the EAS list. They are kept here, labelled, rather than deleted, because you will still find them in older integrations and in counterparties' existing registrations.
| Scheme | What it was used for | On the current list |
|---|---|---|
0213 | Estonian registry code | Estonian participants appear as 9931 (VAT number) |
0215 | Finnish Y-tunnus | 0216 (OVTcode) is the Finnish code on the list |
9906 | Italian VAT, IT + 11 digits | 0211 (Partita IVA) |
9917 | Dutch VAT | 9944 (Netherlands VAT number) |
9955 | Swedish VAT | No Swedish VAT code is on the list; use 0007 (Organisationsnummer) |
9956 | Belgian VAT, BE + 10 digits | 0208, the enterprise number, which Belgium mandates for participants a service provider registers |
GoRoute's own invoice validation still accepts five of the six, so a document carrying one is
not rejected for that reason alone. The exception is 0215: it is not in GoRoute's accepted
scheme list, and an invoice addressed with it fails EDEC_001 ("Invalid Peppol participant
scheme"). Before you rely on any of the six, confirm which scheme the participant is actually
registered under with participant lookup โ do not assume.
Validating Identifiersโ
Earlier revisions of this page documented a GET route under /api/v1 that checked a
scheme and identifier for format validity and returned valid, scheme_name and
format_description. No such endpoint exists, and nothing in the API returns those
fields.
Check identifier format in your own code against the format rules below. To find out whether an identifier is actually registered on the network, use participant lookup.
Identifier format is enforced only as part of validating a whole document โ for example the
country layer's COUNTRY_AU_001 (ABN must be 11 digits) and COUNTRY_AU_002 (ABN checksum)
on POST /api/v1/invoices/validate. There is no way to ask about an identifier on its own.
Looking Up Participantsโ
Check whether a participant is registered on Peppol:
import requests
response = requests.get(
"https://app.goroute.ai/peppol-api/api/v1/participants/lookup",
params={"peppol_id": "0106:12345678"},
headers={"X-API-Key": "your_api_key"},
)
participant = response.json()
if participant["found"]:
print("Confirmed:", participant["participant_id"], participant["country"])
print("Can receive:", participant["capabilities"])
else:
# found: false means the network did not confirm the participant. That
# covers both "not registered" and "no registry answered", which this
# endpoint reports identically โ so do not tell anyone their identifier
# is wrong on the strength of it.
print("Not confirmed:", participant["message"])
scheme:value formThe lookup takes a single peppol_id (or its alias identifier) containing a colon โ
0106:12345678. Passing scheme and identifier as two separate parameters does not
work; a value without a colon returns 400 with
Provide peppol_id as scheme:value, e.g. 0248:OM1100099003.
Identifier Requirementsโ
Format Rulesโ
Each scheme has specific format requirements:
| Scheme | Length | Format | Check Digit |
|---|---|---|---|
0106 | 8 | Numeric only | No |
0192 | 9 | Numeric only | Modulo 11 |
0208 | 10 | Numeric only | Modulo 97 |
9930 | 11 | DE + 9 digits | Yes |
Common Validation Errorsโ
- Including country prefix when not required (e.g.,
NLin KVK numbers) - Omitting country prefix when required (e.g.,
DEin German VAT) - Wrong number of digits
- Including spaces or special characters
Best Practicesโ
1. Validate Earlyโ
Always validate identifiers before storing:
import re
# Format rules are yours to enforce โ see the table above.
SCHEME_FORMATS = {
"0106": re.compile(r"^\d{8}$"), # NL KVK
"0192": re.compile(r"^\d{9}$"), # NO Org.nr
"0208": re.compile(r"^\d{10}$"), # BE CBE
}
def is_well_formed(scheme: str, identifier: str) -> bool:
"""Check identifier format locally. There is no API for this."""
pattern = SCHEME_FORMATS.get(scheme)
return bool(pattern and pattern.match(identifier))
def network_confirmed(peppol_id: str) -> bool:
"""Whether the network confirmed the participant.
peppol_id must be in scheme:value form. This is not the same question as
"is this participant registered": False also comes back when no registry
could be reached, and the lookup reports the two identically. Use it to
decide whether to send, never to tell someone their identifier is wrong.
"""
response = requests.get(
f"{API_BASE}/api/v1/participants/lookup",
params={"peppol_id": peppol_id},
headers={"X-API-Key": API_KEY},
)
return response.json().get("found", False)
A False from network_confirmed is a reason to hold the document and try again later. See
Participant Lookup for why the two cases cannot be told
apart.
2. Normalize Before Storageโ
Store identifiers in a consistent format:
def normalize_identifier(scheme: str, identifier: str) -> str:
"""Normalize identifier for storage."""
# Remove spaces and convert to uppercase
identifier = identifier.replace(" ", "").upper()
# Remove common separators
identifier = identifier.replace("-", "").replace(".", "")
return identifier
3. Use the Correct Schemeโ
Different schemes may cover the same organization:
# A Dutch company might be registered with:
# - 0106 (KVK number) - Preferred for Peppol
# - 0190 (OIN) - Government organizations
# - 9944 (Netherlands VAT number) - the VAT scheme on the current EAS list
# (9917 is the withdrawn predecessor; older registrations still use it)
# Always check which scheme the receiver is registered with
Scheme Selection Guideโ
When should you use which scheme?
| Country | B2B | B2G | Recommended |
|---|---|---|---|
| ๐ณ๐ฑ Netherlands | 0106 | 0190 | 0106 (KVK) |
| ๐ฉ๐ช Germany | 9930 | 0204 | 9930 (VAT) for business buyers; the Leitweg-ID (0204) your public-sector buyer gives you |
| ๐ง๐ช Belgium | 0208 | 0208 | 0208 (enterprise number) โ the scheme Belgium requires for participants GoRoute registers |
| ๐ซ๐ท France | 0009 | 0009 | 0009 (SIRET) |
| ๐ฎ๐น Italy | 0210 or 0211 | 0201 | 0211 (Partita IVA) for business buyers; 0201 (Codice Univoco iPA) for public bodies |
| ๐ณ๐ด Norway | 0192 | 0192 | 0192 (Org.nr) โ the only scheme Norway allows |
| ๐ธ๐ช Sweden | 0007 | 0007 | 0007 (Org.nr) |
API Referenceโ
Validate identifierโ
There is no such endpoint. See Validating Identifiers above.
Lookup participantโ
GET /api/v1/participants/lookup
Requires the participants:read permission.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
peppol_id | string | Yes (or identifier) | The full identifier in scheme:value form, e.g. 0106:12345678 |
identifier | string | Accepted as an alias for peppol_id | Same scheme:value form |
A value with no colon returns 400.
Response:
{
"found": true,
"participant_id": "0106:12345678",
"name": "Example BV",
"country": "NL",
"capabilities": [
"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"
],
"message": null
}
The response is flat โ there is no nested participant object, no registered_at, and
the document types are in capabilities, not document_types. When the participant is not
found, found is false and message explains why; the call still returns 200.