Integrate your ERP with the UAE Peppol network
One REST endpoint turns your accounting-system invoice into a validated PINT-AE document, sends it over Peppol, and reports it to the FTA. Send us structured JSON — we handle the UBL, validation, receiver routing, fallbacks and reporting. This page lists every endpoint, field and code value you need to integrate on your own.
Authentication
All requests use a per-tenant API token as a Bearer header. Create and rotate tokens in the portal under API Tokens. Your token identifies the sender — the seller on every document is your own Peppol identity, so tokens cannot impersonate another business.
https://ozeinvoice.ae/api/v1# every request carries your token Authorization: Bearer pp_your_token_here Content-Type: application/json
Send an invoice
Post the invoice as JSON. We build the PINT-AE UBL, validate it, derive the receiver, send over Peppol and report to the FTA. You never send the seller — it is taken from your tenant profile.
# complete invoice — all required fields curl -X POST https://ozeinvoice.ae/api/v1/invoices \ -H "Authorization: Bearer pp_your_token" \ -H "Content-Type: application/json" \ -d '{ "doc_type": "Invoice", "invoice_number": "INV-1001", "issue_date": "2026-09-14", "due_date": "2026-10-14", "currency": "AED", "payment_means_code": "30", "transaction_type": "Standard", "buyer": { "name": "Acme Trading LLC", "trn": "100000000000003", "address": "Sheikh Zayed Road", "city": "Dubai", "state": "Dubai", "country": "AE" }, "lines": [ { "description": "Consulting", "qty": 1, "unit": "HUR", "price": 500.00, "vat_rate": 5, "tax_classification": "S" } ] }'
trn empty and we route the buyer to
the FTA fallback endpoint automatically. No emirate? If you send city
we use it as the emirate when the state is blank.Request fields
Send raw values — mapping of your own units, tax labels and payment methods to the code lists below is your side; validation and compliance are ours.
Legend. required send it on every document. Where a default is noted we apply it automatically if you omit the field — but a production integration should set it deliberately, not rely on the default. optional is a matching aid only.
| Field | Req | Notes |
|---|---|---|
| invoice_number | required | Unique per account. A repeat returns 409 duplicate. |
| issue_date | required | YYYY-MM-DD. |
| due_date | required | Default if omitted: your configured payment term, else due on receipt. |
| currency | required | Default if omitted: AED. |
| doc_type | required | Invoice or CreditNote. Default if omitted: Invoice. |
| payment_means_code | required | UNCL 4461 code. Default if omitted: 30. table |
| transaction_type | required | Label or 8-flag code. Default if omitted: Standard. table |
| buyer.name | required | Buyer legal name. |
| buyer.trn | required | 15-digit TRN. If the buyer has none, leave blank → routed to the FTA fallback endpoint. |
| buyer.address / city / state / country | required | Street, city, emirate, country. Emirate falls back to city if state is blank. zip optional. |
| buyer.erp_id | optional | Your customer id — we match/upsert on it. |
| lines[].description | required | Line description. |
| lines[].qty | required | Default if omitted: 1. |
| lines[].unit | required | UN/ECE code. Default if omitted: C62. table |
| lines[].price | required | Unit price, exclusive of VAT. |
| lines[].vat_rate | required | Percentage, e.g. 5 or 0. |
| lines[].tax_classification | required | S/Z/E/O or a label. Default if omitted: inferred from the rate. table |
| lines[].erp_id | optional | Your item id — we match/upsert the catalogue on it. |
Field-name aliases (PINT-AE names)
If you prefer the official PINT-AE / MoF field names, you can send those instead of the short names above — both are accepted (the short name wins if you send both):
| Short name | PINT-AE alias |
|---|---|
| currency | invoice_currency_code |
| transaction_type | invoice_transaction_type_code |
| payment_means_code | payment_means_type_code |
| due_date | payment_due_date |
| buyer.name | buyer.buyer_name |
| buyer.trn | buyer.buyer_tax_identifier |
| buyer.address | buyer.buyer_address_line_1 |
| buyer.city | buyer.buyer_city |
| buyer.state | buyer.buyer_country_subdivision |
| buyer.country | buyer.buyer_country_code |
| lines[].qty | lines[].invoiced_quantity |
| lines[].unit | lines[].unit_of_measure_code |
| lines[].price | lines[].item_net_price |
| lines[].vat_rate | lines[].invoiced_item_tax_rate |
| lines[].tax_classification | lines[].invoiced_item_tax_category_code |
| lines[].description | lines[].item_name / item_description |
| credit_reason_code | credit_note_reason_code |
| preceding_invoice_number | preceding_invoice_reference |
Credit notes
A credit note takes all the same fields as an invoice above
(buyer, lines, currency, payment means, transaction type — everything except a due date, which a
credit note doesn't carry). You simply set doc_type to
CreditNote and add the fields below on top:
| Additional field | Req | Notes |
|---|---|---|
| doc_type | yes | CreditNote |
| credit_reason_code | yes | Reason code. table (defaults to DL8.61.1.E if omitted). |
| credit_reason_text | no | Free-text explanation (shown as description). |
| preceding_invoice_number | yes* | The original invoice this corrects. *Not required for VD (volume discount). |
| preceding_invoice_date | no | Original invoice date. |
# complete credit note — same fields as an invoice, plus the credit-note ones curl -X POST https://ozeinvoice.ae/api/v1/invoices \ -H "Authorization: Bearer pp_your_token" \ -H "Content-Type: application/json" \ -d '{ "doc_type": "CreditNote", "invoice_number": "CN-2001", "issue_date": "2026-09-20", "currency": "AED", "transaction_type": "Standard", "credit_reason_code": "DL8.61.1.D", "credit_reason_text": "Goods returned by customer", "preceding_invoice_number": "INV-1001", "preceding_invoice_date": "2026-09-14", "buyer": { "name": "Acme Trading LLC", "trn": "100000000000003", "address": "Sheikh Zayed Road", "city": "Dubai", "state": "Dubai", "country": "AE" }, "lines": [ { "description": "Consulting", "qty": 1, "unit": "HUR", "price": 500.00, "vat_rate": 5, "tax_classification": "S" } ] }'
credit_reason_code to
VD — then preceding_invoice_number is not required.Testing — dry run
Add ?dry_run=1 (or "dry_run": true)
to validate a payload without sending anything. Nothing is sent over Peppol, nothing is reported to the
FTA, and it does not count toward your monthly usage. Use it while you build.
curl -X POST "https://ozeinvoice.ae/api/v1/invoices?dry_run=1" \
-H "Authorization: Bearer pp_your_token" \
-H "Content-Type: application/json" \
-d '{ ... }'
Responses & errors
Success
{ "ok": true, "data": { "document_id": 123, "status": "SENT" } }
Dry run
{ "ok": true, "data": {
"dry_run": true, "valid": true,
"errors": [], "error_details": [],
"schematron_version": "PINT AE 1.0.4"
} }
Validation failure — per-field
{ "ok": true, "data": {
"valid": false,
"error_details": [
{ "code": "ibr-144-ae", "field": "buyer.address",
"message": "The buyer address is incomplete — provide street, city and emirate." }
]
} }
Other statuses: 400 missing/invalid field, 401
bad token, 409 duplicate invoice number.
Idempotency
Retries are safe. An invoice number is unique per account, and you can also send
an Idempotency-Key header — a repeat with the same key returns the original
result instead of sending twice.
Idempotency-Key: INV-1001
Ping — check your connection
Returns your tenant identity and capabilities. No side effects — a safe way to confirm your token and base URL are correct.
Receiving invoices
List inbound documents received on Peppol, or fetch one — as raw UBL and as a
parsed JSON summary (supplier, buyer, lines, tax, totals). You can also subscribe to
invoice.received webhooks in the portal instead of polling.
Payment means (UNCL 4461)
Send the code in payment_means_code. Any valid UNCL 4461
code is accepted; the common ones:
| Code | Meaning |
|---|---|
| 10 | Cash |
| 20 | Cheque |
| 30 | Credit transfer (bank transfer) — default; requires a payee IBAN |
| 42 | Payment to bank account |
| 48 | Bank card |
| 49 | Direct debit |
| 54 | Credit card |
| 55 | Debit card |
| 58 | SEPA credit transfer |
| 59 | SEPA direct debit |
| 97 | Clearing between partners |
| 1 | Instrument not defined |
Transaction types (BTAE-02)
Send the label (or the 8-flag code) in transaction_type.
| Label | Code | Meaning |
|---|---|---|
| Standard | 00000000 | Ordinary domestic taxable supply (default) |
| Export | 00000001 | Export of goods/services |
| Free Zone supply | 10000000 | Supply within/through a designated free zone |
| Deemed supply | 01000000 | Deemed supply |
| Margin scheme | 00100000 | Profit-margin scheme |
| Summary invoice | 00010000 | Summary (periodic) invoice |
| Continuous supply | 00001000 | Continuous supply |
| Disclosed agent billing | 00000100 | Disclosed agent billing |
| E-commerce | 00000010 | E-commerce supply |
Tax classification
Per line, in tax_classification. A label like
"Zero-rated" is accepted too; if null we infer it from the VAT rate.
| Code | Meaning |
|---|---|
| S | Standard rate (5%) |
| Z | Zero-rated (0%) |
| E | Exempt |
| O | Out of scope |
Credit-note reasons
Send the code in credit_reason_code (a label is accepted).
| Code | Reason |
|---|---|
| DL8.61.1.A | Supply was cancelled |
| DL8.61.1.B | Tax treatment changed (nature of supply changed) |
| DL8.61.1.C | Agreed consideration altered (e.g. bad-debt relief) |
| DL8.61.1.D | Goods/services returned and consideration returned |
| DL8.61.1.E | Other (per code list) |
| VD | Volume discount — may omit the preceding invoice |
Units of measure (UN/ECE Rec 20)
Per line, in unit. Blank defaults to C62.
| Code | Unit |
|---|---|
| C62 | Piece / one (default when blank) |
| EA | Each |
| HUR | Hour |
| DAY | Day |
| MON | Month |
| KGM | Kilogram |
| MTR | Metre |
| LTR | Litre |
| MTK | Square metre |
| MTQ | Cubic metre |
| TNE | Tonne |
| SET | Set |