Oman ๐ด๐ฒ
E-invoicing requirements and Peppol compliance for the Sultanate of Oman.
Overviewโ
| Aspect | Details |
|---|---|
| Tax Authority | Oman Tax Authority (OTA) |
| B2G Mandate | Active โ Peppol network |
| B2B Mandate | Phased rollout (2025โ2027) |
| Required CIUS | PINT OM (Official OpenPeppol PINT Oman) |
| Primary Scheme | 0248 (Oman VATIN) |
| Currency | OMR (Omani Rial, 3 decimal places) |
| VAT Rate | 5% standard rate |
| QR Code | Mandatory โ Fawtara TLV format |
| Digital Signature | XAdES-BES enveloped signature |
Oman joined the Peppol network under the OpenPeppol umbrella. The Oman Tax Authority (OTA) mandates Peppol-based e-invoicing with additional national requirements (Fawtara CIUS) on top of standard Peppol BIS 3.0.
Oman PINT OM (Official Schematrons)โ
The official OpenPeppol PINT OM specification replaces the earlier Fawtara CIUS. It includes billing, self-billing, and Tax Data Document (TDD) Schematrons.
Customization IDsโ
<!-- PINT OM Billing -->
<cbc:CustomizationID>urn:peppol:pint:billing-1@om-1</cbc:CustomizationID>
<!-- PINT OM Self-Billing -->
<cbc:CustomizationID>urn:peppol:pint:selfbilling-1@om-1</cbc:CustomizationID>
<!-- TDD (Tax Data Document) -->
<cbc:CustomizationID>urn:peppol:taxdata:om-1</cbc:CustomizationID>
Profile IDsโ
<!-- Billing / Self-Billing -->
<cbc:ProfileID>urn:peppol:bis:billing</cbc:ProfileID>
<!-- Tax Reporting -->
<cbc:ProfileID>urn:peppol:taxreporting</cbc:ProfileID>
Schematron Packsโ
GoRoute runs the official OpenPeppol PINT OM Schematrons compiled with Saxon-HE 12.4:
| Pack | Schematrons | Description |
|---|---|---|
| Billing Invoice | 2 (preprocessed + jurisdiction-aligned) | Standard tax invoice validation |
| Billing Credit Note | 2 | Credit note validation |
| Self-Billing Invoice | 2 | Self-billing invoice validation |
| Self-Billing Credit Note | 2 | Self-billing credit note validation |
| TDD | 1 | Tax Data Document for CTC reporting |
PINT OM documents are auto-detected by the urn:peppol:pint:billing-N@om-N or urn:peppol:pint:selfbilling-N@om-N CustomizationID pattern and skip baseline CEN+BIS Schematrons.
Ruleset 1.0.1 โ the rules that reject invoicesโ
PINT OM 1.0.1 layers 120 Oman-specific rules (IBR-โฆ-OM) on top of Peppol BIS
Billing 3.0. Most are structural and you will never see them. These are the ones
that actually fail in production.
Document type codesโ
| Code | Document | Notes |
|---|---|---|
380 | Commercial invoice | The standard case |
381 | Credit note | Requires a preceding invoice reference โ see IBR-032-OM |
383 | Debit note | Same preceding-invoice requirement |
386 | Prepayment invoice | |
389 | Self-billed invoice | Buyer issues on the supplier's behalf. Restricted by IBR-177-OM |
261 | Self-billed credit note | Requires a preceding reference and is restricted by IBR-177-OM |
BTOM-001 โ invoice transaction typeโ
Every Oman document carries a 20-character bitmap of 1s and 0s with at
least one bit set. It travels in the name attribute of the type code element,
not as a separate field:
<cbc:InvoiceTypeCode name="00100000000000000000">389</cbc:InvoiceTypeCode>
Fifteen of the twenty positions are defined; the rest are reserved. Two examples that appear in the rules verbatim:
| Bitmap | Meaning |
|---|---|
00100000000000000000 | Self-billed invoice / credit note |
00000000100000000000 | Invoice for import of services under reverse charge |
IBR-001-OM enforces the shape โ exactly 20 characters, [01] only. Several
other rules then key off individual positions, so a structurally valid bitmap
with the wrong bit set still fails.
IBR-032-OM โ the one that catches everyoneโ
Fatal. If the type code is 381, 383 or 261, the document MUST carry
all three of the preceding invoice's reference, issue date and UUID. Missing
any one is a hard rejection:
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>INV-2026-00042</cbc:ID> <!-- IBT-025 -->
<cbc:IssueDate>2026-07-14</cbc:IssueDate> <!-- IBT-026 -->
<cbc:UUID>7d4f1e2a-...-9c3b</cbc:UUID> <!-- BTOM-031 -->
</cac:InvoiceDocumentReference>
</cac:BillingReference>
The UUID is the one people omit, because BIS Billing 3.0 does not ask for it. It must be the original invoice's UUID, not a fresh one.
IBR-177-OM โ self-billing is not general purposeโ
A self-billed invoice 389 or self-billed credit note 261 is only valid when
BTOM-001 is one of a short list: self-billed, import of services under reverse
charge, profit-margin self-billing, or import of goods. Self-billing outside
those cases is rejected โ this is a policy rule, not a formatting one.
IBR-002-OM โ deterministic UUIDsโ
The invoice UUID must be a version 5 UUID, not random. Re-deriving the same invoice must produce the same UUID, which is what makes the preceding-invoice reference in IBR-032-OM verifiable.
IBR-034-OM โ non-OMR invoicesโ
If the invoice currency (IBT-005) is not OMR, the VAT accounting currency
(IBT-006) MUST be present.
Zero-rating and exemption reason codesโ
A zero-rated or exempt line needs more than a Z or E category โ it needs a
reason code from the Oman code list. Without one, validation fails.
| List | Codes | Used for |
|---|---|---|
VATZR-OM-01 โฆ VATZR-OM-16 | 16 | Zero-rating reasons |
VATEX-OM-01 โฆ VATEX-OM-12 | 12 | Exemption reasons |
<cac:TaxCategory>
<cbc:ID>Z</cbc:ID>
<cbc:Percent>0</cbc:Percent>
<cbc:TaxExemptionReasonCode>VATZR-OM-09</cbc:TaxExemptionReasonCode>
<cbc:TaxExemptionReason>Export of service</cbc:TaxExemptionReason>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
Two that matter most to importers and exporters:
VATZR-OM-09โ export of serviceVATZR-OM-12โ re-export of goods
Medicines and Ministry-of-Health-approved medical equipment are zero-rated under Ministerial Decision 59/2021, made under Articles 51โ53 of Sultani Decree 151/2020.
POST /api/v1/invoices/validate/deep runs the full PINT OM 1.0.1 Schematron and
returns every rule that fired, by identifier. Cheaper than a rejection, and the
free sandbox is provisioned within 24 hours.
Identifier Scheme โ 0248โ
Oman uses scheme 0248 (Oman VATIN) for participant identification, and the
identifier keeps its OM prefix.
# Oman participant
participant = {
"scheme": "0248",
"identifier": "OM1100012345",
"name": "Oman Trading Company LLC",
"country": "OM"
}
Peppol ID Formatโ
0248:OM<10-digit-VATIN>
Earlier revisions of this page said 9959. That is wrong for Oman and it is a
costly mistake: 9959:1100012345 is not a registered participant, so the SMP
lookup returns nothing and delivery fails with a transport error rather than a
validation message. The authoritative sources are register-oman-tsmp.py
(โScheme : 0248 (Oman VATIN). Participant id:
iso6523-actorid-upis::0248:OM<VATIN>โ), the OTA sample invoices, and the live
SMP registrations.
Example: 0248:OM1100012345
SMP Lookupโ
# Verify an Oman participant exists on Peppol
curl "https://smp-test.goroute.ai/iso6523-actorid-upis::0248:OM1100012345" # registered, resolves
Currency โ OMR (3 Decimal Places)โ
Oman uses the Omani Rial (OMR) which has 3 decimal places โ unlike EUR/USD which have 2.
<!-- All amounts use 3 decimal places -->
<cbc:TaxAmount currencyID="OMR">50.000</cbc:TaxAmount>
<cbc:TaxInclusiveAmount currencyID="OMR">1050.000</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="OMR">1050.000</cbc:PayableAmount>
Always use 3 decimal places for OMR amounts (e.g., 1050.000, not 1050.00). This applies to all monetary fields in the invoice including line items, tax amounts, and totals.
VAT in Omanโ
Oman introduced VAT at 5% in April 2021.
| Category | Rate | Code | Description |
|---|---|---|---|
| Standard | 5% | S | Most goods and services |
| Zero-rated | 0% | Z | Exports, certain food items, healthcare |
| Exempt | 0% | E | Financial services, residential rent |
Z and E are not enough on their ownA zero-rated or exempt line must also carry a reason code โ see
Zero-rating and exemption reason codes.
A Z category with no VATZR-OM-* code fails validation.
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>5</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
QR Code Requirements (Fawtara)โ
Oman mandates a Fawtara QR code on all invoices. The QR contains TLV (Tag-Length-Value) encoded invoice data that allows offline verification.
TLV Fieldsโ
| Tag | Field | Description | Example |
|---|---|---|---|
| 1 | Seller Name | Seller's registered name (UTF-8) | Al Haya Trading LLC |
| 2 | VAT Number | Seller's tax registration number | OM1234567890 |
| 3 | Timestamp | ISO 8601 issue date/time | 2026-02-08T12:00:00Z |
| 4 | Total with VAT | Tax-inclusive amount (3 decimals) | 1050.000 |
| 5 | VAT Amount | Total VAT (3 decimals) | 50.000 |
| 6 | Invoice Hash | SHA-256 digest (base64) | A1B2C3... |
TLV Encodingโ
Each field is encoded as: Tag (1 byte) + Length (1 byte) + Value (UTF-8 bytes)
Tag 1: [20] = Al Haya Trading LLC
Tag 2: [12] = OM1234567890
Tag 3: [20] = 2026-02-08T12:00:00Z
Tag 4: [8] = 1050.000
Tag 5: [6] = 50.000
Tag 6: [44] = <base64 SHA-256 hash>
The concatenated TLV bytes are base64-encoded and embedded in the UBL XML.
QR Embedding in UBLโ
The QR data is stored in an AdditionalDocumentReference with ID QR:
<cac:AdditionalDocumentReference>
<cbc:ID>QR</cbc:ID>
<cac:Attachment>
<cbc:EmbeddedDocumentBinaryObject mimeCode="text/csv" filename="QR">
AQxBbCBIYXlhIExMQwIKT00xMjM0NTY3ODkw...
</cbc:EmbeddedDocumentBinaryObject>
</cac:Attachment>
</cac:AdditionalDocumentReference>
Invoice Hashโ
A SHA-256 hash of the invoice's business content is also embedded:
<cac:AdditionalDocumentReference>
<cbc:ID>HASH</cbc:ID>
<cac:Attachment>
<cbc:EmbeddedDocumentBinaryObject mimeCode="application/octet-stream" filename="HASH">
kX5xVq2F8a3pZ...base64-encoded-sha256...
</cbc:EmbeddedDocumentBinaryObject>
</cac:Attachment>
</cac:AdditionalDocumentReference>
There are two paths and they behave differently. Check which one you are on before you decide whether to generate the QR code, hash and signature yourself.
Documents GoRoute renders for you โ POST /api/v1/invoices, drafts, the ERP connectors and the pollers. GoRoute composes the UBL from the data you supply, so the Oman signing pipeline runs over that rendered output and adds the compliance elements, when Oman signing is configured for your environment. See What GoRoute adds on the rendered-document paths below.
Documents you submit as final UBL โ POST /api/v1/documents. The XML you send is transmitted exactly as you submitted it. GoRoute adds no QR code, no hash, no cac:Signature and no ext:UBLExtensions on this path. If your invoice needs those elements, put them in the document before you submit it.
That difference is deliberate. A caller posting final UBL has already produced the complete business document, and altering it would break two things you depend on: the copy you archive would no longer match what was transmitted and reported, and any envelope or hash you computed over it would be invalidated.
Digital Signatureโ
Oman requires an XAdES-BES enveloped digital signature on invoices:
- Algorithm: RSA-SHA256
- Canonicalization: Exclusive C14N 1.0
- Certificate: PKCS12 keystore (
.p12) - Location: Inside
ext:UBLExtensions(UBL DSig Profile)
<!-- Signature reference in the invoice body -->
<cac:Signature>
<cbc:ID>urn:oasis:names:specification:ubl:signature:Invoice</cbc:ID>
<cbc:SignatureMethod>urn:oasis:names:specification:ubl:dsig:enveloped:xades</cbc:SignatureMethod>
</cac:Signature>
<!-- Actual cryptographic signature in UBLExtensions (first child of Invoice) -->
<ext:UBLExtensions>
<ext:UBLExtension>
<ext:ExtensionContent>
<sig:UBLDocumentSignatures>
<sac:SignatureInformation>
<ds:Signature Id="invoice-signature">
<!-- SignedInfo, SignatureValue, KeyInfo -->
</ds:Signature>
</sac:SignatureInformation>
</sig:UBLDocumentSignatures>
</ext:ExtensionContent>
</ext:UBLExtension>
</ext:UBLExtensions>
Sending via GoRoute APIโ
JSON Format (Recommended)โ
curl -X POST https://app.goroute.ai/peppol-api/api/v1/invoices \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"invoice": {
"document_type": "invoice",
"number": "OM-INV-2026-001",
"issue_date": "2026-02-08",
"due_date": "2026-03-10",
"currency": "OMR",
"seller": {
"name": "Al Haya Trading LLC",
"vat_number": "OM1234567890",
"peppol_id": "0248:OM1100012345",
"address": {
"street": "Sultan Qaboos Street",
"city": "Muscat",
"postal_code": "100",
"country": "OM"
}
},
"buyer": {
"name": "Oman National Corp",
"vat_number": "OM9876543210",
"peppol_id": "0248:OM1100054321",
"address": {
"street": "Al Khuwair Road",
"city": "Muscat",
"postal_code": "200",
"country": "OM"
}
},
"lines": [
{
"description": "IT Consulting Services - January 2026",
"quantity": 160,
"unit": "HUR",
"unit_price": 25.000,
"tax_rate": 5,
"tax_category": "S"
}
],
"totals": {
"net_amount": 4000.000,
"tax_amount": 200.000,
"payable_amount": 4200.000
}
}
}'
Python (requests)โ
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://app.goroute.ai/peppol-api"
payload = {
"invoice": {
"document_type": "invoice",
"number": "OM-INV-2026-001",
"issue_date": "2026-02-08",
"currency": "OMR",
"seller": {
"name": "Al Haya Trading LLC",
"vat_number": "OM1234567890",
"peppol_id": "0248:OM1100012345",
"address": {"country": "OM"}
},
"buyer": {
"name": "Oman National Corp",
"vat_number": "OM9876543210",
"peppol_id": "0248:OM1100054321",
"address": {"country": "OM"}
},
"lines": [
{
"description": "IT Consulting Services",
"quantity": 160,
"unit": "HUR",
"unit_price": 25.000,
"tax_rate": 5,
"tax_category": "S"
}
],
"totals": {
"net_amount": 4000.000,
"tax_amount": 200.000,
"payable_amount": 4200.000
}
}
}
response = requests.post(
f"{BASE_URL}/api/v1/invoices",
headers={"X-API-Key": API_KEY},
json=payload
)
result = response.json()
print(f"Transaction: {result['transaction_id']}")
print(f"Status: {result['status']}")
# On this path GoRoute renders the UBL, so the QR code, hash and
# signature are added for you - when Oman signing is configured.
UBL XML (Direct)โ
You can also submit UBL you have produced yourself. This is a JSON call to POST /api/v1/documents: the XML travels as a string in the document field. You do not POST raw XML, and this endpoint has no application/xml request body.
On this path GoRoute does not alter your document. It is transmitted exactly as you submitted it โ no QR code, no hash and no signature are added. If your invoice needs those elements, generate them before you send.
import json
import requests
with open("oman-invoice.xml", encoding="utf-8") as f:
invoice_xml = f.read()
response = requests.post(
f"{BASE_URL}/api/v1/documents",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={
"receiver_scheme": "0248",
"receiver_id": "OM1100054321",
"document": invoice_xml,
},
)
# The document above is transmitted exactly as submitted.
print(response.status_code, response.json()["transaction_id"])
The same call with curl, reading a JSON body you have already assembled:
curl -X POST https://app.goroute.ai/peppol-api/api/v1/documents \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @request.json
receiver_scheme, receiver_id and document are the required fields; sender_scheme and sender_id default to your organisation. A successful call returns 202 Accepted with a transaction_id. The same request is documented in full under Sending an Invoice.
This page previously told you to POST raw UBL, with a raw-XML content type, to a send path nested underneath /api/v1/documents, and said GoRoute would add the QR code, hash and signature to it. No such endpoint has ever existed, and code written against it has never worked. Use POST /api/v1/documents as shown above, and note that it does not modify your document.
Tax reporting status (TDD and MLS)โ
Oman's CTC protocol produces two artefacts alongside the invoice itself: a Tax Data
Document (TDD) addressed to the Oman Tax Authority, and one or more Message Level Status
(MLS) responses exchanged between access points. Two endpoints expose them. Both require
the transactions:read permission.
Reporting status for one transactionโ
GET /api/v1/transactions/{transaction_id}/reporting
Returns the tax-reporting state of a transaction in a single call.
| Field | Meaning |
|---|---|
transaction_id | The transaction queried. |
clearance_status | One of prepared_awaiting_authority, cleared, failed, reported, reported_via_substitute, awaiting_ota_go_live, or null when no TDD exists yet. |
ota_live | Whether this environment is configured to report to a live tax authority. |
ota_target | The participant identifier Tax Data Documents are addressed to, as scheme:id. |
ota_target_is_testbed | true when ota_target is the OTA test participant. |
note | A plain-language explanation, or null. See below. |
mls_status | The MLS response code if one has arrived, otherwise sent if an MLS was dispatched, otherwise null. |
tdd.transaction_id | The transaction holding the TDD, or null. |
tdd.sent | Enqueued for transmission โ not "arrived". Read together with tdd.held. |
tdd.held | true when the TDD was built, validated and stored but deliberately not transmitted. A held TDD has reached nobody. |
tdd.delivered | true cleared, false failed, null not yet reconciled. |
tdd.scope | Scope of a substitute report, where one was used. |
tdd.document_url | Relative URL of the stored TDD, or null when there is no TDD. |
mls.transaction_id | The MLS we sent, kept for backward compatibility. |
mls.response_code | Response code carried by the MLS. |
mls.document_url | Relative URL of the MLS, or null. |
mls.legs[] | One entry per leg that exists โ leg is sent or received, each with its own transaction_id and document_url. A received leg also carries response_code and received_at. |
substitute.used | true when a substitute participant was reported to. |
substitute.participant | The substitute participant identifier. |
substitute.reason | Why a substitute was used. |
sla_breached | Whether the reporting SLA for this transaction was breached. |
tdd.sent does not mean the tax authority received itsent means enqueued for transmission. A held TDD is enqueued and then deliberately
not transmitted, so tdd.sent and tdd.held are both true at once. Always read the two
together: if tdd.held is true, nothing has reached the tax authority. clearance_status
already applies that precedence for you โ prefer it over inferring the state from tdd.sent.
Why you may see an explanatory noteโ
When clearance_status is anything other than reported, and this environment is pointed
at the OTA test participant or is not configured as live, the response carries a note
explaining that in prose. This is the expected state today rather than a fault: the invoice
itself is delivered to the buyer over Peppol normally, the TDD is still generated and stored
against the transaction, and the original issue time is preserved so the reporting
obligation can be evidenced later. When the note is absent it is null.
GoRoute publishes no date for OTA production reporting. Use ota_live and
ota_target_is_testbed on this endpoint to see what your own environment is pointed at,
rather than assuming.
Fetching an MLS documentโ
GET /api/v1/transactions/{transaction_id}/mls?leg=sent
Returns the MLS as unaltered UBL ApplicationResponse XML, with content type
application/xml. Nothing is reformatted or re-serialised.
| Parameter | Values | Behaviour |
|---|---|---|
leg | sent | The ApplicationResponse GoRoute issued as C3 to the sending access point (C2). |
leg | received | The ApplicationResponse GoRoute received โ from C2 for an invoice you sent, or from C5 for a TDD. |
leg | omitted | The sent leg if one exists, otherwise the received leg. |
A transaction can have both legs at once, which is why they are addressable separately.
Call the reporting endpoint first and read mls.legs[] to see which legs exist before
fetching one.
Errors
| Status | error_code | When |
|---|---|---|
| 404 | TRANSACTION_NOT_FOUND | No such transaction in your organisation. |
| 404 | MLS_NOT_AVAILABLE | The transaction exists but no MLS of the requested leg has been produced. |
| 404 | DOCUMENT_NOT_STORED | The leg exists but its XML is not retrievable from storage. |
import requests
status = requests.get(
f"{BASE_URL}/api/v1/transactions/{transaction_id}/reporting",
headers={"X-API-Key": API_KEY},
).json()
print(status["clearance_status"], status["ota_target"])
if status["note"]:
print(status["note"])
# Fetch a leg only if the reporting call says it exists.
for leg in status["mls"]["legs"]:
xml = requests.get(
f"{BASE_URL}/api/v1/transactions/{transaction_id}/mls",
params={"leg": leg["leg"]},
headers={"X-API-Key": API_KEY},
).text
Example Oman Invoice (Full UBL)โ
<?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">
<!-- Oman PINT OM -->
<cbc:CustomizationID>urn:peppol:pint:billing-1@om-1</cbc:CustomizationID>
<cbc:ProfileID>urn:peppol:bis:billing</cbc:ProfileID>
<cbc:ID>OM-INV-2026-001</cbc:ID>
<cbc:IssueDate>2026-02-08</cbc:IssueDate>
<cbc:IssueTime>12:00:00Z</cbc:IssueTime>
<cbc:DueDate>2026-03-10</cbc:DueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>OMR</cbc:DocumentCurrencyCode>
<cbc:BuyerReference>PO-2026-456</cbc:BuyerReference>
<!-- Seller (Oman company) -->
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0248">OM1100012345</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Al Haya Trading LLC</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Sultan Qaboos Street</cbc:StreetName>
<cbc:CityName>Muscat</cbc:CityName>
<cbc:PostalZone>100</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>OM</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>OM1234567890</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Al Haya Trading LLC</cbc:RegistrationName>
<cbc:CompanyID>1234567890</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingSupplierParty>
<!-- Buyer (Oman company) -->
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0248">OM1100054321</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Oman National Corp</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Al Khuwair Road</cbc:StreetName>
<cbc:CityName>Muscat</cbc:CityName>
<cbc:PostalZone>200</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>OM</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>OM9876543210</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Oman National Corp</cbc:RegistrationName>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:PaymentMeans>
<cbc:PaymentMeansCode>30</cbc:PaymentMeansCode>
</cac:PaymentMeans>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="OMR">200.000</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="OMR">4000.000</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="OMR">200.000</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>5</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="OMR">4000.000</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="OMR">4000.000</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="OMR">4200.000</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="OMR">4200.000</cbc:PayableAmount>
</cac:LegalMonetaryTotal>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="HUR">160</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="OMR">4000.000</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>IT Consulting Services - January 2026</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>5</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="OMR">25.000</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
</Invoice>
What GoRoute adds on the rendered-document pathsโ
This applies to the paths where GoRoute composes the UBL itself โ POST /api/v1/invoices, drafts, the ERP connectors and the pollers โ and only when Oman signing is configured for your environment. It does not apply to UBL you submit to POST /api/v1/documents, which is transmitted exactly as submitted.
On the rendered-document paths the Oman signing pipeline adds the following before Peppol transmission:
- QR Code โ
AdditionalDocumentReference[ID='QR']with TLV-encoded data - Invoice Hash โ
AdditionalDocumentReference[ID='HASH']with SHA-256 digest - Digital Signature โ
ext:UBLExtensionswith XAdES-BES enveloped signature - cac:Signature โ Signature reference element
If a document already carries its own AdditionalDocumentReference[ID='QR'], the pipeline leaves it in place rather than adding a second one.
If you are submitting final UBL, generate whatever elements your invoice needs yourself.
Validationโ
GoRoute applies multi-layer validation to Oman invoices:
| Layer | Check | Rule |
|---|---|---|
| 1 | UBL 2.1 XSD | Structural XML validity |
| 2 | Business rules | Totals, calculations, required fields |
| 3 | eDEC Code Lists | Participant schemes, currencies, process IDs |
| 3B | PINT OM Schematron | Official OpenPeppol jurisdiction rules (IBR-OM) |
| 3C | TDD Schematron | Tax Data Document validation (CTC reporting) |
PINT OM invoices (detected by urn:peppol:pint:billing-N@om-N in CustomizationID) skip the baseline CEN-EN16931 + Peppol BIS Schematrons. They run only the official PINT OM Schematrons, which already incorporate all required European and Peppol rules along with Oman-specific jurisdiction rules.
TDD (Tax Data Document)โ
Oman's CTC (Continuous Transaction Controls) protocol requires a Tax Data Document to be submitted to the Oman Tax Authority within 15 minutes of invoice issuance. GoRoute generates the TDD automatically:
- Root element:
pxs:TaxData(namespaceurn:peppol:schema:om-taxdata:1.0) - Receiving party: Oman Tax Authority (
0242:000773) - Validated against
TDD-OM-peppol-om-tdd.xsltbefore submission - Supports document types: Send, Receive, Withdraw, Forward
Comparison with Saudi Arabia (ZATCA)โ
Oman's Fawtara system is inspired by Saudi Arabia's ZATCA but with key differences:
| Feature | Oman (OTA) | Saudi Arabia (ZATCA) |
|---|---|---|
| Network | Peppol | ZATCA platform |
| Base Standard | Peppol BIS 3.0 | UBL 2.1 (no Peppol) |
| QR Format | TLV (Tags 1-6) | TLV (Tags 1-9) |
| Signature | XAdES-BES | XMLDSIG |
| Currency Decimals | 3 (OMR) | 2 (SAR) |
| Tax Rate | 5% | 15% |
| Clearance | Via Peppol AP | Via ZATCA portal |
| Certificate | Peppol AP cert | ZATCA-issued cert |
Troubleshootingโ
Common Issuesโ
"Amount must have 3 decimal places"
- OMR uses 3 decimal places. Use
1050.000not1050.00.
"Scheme not recognized"
- Ensure you're using the latest eDEC code lists. GoRoute updates these automatically.
"QR code shows gibberish when scanned"
- Fawtara QR codes contain TLV binary data, not plain text. Use a Fawtara-compatible reader or the GoRoute dashboard to verify.
"CustomizationID validation failed"
- Use the exact Oman CIUS CustomizationID shown above. The
#conformant#suffix is required.
Resourcesโ
- GoRoute API Reference โ Full endpoint documentation
- Oman Tax Authority โ Official OTA portal
- OpenPeppol โ Peppol specifications
- GoRoute Dashboard โ Send and manage invoices
Further readingโ
- PINT OM version updates and service-provider readiness
- Oman's Fawtara 5-corner model explained
- Data residency and compliance in Oman
- Oman e-invoicing overview โ pricing and onboarding
- 2026 e-invoicing mandate tracker โ every country, one page
- How to choose a Peppol Access Point
- One API for multi-country e-invoicing
Ready to build? The free developer sandbox gives you test credentials and a registered participant, provisioned within 24 hours on business days. Recorded ERP integration walkthroughs are on the tutorials page.