Check if a Singapore company can receive e-invoices, from one JSON endpoint
Singapore is phasing in a GST e-invoicing mandate (InvoiceNow) between 2026 and 2031. If you build or integrate billing systems here, you will eventually need to answer a very practical question: Can this counterparty a
Singapore is phasing in a GST e-invoicing mandate (InvoiceNow) between 2026 and 2031. If you build or integrate billing systems here, you will eventually need to answer a very practical question:
Can this counterparty actually receive an e-invoice, and in what format?
The Peppol Directory answers it over a plain JSON endpoint. No key, no signup. Here is what I learned wiring it up, including two dead ends worth skipping.
The endpoint
https://directory.peppol.eu/search/1.0/json?country=SG&rpc=N
curl -s "https://directory.peppol.eu/search/1.0/json?country=SG&rpc=N" | jq '."total-result-count"'
# 68205
68,205 Singapore participants as I write this. rpc=N skips the participant-count rollup and makes the response noticeably smaller.
Paging uses resultPageIndex and resultPageCount:
curl -s "https://directory.peppol.eu/search/1.0/json?country=SG&rpc=N&resultPageIndex=0&resultPageCount=3" \
| jq '.matches | length'
# 3
The ID format is the part that trips people up
Singapore participants are identified as:
scheme: iso6523-actorid-upis
value: 0195:sguen<UEN>
0195 is the Peppol issuing-agency code for Singapore's UEN, and the value is lowercase. So UEN 202319825C becomes 0195:sguen202319825c.
Get it wrong and you get a clean, silent zero results — not an error. That cost me longer than I would like to admit.
To look up one company, the two halves are joined by a double colon:
PID="iso6523-actorid-upis::0195:sguen202319825c"
curl -s -G "https://directory.peppol.eu/search/1.0/json" --data-urlencode "participant=$PID" | jq
What comes back
{
"total-result-count": 1,
"matches": [
{
"participantID": { "scheme": "iso6523-actorid-upis", "value": "0195:sguen202319825c" },
"entities": [
{
"name": [{ "name": "XIN TONGYUN LOGISTIC PTE LTD", "language": "en" }],
"countryCode": "SG",
"regDate": "..."
}
],
"docTypes": [ "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#conformant#urn:fdc:peppol.eu:2017:poacc:billing:international:sg:3.0::2.1" ]
}
]
}
Two things worth noting.
name is an array of localised names, not a string. entities[0].name[0].name is the one you usually want.
docTypes is the actually useful field. It tells you what that company can receive — Invoice, CreditNote, ApplicationResponse, and which profile. If you are about to send a credit note to someone who only registered for invoices, this is where you find out first rather than after a rejected transmission.
const canReceiveCreditNote = m.docTypes.some(d => d.includes("CreditNote-2"));
Dead end 1: you cannot call this from the browser
There is no Access-Control-Allow-Origin header on the response:
curl -sI "https://directory.peppol.eu/search/1.0/json?country=SG&rpc=N" | grep -i access-control
# (nothing)
So fetch() from a page is blocked. You need a server-side proxy, or — if your use case is lookup rather than live sync — a periodic snapshot. For a static tool, snapshotting the SG slice and shipping it as a JSON index is far cheaper than proxying every keystroke.
Dead end 2: skip the SML DNS trick
Older Peppol material describes resolving a participant's SMP by MD5-hashing the lowercase participant ID and doing a DNS lookup against an SML zone:
B-<md5>.iso6523-actorid-upis.<sml-zone>
I tried it against every zone still floating around in documentation and blog posts, including the old EC edelivery.tech.ec.europa.eu and the newer participant.sml.prod.tech.peppol.org. All NXDOMAIN, for a participant the Directory API resolves happily.
Unless you are building a full Access Point, the Directory API gives you what you need without touching DNS at all. Do not spend an afternoon on this like I did.
Why this matters right now
Under the IRAS GST InvoiceNow Requirement, newly GST-registered businesses come onboard from 2026, then existing registrants by turnover through 2031. Roughly 90,000 businesses end up in scope. Anyone building AR/AP tooling for the Singapore market is going to be asked "is this customer reachable yet?" — and this endpoint is the cheapest way to answer it.
I built a few free checkers on top of this data for non-technical business owners: is my accounting software InvoiceNow-ready (against the accredited-provider list), and when my business actually has to comply. Plain HTML, no signup, no analytics — the calculation runs in the browser.
If you are integrating Peppol in Singapore and hit something I missed, I would genuinely like to hear it.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.