Invoice validation
Every invoice you push is validated against the UAE PINT-AE rules, converted to UBL XML and delivered over Peppol. POST /oauth/invoices/validate runs that same validation without storing or sending anything, so you can fix your data before an invoice exists. This page documents what is checked, which request field ends up where in the XML, and how to read a failure.
How it fits the send flow
An invoice you push is validated twice, with the same rules both times:
- When you submit it.
POST /oauth/invoicesandPATCH /oauth/invoices/{invoice_id}validate the invoice before accepting it. If it fails, the request is answered HTTP 422 with the failing rules inerrors, and nothing is stored or queued. The invoice number stays free, and a rejected update leaves the stored invoice unchanged. A valid invoice is answered 202 and queued. - Just before it is sent. The send pipeline checks the invoice again right before it builds the XML. A failure here usually means something changed after the invoice was accepted, typically the entity's details or the exchange rate. It then shows up as a
VALIDATION_FAILEDstatus on the invoice, with the sameerrors. Fix the cause and PATCH the invoice to re-queue it.
The seller party comes from your entity record, not from the request body. A seller error, such as a non-conformant entity TRN, is fixed by updating the entity. Changing the invoice payload does not fix it.
POST /oauth/invoices/validate runs the same rule engine and the same XML builder as the send, so a payload that passes there is accepted by Push invoice. Nothing is persisted and no invoice number is reserved — validate the same payload as often as you like.
The response is always HTTP 200, whether or not the data is valid. data.is_valid carries the verdict.
Request and response
The body is exactly the body of Push invoice — the same schema, the same conditional rules.
POST /oauth/invoices/validate
Authorization: Bearer <access_token>
Content-Type: application/json
{
"entity_id": "64a1b2c3d4e5f6a7b8c9d0e1",
"invoice_number": "INV-1001",
"date": "2026-08-12",
"due_date": "2026-08-14",
"currency": "AED",
"payment_means_code": "30",
"payment_account_id": "AE070331234567890123456",
"seller": {
"registration_type": "EID",
"issuing_authority": "ICP"
},
"buyer": {
"name": "Buyer LLC",
"trn": "104239483100003",
"country": "AE",
"address_line1": "Sheikh Zayed Road 12",
"city": "Dubai",
"region": "Dubai",
"legal_registration_id": "CN-1234567",
"legal_registration_type": "TL",
"authority_name": "Department of Economic Development - Dubai"
},
"line_items": [
{
"description": "Mercedes Benz",
"item_name": "Mercedes",
"quantity": 1,
"unit_amount": 300000,
"tax_amount": 15000,
"line_amount": 300000,
"tax_rate": 5,
"tax_category": "S"
}
]
}A payload with nothing wrong with it:
{
"message": "Invoice data is valid and ready to send",
"data": {
"is_valid": true,
"entity_id": "64a1b2c3d4e5f6a7b8c9d0e1",
"invoice_number": "INV-1001",
"total_errors": 0,
"errors": [],
"warnings": []
}
}The same payload with a 3-digit buyer TRN and a zero-VAT line. Each entry names the section it came from, so you can map it back to the part of your payload that needs fixing:
{
"message": "Invoice data has validation errors",
"data": {
"is_valid": false,
"entity_id": "64a1b2c3d4e5f6a7b8c9d0e1",
"invoice_number": "INV-1001",
"total_errors": 3,
"errors": [
{ "message": "Contact TaxNumber (TRN) must be 15 characters long.", "section": "contact_trn" },
{ "message": "Total tax amount cannot be zero.", "section": "tax" },
{ "message": "Tax amount cannot be zero.", "section": "invoice_items" }
],
"warnings": []
}
}data.warnings is separate on purpose: it lists things that are not rule failures but would still make the send fail. Today that means a duplicate invoice number for the entity, which Push invoice rejects with 409. A payload can be is_valid: true and still carry a warning.
Push invoice reports the same errors when you skip the dry run. It answers with a 422 rather than a 200, since nothing was created. Here the entity holds a TRN that is not a conformant UAE TRN:
HTTP/1.1 422 Unprocessable Entity
{
"error": "Invoice failed validation and was not sent. Nothing was stored and the invoice number is still free: fix the errors listed and send the invoice again.",
"invoice_number": "INV-1001",
"total_errors": 1,
"errors": [
{
"message": "Seller VAT identifier (IBT-031) is missing or not a conformant UAE TRN. It is taken from the entity record, not from the invoice payload, and must be 15 digits, starting with 1, ending with 03 or 001 (ibr-132-ae). Update the TRN on the entity, then send the invoice again.",
"section": "seller_party"
}
]
}Error sections
Every error carries one of these section names.
| section | What it covers |
|---|---|
endpoint_ids | The entity has no Peppol registry code — it is not registered for sending yet. |
contact_trn | Buyer identity: TRN missing or not 15 characters, buyer name missing, address country missing or not a supported country. |
invoice_header | Document level: invoice number, issue date format, currency code, due date format, document identifier. |
seller_party | Seller identity, address, TRN and legal registration — including ibr-173-ae. |
buyer_party | Buyer identity, postal address and legal registration — address line 1, city and region for every buyer (ibr-144-ae), and the BTUAE-16 code check. |
tax | Document VAT: total tax or taxable amount is zero, currency mismatch, percentage out of range. |
legal_monetary | Document totals: line extension, tax exclusive/inclusive and payable amounts. |
invoice_items | Per line: zero tax, zero line amount, zero price, missing account code, missing description. |
Seller and buyer registration type (BTUAE-15 / BTUAE-16)
A UAE seller identified by the "0235" scheme must qualify its legal registration identifier with a registration type. ibr-173-ae is fatal: the gateway rejects the whole document when the type is missing or outside this list, so the validator reports it as an error rather than letting the send fail later. The buyer's BTUAE-16 uses the same four codes.
| Code | Meaning | Companion field | Notes |
|---|---|---|---|
TL | Commercial / Trade license | issuing_authority — required | The default when you send no seller block and the entity has a trade licence number. |
EID | Emirates ID | issuing_authority — optional | Emitted as @schemeAgencyName when supplied. |
PAS | Passport | passport_country — required | The passport's issuing country becomes @schemeAgencyName. |
CD | Cabinet decision | issuing_authority — optional | Emitted as @schemeAgencyName when supplied. |
The code is emitted as @schemeAgencyID on PartyLegalEntity/cbc:CompanyID, and the companion field as @schemeAgencyName. Lower case is accepted and normalised; anything outside the list is rejected with 400 before validation even runs.
You can set this once instead of per invoice. Register legal_registration_type (and issuing_authority or passport_country) on the entity via PATCH /oauth/entities and every invoice for that entity carries it — the per-invoice seller block then only needs sending when one invoice must differ, and it overrides the entity for that invoice alone.
With neither the entity nor the payload supplying a type, an entity that has a registration number on file falls back to TL; an entity with no number at all emits its registry code and no @schemeAgencyID, which fails ibr-173-ae for an AE seller — so register one of the two.
VAT category and zero-tax lines (IBT-151)
Set line_items[].tax_category per line. It is the line's VAT treatment, and a zero tax amount is valid on every category except S — a zero-rated, exempt, out-of-scope, reverse-charge or margin line charges no VAT, and the rules make a non-zero amount on those categories fatal. Categories may be mixed on one invoice: each one is delivered as its own cac:TaxSubtotal.
| Code | Meaning | Rules |
|---|---|---|
S | Standard rated | Must charge VAT at 5% (ibr-190-ae). A zero tax_amount on an “S” line is rejected — state the line’s real category instead. |
Z | Zero rated | tax_amount must be 0. No exemption reason. |
E | Exempt | tax_amount must be 0 and tax_exemption_reason is required (ibr-167-ae) — the request is refused without it. |
O | Out of scope of tax | tax_amount must be 0. An invoice whose every line is E or O is delivered as invoice type 480, and such a credit note as 81 (ibr-151-ae). |
AE | Reverse charge | tax_amount must be 0 — the buyer accounts for the VAT. Requires buyer.trn, item_type (goods code) and item_standard_id. |
N | Margin scheme | tax_amount must be 0; the rate stays 5% and the VAT is not disclosed. |
Full labels are accepted too ("Zero Rated", "Exempt", "Reverse Charge"), as are the field names tax_category_code, vat_category and category. Anything outside the list is reported against the line rather than silently treated as standard rated.
An exempt line needs tax_exemption_reason (IBT-121); the request is refused without it. These four are the UAE codelist reasons; the list is not closed, so any other code you are required to use is carried through as sent.
| Code | Reason |
|---|---|
DL8.46.1 | Certain financial services |
DL8.46.2 | Residential units — lease or sale |
DL8.46.3 | Bare land |
DL8.46.4 | Local passenger transport |
A reverse-charge line additionally needs item_type — the type of goods, one of DL8.48.8.1 (gold and diamonds), DL8.48.8.2 (electronic devices), DL8.48.3.1 (crude or refined oil), DL8.48.3.2 (natural gas) or DL8.48.3.3 (pure hydrocarbons) — plus item_standard_id (your item code) and the buyer's TRN.
How tax amounts are checked
Your line tax_amount is used exactly as you send it — it is never recalculated. The checks are:
- Per line: an S line must carry tax; Z, E, O, AE and N lines must carry 0 (see the table above).
- Per VAT category: the standard-rated tax must equal its taxable amount × 5%, within ±0.02 (aligned-ibrp-s-09, fatal). Round each line normally and you stay inside that margin.
- Per invoice: the VAT total is total_tax when you send it, otherwise the sum of the line tax amounts. A small difference between total_tax and your lines is absorbed into the largest standard-rated category (ibr-co-14), which must still pass the ±0.02 check.
UAE supply types (BTUAE-02 and IBT-151)
A supply type is declared in one of two ways. Reverse charge and zero rated are a line's tax_category. The others are flags in transaction_type_code: eight characters, each 0 or 1, in the order free trade zone, deemed supply, margin scheme, summary invoice, continuous supply, disclosed agent, e-commerce, exports. A flag makes its extra fields mandatory, and a field sent without its flag is accepted and ignored.
| Supply | Declare it with | Also send | What happens |
|---|---|---|---|
| Reverse charge (RCM) | line tax_category "AE" | buyer.trn; per line item_type (goods code) and item_standard_id; tax_amount 0 | Delivers an AE tax subtotal. The buyer accounts for the VAT. A missing goods code or item id fails validation. |
| Zero rated | line tax_category "Z" | tax_rate 0 and tax_amount 0 | Delivers a Z tax subtotal. Nothing else is required. |
| Free trade zone | transaction_type_code "10000000" (position 1) | beneficiary_id — the TRN or TIN of the party the supply is for | Delivers the beneficiary as cac:BuyerCustomerParty. Without beneficiary_id the request is refused (400). |
| Exports | transaction_type_code "00000001" (position 8), with "Z" lines for a qualifying export | the delivery block (address_line1, city, country_subdivision, country); buyer.participant_id when the buyer is on Peppol | Turns on the export rules: a buyer not on Peppol is addressed by the FTA placeholder 0235:9900000099, and a non-AED invoice gets the Central Bank rate when you send none. |
| E-commerce | transaction_type_code "00000010" (position 7) | the delivery block (address_line1, city, country_subdivision) | Delivers the deliver-to address (ibr-142-ae). Without it the request is refused (400). |
| Deemed supply | transaction_type_code "01000000" (position 2) | nothing extra — there is no customer | Addresses the buyer to the FTA placeholder 0235:9900000097. |
| Summary invoice | transaction_type_code "00010000" (position 4) | invoice_period_start and/or invoice_period_end (YYYY-MM-DD) | Delivers cac:InvoicePeriod. With neither date the request is refused (400). |
| Disclosed agent billing | transaction_type_code "00000100" (position 6) | principal_id — the principal's UAE TRN, which must not be your own | Delivers the principal as cac:SellerSupplierParty. A missing or malformed TRN is refused (400); your own TRN fails validation (ibr-176-ae). |
Deemed supply, margin scheme and summary invoice cannot be declared on a document whose every line is exempt or out of scope — it is sent as out of scope of tax (480 or 81), and the request is refused (400, ibr-157-ae). Zero rating is not inferred from the export flag. Send "Z" on a line that qualifies, and "S" on one that does not.
Payment means (IBT-081)
A standard invoice must carry a payment means code (ibr-191-ae), so one is always emitted: 30 credit transfer, 10 cash, 20 cheque, 48 bank card, 49 direct debit. Sending 30 requires payment_account_id (the IBAN), which is emitted as PayeeFinancialAccount/cbc:ID.
Send no payment fields at all and the invoice carries code 1 ("Instrument Not Defined") rather than claiming a credit transfer to an account you never supplied.
Field-to-XML reference
Where each part of your request ends up in the delivered UBL, with the PINT-AE business term it satisfies. The seller party is built from your entity record, not the invoice body — keep its address, TRN and trade licence up to date.
| Field | UBL element | Term / rule | |
|---|---|---|---|
| Document | |||
invoice_number | cbc:ID / cbc:UUID * | IBT-001 | required |
date | cbc:IssueDate | IBT-002 | required |
due_date | cbc:DueDate | IBT-009 | optional |
currency | cbc:DocumentCurrencyCode | IBT-005 | optional |
| Seller party — from your entity record | |||
entity.area / street | cac:PostalAddress/cbc:StreetName | IBT-035 | entity |
entity.city | cac:PostalAddress/cbc:CityName | IBT-037 | entity |
entity.postal_code | cac:PostalAddress/cbc:PostalZone | IBT-038 | entity |
entity.state | cac:PostalAddress/cbc:CountrySubentity | IBT-039 | entity |
entity.trn | cac:PartyTaxScheme/cbc:CompanyID | IBT-031 | entity |
entity.legal_registration_id | cac:PartyLegalEntity/cbc:CompanyID | IBT-030 | entity |
entity.legal_registration_type | CompanyID/@schemeAgencyID | BTUAE-15 | entity |
entity.issuing_authority | CompanyID/@schemeAgencyName | BTUAE-12 | entity |
entity.passport_country | CompanyID/@schemeAgencyName | BTUAE-18 | entity |
| Seller legal registration — per invoice | |||
seller.legal_registration_id | cac:PartyLegalEntity/cbc:CompanyID | IBT-030 | conditional |
seller.registration_type | CompanyID/@schemeAgencyID | BTUAE-15 · ibr-173-ae | conditional |
seller.issuing_authority | CompanyID/@schemeAgencyName | BTUAE-12 | conditional |
seller.passport_country | CompanyID/@schemeAgencyName | BTUAE-18 | conditional |
| Buyer party — from your request | |||
buyer.name | cac:PartyLegalEntity/cbc:RegistrationName | IBT-044 | required |
buyer.trn | cbc:EndpointID + cac:PartyTaxScheme/cbc:CompanyID | IBT-049 / IBT-048 | required |
buyer.address_line1 | cac:PostalAddress/cbc:StreetName | IBT-050 | required |
buyer.city | cac:PostalAddress/cbc:CityName | IBT-052 | required |
buyer.region | cac:PostalAddress/cbc:CountrySubentity | IBT-054 | required |
buyer.country | cac:PostalAddress/cac:Country/cbc:IdentificationCode | IBT-055 | required |
buyer.legal_registration_id | cac:PartyLegalEntity/cbc:CompanyID | IBT-047 | optional |
buyer.legal_registration_type | CompanyID/@schemeAgencyID | BTUAE-16 | optional |
buyer.authority_name | CompanyID/@schemeAgencyName | BTUAE-11 | conditional |
buyer.passport_country_code | CompanyID/@schemeAgencyName | BTUAE-19 | conditional |
| Transaction type | |||
transaction_type_code | cbc:ProfileExecutionID | BTUAE-02 · ibr-154-ae | optional |
beneficiary_id | cac:BuyerCustomerParty/cac:Party/cac:PartyIdentification/cbc:ID | BTAE-01 · ibr-007-ae | conditional |
principal_id | cac:SellerSupplierParty/cac:Party/cac:PartyIdentification/cbc:ID | BTAE-14 · ibr-137-ae | conditional |
invoice_period_start | cac:InvoicePeriod/cbc:StartDate | IBT-073 · ibr-138-ae | conditional |
invoice_period_end | cac:InvoicePeriod/cbc:EndDate | IBT-074 · ibr-138-ae | conditional |
| Payment | |||
payment_means_code | cac:PaymentMeans/cbc:PaymentMeansCode | IBT-081 · ibr-191-ae | optional |
payment_account_id | cac:PayeeFinancialAccount/cbc:ID | IBT-084 | conditional |
| Totals and lines | |||
sub_total / total_tax | cac:TaxTotal + cac:LegalMonetaryTotal | IBG-22 / IBG-23 | optional |
line_items[].description | cac:Item/cbc:Description + cbc:Name | IBT-154 / IBT-153 | required |
line_items[].quantity | cbc:InvoicedQuantity | IBT-129 | required |
line_items[].unit_amount | cac:Price/cbc:PriceAmount | IBT-146 | required |
line_items[].tax_amount | cac:ItemPriceExtension/cac:TaxTotal | IBT-117 | required |
line_items[].tax_category | cac:ClassifiedTaxCategory/cbc:ID | IBT-151 | optional |
line_items[].tax_exemption_reason | cbc:TaxExemptionReasonCode | IBT-121 · ibr-167-ae | conditional |
line_items[].item_type | cac:CommodityClassification/cbc:NatureCode | BTUAE-09 · ibr-006-ae | conditional |
line_items[].item_standard_id | cac:StandardItemIdentification/cbc:ID | IBT-157 · ibr-174-ae | conditional |
* The document identifier is a system-generated UUID, stamped once per invoice record and emitted as both cbc:ID and cbc:UUID. Your invoice_number identifies the invoice in this API and must be unique per entity.
What the delivered XML looks like
The parties and payment means produced by the request above:
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0235">1042394831</cbc:EndpointID>
<cac:PostalAddress>
<cbc:StreetName>Business Bay</cbc:StreetName>
<cbc:CityName>Dubai</cbc:CityName>
<cbc:PostalZone>PO Box 77</cbc:PostalZone>
<cbc:CountrySubentity>DXB</cbc:CountrySubentity>
<cac:Country>
<cbc:IdentificationCode>AE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>104239483100003</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>E-Invoice Dev</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="EID" schemeAgencyName="ICP">2309778</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0235">1042394831</cbc:EndpointID>
<cac:PostalAddress>
<cbc:StreetName>Sheikh Zayed Road 12</cbc:StreetName>
<cbc:CityName>Dubai</cbc:CityName>
<cbc:CountrySubentity>DXB</cbc:CountrySubentity>
<cac:Country>
<cbc:IdentificationCode>AE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
<cac:PartyTaxScheme>
<cbc:CompanyID>104239483100003</cbc:CompanyID>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:PartyTaxScheme>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Buyer LLC</cbc:RegistrationName>
<cbc:CompanyID schemeAgencyID="TL"
schemeAgencyName="Department of Economic Development - Dubai">CN-1234567</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:PaymentMeans>
<cbc:PaymentMeansCode name="Credit transfer">30</cbc:PaymentMeansCode>
<cac:PayeeFinancialAccount>
<cbc:ID>AE070331234567890123456</cbc:ID>
</cac:PayeeFinancialAccount>
</cac:PaymentMeans>Known limitations
These fields are accepted by the schema but do not yet reach the delivered XML on this flow. Do not rely on them until they are announced as supported:
reference(document note),buyer.email,buyer.phone,buyer.electronic_address.- Line level:
item_name(the description is used),discount_percent,hs_code.