Skip to main content

Oman ๐Ÿ‡ด๐Ÿ‡ฒ

E-invoicing requirements and Peppol compliance for the Sultanate of Oman.

Overviewโ€‹

AspectDetails
Tax AuthorityOman Tax Authority (OTA)
B2G MandateActive โ€” Peppol network
B2B MandateMandatory 1 April 2027 above OMR 5,000,000 of annual supplies, 1 October 2027 at or below (Decision No. 189/2026)
Required CIUSPINT OM (Official OpenPeppol PINT Oman)
Primary Scheme0248 (Oman VATIN)
CurrencyOMR (Omani Rial, 3 decimal places)
VAT Rate5% standard rate
QR CodeMandatory โ€” Fawtara TLV format
Invoice UUIDUUIDv5 derived per Solution Reference Architecture v1.0.3 ยง10.2.3
Oman Peppol Authority

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:

WaveMandatory fromWho it binds
One1 April 2027Taxpayers whose annual supplies exceed OMR 5,000,000
Two1 October 2027Taxpayers 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.

These are obligations, not adoption targets

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:

PackSchematronsDescription
Billing Invoice2 (preprocessed + jurisdiction-aligned)Standard tax invoice validation
Billing Credit Note2Credit note validation
Self-Billing Invoice2Self-billing invoice validation
Self-Billing Credit Note2Self-billing credit note validation
TDD1Tax 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โ€‹

CodeDocumentNotes
380Commercial invoiceThe standard case
381Credit noteRequires a preceding invoice reference โ€” see IBR-032-OM
383Debit noteSame preceding-invoice requirement
386Prepayment invoice
389Self-billed invoiceBuyer issues on the supplier's behalf. Restricted by IBR-177-OM
261Self-billed credit noteRequires 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:

BitmapMeaning
00100000000000000000Self-billed invoice / credit note
00000000100000000000Invoice 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.

ListCodesUsed for
VATZR-OM-01 โ€ฆ VATZR-OM-1616Zero-rating reasons
VATEX-OM-01 โ€ฆ VATEX-OM-1212Exemption 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.

CodeMeaning
VATZR-OM-01Qualifying Supply of Food items specified by a decision from the Chairman
VATZR-OM-02Qualifying Supply of Medicines and medical equipment
VATZR-OM-03Supply of investment gold, silver, and platinum
VATZR-OM-04Supplies of international and intra GCC transport of Goods or passengers
VATZR-OM-05Supply of Services related to International and Intra GCC transportation of Goods or passengers
VATZR-OM-06Supply of qualifying air, sea and land means of transport
VATZR-OM-07Supply of rescue planes, and rescue and assistance boats
VATZR-OM-08Supply of oil, oil derivatives and natural gas
VATZR-OM-09Export of service
VATZR-OM-10Direct export of goods
VATZR-OM-11Indirect export of goods
VATZR-OM-12Re-export of goods
VATZR-OM-13Supply from a Special Zone to a Special Zone, or within a Special Zone
VATZR-OM-14Supply from Mainland to Special Zone
VATZR-OM-15Customs Duty Suspension Statuses to a Special Zone
VATZR-OM-16Special 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.

CodeMeaning
VATEX-OM-01Qualifying Financial Services
VATEX-OM-02Educational Services and Related Goods and Services
VATEX-OM-03Healthcare Services and related Goods and Services
VATEX-OM-04Rental of properties for residential purposes
VATEX-OM-05Local Passenger Transport
VATEX-OM-06Undeveloped land (bare land)
VATEX-OM-07Resale of residential properties
VATEX-OM-08Imported goods supplied to diplomatic and consular bodies, international organizations, and the heads and members of diplomatic and consular bodies
VATEX-OM-09Imports for the Armed Forces and Internal Security Forces
VATEX-OM-10Imported supplies for non-profit charities
VATEX-OM-11Import of returned goods
VATEX-OM-12Imported 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.

A wrong code is worse than a rejection

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:

CodeUse it forAlso needed on the invoice
VATZR-OM-10Direct export of goodsโ€”
VATZR-OM-11Indirect export of goodsโ€”
VATZR-OM-12Re-export of goodsThe 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-09Export of serviceservice_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-04International and intra-GCC transport of goods or passengersโ€”
VATZR-OM-05Services 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/invoices answers with status: "validation_failed" and an error with code BUS_024, whose field points at the line (lines[n].tax_exemption_reason_code, or lines[n].service_type_code / delivery_country for VATZR-OM-09).
  • POST /api/v1/invoices/validate and POST /api/v1/invoices/preview-xml report 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, /validate and /preview-xml answer with validation error BUS_029, whose field is the line's lines[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 simplified or summary).
  • 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 columnOn the e-invoice
supporting_document_idcac:AdditionalDocumentReference/cbc:ID (IBT-122)
supporting_document_uuidcac: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.

Check that your reference actually reaches the e-invoice

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.

Validate before you send

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_kindRequiredField
import_goodsImport date (BTOM-030)import_date
import_goodsCustoms declaration (bayan) number (BTOM-021)customs_declaration_number
import_goodsIncoterms (BTOM-022), as on the declarationincoterms
import_goodsYour Importer Customs ID (IBR-153-OM)buyer.identifier, with buyer.identifier_scheme ICID
import_goodsEach line's country of origin (IBR-084-OM)lines[n].origin_country
import_goodsWhether 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 goodslines[n].item_type (and lines[n].hs_code for goods)
bothThe foreign supplier's countryseller.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.

FieldWhat to put in it
foreign_invoice_numberThe foreign supplier's own invoice number for the invoice this import records. Up to 150 characters.
foreign_invoice_dateThe issue date of that foreign supplier's invoice.
supplier_identifierThe 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 column

They 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.

CallWhat it does
POST /api/v1/oman/importsRecords 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/importsLists 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:

StatusWhat it means
200Oman 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_LIVEGoRoute has built the path but it is not switched on for your platform yet. Nothing you send is recorded and nothing is filed
404The 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, whose om_invoice_kind is import_goods or import_services_rcm. It carries foreign_invoice_number, foreign_invoice_date and supplier_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 with filename and content_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 own attachments instead โ€” pdf is a field of the request, not of the invoice, and a body without it is refused.
  • rate and rate_date โ€” for an invoice not in OMR: rate is OMR per one unit of the invoice's currency. Both sit beside invoice, 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.
  • 409 with error_code IMPORT_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.

How this section was verified

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>
Not 9959

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>
3 Decimal Places

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.

CategoryRateCodeDescription
Standard5%SMost goods and services
Zero-rated0%ZExports, certain food items, medicines and medical equipment
Exempt0%EFinancial services, healthcare, education, residential rent
Z and E are not enough on their own

A 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)โ€‹

TagFieldSourceExample
1QR versionfixed1.1
2Invoice typefixed, B2C01
3Invoice numberIBT-001INVOICE-01
4Seller nameIBT-027ุจุงุฆุน
5Seller VATINIBT-031OM1234567891
6Invoice dateIBT-002, YYYY-MM-DD2026-05-13
7Invoice timeIBT-168, HH:MM:SS18:31:00
8Total with VATIBT-112302.340
9VAT totalIBT-11014.520
10Seller UUIDBTOM-004, UUIDv5454cb7ea-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>
Whether GoRoute adds the QR depends on how you send the document

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โ€‹

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.

Send Omani invoices as PINT OM, or as invoice data

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.

Earlier revisions of this page documented an endpoint that does not exist

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_copy event 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_code SEND_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 scope IP. The rejection is recorded on your invoice and sent to you as mls.received and response.rejected, naming your invoice. It is the buyer's answer, not the Tax Authority's, so no tdd.rejected is 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โ€‹

ScopeWhenWhat it means for you
IPThe 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.
INPNo 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:

OutcomeEvents, in order
Copy delivered, reported IPtransaction.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 failedtransaction.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 eithertransaction.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.

FieldMeaning
transaction_idThe transaction queried.
clearance_statusOne 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_reasonWhy 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, reasonnull 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.
holdnull 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_liveWhether this environment is configured to report to a live tax authority.
ota_targetThe participant identifier Tax Data Documents are addressed to, as scheme:id.
ota_target_is_testbedtrue when ota_target is the OTA test participant.
noteA plain-language explanation, or null. See below.
mls_statusThe MLS response code if one has arrived, otherwise sent if an MLS was dispatched, otherwise null.
tdd.transaction_idThe transaction holding the TDD, or null.
tdd.sentEnqueued for transmission โ€” not "arrived". Read together with tdd.held.
tdd.heldtrue 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.deliveredtrue cleared, false failed, null not yet reconciled.
tdd.authority_responseThe 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.redrivenull 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.scopeScope of a substitute report, where one was used.
tdd.document_urlRelative URL of the stored TDD, or null when there is no TDD.
mls.transaction_idThe MLS we sent, kept for backward compatibility.
mls.response_codeResponse code carried by the MLS.
mls.document_urlRelative 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.usedtrue when a substitute participant was reported to.
substitute.participantThe substitute participant identifier.
substitute.reasonWhy a substitute was used.
sla_breachedWhether the reporting SLA for this transaction was breached.
Treat reason_code as an open set

The 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 it

sent 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_status is rejected, clearance_reason_code is C5_OM_REJECTED and clearance_reason carries the authority's own code and description. rejected is not final. If the authority accepts the Resubmit, the invoice reads cleared and tdd.cleared follows. If it rejects the Resubmit too, GoRoute does not correct it again: clearance_status is failed, reporting_state is error, reason_code is C5_OM_REJECTED_RESUBMIT_FAILED, and tdd.failed follows.
  • 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_status is failed, reporting_state is error, reason_code is C5_OM_REJECTED_NO_RESUBMIT, reason carries the authority's own code and description, and tdd.failed follows.

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

Statuserror_codeWhen
404TRANSACTION_NOT_FOUNDNo such transaction in your organisation.
404TDD_NOT_AVAILABLEThe transaction exists but no TDD has been produced for it.
404DOCUMENT_NOT_STOREDA TDD exists but its XML is not retrievable from storage.
document_url is not a complete path โ€” add /api/v1

The 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 wantWho wrote itThe call to use
The one GoRoute sent as C3, acknowledging a document that arrived for youGoRoute, on your behalfGET /api/v1/transactions/{transaction_id}/mls?leg=sent
The buyer's accept or reject of an invoice you sentThe 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 DocumentThe Tax Authority (C5)GET /api/v1/oman/transactions/{transaction_id}/mls?leg=authority
The core call cannot return the buyer's or the Authority's answer

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.

ParameterValuesBehaviour
legsentThe ApplicationResponse GoRoute issued as C3 to the sending access point (C2). This is the leg this call is for.
legreceivedAccepted by the call, but see the warning above: for an Omani invoice it answers 404 DOCUMENT_NOT_STORED. Use the Oman call instead.
legomittedThe 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

Statuserror_codeWhen
404TRANSACTION_NOT_FOUNDNo such transaction in your organisation.
404MLS_NOT_AVAILABLEThe transaction exists but no MLS leg of the kind you asked for has been recorded on it at all.
404DOCUMENT_NOT_STOREDThe 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:

PointDetail
legRequired. 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.
Permissiontransactions:read, the same as the core call.
Response200 with the stored UBL ApplicationResponse, unaltered, as application/xml.
TransactionYour own invoice, read in your own organisation. A transaction belonging to someone else is a 404, not a 403.

Errors

Statuserror_codeWhen
404TRANSACTION_NOT_FOUNDNo such transaction in your organisation.
404MLS_NOT_AVAILABLENo 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.
404DOCUMENT_NOT_STOREDThe 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.

This call exists only where the Oman pack is switched on

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โ€‹

Applies only where GoRoute renders the document

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:

  1. QR Code โ€” AdditionalDocumentReference[ID='QR'] with the Appendix D TLV
  2. 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, NA as its city and telephone, 000 as its postal code, the credit note's own number and date as the invoice it corrects, and so on.
Not on every service โ€” check yours before you rely on the refusal

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_034 comes 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.

RequiredFieldRule
The seller's street, city and postal code (PO Box)seller.address.street, .city, .postal_codeIBR-010-OM
The seller's telephoneseller.phone or seller.contact.phoneIBR-011-OM
The buyer's street, city and postal codebuyer.address.street, .city, .postal_codeIBR-019-OM
A party's country, unless it is Omani by its 0248 Peppol ID or OM VAT numberseller.address.country, buyer.address.country
The invoice a credit or debit note corrects: its number, issue date, UUID and a reason codepreceding_invoice.number, .issue_date, .uuid, .reason_codeIBR-032-OM, IBR-023-OM
A profit-margin invoice's preceding invoicepreceding_invoiceIBR-175-OM
The prepayment invoice a prepayment invoice settlesprepayment_invoice_number, prepayment_invoice_uuidIBR-058-OM
A summary or continuous supply's periodinvoice_period_start, invoice_period_endIBR-037-OM
Where an export or an e-commerce supply is delivereddelivery_country (and delivery_street, delivery_city, delivery_postal_code for e-commerce), or the buyer's own addressIBR-014-OM, IBR-040-OM
A special-zone supply's zones and licence numbersseller.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 numberthird_partyIBR-015-OM
A zero-rated or exempt line's reason codelines[n].tax_exemption_reason_codeIBR-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, /validate and /preview-xml answer with validation error BUS_034, one error per missing value, its field set 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} as seller.phone, seller.address and importer.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:

LayerCheckRule
1UBL 2.1 XSDStructural XML validity
2Business rulesTotals, calculations, required fields
3eDEC Code ListsParticipant schemes, currencies, process IDs
3BPINT OM SchematronOfficial OpenPeppol jurisdiction rules (IBR-OM)
3CTDD SchematronTax Data Document validation (CTC reporting)
PINT vs Baseline

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 (namespace urn: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.xslt before submission
  • Document type codes (pxs:DocumentTypeCode): S Submit, R Resubmit, D Disregard (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:

FeatureOman (OTA)Saudi Arabia (ZATCA)
NetworkPeppolZATCA platform
Base StandardPeppol BIS 3.0UBL 2.1 (no Peppol)
QR FormatTLV (Tags 1-6)TLV (Tags 1-9)
SignatureNone requiredXMLDSIG
Currency Decimals3 (OMR)2 (SAR)
Tax Rate5%15%
ClearanceVia Peppol APVia ZATCA portal
CertificatePeppol AP certZATCA-issued cert

Troubleshootingโ€‹

Common Issuesโ€‹

"Amount must have 3 decimal places"

  • OMR uses 3 decimal places. Use 1050.000 not 1050.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-0248 and the message Unknown identifier scheme: 0248, pointing at buyer.peppol_id or seller.peppol_id.
  • Your VAT number is not the problem. 0248 is 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/invoices and by POST /api/v1/invoices/validate, which do not check identifier schemes against that list. Note that POST /api/v1/invoices/validate/deep runs 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-1 for an invoice or credit note, urn:peppol:pint:selfbilling-1@om-1 for a self-billing invoice or credit note, and urn:peppol:taxdata:om-1 for 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 rule ibr-tdd-01, which requires the identifier to be exactly urn: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 follows urn:cen.eu:en16931:2017#conformant#โ€ฆ. Written that way round for Oman, the value no longer starts with urn:peppol:pint:billing-1@om-1 and ALIGNED-IBRP-001-OM fails. 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โ€‹

Further readingโ€‹

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.