Build an InvoiceNow (Peppol SG) E-Invoice into PINT SG UBL XML
Skill: Convert invoice data into a Peppol PINT SG UBL Invoice for InvoiceNow
Region: Singapore
Category: E-invoicing — InvoiceNow (IMDA Peppol, IRAS GST)
Does: Takes invoice data (PDF/CSV/JSON/pasted) and produces a Peppol PINT SG UBL 2.1 Invoice transmitted over the InvoiceNow (Peppol) network, with the UEN-based Peppol participant ID, Singapore GST category codes, and 9% GST breakdown.
Standard: Peppol PINT Singapore (SG Billing) — UBL 2.1 Invoice, CustomizationID urn:peppol:pint:billing-1@sg-1
The AI produces the XML business document only. The four-corner delivery, sender/receiver access-point routing, and the IRAS GST InvoiceNow transmission (the additional fifth-corner feed to IRAS for GST-registered businesses) are handled by your Access Point / solution provider. Validate against the current PINT SG rules (BIS/Schematron) before sending — the PINT specification and IRAS transmission scope phase in over 2025–2026.
When this applies
- A Singapore supplier sends a structured e-invoice to a customer over the InvoiceNow (Peppol) network operated under IMDA.
- GST-registered businesses additionally transmit invoice data to IRAS through InvoiceNow under the phased timeline (newly incorporated/voluntary registrants from 2025; broader GST-registered population from 2026 — verify your trigger date).
- Not for pure paper/PDF invoicing, and not for the GST F5 return itself (that is a separate periodic summary — see
tax/build-gst-f5-return.md).
Conversion procedure
- Read the source. Accept a PDF text layer, CSV, JSON, or pasted invoice. Identify supplier, customer, line items, GST treatment, and totals.
- Extract fields. Capture both parties' UEN, the invoice number and issue date, currency (
SGDunless stated), each line's quantity/unit price/GST category, and the document totals. If the supplier or customer UEN, the invoice number, or any line amount is missing, stop and ask — never invent a UEN. - Normalize. Dates to
YYYY-MM-DD. Amounts to a dot decimal, 2 places. Map the GST treatment of every line to a single-letter UBL tax category code (see code tables). Build each Peppol Endpoint ID as scheme0195(SG:UEN) + the UEN value. - Compute. Per line:
LineExtensionAmount = Quantity × PriceAmount(2 dp). Group lines by tax category/rate intoTaxSubtotals.TaxAmount = TaxableAmount × rate(2 dp). ReconcileLegalMonetaryTotal. - Emit the Invoice using the document structure and worked example below as the template.
- Validate against the checklist and the current PINT SG Schematron.
Source → PINT SG UBL field map
| From the source | → Target element / field |
|---|---|
| Customisation (fixed) | cbc:CustomizationID (urn:peppol:pint:billing-1@sg-1) |
| Profile (fixed) | cbc:ProfileID (urn:peppol:bis:billing) |
| Invoice number | cbc:ID |
Issue date → YYYY-MM-DD |
cbc:IssueDate |
| Invoice type (380 commercial) | cbc:InvoiceTypeCode |
| Currency (SGD) | cbc:DocumentCurrencyCode |
| Supplier UEN | cac:AccountingSupplierParty/.../cbc:EndpointID schemeID="0195" and PartyTaxScheme/cbc:CompanyID |
| Supplier name | cac:AccountingSupplierParty/.../cac:PartyLegalEntity/cbc:RegistrationName |
| Customer UEN | cac:AccountingCustomerParty/.../cbc:EndpointID schemeID="0195" |
| Customer name | cac:AccountingCustomerParty/.../cac:PartyLegalEntity/cbc:RegistrationName |
| GST category per line | cac:ClassifiedTaxCategory/cbc:ID (SR/ZR/ES/OS) |
| GST rate (9) | cac:ClassifiedTaxCategory/cbc:Percent |
| Taxable base by category | cac:TaxSubtotal/cbc:TaxableAmount |
| GST by category | cac:TaxSubtotal/cbc:TaxAmount |
| Line qty × unit price | cac:InvoiceLine/cbc:LineExtensionAmount |
| Totals | cac:LegalMonetaryTotal |
Every required output element appears above. Repeat cac:InvoiceLine per line and cac:TaxSubtotal per distinct category/rate.
Document structure
Invoice
├── cbc:CustomizationID (urn:peppol:pint:billing-1@sg-1)
├── cbc:ProfileID (urn:peppol:bis:billing)
├── cbc:ID (invoice number)
├── cbc:IssueDate (YYYY-MM-DD)
├── cbc:InvoiceTypeCode (380)
├── cbc:DocumentCurrencyCode (SGD)
├── cac:AccountingSupplierParty
│ └── cac:Party
│ ├── cbc:EndpointID schemeID="0195" (supplier UEN)
│ ├── cac:PartyTaxScheme/cbc:CompanyID (GST registration / UEN)
│ └── cac:PartyLegalEntity/cbc:RegistrationName
├── cac:AccountingCustomerParty
│ └── cac:Party
│ ├── cbc:EndpointID schemeID="0195" (customer UEN)
│ └── cac:PartyLegalEntity/cbc:RegistrationName
├── cac:TaxTotal
│ ├── cbc:TaxAmount
│ └── cac:TaxSubtotal ... (one per category/rate)
├── cac:LegalMonetaryTotal
│ ├── cbc:LineExtensionAmount
│ ├── cbc:TaxExclusiveAmount
│ ├── cbc:TaxInclusiveAmount
│ └── cbc:PayableAmount
└── cac:InvoiceLine ... (one per line)
Code tables
GST category (cac:ClassifiedTaxCategory/cbc:ID, UNCL5305)
| Code | Meaning | Typical Percent |
|---|---|---|
SR |
Standard-rated supply | 9 |
ZR |
Zero-rated supply (exports / international services) | 0 |
ES |
Exempt supply (financial services, residential property) | n/a |
OS |
Out-of-scope supply | n/a |
cbc:InvoiceTypeCode (UNCL1001, selected)
| Code | Meaning |
|---|---|
380 |
Commercial invoice |
381 |
Credit note (use the CreditNote document) |
schemeID for SG participant identifiers
| Scheme | Meaning |
|---|---|
0195 |
Singapore UEN (Peppol EAS) — used for Endpoint and Party IDs |
Calculation rules
- Line extension =
Quantity × PriceAmount, rounded to 2 dp. - TaxableAmount per category = Σ of line extensions in that category.
- TaxAmount per category =
TaxableAmount × rate(e.g.100.00 × 9% = 9.00), 2 dp.ZR/ES/OS→0.00. cbc:TaxExclusiveAmount= Σ line extensions;TaxInclusiveAmount= TaxExclusive + total tax;PayableAmount= TaxInclusive − any prepaid.- Amounts carry the
currencyIDattribute (SGD). Recompute every total; flag any mismatch with the source.
Worked example (end-to-end)
Input — pasted invoice
Supplier: Acme Tech Pte Ltd UEN 201912345A (GST registered)
Customer: Buyer Holdings Pte Ltd UEN 200612345B
Invoice INV-2026-0042, dated 14 Jun 2026, SGD
Line 1: Consulting services, 10 hrs @ 100.00 — standard-rated 9%
After extraction + normalization (intermediate)
{
"id": "INV-2026-0042", "issueDate": "2026-06-14", "currency": "SGD",
"supplier": {"uen": "201912345A", "name": "Acme Tech Pte Ltd"},
"customer": {"uen": "200612345B", "name": "Buyer Holdings Pte Ltd"},
"lines": [{"desc": "Consulting services", "qty": 10, "price": 100.00, "cat": "SR", "rate": 9, "net": 1000.00}]
}
Output — PINT SG UBL Invoice
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:CustomizationID>urn:peppol:pint:billing-1@sg-1</cbc:CustomizationID>
<cbc:ProfileID>urn:peppol:bis:billing</cbc:ProfileID>
<cbc:ID>INV-2026-0042</cbc:ID>
<cbc:IssueDate>2026-06-14</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>SGD</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0195">201912345A</cbc:EndpointID>
<cac:PartyTaxScheme>
<cbc:CompanyID>201912345A</cbc:CompanyID>
<cac:TaxScheme><cbc:ID>GST</cbc:ID></cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Acme Tech Pte Ltd</cbc:RegistrationName>
<cbc:CompanyID schemeID="0195">201912345A</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0195">200612345B</cbc:EndpointID>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Buyer Holdings Pte Ltd</cbc:RegistrationName>
<cbc:CompanyID schemeID="0195">200612345B</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="SGD">90.00</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="SGD">1000.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="SGD">90.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>SR</cbc:ID>
<cbc:Percent>9</cbc:Percent>
<cac:TaxScheme><cbc:ID>GST</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="SGD">1000.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="SGD">1000.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="SGD">1090.00</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="SGD">1090.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="HUR">10</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="SGD">1000.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Consulting services</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>SR</cbc:ID>
<cbc:Percent>9</cbc:Percent>
<cac:TaxScheme><cbc:ID>GST</cbc:ID></cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price><cbc:PriceAmount currencyID="SGD">100.00</cbc:PriceAmount></cac:Price>
</cac:InvoiceLine>
</Invoice>
Normalisations shown: 14 Jun 2026 → 2026-06-14; UEN wrapped as schemeID="0195"; line net recomputed 10 × 100.00 = 1000.00; GST recomputed 1000.00 × 9% = 90.00; TaxInclusiveAmount = 1000.00 + 90.00 = 1090.00.
Validation checklist
- All required fields extracted; AI asked about any missing UEN, invoice number, date, or amount (nothing invented)
-
cbc:CustomizationID=urn:peppol:pint:billing-1@sg-1;cbc:ProfileIDset - Both parties carry
EndpointID schemeID="0195"with a valid UEN - Each line has a
ClassifiedTaxCategory(SR/ZR/ES/OS) with the correctPercent - One
cac:TaxSubtotalper category;TaxAmount = TaxableAmount × rate(2 dp) -
LegalMonetaryTotalreconciles: TaxExclusive = Σ lines; TaxInclusive = TaxExclusive + total tax - All amounts carry
currencyID="SGD", 2 dp; datesYYYY-MM-DD - Document validates against the current PINT SG Schematron before transmission
Last updated: 2026-06-13 — verify the active PINT SG specification version, Peppol BIS/Schematron rules, GST rate, and the IRAS GST InvoiceNow transmission timeline before use.