Get started

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

POST /oauth/invoices stores your invoice and queues it; the validate → XML → deliver steps then run asynchronously. A rejection therefore surfaces as a status on the invoice, not as an error on your original request. Validating first turns that asynchronous failure into a synchronous answer.

The dry run uses the identical rule engine and the identical XML builder as the send, so a payload that passes here passes there. 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.

Request
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:

200 — valid
{
  "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:

200 — invalid
{
  "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.

Error sections

Every error carries one of these section names.

sectionWhat it covers
endpoint_idsThe entity has no Peppol registry code — it is not registered for sending yet.
contact_trnBuyer identity: TRN missing or not 15 characters, buyer name missing, address country missing or not a supported country.
invoice_headerDocument level: invoice number, issue date format, currency code, due date format, document identifier.
seller_partySeller identity, address, TRN and legal registration — including ibr-173-ae.
buyer_partyBuyer identity, address and legal registration — including the BTUAE-16 code check.
taxDocument VAT: total tax or taxable amount is zero, currency mismatch, percentage out of range.
legal_monetaryDocument totals: line extension, tax exclusive/inclusive and payable amounts.
invoice_itemsPer 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.

CodeMeaningCompanion fieldNotes
TLCommercial / Trade licenseissuing_authority — requiredThe default when you send no seller block and the entity has a trade licence number.
EIDEmirates IDissuing_authority — optionalEmitted as @schemeAgencyName when supplied.
PASPassportpassport_country — requiredThe passport's issuing country becomes @schemeAgencyName.
CDCabinet decisionissuing_authority — optionalEmitted 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 POST/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.

CodeMeaningRules
SStandard ratedMust charge VAT at 5% (ibr-190-ae). A zero tax_amount on an “S” line is rejected — state the line’s real category instead.
ZZero ratedtax_amount must be 0. No exemption reason.
EExempttax_amount must be 0 and tax_exemption_reason is required (ibr-167-ae) — the request is refused without it.
OOut of scope of taxtax_amount must be 0. An invoice whose every line is E or O is delivered as invoice type 480 (ibr-151-ae).
AEReverse chargetax_amount must be 0 — the buyer accounts for the VAT. Requires buyer.trn, item_type (goods code) and item_standard_id.
NMargin schemetax_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.

CodeReason
DL8.46.1Certain financial services
DL8.46.2Residential units — lease or sale
DL8.46.3Bare land
DL8.46.4Local 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.

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.

FieldUBL elementTerm / rule
Document
invoice_numbercbc:ID / cbc:UUID *IBT-001required
datecbc:IssueDateIBT-002required
due_datecbc:DueDateIBT-009optional
currencycbc:DocumentCurrencyCodeIBT-005optional
Seller party — from your entity record
entity.area / streetcac:PostalAddress/cbc:StreetNameIBT-035entity
entity.citycac:PostalAddress/cbc:CityNameIBT-037entity
entity.postal_codecac:PostalAddress/cbc:PostalZoneIBT-038entity
entity.statecac:PostalAddress/cbc:CountrySubentityIBT-039entity
entity.trncac:PartyTaxScheme/cbc:CompanyIDIBT-031entity
entity.legal_registration_idcac:PartyLegalEntity/cbc:CompanyIDIBT-030entity
entity.legal_registration_typeCompanyID/@schemeAgencyIDBTUAE-15entity
entity.issuing_authorityCompanyID/@schemeAgencyNameBTUAE-12entity
entity.passport_countryCompanyID/@schemeAgencyNameBTUAE-18entity
Seller legal registration — per invoice
seller.legal_registration_idcac:PartyLegalEntity/cbc:CompanyIDIBT-030conditional
seller.registration_typeCompanyID/@schemeAgencyIDBTUAE-15 · ibr-173-aeconditional
seller.issuing_authorityCompanyID/@schemeAgencyNameBTUAE-12conditional
seller.passport_countryCompanyID/@schemeAgencyNameBTUAE-18conditional
Buyer party — from your request
buyer.namecac:PartyLegalEntity/cbc:RegistrationNameIBT-044required
buyer.trncbc:EndpointID + cac:PartyTaxScheme/cbc:CompanyIDIBT-049 / IBT-048required
buyer.address_line1cac:PostalAddress/cbc:StreetNameIBT-050optional
buyer.citycac:PostalAddress/cbc:CityNameIBT-052optional
buyer.regioncac:PostalAddress/cbc:CountrySubentityIBT-054optional
buyer.countrycac:PostalAddress/cac:Country/cbc:IdentificationCodeIBT-055required
buyer.legal_registration_idcac:PartyLegalEntity/cbc:CompanyIDIBT-047optional
buyer.legal_registration_typeCompanyID/@schemeAgencyIDBTUAE-16optional
buyer.authority_nameCompanyID/@schemeAgencyNameBTUAE-11conditional
buyer.passport_country_codeCompanyID/@schemeAgencyNameBTUAE-19conditional
Payment
payment_means_codecac:PaymentMeans/cbc:PaymentMeansCodeIBT-081 · ibr-191-aeoptional
payment_account_idcac:PayeeFinancialAccount/cbc:IDIBT-084conditional
Totals and lines
sub_total / total_taxcac:TaxTotal + cac:LegalMonetaryTotalIBG-22 / IBG-23optional
line_items[].descriptioncac:Item/cbc:Description + cbc:NameIBT-154 / IBT-153required
line_items[].quantitycbc:InvoicedQuantityIBT-129required
line_items[].unit_amountcac:Price/cbc:PriceAmountIBT-146required
line_items[].tax_amountcac:ItemPriceExtension/cac:TaxTotalIBT-117required
line_items[].tax_categorycac:ClassifiedTaxCategory/cbc:IDIBT-151optional
line_items[].tax_exemption_reasoncbc:TaxExemptionReasonCodeIBT-121 · ibr-167-aeconditional
line_items[].item_typecac:CommodityClassification/cbc:NatureCodeBTUAE-09 · ibr-006-aeconditional
line_items[].item_standard_idcac:StandardItemIdentification/cbc:IDIBT-157 · ibr-174-aeconditional

* 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:

UBL excerpt
<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:

  • invoice_type — every document is emitted as InvoiceTypeCode 380 (tax invoice). Credit notes are not supported yet.
  • transaction_type_code — the free-zone / export / deemed supply flags are not emitted; ProfileExecutionID is always 00000000.
  • reference (document note), buyer.phone, buyer.electronic_address.
  • Line level: unit (always EA), item_name (the description is used), discount_percent, hs_code, service_code.