Send your first invoice
Submit a UAE tax invoice to sandbox with one API request, then check its status. You need a sandbox API key, a UAE company onboarded in sandbox, and a source with a mapping.
Set your key
export COMPLYANCE_API_KEY='paste-your-sandbox-key'The examples on this page read the key from COMPLYANCE_API_KEY, so it never appears in your code.
Save the invoice
Save this request body as invoice.json:
{
"country": "AE",
"environment": "sandbox",
"purpose": "invoicing",
"source": "acme-erp:1.0.0",
"documentType": {
"base": "tax_invoice",
"modifiers": []
},
"payload": {
"header": {
"documentType": {
"base": "tax_invoice"
},
"documentNumber": "INV-2026-000184",
"issueDate": "2026-09-18",
"currency": "AED",
"dueDate": "2026-10-18"
},
"parties": {
"seller": {
"name": "Acme Trading LLC",
"address": {
"country": "AE",
"city": "Dubai",
"addressLine1": "12 Harbour Road",
"stateOrProvince": "DXB"
},
"taxIds": [
{
"type": "VAT",
"value": "100012345600003"
}
],
"registrationNumbers": [
{
"type": "TL",
"value": "CN-1234567"
}
],
"peppolId": "1000123456"
},
"buyer": {
"name": "Gulf Retail FZ-LLC",
"address": {
"country": "AE",
"addressLine1": "48 Market Street",
"city": "Dubai",
"stateOrProvince": "DXB"
},
"taxIds": [
{
"type": "VAT",
"value": "100098765400003"
}
],
"registrationNumbers": [
{
"type": "TL",
"value": "CN-7654321"
}
],
"peppolId": "1000987654"
}
},
"lineItems": [
{
"id": "1",
"name": "Cloud subscription — Business plan",
"quantity": 20,
"unitCode": "PCE",
"taxAmount": 100,
"lineTaxableValue": 2000,
"taxCategory": "S",
"taxRate": 5,
"price": {
"amount": 100,
"grossPrice": 100
}
}
],
"payment": {
"paymentMeans": [
{
"paymentMeansCode": "CREDIT_TRANSFER",
"creditTransferInfo": {
"payeeFinancialAccountId": "AE070331234567890123456"
}
}
]
},
"totals": {
"totalAmountExcludingTax": 2000,
"totalAmountIncludingTax": 2100,
"amountDue": 2100,
"totalLineTaxableAmount": 2000,
"totalTaxAmount": 100
},
"extensions": {
"peppol_seller_peppolIdScheme": "0235",
"peppol_buyer_peppolIdScheme": "0235",
"ae_seller_authorityName": "Dubai Department of Economy and Tourism",
"ae_buyer_authorityName": "Dubai Department of Economy and Tourism"
}
}
}Change these values before you send it:
source: your own source. The sampleacme-erp:1.0.0is not in your workspace.documentNumber: a number you have not sent before.- The seller: your onboarded company’s name and VAT registration number (TRN), and its TIN in
peppolId.peppolIdis the 10-digit TIN, andpeppol_seller_peppolIdSchemeis0235, the scheme for UAE TINs, so together they are the Peppol ID the company was registered with.
purpose: invoicing submits the document; validate only checks it. See Purpose. environment must match your key, so keep sandbox with a sandbox key. country and documentType decide which payload fields are required. Complyance reads the payload through your source’s mapping; with the Default GETS Mapping, the payload is GETS. stateOrProvince takes the emirate’s code, such as DXB for Dubai.
Send it
curl --request POST 'https://prod.gets.complyance.io/api/v3/unify' \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'new-api: true' \
--data @invoice.jsonimport { readFile } from 'node:fs/promises';
const response = await fetch('https://prod.gets.complyance.io/api/v3/unify', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.COMPLYANCE_API_KEY}`,
'Content-Type': 'application/json',
Accept: 'application/json',
'new-api': 'true',
},
body: await readFile('invoice.json', 'utf8'),
});
console.log(response.status, await response.json());import os
import requests
with open("invoice.json") as f:
body = f.read()
response = requests.post(
"https://prod.gets.complyance.io/api/v3/unify",
headers={
"Authorization": f"Bearer {os.environ['COMPLYANCE_API_KEY']}",
"Content-Type": "application/json",
"Accept": "application/json",
"new-api": "true",
},
data=body,
)
print(response.status_code, response.json())Send new-api: true with every submission. Without it, the request is read in the earlier format.
To try it in your browser first, open Submit a document in the API playground. It builds this request for any country and sends it to sandbox.
Read the response
You should see HTTP 200:
{
"documentId": "01K5EXAMPLE00000000000142",
"documentNumber": "INV-2026-000184",
"message": "Your invoice was validated and is ready for submission.",
"Base64XML": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4…"
}200 means the invoice passed validation and was stored. Delivery to the tax authority or the Peppol network continues after the response. Base64XML is the e-invoice that was produced, Base64-encoded.
Save the documentId. You need it to check the status.
Check the status
export DOCUMENT_ID='01K5EXAMPLE00000000000142'
curl "https://prod.gets.complyance.io/api/v3/documents/${DOCUMENT_ID}?type=sales" \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Accept: application/json'{
"success": true,
"data": {
"documentId": "01K5EXAMPLE00000000000142",
"documentNumber": "INV-2026-000184",
"country": "AE",
"environment": "sandbox",
"state": "final_valid",
"isTerminal": true,
"lastUpdatedAt": "2026-09-22T10:00:04.000Z",
"errors": [],
"compliance": {
"uuid": "6f1c2a9e-3b4d-4e8f-9a7b-1c2d3e4f5a6b",
"ftaApprovedStatus": "APPROVED",
"businessProcessIdentifier": "urn:peppol:bis:billing",
"specificationIdentifier": "urn:peppol:pint:billing-1@ae-1"
},
"validationResults": {
"status": "Valid",
"validationSteps": [
{ "name": "Validation Passed", "status": "Valid", "error": null },
{ "name": "Queue Admitted", "status": "Valid", "error": null },
{ "name": "Delivery Processing Started", "status": "Valid", "error": null },
{ "name": "Final Valid", "status": "Valid", "error": null }
]
}
}
}While isTerminal is false, the invoice is still being processed, so check again later, waiting a little longer each time. final_valid means it was accepted as compliant. In the Invoice Portal, it shows as Compliant. Every state, and the fields this example leaves out, are in Documents and status.
If something goes wrong
401 with {"error": "Unauthorized"}: the key is missing, invalid, revoked or expired. Check that COMPLYANCE_API_KEY is set in the shell you send from.
403 with permission_denied: the key belongs to a different environment from environment. The message says a mock API key is required for sandbox: a mock key is a sandbox key. Use a sandbox key with sandbox.
404: the source is not in your workspace. Check its name and version.
412 with failed_precondition: the source has no mapping for this country and document type, or the mapping’s test cases have not passed. The reason says which; see When the mapping is not ready.
422 with GETS-HEAD-010: a document with this documentNumber is already stored and past validation. If this is a new invoice, give it a number you have not used and send it again. If you were retrying a request that timed out, the first one worked; see Retries and duplicates.
422 with AE-PEPPOL-008: the seller’s peppolId is not your onboarded company’s TIN. Use the 10-digit TIN the company was onboarded with.
Any other 422: the invoice failed validation. Each entry in errors names the field in GETS (getsPath) and in your request (payloadPath), and the rule it broke. Fix the field, then send the invoice again with the same documentNumber: Complyance updates the invoice it stored.
Next step
See which fields your own invoices need in the field reference. Each country’s guide then covers its onboarding, rules and examples: United Arab Emirates, Saudi Arabia, Malaysia, Oman, Belgium and Germany.
Last updated