- Build Your Integration
Submit a document
Stage 3 of 6. Submit each invoice and credit note of a taxpayer to Wafeq ASP. Wafeq ASP then delivers it and reports it to the FTA.
This page uses these terms:
- Taxpayer: a business from which your organization has an active grant. On each document that your app submits, the taxpayer is the seller.
- Buyer: the business that receives the document.
- Document: an invoice or a credit note. This page shows invoices. Credit notes use
POST /credit-notesandPOST /credit-notes/validatein the same way. - Peppol ID: the address of a business on Peppol. For a UAE business, it is
0235:followed by the TIN of the business. - TIN: the 10-digit number that the FTA gives a business.
- VAT TRN: the VAT registration number of a business: 15 digits, ending in
03. - Legal registration: the registration that legally identifies a business or a person. It is a trade or commercial licence (
TL), an Emirates ID (EID), a passport (PAS) or a Cabinet decision (CD). - FTA placeholder address: an address that Wafeq ASP uses instead of the Peppol ID of the buyer. Wafeq ASP uses it when it reports a document to the FTA only.
Your app submits each document in two calls:
- A check, with
POST /invoices/validate. Wafeq ASP runs all its checks. It keeps nothing, delivers nothing and reports nothing. - The submission, with
POST /invoicesand the same request body. Wafeq ASP checks the document again, then delivers it and reports it to the FTA.
Before you start
- The business is connected. Your app has its Taxpayer ID and its grant ID. Refer to Connect a taxpayer.
- Your organization has a webhook endpoint. Refer to Set up your organization.
Submit a document: the five steps
| Step | Action | Result |
|---|---|---|
| 1 | Collect the details of the buyer. | Wafeq ASP can find the buyer, or can report to the FTA only. |
| 2 | Build the request body. | The body describes the sale. |
| 3 | Check the document. | You know if the document is correct, and where it will go. |
| 4 | Submit the document. | Wafeq ASP accepts the document, or refuses it. |
| 5 | Read the response. | Your app knows what to do next. |
Step 1: Collect the details of the buyer
The details of the buyer decide where the document goes. Keep these details for each buyer in your app, and send them in customer:
- Address, in
customer.address. Always send it. For a UAE buyer, put the emirate incountry_subentity, for exampleDXB,AUHorSHJ. For a buyer outside the UAE, put its region, for example its state or county. - TIN, in
customer.id, written as0235:<TIN>. Send it when the buyer has a TIN. For a buyer outside the UAE, send its Peppol ID in the scheme of its country, if it has one. - VAT TRN, in
customer.vat_number. Send it when the buyer is registered for VAT. - Legal registration, in
customer.legal_registration. It is necessary on commercial invoices (480) and commercial credit notes (81). Refer to Document types.
One more field is necessary in one case only: when you send neither a UAE TIN nor a VAT TRN for the buyer. In this case, send customer.identifier. The UAE rules then require a number that identifies the buyer. A buyer outside the UAE usually has neither, so send customer.identifier for it, for example its company registration number.
- The identifier is a plain value with no type. Use a number under which the buyer is registered, for example its trade licence number.
- To say what kind of number it is, add
customer.identifier_scheme. This is a 4-digit ISO 6523 code, for example0235for a UAE TIN. It is optional. - In the PINT-AE specification, this field is the buyer identifier (IBT-046).
Wafeq ASP then routes the document by these rules:
The buyer has a TIN and is on Peppol.
- Send:
customer.idas0235:<TIN>. - Result: Wafeq ASP delivers the document to the e-invoicing provider of the buyer, and reports it to the FTA.
The buyer has a TIN but is not on Peppol.
- Send:
customer.idas0235:<TIN>, the same as above. - Result: Wafeq ASP looks for the buyer on Peppol and does not find it. Wafeq ASP does not deliver the document. Wafeq ASP reports it to the FTA only, with the FTA placeholder address
0235:9900000098instead of the Peppol ID of the buyer. The TIN of the buyer stays in the document as its identifier. - Later: Wafeq ASP looks for the buyer again for each new document. When the buyer joins Peppol, Wafeq ASP delivers the documents that your app submits after that. Wafeq ASP does not deliver the earlier documents again.
You know the VAT TRN of the buyer, but not its TIN.
- Send:
customer.vat_number. Do not sendcustomer.id. - Result: Wafeq ASP reports the document to the FTA only, also if the buyer is on Peppol. Without the TIN, Wafeq ASP cannot look for the buyer on Peppol. Ask the buyer for its TIN. Key terms tells you where a TIN comes from.
The buyer has no TIN and no VAT TRN.
- Send:
customer.identifier. Do not sendcustomer.id. - Result: Wafeq ASP reports the document to the FTA only. Without an identifier, Wafeq ASP refuses the document (
SCH:ibr-135-ae).
For exports and deemed supplies, Wafeq ASP uses other FTA placeholder addresses. Refer to Who receives the document.
Step 2: Build the request body
Put the data of the sale in the request body:
- The buyer, from step 1.
- The lines: for each line, the item, the quantity, the price, and the VAT category and rate. Refer to VAT on each line.
- The special scenario, if the sale is one, in
transaction_type_code. Refer to Special scenarios. - Your own ID for the document, in
source_id. Each event about the document contains this value.
Do not send the seller, the totals or the document type. Wafeq ASP adds them. Wafeq ASP takes the seller from the registered profile of the taxpayer.
Each invoice number can be used one time only for each taxpayer. Wafeq ASP refuses a number that the taxpayer already used.
Example:
{
"invoice_number": "INV-2026-0042",
"issue_date": "2026-10-15",
"due_date": "2026-11-14",
"source_id": "erp-invoice-8812",
"customer": {
"id": "0235:1999200000",
"name": "Gulf Fresh Foods LLC",
"vat_number": "199920000000003",
"legal_registration": { "type": "TL", "id": "CN-7654321", "authority": "Dubai Economy and Tourism" },
"address": { "street": "Warehouse 7, Al Quoz Industrial 3", "city": "Dubai", "country": "AE", "country_subentity": "DXB" }
},
"line_items": [
{ "description": "Office chairs", "quantity": 10, "unit_price": 450, "tax_category": "S", "tax_percent": 5 }
]
}Step 3: Check the document
Send the request body to POST /invoices/validate. Send the headers of the taxpayer:
curl https://api.wafeq.com/ae/v1/invoices/validate \
-H "Authorization: Api-Key $API_KEY" \
-H "x-ae-taxpayer-id: $TAXPAYER_ID" \
-H "x-ae-grant-id: $GRANT_ID" \
-H "Content-Type: application/json" \
-d @invoice.jsonWafeq ASP runs all the checks of a submission. Wafeq ASP keeps nothing, delivers nothing and reports nothing. Your app can check a document as often as necessary, for example each time the user saves it.
A correct document gives this response:
{
"valid": true,
"invoice_id": "INV-2026-0042",
"invoice_type_code": "380",
"profile_execution_id": "00000000",
"effective_recipient_id": "0235:1999200000",
"route": "delivery",
"errors": [],
"warnings": [],
"info": []
}The response tells you what the submission will do:
invoice_type_code: the document type.effective_recipient_id: the address of the document. This is the Peppol ID of the buyer, or an FTA placeholder address.profile_execution_id: the special scenarios, as the 8 flags oftransaction_type_code.00000000means none.route: where the document will go.
route | Meaning |
|---|---|
delivery | Wafeq ASP will deliver it to the buyer and report it to the FTA. |
reporting_only | Wafeq ASP will report it to the FTA only. |
undeliverable | The buyer is not on Peppol, and the document cannot go to the FTA only. |
unconfirmed | Wafeq ASP could not look for the buyer at this time, or the check stopped early. |
The submission looks for the buyer again. Thus the route can change between the check and the submission.
A document with an error gives this response:
{
"valid": false,
"invoice_id": "INV-2026-0042",
"invoice_type_code": "380",
"route": "delivery",
"errors": [
{
"code": "SCH:ibr-167-ae",
"message": "[ibr-167-ae]-Line VAT information (IBG-30) with Invoiced item VAT category code (IBT-151) as 'Exempt from VAT' MUST have a VAT exemption reason code (IBT-186).",
"severity": "error"
}
],
"warnings": [],
"info": []
}Show each error to your user before the user finalizes the document. A code that starts with SCH: names an official UAE rule. Its message uses the field numbers of the PINT-AE specification:
IBT-…is a field.IBG-…is a group of fields.BTAE-…is a field that only the UAE uses.
The example above means that an exempt line has no exemption reason code (tax_exemption_reason_code). For all error codes, refer to Errors.
Step 4: Submit the document
When the check passes and your user finalizes the document, send the same body to POST /invoices. Add an Idempotency-Key header:
curl https://api.wafeq.com/ae/v1/invoices \
-H "Authorization: Api-Key $API_KEY" \
-H "x-ae-taxpayer-id: $TAXPAYER_ID" \
-H "x-ae-grant-id: $GRANT_ID" \
-H "Idempotency-Key: erp-invoice-8812-3f9a1c" \
-H "Prefer: respond-async" \
-H "Content-Type: application/json" \
-d @invoice.jsonIdempotency-Key makes the submission safe to repeat:
- Make one key for each version of the document. For example, use your document ID and a short hash of the body.
- A key has 1 to 128 visible ASCII characters.
- Store the key before your app sends the request.
- Send a key with each submission. Without a key, Wafeq ASP refuses a document that goes to the FTA only, and a queued submission (
idempotency_key_required).
Prefer: respond-async asks for queued delivery. Send it with each submission:
- Wafeq ASP accepts the document and answers immediately with
202. Then Wafeq ASP delivers the document in the background. - If the provider of the buyer rejects the document later, your app can submit a correction. Wafeq ASP accepts a correction only for a document that your app submitted with this header. Refer to Correct or cancel a document.
- Without this header, the call waits until the delivery to the provider of the buyer ends.
Step 5: Read the response
Use the status field to decide what to do. Do not use the success field for this. Store document_id: this ID identifies the document from now on.
202, status: queued
- Meaning: Wafeq ASP accepted the document. The delivery to the buyer runs in the background.
- Next: track the document (stage 4). The
Locationheader points to the status of the delivery.
202, status: reporting_only
- Meaning: Wafeq ASP accepted the document. Wafeq ASP reports it to the FTA only.
- Next: track the report to the FTA (stage 4).
200, status: delivered
- Meaning: Wafeq ASP accepted the document and delivered it to the provider of the buyer.
- Next: track the report to the FTA (stage 4).
200, status: failed, with a document_id
- Meaning: Wafeq ASP accepted the document and reports it to the FTA. The delivery to the provider of the buyer failed.
- Next: do not submit the document again. Track it (stage 4).
200, success: false, any other status
- Meaning: Wafeq ASP refused the document. Wafeq ASP kept nothing.
- Next: read
errorandvalidation_errors. Correct the document, check it again and submit it again. A changed body needs a newIdempotency-Key.
Other responses, for example 402, 409, 422, 429 and 503, are in Errors.
If a submission gets no response
This section is not a step. Sometimes your app does not know if Wafeq ASP accepted a submission, for example after a timeout or a lost connection. This section tells you what to do.
There is no retry endpoint. To retry, send the same request again, with the same Idempotency-Key:
- If Wafeq ASP accepted the first request, it returns the stored response. Wafeq ASP does not submit the document two times.
- If Wafeq ASP did not accept the first request, it processes the request now.
Do not use a new key to retry. A new key can submit the document a second time. The only exception is idempotency_unconfirmed, below. A key does not expire. A key applies to one taxpayer and to your organization.
Two answers need special care:
409 idempotency_in_progress: the first request with this key is still in progress. Wait, then send the same request again.409 idempotency_unconfirmed: the first request stopped, and its result is not known. Do not send the same key again, because the answer does not change. Look for the document number inGET /invoicesorGET /credit-notes. Filter by the issue date of the document. If the document is there, Wafeq ASP accepted it. Track it. If the document is not there, submit it again with a new key.
If you send the same key with a different body, Wafeq ASP refuses it with 409 idempotency_key_reused. For all other answers, refer to Errors.
Documents in Sandbox
In Sandbox, Wafeq ASP does not look for buyers on Peppol, and it delivers nothing. Two test buyers let you see the other routes. Refer to Sandbox.
Next
Stage 4: Track a document.
Updated about 14 hours ago