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 | Mandatory 1 April 2027 above OMR 5,000,000 of annual supplies, 1 October 2027 at or below (Decision No. 189/2026) |
| 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 |
| Invoice UUID | UUIDv5 derived per Solution Reference Architecture v1.0.3 ยง10.2.3 |
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.
When the mandate starts โ Decision No. 189/2026โ
Oman has fixed dates. Decision No. 189/2026 of the Chairman of the Tax Authority amends the Executive Regulations of the VAT Law (Decision 53/2021, issued under Royal Decree 121/2020) and makes electronic tax invoices compulsory in two waves:
| Wave | Mandatory from | Who it binds |
|---|---|---|
| One | 1 April 2027 | Taxpayers whose annual supplies exceed OMR 5,000,000 |
| Two | 1 October 2027 | Taxpayers whose annual supplies are OMR 5,000,000 or below |
The decision replaces Article 143 and the second paragraph of Article 146, and adds Articles 143 bis, 143 bis 1 and 143 bis 2. The amended Article 146 puts the simplified tax invoice on the same timeframes as Article 143, so retail and point-of-sale sit on the same clock as B2B โ there is no separate, later date for consumer-facing invoices.
Read these dates differently from the government adoption percentages you will see on other country pages, such as Australia's. These are deadlines that bind taxpayers. On and after the date that applies to you, an invoice that is not an electronic tax invoice in the approved form does not meet the requirement.
What this page deliberately does not tell you, because we could not source it well enough to publish:
- Which twelve months count towards the OMR 5,000,000 threshold. If you are near the line, ask the Tax Authority which period it measures rather than assuming.
- Penalties. Figures circulate in press coverage that we could not trace to the decision, so we publish none.
- The pilot cohort. Sources disagree on its size and on whether it is voluntary.
For what a document must actually contain, this page is the specification: GoRoute validates every Oman document against the official OpenPeppol PINT OM Schematrons described below.
Source. These dates are published by GoRoute at Oman sets e-invoicing dates: 1 April and 1 October 2027. We have not read the decision in an official gazette or on a Tax Authority site, so treat the article, not this page, as the citation of record.
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 (BTOM-002) must be a version 5 UUID, not random, and the
Solution Reference Architecture v1.0.3 (section 10.2.3) fixes how it is derived:
UUIDv5 in namespace e0bc4ac8-b025-46e5-a76d-0c893fc3027e over seven fields
joined by single spaces โ
IBT-034-1 scheme ยท IBT-034 seller electronic address ยท IBT-003 type code
IBT-001 number ยท IBT-002 date ยท IBT-110 VAT total
IBT-112 total with VAT (+ BTOM-020 total amount due, profit-margin only)
Amounts enter the seed as the exact strings in the XML. The Architecture assigns the derivation to C2, the sending access point, so it is the same value C3 and the Tax Authority derive from the document โ which is what makes the preceding-invoice reference in IBR-032-OM verifiable.
On the rendered paths GoRoute derives it from the finished document. On
POST /api/v1/documents the Schematron only checks the shape, so GoRoute also
checks the value: a cbc:UUID that is not the derivation is refused, with the
expected value in the error, and POST /api/v1/documents/validate reports it as a BTOM-002
error before you send.
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>
Zero-rating reason codes โ CL-10-OMโ
All sixteen codes, in the wording published by the Peppol Authority for Oman.
| Code | Meaning |
|---|---|
VATZR-OM-01 | Qualifying Supply of Food items specified by a decision from the Chairman |
VATZR-OM-02 | Qualifying Supply of Medicines and medical equipment |
VATZR-OM-03 | Supply of investment gold, silver, and platinum |
VATZR-OM-04 | Supplies of international and intra GCC transport of Goods or passengers |
VATZR-OM-05 | Supply of Services related to International and Intra GCC transportation of Goods or passengers |
VATZR-OM-06 | Supply of qualifying air, sea and land means of transport |
VATZR-OM-07 | Supply of rescue planes, and rescue and assistance boats |
VATZR-OM-08 | Supply of oil, oil derivatives and natural gas |
VATZR-OM-09 | Export of service |
VATZR-OM-10 | Direct export of goods |
VATZR-OM-11 | Indirect export of goods |
VATZR-OM-12 | Re-export of goods |
VATZR-OM-13 | Supply from a Special Zone to a Special Zone, or within a Special Zone |
VATZR-OM-14 | Supply from Mainland to Special Zone |
VATZR-OM-15 | Customs Duty Suspension Statuses to a Special Zone |
VATZR-OM-16 | Special Zone to Customs Duty Suspension Statuses |
Exemption reason codes โ CL-05-OMโ
All twelve codes, in the wording published by the Peppol Authority for Oman.
| Code | Meaning |
|---|---|
VATEX-OM-01 | Qualifying Financial Services |
VATEX-OM-02 | Educational Services and Related Goods and Services |
VATEX-OM-03 | Healthcare Services and related Goods and Services |
VATEX-OM-04 | Rental of properties for residential purposes |
VATEX-OM-05 | Local Passenger Transport |
VATEX-OM-06 | Undeveloped land (bare land) |
VATEX-OM-07 | Resale of residential properties |
VATEX-OM-08 | Imported goods supplied to diplomatic and consular bodies, international organizations, and the heads and members of diplomatic and consular bodies |
VATEX-OM-09 | Imports for the Armed Forces and Internal Security Forces |
VATEX-OM-10 | Imported supplies for non-profit charities |
VATEX-OM-11 | Import of returned goods |
VATEX-OM-12 | Imported Goods in cases where the supply of such Goods is exempted from Tax or subject to Tax at a rate of zero percent in the Final Destination Point of Entry |
Where these come from. Both lists are the Peppol Authority for Oman's own published code lists โ CL-10-OM and CL-05-OM, both read on 16 September 2026. They match, row for row, the Oman Tax Authority's own "Oman eInvoicing Data Dictionary" workbook (v1.0.1), which we hold a transcription of. Where the two differ in capitalisation or phrasing, the Authority's wording above is what is published.
Every code in these lists is valid, so validation cannot tell you that you picked the wrong one. An invoice carrying a valid-but-wrong reason code is accepted and files a misdeclared exemption. Choose the code that actually describes the supply, and ask the Oman Tax Authority when none of them fits โ do not pick the nearest.
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. On the invoice that is VATZR-OM-02, "Qualifying Supply of Medicines and
medical equipment" โ not VATZR-OM-01, which is the food-items code.
Export invoices โ which zero-rating codeโ
An export invoice has the export flag in its BTOM-001 transaction type. When you send
invoice data, that means om_invoice_kind includes export, and delivery_country gives
the deliver-to country. A standard-rated export line (category S) needs no reason code.
A zero-rated export line (category Z) needs an export code:
| Code | Use it for | Also needed on the invoice |
|---|---|---|
VATZR-OM-10 | Direct export of goods | โ |
VATZR-OM-11 | Indirect export of goods | โ |
VATZR-OM-12 | Re-export of goods | The supporting document's reference and its UUID (IBR-013-OM), both of them: supporting_document_id and supporting_document_uuid. See Zero-rated export lines, and a re-export's supporting document. Neither Odoo nor Zoho Books has a field for them, so a re-export from either connector is parked with a message telling you to send it through the invoice API or a CSV import instead. |
VATZR-OM-09 | Export of service | service_type_code on the line, an 8-digit code from CL-12-OM (IBR-155-OM), and a deliver-to country other than OM (IBR-012-OM): delivery_country, or the buyer's address country when delivery_country is not set. |
VATZR-OM-04 | International and intra-GCC transport of goods or passengers | โ |
VATZR-OM-05 | Services related to international and intra-GCC transport of goods or passengers | โ |
On an export, GoRoute does not choose a code for you. In particular it never fills in
VATZR-OM-01, which means qualifying food, not "export". A zero-rated export line with no
code, or with a code that does not describe an export, is refused before anything is sent:
POST /api/v1/invoicesanswers withstatus: "validation_failed"and an error with codeBUS_024, whosefieldpoints at the line (lines[n].tax_exemption_reason_code, orlines[n].service_type_code/delivery_countryforVATZR-OM-09).POST /api/v1/invoices/validateandPOST /api/v1/invoices/preview-xmlreport the same error, so you can check before sending.- In a CSV import, that row is not sent and the import job lists it as an error, with the same explanation.
- An invoice pulled from Odoo or Zoho Books with the same problem is parked, with the reason.
The four export codes need an export. VATZR-OM-09, -10, -11 and -12 describe
nothing but an export, so a zero-rated line carrying one of them on a document that is not
marked as an export is refused too โ until now it was filed with the Tax Authority as a
domestic supply zero-rated as an export, and no Schematron rule caught it. GoRoute does not
decide for you which of the two is wrong, even when the buyer is abroad: you either add
export to om_invoice_kind (a CSV or Excel import has the same om_invoice_kind column),
or give the line the code of the domestic zero-rating. The international-transport codes
VATZR-OM-04 and -05 stay allowed on a domestic document.
POST /api/v1/invoices,/validateand/preview-xmlanswer with validation errorBUS_029, whosefieldis the line'slines[n].tax_exemption_reason_code. When the buyer's address is outside Oman, the message says so.- A CSV or Excel import rejects the row with the same sentence.
- A credit note is held to the same rule. So is a document declared through another kind
that cannot be an export (for example
simplifiedorsummary). - The Odoo and Zoho Books connectors mark the export themselves from the customer's country, so for them this means a customer in Oman, or with no country, whose line carries one of the four codes. That invoice is parked, with the remedy in the ERP's terms: set the customer's country if it is abroad, or use a domestic code.
If your invoices come from Odoo or Zoho Books, the code has to reach the invoice through the connector. How to do that, and what happens to an invoice that has no code, is on Connectors.
Zero-rated export lines, and a re-export's supporting documentโ
On an export (om_invoice_kind includes export, so BTOM-001 position 7 is set) a
zero-rated line must say why it is zero-rated with an export code: VATZR-OM-10 direct
export of goods, VATZR-OM-11 indirect export of goods, VATZR-OM-12 re-export of goods,
VATZR-OM-09 export of service, or VATZR-OM-04 / VATZR-OM-05 international transport.
GoRoute does not fill one in for you. A standard-rated export line needs no code.
A re-export of goods (VATZR-OM-12) must also reference its supporting document โ its
reference (IBT-122) and its UUID (BTOM-023). This is the fatal rule IBR-013-OM. Give
both on the invoice, once per document:
| Invoice API field / CSV column | On the e-invoice |
|---|---|
supporting_document_id | cac:AdditionalDocumentReference/cbc:ID (IBT-122) |
supporting_document_uuid | cac:AdditionalDocumentReference/cbc:UUID (BTOM-023) |
{
"invoice": {
"document_type": "invoice",
"om_invoice_kind": "export",
"number": "OM-EXP-2026-0042",
"supporting_document_id": "BOE-2026-0042",
"supporting_document_uuid": "f12f329f-6430-4399-b661-7c5cd9c3a9e6",
"lines": [
{
"description": "Re-exported pumps",
"quantity": 2,
"unit_price": 500,
"tax_category": "zero",
"tax_rate": 0,
"tax_exemption_reason_code": "VATZR-OM-12"
}
]
}
}
(Seller, buyer and totals are left out of the example.) GoRoute writes the two values as one
cac:AdditionalDocumentReference with no cbc:DocumentTypeCode, as in the Tax Authority's
own export sample.
Before you rely on this for a real re-export, send the invoice to
POST /api/v1/invoices/preview-xml and look in the XML that comes back for a
cac:AdditionalDocumentReference whose cbc:ID is your supporting document's reference.
If it is there, you are ready to file. If it is not there, do not file the re-export: the
service you are talking to does not yet accept these two fields, and it will not tell you so โ
a field it does not recognise is ignored rather than refused, so the invoice would be filed
without the reference IBR-013-OM requires. Please tell us if that happens โ see
Support โ and we will get the service brought current.
If either value is missing, POST /api/v1/invoices, POST /api/v1/invoices/validate and
POST /api/v1/invoices/preview-xml answer with validation error BUS_024, and its
field names the one to fill โ supporting_document_id, or supporting_document_uuid
when only the UUID is missing; the message names every one that is empty.
Nothing is rendered or sent. A CSV or Excel import rejects the row with the same sentence.
supporting_document_id cannot be QR or HASH (in any case, with or without spaces), on
any Oman document: GoRoute writes the invoice's QR code, and its hash, as
cac:AdditionalDocumentReference elements with those IDs. Such a value is refused with
validation error BUS_025 against supporting_document_id, and a CSV or Excel import
rejects the row.
supporting_document_uuid must be a UUID written in the usual form: 32 hexadecimal digits
in groups of 8-4-4-4-12, for example f12f329f-6430-4399-b661-7c5cd9c3a9e6. Upper or lower
case is accepted, and so is any UUID version. Any other value is refused with validation error
BUS_026 against supporting_document_uuid, and a CSV or Excel import rejects the row.
That includes the sample in the Tax Authority's data dictionary, which has only seven digits
in its first group.
When the buyer takes only BIS Billing 3.0 and receives a
BIS 3.0 copy, that copy carries the same reference: IBT-122 as
cbc:ID, with the UUID written in cbc:DocumentDescription. BIS 3.0 does not allow a
cbc:UUID element there.
A credit note that GoRoute creates from an invoice (POST /api/v1/invoices/credit-note) copies
several fields from the invoice: om_invoice_kind, the supporting document, and the
details that the invoice's kind requires (the deliver-to country, the import customs details,
the invoicing period, the prepayment reference and the third party). So a credit note of an
export is also an export. These fields are copied only when the seller or the buyer is in
Oman; a credit note of any other country's invoice is built exactly as before.
The Odoo and Zoho Books connectors mark an Omani seller's invoice as an export whenever the
customer's country is outside Oman, the other GCC states included. A customer with an Omani
VAT number (OMโฆ) and a country outside Oman is parked instead, because the two disagree.
So is an Odoo customer whose country code is not an ISO 3166-1 code, since GoRoute cannot
tell whether it is abroad.
Neither ERP has a field
for the supporting document, so a re-export (VATZR-OM-12) from either is parked with a
message that says to send it through the invoice API or CSV import instead. See
Connectors.
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.
Imports โ the customs data is requiredโ
An import you record yourself (om_invoice_kind import_goods or import_services_rcm, through
the API, a CSV or Excel import or the portal) must carry the data the Tax Authority requires.
Supply every value below yourself. Do not leave one out and assume GoRoute will either reject
the document or work the value out โ check, as described under the table.
om_invoice_kind | Required | Field |
|---|---|---|
import_goods | Import date (BTOM-030) | import_date |
import_goods | Customs declaration (bayan) number (BTOM-021) | customs_declaration_number |
import_goods | Incoterms (BTOM-022), as on the declaration | incoterms |
import_goods | Your Importer Customs ID (IBR-153-OM) | buyer.identifier, with buyer.identifier_scheme ICID |
import_goods | Each line's country of origin (IBR-084-OM) | lines[n].origin_country |
import_goods | Whether each line is goods or services (BTOM-019): G with its HS code, or S. A line with an HS code and no item_type is goods | lines[n].item_type (and lines[n].hs_code for goods) |
| both | The foreign supplier's country | seller.address.country |
An import of services under reverse charge (import_services_rcm) needs only the supplier's
country from this list. Everything else in the table applies to an import of goods.
Check before you rely on it. Send the import to POST /api/v1/invoices/validate. A value that
is missing comes back as validation error BUS_031 โ one error per missing value, with field
set to the field named in the table and a message that says which value it is. The same
validation runs on every path that sends: POST /api/v1/invoices, /preview-xml, a CSV or Excel
import row, and sending a draft (refused with HTTP 400, carrying the same sentences). So what
/validate tells you is what the other paths will do with the same document.
If /validate accepts an import that leaves these fields empty, they are being filled in for
you, and you should not file it. The values used are NA as the customs declaration (bayan)
number, CIF as the Incoterms, the issue date as the import date, NA as each line's country of
origin, your own VAT number (or your Peppol identifier) as the Importer Customs ID under scheme
ICID, and Oman as the foreign supplier's country; a line with neither an item_type nor an HS
code is taken as services. Two of those are worse than blank rather than merely wrong: NA is
Namibia's ISO 3166-1 country code, so a line reads as originating in Namibia, and an Importer
Customs ID that is really your VAT number identifies you to the Tax Authority under the wrong
scheme. Supply the values either way.
On a CSV or Excel import, check the supplier columns the same way. The seller of an import is the
foreign supplier, not you. If a row with the seller_* columns left blank is accepted, the
supplier is being merged from your own company profile โ your Omani country, your VAT number and
your Peppol identifier โ and a supplier address with no country is written as US. Fill
seller_name, seller_country and seller_identifier on every import row.
Name the foreign invoice the import recordsโ
This is separate from the table above, and it is important that you read it that way. The table lists what the Oman Tax Authority requires. The three fields below are GoRoute's own requirement, not the Authority's: nothing in an Oman Tax Authority publication asks for them. We ask for them so that one foreign invoice is recorded once. Your organisation, the foreign supplier's identifier, and the foreign invoice's own number and date together identify a single foreign invoice, which is how we can tell a genuine second import from the same import keyed twice.
They apply to both import kinds โ import_goods and import_services_rcm.
| Field | What to put in it |
|---|---|
foreign_invoice_number | The foreign supplier's own invoice number for the invoice this import records. Up to 150 characters. |
foreign_invoice_date | The issue date of that foreign supplier's invoice. |
supplier_identifier | The foreign supplier's identifier as its own invoice states it โ for example its tax number or its company registration number. Up to 100 characters. |
Today a missing value is accepted, not refused. If you leave one of the three out, the document goes through exactly as it did before and GoRoute logs a line on our side naming what was missing. You will see nothing in the API response, so do not treat a successful send as evidence that you supplied them.
That is planned to change. When enforcement is switched on, an import that leaves any of the
three empty is refused with validation error BUS_032 โ one error per missing field, with
field set to the field's own name and a message saying which value is missing. Supplying all
three now is what stops that change from breaking your integration later. GoRoute will tell
integrators that post imports before the switch is made.
Nothing is written into the XML from them yet. They are a reference we hold against the record; they do not appear in the UBL document that goes to the Tax Authority, so adding them changes nothing about the document you are already sending.
On a CSV or Excel import, the columns are foreign_invoice_number (also accepted as
foreign_invoice_no or supplier_invoice_number), foreign_invoice_date (also
supplier_invoice_date) and supplier_identifier (also foreign_supplier_identifier).
supplier_identifier is not the seller_identifier columnThey are different things and both belong on an import row. seller_identifier โ named at the end
of the section above โ identifies the foreign supplier as a party on the document: it is
required by rule IBR-007-OM, it is paired with seller_identifier_scheme from the Oman code list
CL-06-OM, and it is written into the UBL. supplier_identifier is the identifier that supplier
prints on the invoice you are recording, copied as it appears there; it stays with the record
and is not written into the UBL. Filling one does not fill the other.
An import that reaches you over Peppolโ
The Tax Authority's rule is in the Solution Architecture v1.0.3, section 6.5 and Table 3 row 12. A foreign supplier's invoice that reaches an Omani buyer over Peppol cannot itself be used to produce the tax data document (TDD). Instead, the buyer's service provider, as the receiving access point (C3), builds a separate self-billed PINT OM invoice from it. It never sends that invoice to the supplier, and reports the TDD of the self-billed invoice to the Tax Authority. The foreign invoice itself is never reported. This applies to an import of goods and to an import of services under reverse charge alike.
The self-billed invoice needs data the foreign invoice does not carry: for an import of goods,
the customs data in the table above. A received import therefore
waits for that data before anything is filed โ its status is awaiting_buyer_data โ and you
supply the missing data through the imports API below.
If you both receive imports over Peppol and record them yourself, you do not need to contact support to avoid a double report. One foreign invoice is filed once: your organisation together with the foreign supplier's identifier and that invoice's own number and date identifies a single foreign invoice, and a second record of the same one is given the first one's Tax Data Document. The three fields that make that work are described under Name the foreign invoice the import records.
The imports API โ see your imports and complete the ones that are waitingโ
Three calls drive the whole path. Every one of them needs your API key.
| Call | What it does |
|---|---|
POST /api/v1/oman/imports | Records a foreign supplier's invoice you received as a PDF or by email, so GoRoute files it as a self-billed import |
GET /api/v1/oman/imports | Lists your imports, newest first โ the ones received over Peppol and the ones you recorded โ each with its status, the reason it is waiting and what is missing |
PATCH /api/v1/oman/imports/{transaction_id} | Supplies the data an import is waiting for, after which GoRoute files it |
Check whether the path is open for you, in one callโ
Call GET /api/v1/oman/imports and read the status code. It answers the question for the exact
GoRoute platform you are calling, which is the only answer worth having:
| Status | What it means |
|---|---|
200 | Oman imports are switched on for you. The body lists your imports; an empty items list simply means you have none yet |
409 with error_code IMPORT_SB_NOT_LIVE | GoRoute has built the path but it is not switched on for your platform yet. Nothing you send is recorded and nothing is filed |
404 | The Oman pack is not enabled on the host you called at all. Check the base URL |
Treat that check as authoritative over anything this page says about availability.
Recording a foreign invoice yourselfโ
POST /api/v1/oman/imports does not take a bare invoice. The body is a wrapper: the invoice
goes under invoice, and the foreign invoice's PDF goes beside it under pdf. Both are required.
{
"invoice": { "om_invoice_kind": "import_goods", "number": "SB-2026-0041" },
"pdf": { "filename": "supplier-inv-4471.pdf", "content_base64": "JVBERi0xLjQK..." },
"rate": 0.385,
"rate_date": "2026-09-28"
}
invoice(required) โ the import as an ordinary invoice body, whoseom_invoice_kindisimport_goodsorimport_services_rcm. It carriesforeign_invoice_number,foreign_invoice_dateandsupplier_identifierโ the three fields described above โ and the customs data from the table above.pdf(required) โ the foreign invoice as you received it, as an object withfilenameandcontent_base64(the PDF, base64 encoded). GoRoute attaches it to the record for you and sends it to no one: the self-billed PINT OM document GoRoute files carries no attachments. Do not put the PDF in the invoice's ownattachmentsinstead โpdfis a field of the request, not of the invoice, and a body without it is refused.rateandrate_dateโ for an invoice not in OMR:rateis OMR per one unit of the invoice's currency. Both sit besideinvoice, not inside it. GoRoute looks no rate up. Without one the import is refused.
A successful call answers 202. The record is issued and its one job is the tax report โ
nothing is transmitted to the foreign supplier.
Anything the Tax Authority would refuse comes back as 422 with every finding named, and
nothing is recorded. That is deliberate: a recorded import never sits waiting for data this API
cannot take.
Completing an import that is waitingโ
GET /api/v1/oman/imports?status=awaiting_buyer_data lists only the imports waiting for you.
limit (1โ200, default 50) and offset page the list. Each entry gives you transaction_id,
source (received over Peppol, or recorded by you), status, reason_code, reason, the
findings it is waiting on, the three foreign-invoice fields, tdd_transaction_id once a report
exists, deadline_at, and before_reporting_cutoff.
Send the missing data to PATCH /api/v1/oman/imports/{transaction_id}. Only the fields you give
are changed. For an import received over Peppol you can supply the customs data, your Importer
Customs ID, the VAT due (per line where it differs), the exchange rate for a document not in OMR,
your own self-billing number and the self-billed import a credit note corrects. For an import you
recorded yourself, the call takes the customs data, each line's origin, HS code and goods/services
marker, and the exchange rate โ the other fields belong to a received invoice only and are
refused with 422.
Both kinds also take what the Tax Authority requires and the foreign invoice may not give, which GoRoute does not make up: the supplier's telephone and address as its invoice states them, and your own street, city and postal code. Only the fields you give change.
{
"customs_declaration_number": "MCT-2026-0101010",
"seller": { "phone": "+49 40 3000 0000", "address": { "postal_code": "20457" } },
"importer": { "identifier": "IC-2026-004455", "identifier_scheme": "ICID",
"address": { "street": "Way 3021", "city": "Muscat", "postal_code": "112" } }
}
A successful call answers 202 and GoRoute files the import: one Tax Data Document per
foreign invoice. Two refusals are worth handling explicitly:
422โ the data would still be refused by the Tax Authority. Every finding is named and nothing is changed.409witherror_codeIMPORT_BEFORE_REPORTING_CUTOFFโ the import was created before your platform's reporting cut-off date, so it is never filed. Nothing is changed.
Where the outcome arrivesโ
There are no import-specific webhook events. The import's reporting leg sends the ordinary
tax-authority events โ authority.held while it waits for you, carrying the wait's reason code,
then authority.submitted, authority.acknowledged or authority.rejected โ which are the same
events described under Configure Webhooks. The report itself is
readable through the ordinary reporting call described under
Reporting status for one transaction: a sent import
record's import Tax Data Document is its TDD.
Every address, field, status code and error code above was read from the GoRoute source that implements them and from the test service's own published interface. It was not produced by calling these three endpoints โ this office holds no API key. The one-call check under Check whether the path is open for you is therefore also your protection against this page being wrong.
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, medicines and medical equipment |
| Exempt | 0% | E | Financial services, healthcare, education, 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. On an export invoice the code
must be one of the export codes โ see Export invoices.
<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 requires a QR code on B2C invoices โ full or simplified โ so the copy the consumer is given can be verified offline. It is defined in the Oman Solution Reference Architecture v1.0.3, section 4.2 and Appendix D. B2B invoices carry no QR: the code asserts a B2C invoice type and a Seller UUID that exist only for the consumer flow.
TLV fields โ B2C invoice (Table 5)โ
| Tag | Field | Source | Example |
|---|---|---|---|
| 1 | QR version | fixed | 1.1 |
| 2 | Invoice type | fixed, B2C | 01 |
| 3 | Invoice number | IBT-001 | INVOICE-01 |
| 4 | Seller name | IBT-027 | ุจุงุฆุน |
| 5 | Seller VATIN | IBT-031 | OM1234567891 |
| 6 | Invoice date | IBT-002, YYYY-MM-DD | 2026-05-13 |
| 7 | Invoice time | IBT-168, HH:MM:SS | 18:31:00 |
| 8 | Total with VAT | IBT-112 | 302.340 |
| 9 | VAT total | IBT-110 | 14.520 |
| 10 | Seller UUID | BTOM-004, UUIDv5 | 454cb7ea-f1f7-5b69-9e0b-756f002efa52 |
TLV fields โ profit-margin invoice (Table 6)โ
A profit-margin B2C invoice carries invoice type 02, no VAT total, and the
total amount due (BTOM-020) at tag 8. The Seller UUID moves to tag 9, so the
code has nine tags.
Seller UUID (BTOM-004)โ
A version 5 UUID in namespace e0bc4ac8-b025-46e5-a76d-0c893fc3027e over the
other fields, joined with | and upper-cased (Appendix D.1):
1.1|01|INVOICE-01|ุจุงุฆุน|OM1234567891|2026-05-13|18:31:00|302.340|14.520
For a profit-margin invoice the VAT total is left out of the seed. The same value is carried as BTOM-004 in the UBL, so the document and its QR cannot disagree.
TLV encodingโ
Each field is tag (1 byte) + length (1 byte) + value (UTF-8 bytes). The length
is the byte length of the UTF-8 value, not the character count: the
four-letter Arabic name above has length 8. The concatenated bytes are
base64-encoded, and the result must not exceed 700 characters.
01 03 1.1
02 02 01
03 0a INVOICE-01
04 08 <8 bytes of UTF-8 Arabic>
05 0c OM1234567891
06 0a 2026-05-13
07 08 18:31:00
08 07 302.340
09 06 14.520
0a 24 454cb7ea-f1f7-5b69-9e0b-756f002efa52
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">
AQMxLjECAjAxAwpJTlZPSUNFLTAx...
</cbc:EmbeddedDocumentBinaryObject>
</cac:Attachment>
</cac:AdditionalDocumentReference>
Documents GoRoute renders for you โ POST /api/v1/invoices, drafts, the ERP
connectors, the pollers and the retail and POS counters. GoRoute composes the
UBL and, when the invoice is B2C, builds the QR from the finished document and
embeds it. A value of yours that GoRoute writes as a reference ID cannot be
QR or HASH (in any case, with or without spaces), since those are the IDs of
the QR code and hash GoRoute adds: a supporting document's ID (BUS_025), a
credit note's project_reference, or a prepayment invoice's
prepayment_invoice_number โ or its number when no prepayment number is given
(BUS_027, against that field). POST /api/v1/invoices, /validate and
/preview-xml answer with the validation error, a CSV or Excel import rejects
the row, and the connectors park the invoice with the same sentence. An invoice's
own project reference is written as cac:ProjectReference, not as a reference
ID, and is not restricted. Only a reference with the QR embedded, as above,
ever counts as the QR.
Documents you submit as final UBL โ POST /api/v1/documents. The XML is
transmitted exactly as you submitted it, and no QR is added. If your B2C invoice
needs one, embed it before you submit.
That difference is deliberate: altering a final document would leave the copy you archive different from what was transmitted and reported.
No signature, no hashโ
Oman requires no digital signature and no document hash on the invoice.
None of the Oman specifications โ the Solution Reference Architecture, PINT OM
1.0.1, the Peppol Authority Specific Requirements or the tax-reporting guides โ
asks for one, and the PINT OM Schematron rejects the Saudi-style artefacts: a
hash reference fails ibr-cl-24 and a signature envelope fails ibr-079.
GoRoute adds neither. If you are porting a ZATCA integration, strip them.
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",
"phone": "+968 24000000",
"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",
"phone": "+968 24000000",
"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",
"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 a B2C invoice gets its
# QR code added for you.
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 is added. If your invoice needs those elements, generate them before you send.
An invoice or credit note from an Omani seller โ sent from a 0248 participant, or naming
an OM VAT number as the seller's โ must be PINT OM (urn:peppol:pint:billing-1@om-1), or
be sent as invoice data to POST /api/v1/invoices so GoRoute renders it as PINT OM and
reports it to the Tax Authority. A BIS Billing 3.0 invoice from an Omani seller is accepted
and logged for now, but do not rely on that: GoRoute can refuse it. The refusal is HTTP 422
with error_code OMAN_PINT_OM_REQUIRED, and its message starts with [OMAN-PINT-OM-REQUIRED].
Only a new document is refused: sending again a document GoRoute has already accepted returns
its existing transaction, as any idempotent resend does.
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.
Resending a failed invoice and its issue dateโ
The invoice UUID is derived from the issue date among its seven fields (IBR-002-OM, above), and a B2C invoice's QR code carries the issue date, the time and that UUID. An invoice sent again with a new date would be a different invoice under the same number, reported to the Tax Authority a second time.
GoRoute protects you from that. For an Omani invoice,
POST /api/v1/transactions/{transaction_id}/resend never rebuilds or re-dates anything: the
stored document โ the one first sent โ is queued again under the same transaction and message id,
to the same receiver, exactly as POST /api/v1/transactions/{transaction_id}/retry does.
update_timestamps left out (or false) keeps the date whatever else the body says;
"update_timestamps": true is refused with 422 OMAN_RESEND_CANNOT_REDATE and nothing is
sent. An invoice from an Omani seller that went out as Peppol BIS 3.0 rather than PINT OM
cannot be reported, so it is not resent at all: 422 OMAN_RESEND_NOT_PINT_OM, whatever the
body; send a new invoice under a new number instead.
To change an invoice you have issued, send a credit note for it and a new invoice under a new number.
A resend of an Omani invoice therefore never re-dates or rebuilds it. Where nothing about the
invoice must change, POST /api/v1/transactions/{transaction_id}/retry is the simpler call: it
takes no body and queues the same transaction and message id, provided the send is failed or
retrying and the document is still in storage. The full resend reference is under
Tracking.
Buyers outside Oman who accept only BIS 3.0โ
Some buyers outside Oman are on Peppol but accept only Peppol BIS Billing 3.0, not PINT OM.
When you send an invoice or credit note as invoice data (POST /api/v1/invoices, drafts, CSV
import or an ERP connector) to a buyer outside Oman whose Peppol registration lists BIS
Billing 3.0 and not PINT OM:
- The buyer receives a BIS Billing 3.0 copy of the invoice over Peppol. It has the same
number, issue date, parties, lines and amounts, and carries the PINT OM invoice's UUID
in an
AdditionalDocumentReference. - The PINT OM invoice remains the invoice of record and is reported to the Oman Tax
Authority once the copy has been delivered. The PINT OM send itself is refused first,
because the buyer does not accept PINT OM. GoRoute does not report that refusal to you as a
failure. Once the copy has been delivered and the report filed, your transaction shows as
delivered and the
tdd.reported_via_bis_copyevent links it to the copy. - If the copy could not state exactly what the PINT OM invoice states, it is not sent. In that case, or if the copy cannot be delivered, the invoice is reported the same way as an invoice to a buyer who is not on Peppol. See BIS 3.0 buyer copy for when this happens.
- You are not billed for the copy as a second document.
A buyer registered for PINT OM (including through the PINT billing wildcard) receives the
PINT OM invoice as usual. UBL you submit yourself to POST /api/v1/documents is transmitted
as submitted and gets no copy.
BIS 3.0 buyer copyโ
When a copy is madeโ
GoRoute makes a BIS 3.0 copy only when all of these are true:
- You sent the invoice or credit note as invoice data, so GoRoute rendered the PINT OM document itself. The copy is rendered from the same invoice data.
- The seller is in Oman and the buyer is outside Oman.
- The buyer's Peppol identifier is not an Omani (
0248) or tax-authority (0242) one. - The PINT OM send was refused because the buyer does not accept PINT OM, and the buyer's Peppol registration lists the BIS Billing 3.0 document of the same kind (invoice or credit note).
- Nothing has been reported for the invoice yet.
Before sending, GoRoute compares the copy with the PINT OM invoice field by field: number, issue date, type code, currencies, the preceding invoice of a credit note, both parties (electronic address, legal name, VAT number, country), every line (quantity and unit, net amount, item name, VAT category and rate, net unit price), the VAT breakdown and every total. If any of them would differ, no copy is sent.
When the stand-in is used insteadโ
The invoice is reported under the ยง10.3.1 stand-in participant 0248:997770000097 with
scope INP, exactly as for a buyer who is not on Peppol, when:
- An amount has a non-zero third decimal. BIS 3.0 states prices and amounts to two decimals, so, for example, a unit price of OMR 0.229 cannot be copied. OMR amounts whose third decimal is 0, such as OMR 25.500, are copied.
- A unit, VAT category or other compared field would differ between the copy and the PINT OM invoice.
- The copy fails BIS 3.0 validation, or cannot be queued.
- The buyer's Access Point refuses the copy at the Peppol transport, or the copy's send otherwise fails for good.
- GoRoute gives up on the copy: the job that makes it fails on every attempt, or the
copy's send is abandoned before it was handed to the Peppol transport. The copy then shows
as failed with
error_codeSEND_DEAD_LETTERED. The invoice is never left unreported. (A copy whose send is abandoned after it was handed to the transport may have reached the buyer. No report is filed automatically for it: GoRoute's operators check the Peppol receipt and file the report that matches, through the copy if it was delivered, the stand-in if it was not.) - The copy is switched off for your environment.
In every one of these cases the reason is recorded on the transaction:
metadata.oman_reporting.bis_copy_status is skipped (no copy was sent;
bis_copy_skipped_reason says why) or failed (the copy was sent and failed;
bis_copy_error says why).
Buyers whose Access Point still runs BIS 3.0.20โ
Oman's 0248 identifier scheme first appears in the Peppol list of electronic address
schemes in BIS Billing 3.0.21 (May 2026). An Access Point that still validates with 3.0.20
rejects the copy, because the seller's electronic address carries schemeID="0248"
(PEPPOL-EN16931-CL008, BR-CL-25). The same is true of any BIS 3.0 document from an Omani
sender. Two things can happen:
- The Access Point refuses the copy at the Peppol transport. The copy has failed, so the
invoice is reported under the stand-in with scope
INP. - The Access Point accepts the copy and then rejects it with a negative Message Level
Status (
RE). The invoice has already been reported with scopeIP. The rejection is recorded on your invoice and sent to you asmls.receivedandresponse.rejected, naming your invoice. It is the buyer's answer, not the Tax Authority's, so notdd.rejectedis sent for it. As for any invoice whose buyer rejects it, GoRoute then files a Disregard of the invoice's report. Send the buyer the invoice by another route and contact support if the report needs correcting.
What IP and INP mean for youโ
| Scope | When | What it means for you |
|---|---|---|
IP | The BIS 3.0 copy was delivered over Peppol. | The buyer has the invoice. The PINT OM invoice was reported as an international invoice exchanged over Peppol. Nothing more to do. |
INP | No copy was made, or it could not be delivered. | The buyer does not have the invoice. It was reported as an international invoice not exchanged over Peppol, under the stand-in 0248:997770000097. Send the invoice to the buyer yourself, for example as a PDF. |
Events you receiveโ
Neither case sends transaction.failed for the PINT OM send the buyer refused. The event
that reports the outcome is sent instead:
| Outcome | Events, in order |
|---|---|
Copy delivered, reported IP | transaction.delivered for the copy's transaction, then transaction.delivered for your invoice (with delivered_via: "bis_copy") and tdd.reported_via_bis_copy for your invoice. |
Stand-in, reported INP (including a buyer not on Peppol at all) | tdd.reported_via_substitute for your invoice. |
| Copy delivered, but the report then failed | transaction.delivered for the copy's transaction, then transaction.failed for your invoice, with fallback set to bis_copy. The buyer has the invoice, but it was not reported. Contact support. |
| The stand-in report could not be filed either | transaction.failed for your invoice, with fallback and fallback_error. The invoice was not reported. Contact support. |
See Omani invoices the buyer cannot take as PINT OM
for the payloads. If your integration listens only for transaction.delivered and
transaction.failed, add tdd.reported_via_substitute: an invoice reported under the
stand-in sends neither.
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. Four endpoints expose them: one for the
reporting status, one to fetch the TDD, and two to fetch an MLS โ which of the two you need
depends on who wrote the MLS, and
Fetching an MLS document is the rule. All four 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, rejected, reported, reported_via_substitute, awaiting_ota_go_live, held (a report held before any TDD was built for it, with its reason in reason_code and reason), or null when no TDD exists yet. failed is final; reporting_state says whether it is a report that cannot be filed. rejected: the tax authority rejected the report and GoRoute's corrected report is under way; not final. See When a report does not reach the tax authority and When the tax authority rejects a report. |
clearance_reason_code, clearance_reason | Why the report reads prepared_awaiting_authority, held or rejected: a stable code and one sentence you can show to a person, the same pair GET /transactions/{id} carries. null for any other clearance_status. |
reporting_state, reason_code, reason | null while nothing has gone wrong. error when the report, or a correction to it, cannot be filed; held while a report that did not reach the tax authority waits for reporting for your taxpayer to be switched back on, or while a report whose processing stopped waits for GoRoute (see When a report's processing stops). reason_code is a stable code such as C5_OM_TDD_TRANSPORT_FAILED, and reason is one sentence you can show to a person. |
hold | null unless the report's processing stopped: its reason (awaiting_operator_decision or lookup_unavailable) and since (UTC). See When a report's processing stops. |
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. Two things hold one: the environment not being configured to report to the tax authority, or โ where an enabled-reporter list is in use โ this taxpayer not being on it. |
tdd.delivered | true cleared, false failed, null not yet reconciled. |
tdd.authority_response | The tax authority's answer to the TDD, or null until it answers: response_code (AP accepted, RE rejected), its description, received_at and mls_id. |
tdd.redrive | null unless the TDD failed to reach the tax authority. Otherwise GoRoute's automatic re-sends of it: attempts, max_attempts, state (in_progress, held, error, reported, or answered when the authority answered it after all), next_attempt_at and last_error_code. |
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 we sent, 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. The document_url on a received leg does not work: it points at the core MLS call, which cannot return that leg. Read the leg's presence and response_code from here, but fetch it with the Oman call โ see Fetching an MLS document. The Tax Authority's MLS is not listed here at all; tdd.authority_response is where it appears. |
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. |
reason_code as an open setThe codes named on this page are the ones worth writing code for, but the set grows as
reporting gains new cases, and your own service may be a release behind or ahead of this page.
So branch on the codes you have deliberately handled, and for anything else show the
reason sentence to the person instead of a fallback of your own. reason is written to be
read by a human being and is never an internal error message, so it is always safe to show. A
switch with no default is the one thing to avoid here: a code you have not seen should
produce a useful message, not silence.
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.
When a report does not reach the tax authorityโ
If the tax authority's access point cannot be reached, GoRoute sends the same Tax Data
Document again automatically (the same document and message identifier, so nothing is filed
twice), with growing gaps between tries, for about 20 hours. While it does, clearance_status
stays reported (sent, outcome not yet known), exactly as during the first few minutes of
any report, and tdd.redrive shows the attempts and when the next one is due. It does not
read failed: an integration that treats failed as final would otherwise stop following an
invoice whose report is still on its way. The report's own transaction sends
transaction.retrying, not transaction.failed.
If reporting for your taxpayer is switched off meanwhile, the report waits: clearance_status
is prepared_awaiting_authority, reporting_state is held, and GoRoute sends it again once
reporting is switched back on. Like any held report it says why: reason_code, the
transaction's clearance_reason_code and tdd.held_reason_code are C5_OM_TDD_REDRIVE_HELD,
with the sentence in reason, clearance_reason and tdd.held_reason.
If the re-sends run out, or the report fails in a way that sending it again would not fix,
clearance_status is failed, reporting_state is error, reason_code and reason say
why, and the tdd.failed webhook names your invoice. A
later delivery, or the authority accepting the report after all, clears the error.
If the Tax Data Document cannot be built or filed at all, the same fields say so from the first
failure, and reason_code says why: C5_OM_REPORT_SOURCE_MISSING if the invoice document it is
built from cannot be found, C5_OM_TDD_BUILD_FAILED if the report cannot be built from it, or
C5_OM_TDD_KEY_TAKEN if its filing reference is held by another document. tdd.failed follows
once the same reason has repeated three times (the default). GoRoute keeps trying automatically,
and the error is cleared once the report is filed. While reporting for your taxpayer is on hold,
neither happens: the report would be held rather than filed, so the invoice does not read failed
and tdd.failed is not sent. Once the hold is lifted, the next failure makes it read failed, and
tdd.failed follows at once if the same reason has already repeated three times. The same goes
for an invoice that reads held because GoRoute's seller-side reporting was switched off when it
was delivered (reason_code C5_OM_REPORTING_DISABLED): it keeps that reason until that
reporting is switched back on, and only then can a failure make it read failed.
When a report's processing stopsโ
Rarely, the automatic processing behind a report stops before it finishes: the copy of an
invoice sent to a buyer who takes only Peppol BIS may or may not have arrived, the Peppol
directory cannot be reached to check whether your buyer can receive the invoice, or a step
has been waiting for more than seven days. GoRoute's operations team sees it and decides how
the report is filed; nothing is filed on a guess. Meanwhile clearance_status is held
(nothing has been built for the report yet), or prepared_awaiting_authority when its Tax
Data Document had already been built, reporting_state is held, and reason_code, the
transaction's clearance_reason_code and tdd.held_reason_code are
C5_OM_AWAITING_OPERATOR_DECISION (waiting for GoRoute's decision) or
C5_OM_LOOKUP_UNAVAILABLE (the directory is checked again automatically), with the sentence
in reason, clearance_reason, tdd.held_reason and note. hold gives the reason and
since when.
If the Tax Data Document itself had already been handed over for sending when its processing
stopped, it may have reached the Oman Tax Authority: reason then says so, and never that the
report has not been filed. A Tax Data Document the authority has answered is never held: it
reads what the authority said (see When the tax authority rejects a report).
The hold ends as soon as the report moves on: once it is filed, a correction is filed for it,
the authority answers it, or the invoice is sent again, the transaction reads what happened
next.
When the tax authority rejects a reportโ
The tax authority answers every Tax Data Document with a status message, which
tdd.authority_response shows. If it rejects the report (response code RE), the
tdd.rejected webhook names your invoice, whether or
not the TDD was delivered, and what the invoice reads depends on whether GoRoute can file a
corrected report (a Resubmit) for it itself.
- GoRoute files the Resubmit. It can only for the report of an invoice you received.
Until the authority answers the Resubmit,
clearance_statusisrejected,clearance_reason_codeisC5_OM_REJECTEDandclearance_reasoncarries the authority's own code and description.rejectedis not final. If the authority accepts the Resubmit, the invoice readsclearedandtdd.clearedfollows. If it rejects the Resubmit too, GoRoute does not correct it again:clearance_statusisfailed,reporting_stateiserror,reason_codeisC5_OM_REJECTED_RESUBMIT_FAILED, andtdd.failedfollows. - GoRoute cannot file a Resubmit automatically. This applies to the report of an invoice
you sent, of a POS sale, or of one filed under the stand-in participant. The invoice does not read
rejected; it is in error straight away:clearance_statusisfailed,reporting_stateiserror,reason_codeisC5_OM_REJECTED_NO_RESUBMIT,reasoncarries the authority's own code and description, andtdd.failedfollows.
Contact GoRoute support for an invoice in either error.
Why you may see an explanatory noteโ
When clearance_status is anything other than reported or rejected, reporting_state is
not error, the report's processing has not stopped (hold is null; while it has, note
is the sentence in reason), 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.
A held report with no note at all means something specific. If clearance_status is
prepared_awaiting_authority while ota_live is true and ota_target_is_testbed is
false, the response carries no explanatory note, because neither condition above
applies. That combination means the environment is configured to report to the tax
authority, but this taxpayer is not one of those enabled for transmission. Where an
enabled-reporter list is in use, only the taxpayers named on it have their Tax Data
Documents dispatched; everyone else's are held exactly as if reporting were switched off.
The list is a deployment setting and no API call exposes it, so there is nothing you
can read to confirm it โ ask GoRoute whether your taxpayer identifier is enabled.
clearance_status remains the field to read: it applies the held-first precedence for you,
whichever of the two reasons applies.
Fetching the Tax Data Documentโ
GET /api/v1/transactions/{transaction_id}/tdd
Returns the stored TDD as UBL XML with content type application/xml. The document is
returned exactly as it was stored โ nothing is reformatted or re-serialised. Use the
transaction identifier of the invoice, not of the TDD itself; the endpoint resolves the
TDD from the invoice for you.
There is no parameter to choose which TDD you get. A transaction can have up to three:
the TDD GoRoute produced itself, a TDD produced by the sending access point (C2), and a
substitute TDD. The endpoint returns the first of those three that exists, in that order,
and there is no equivalent of the leg parameter on the MLS call. To see which one you are
about to receive, call the reporting endpoint first: tdd.transaction_id applies the same
order, so it identifies the document this call will return, and substitute.used tells you
whether a substitute was reported to.
Errors
| Status | error_code | When |
|---|---|---|
| 404 | TRANSACTION_NOT_FOUND | No such transaction in your organisation. |
| 404 | TDD_NOT_AVAILABLE | The transaction exists but no TDD has been produced for it. |
| 404 | DOCUMENT_NOT_STORED | A TDD exists but its XML is not retrievable from storage. |
document_url is not a complete path โ add /api/v1The document_url values in the reporting response are built without the /api/v1
prefix: tdd.document_url comes back as /transactions/{transaction_id}/tdd, and the MLS
URLs are built the same way. Joining your API base URL to that value as-is produces a 404.
Either prepend /api/v1, or build the address from the request line above rather than from
the response field. For the MLS there is a second reason to build your own address: the
received leg's document_url names a call that cannot return it. See
Fetching an MLS document.
Being able to download a TDD does not mean the Tax Authority received it. A held TDD is
built, validated, stored and fetchable through this endpoint while having reached nobody โ
check tdd.held and clearance_status on the reporting endpoint before treating a
successful download as evidence of reporting.
Fetching an MLS documentโ
There are two calls, and which one you need depends on who wrote the MLS you want. Get this the wrong way round and you will be told there is no answer when there is one, so the rule comes first:
| The MLS you want | Who wrote it | The call to use |
|---|---|---|
| The one GoRoute sent as C3, acknowledging a document that arrived for you | GoRoute, on your behalf | GET /api/v1/transactions/{transaction_id}/mls?leg=sent |
| The buyer's accept or reject of an invoice you sent | The buyer's access point (C2) | GET /api/v1/oman/transactions/{transaction_id}/mls?leg=received |
| The Oman Tax Authority's answer on your invoice's Tax Data Document | The Tax Authority (C5) | GET /api/v1/oman/transactions/{transaction_id}/mls?leg=authority |
GET /api/v1/transactions/{transaction_id}/mls?leg=received looks the MLS up inside your
own organisation. Both of the answers that come back to you โ the buyer's and the Tax
Authority's โ are addressed to GoRoute's own service-provider identity rather than to yours,
so they are filed under GoRoute's organisation and that lookup finds nothing. The call
answers 404 DOCUMENT_NOT_STORED, and it does so whether or not the buyer has answered.
A 404 from that call is therefore not evidence that nobody has answered. If you have
been treating it that way, you may have missed rejections. Use the Oman call below for the
received and authority legs. leg=sent on the core call is unaffected and still correct.
The core call โ the MLS GoRoute sentโ
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. Permission:
transactions:read.
| Parameter | Values | Behaviour |
|---|---|---|
leg | sent | The ApplicationResponse GoRoute issued as C3 to the sending access point (C2). This is the leg this call is for. |
leg | received | Accepted by the call, but see the warning above: for an Omani invoice it answers 404 DOCUMENT_NOT_STORED. Use the Oman call instead. |
leg | omitted | The sent leg if one exists, otherwise it falls back to the received leg โ and so to the same 404. Name the leg rather than omitting it. |
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 leg of the kind you asked for has been recorded on it at all. |
| 404 | DOCUMENT_NOT_STORED | The leg is recorded on the transaction but the call cannot read it. For leg=received on an Omani invoice this is the normal answer, for the reason in the warning above โ not a sign of missing storage. |
The Oman call โ the buyer's answer and the Tax Authority'sโ
GET /api/v1/oman/transactions/{transaction_id}/mls?leg=received
This reads the MLS across organisations, after proving that the document it returns really does answer the invoice you named. Details:
| Point | Detail |
|---|---|
leg | Required. received โ the buyer's access point's MLS on your invoice. authority โ the Tax Authority's MLS on your invoice's Tax Data Document. There is no default and no sent value here. |
| Permission | transactions:read, the same as the core call. |
| Response | 200 with the stored UBL ApplicationResponse, unaltered, as application/xml. |
| Transaction | Your own invoice, read in your own organisation. A transaction belonging to someone else is a 404, not a 403. |
Errors
| Status | error_code | When |
|---|---|---|
| 404 | TRANSACTION_NOT_FOUND | No such transaction in your organisation. |
| 404 | MLS_NOT_AVAILABLE | No MLS of that leg has been received yet. This is also the answer if the stored MLS turns out not to answer the invoice you named, so that a mismatch can never leak another document. |
| 404 | DOCUMENT_NOT_STORED | The MLS was received but its XML is not retrievable from storage. |
The authority leg is new capability rather than a replacement: the core call has no
selector for the Tax Authority's MLS at all, so before this call existed there was no way to
download it.
It is part of the Oman country pack, and a pack is mounted only on a service configured to
run it. It is live on the test service; it is not mounted on app.goroute.ai today, even
though that service runs the same build. You can tell the two apart in one request, with no
API key and any transaction id:
- 401 โ the route exists and is asking you to authenticate. It is available.
- 404 โ the route is not mounted on that service.
Compare it against /api/v1/transactions/{id}/mls, which answers 401 everywhere: if the
Oman path gives 404 while the core path gives 401, the pack is off rather than your request
being wrong.
Fetching both, in orderโ
Ask the reporting endpoint which legs exist, then fetch each from the call that can return
it. Note that mls.legs[] publishes a document_url for the received leg that points at the
core call โ the address that cannot serve it. Build the address from the request lines above
rather than from that field.
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"])
legs = {leg["leg"] for leg in status["mls"]["legs"]}
# The MLS we sent: the core call.
if "sent" in legs:
sent_xml = requests.get(
f"{BASE_URL}/api/v1/transactions/{transaction_id}/mls",
params={"leg": "sent"},
headers={"X-API-Key": API_KEY},
).text
# The buyer's answer: the Oman call. The core call answers 404 for this leg.
if "received" in legs:
received_xml = requests.get(
f"{BASE_URL}/api/v1/oman/transactions/{transaction_id}/mls",
params={"leg": "received"},
headers={"X-API-Key": API_KEY},
).text
# The Tax Authority's answer on the TDD. It is not listed in mls.legs[];
# tdd.authority_response tells you whether it has arrived.
if status["tdd"]["authority_response"]:
authority_xml = requests.get(
f"{BASE_URL}/api/v1/oman/transactions/{transaction_id}/mls",
params={"leg": "authority"},
headers={"X-API-Key": API_KEY},
).text
If you would rather not poll at all, the buyer's status message also reaches the invoice's owner as a webhook event โ see Status events for invoices you sent.
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. It does not apply to UBL you submit to POST /api/v1/documents, which is transmitted exactly as submitted.
On the rendered-document paths GoRoute adds one element before Peppol transmission, and only on a B2C invoice:
- QR Code โ
AdditionalDocumentReference[ID='QR']with the Appendix D TLV - A readable PDF (
GET /api/v1/transactions/{id}/pdf) titled by document kind โ ูุงุชูุฑุฉ ุถุฑูุจูุฉ ู ุจุณุทุฉ / Simplified Tax Invoice, ูุงุชูุฑุฉ ุถุฑูุจูุฉ / Tax Invoice, ุฅุดุนุงุฑ ุฏุงุฆู / Credit Note โ with the same QR printed on it. "Fawtara" (ููุชุฑุฉ) names the programme; the document is a tax invoice.
No hash and no signature are added: Oman defines neither.
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.
Values you must supply yourselfโ
Every value in the table below is one the Tax Authority requires on the document. Supply all of them. What happens when you leave one empty depends on which GoRoute service you are sending through, and the difference matters to you as the taxpayer:
- Where the refusal is switched on, a rendered document missing one of these values is refused by name โ on a B2B, B2G, export, import or self-billing document and on every credit or debit note โ and nothing is sent or reported.
- Where it is not, GoRoute fills the gap with a value of its own and files it as if it were
yours: the party's name as its street,
NAas its city and telephone,000as its postal code, the credit note's own number and date as the invoice it corrects, and so on.
Whether the refusal is on is a setting each GoRoute service chooses. Some services have it on. Others have deliberately left it off pending a decision, and there a document missing one of these values is still rendered with the invented values above, and those values are still filed with the Tax Authority as yours.
Check the service you actually use. POST the invoice to
POST /api/v1/invoices/validate, which never sends anything:
BUS_034comes back for the missing value โ the refusal is live for you.- It does not โ GoRoute is still filling that value in and filing it as yours. Supply it yourself.
Supplying every value in the table is correct on every service, whichever way the refusal is set. It is the only approach that is.
| Required | Field | Rule |
|---|---|---|
| The seller's street, city and postal code (PO Box) | seller.address.street, .city, .postal_code | IBR-010-OM |
| The seller's telephone | seller.phone or seller.contact.phone | IBR-011-OM |
| The buyer's street, city and postal code | buyer.address.street, .city, .postal_code | IBR-019-OM |
A party's country, unless it is Omani by its 0248 Peppol ID or OM VAT number | seller.address.country, buyer.address.country | |
| The invoice a credit or debit note corrects: its number, issue date, UUID and a reason code | preceding_invoice.number, .issue_date, .uuid, .reason_code | IBR-032-OM, IBR-023-OM |
| A profit-margin invoice's preceding invoice | preceding_invoice | IBR-175-OM |
| The prepayment invoice a prepayment invoice settles | prepayment_invoice_number, prepayment_invoice_uuid | IBR-058-OM |
| A summary or continuous supply's period | invoice_period_start, invoice_period_end | IBR-037-OM |
| Where an export or an e-commerce supply is delivered | delivery_country (and delivery_street, delivery_city, delivery_postal_code for e-commerce), or the buyer's own address | IBR-014-OM, IBR-040-OM |
| A special-zone supply's zones and licence numbers | seller.country_subdivision_code, buyer.country_subdivision_code, seller.identifier, buyer.identifier (scheme SZLN) | IBR-150/151/152-OM |
| A third-party invoice's agent, with its address and Omani VAT number | third_party | IBR-015-OM |
| A zero-rated or exempt line's reason code | lines[n].tax_exemption_reason_code | IBR-069-OM |
For an invoice issued before e-invoicing began, give 00000000-0000-0000-0000-000000000000 as
preceding_invoice.uuid: GoRoute does not assume it. The buyer's telephone is not required, so
a buyer without one is sent without one rather than with NA.
On a service where the refusal is switched on, a missing value is reported like this:
POST /api/v1/invoices,/validateand/preview-xmlanswer with validation errorBUS_034, one error per missing value, itsfieldset as in the table.- A CSV or Excel import rejects the row with the same sentence.
- An invoice pulled from Odoo or Zoho Books is parked, with the reason, until it is corrected in the ERP. Zoho Books sends GoRoute no company address or telephone, so for a Zoho connection they are your company settings in GoRoute (street, city, postal code, telephone): set them there.
- An import that reaches you over Peppol waits for the missing values, named, like the customs
data. A foreign supplier's invoice often gives no telephone, and need not give your street:
send them with the customs data to
PATCH /api/v1/oman/imports/{transaction_id}asseller.phone,seller.addressandimporter.address(see Completing an import that is waiting).
B2C is unchanged. A simplified invoice, or an invoice to a buyer with no VAT number and no Peppol ID, keeps the values the Tax Authority prescribed for the general customer, as before, the seller's own included. That covers a government buyer with no VAT number and no Peppol ID too: it is addressed as the general customer, so such a B2G invoice is not refused either. Only the invoice a B2C credit or debit note corrects, and a B2C profit-margin invoice's preceding invoice, are required. Receipts issued through the POS counters (fuel, exchange, retail) are unchanged too.
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 continuous transaction controls require a Tax Data Document (TDD) to be reported to the Oman Tax Authority. GoRoute builds and files it for you:
- Root element:
pxs:TaxData(namespaceurn:peppol:schema:om-taxdata:1.0) - Receiving party: the Oman Tax Authority's Peppol participant (scheme
0242). GoRoute fills it in; you never set it. - Validated against
TDD-OM-peppol-om-tdd.xsltbefore submission - Document type codes (
pxs:DocumentTypeCode):SSubmit,RResubmit,DDisregard (OpenPeppol code list)
This page does not give a reporting deadline, because we could not trace one to a Tax Authority or OpenPeppol publication.
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 | None required | 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" / "Unknown identifier scheme: 0248"
- What you may see. A CSV or Excel import of an Omani invoice is refused with the rule
EDEC-ICD-0248and the messageUnknown identifier scheme: 0248, pointing atbuyer.peppol_idorseller.peppol_id. - Your VAT number is not the problem.
0248is a valid, active Peppol participant identifier scheme โOM:VAT, the Oman Value Added Tax Identification Number issued by the Tax Authority, Oman. It has been in the official Peppol code lists since version 9.6. A refusal here is about the code list our validation is comparing against, not about your data, so there is nothing to correct on the invoice. - A route that works today. The same invoice is accepted by
POST /api/v1/invoicesand byPOST /api/v1/invoices/validate, which do not check identifier schemes against that list. Note thatPOST /api/v1/invoices/validate/deepruns the same check as the import, so it reports the same finding. - Please tell us if you see it โ see Support โ quoting the rule
EDEC-ICD-0248, so we can bring the code list on your cell up to date.
"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 one of the three PINT OM identifiers exactly as written, on its own, with nothing before
or after it:
urn:peppol:pint:billing-1@om-1for an invoice or credit note,urn:peppol:pint:selfbilling-1@om-1for a self-billing invoice or credit note, andurn:peppol:taxdata:om-1for a Tax Data Document. - For a billing or self-billing document the check is the fatal rule
ALIGNED-IBRP-001-OM, which requires the specification identifier (IBT-024) to start with the value for the kind of document you are sending. For a Tax Data Document it is the fatal ruleibr-tdd-01, which requires the identifier to be exactlyurn:peppol:taxdata:om-1. - Do not add a
#conformant#suffix, and do not put anything in front of the identifier. No Omani identifier has a#conformant#part. That convention belongs to CEN EN 16931 conformant CIUSes such as Singapore's, where the CIUS identifier followsurn:cen.eu:en16931:2017#conformant#โฆ. Written that way round for Oman, the value no longer starts withurn:peppol:pint:billing-1@om-1andALIGNED-IBRP-001-OMfails. Appended instead, it gets past that rule and then breaks the send in a harder place to diagnose: the document type no longer matches the entry published in the Peppol document-type list, so the receiver lookup finds nothing. Earlier revisions of this page said the suffix was required. That was wrong โ a leftover from the retired Fawtara CIUS โ and following it made a document worse, not better.
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.