Submit and retrieve
Submit a document to Unify, read the response, then follow the document until it is delivered.
Endpoint
POST https://prod.gets.complyance.io/api/v3/unifyOne request carries one document, or a batch in an invoices array. Complyance reads the payload through your source’s mapping, checks the document against the rules for its country and document type, then continues delivery to the tax authority or the Peppol network.
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer YOUR_API_KEY. See Authentication. | Yes |
new-api | true. Selects this request format for a JSON body. Without it, a JSON request is read in the earlier format. | Yes, for JSON |
Content-Type | application/json, or application/xml for an XML request | Yes |
Accept | application/json | No |
Request body
The envelope names the country and document type; payload carries the invoice data your source sends.
| Field | Type | Description | Required |
|---|---|---|---|
country | string | ISO 3166-1 alpha-2 country code for this document. | Yes |
environment | string | sandbox or production. Must match your key: a sandbox key for sandbox, a production key for production. | Yes |
purpose | string | invoicing, validate or mapping. See Purpose. | Yes |
source | string | Your source, as name:version. | Yes, except with mapping |
documentType | object | base, modifiers[] and an optional variant. Determines which invoice fields the selected country requires. See document types. | Yes |
payload | object | The invoice in the shape your source’s mapping reads. With the Default GETS Mapping, that shape is GETS itself; see the GETS field reference. | Yes |
A source is the system that sends invoices, such as your ERP, registered in your workspace. Its name and version are the values you gave it when you created it, such as acme-erp and 1.0.0, or acme-dubai and v1 for a company onboarded through Connect. The version is free text, not a version number Complyance checks, but it must match the registered version exactly. Complyance splits source at its last colon.
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.jsonA complete invoice.json for the UAE is in the quickstart. The API playground builds a request for any country and sends it to sandbox.
Formats
Send dates as YYYY-MM-DD and amounts as JSON numbers, such as 2100 or 2100.5. Most countries’ rules allow no more than two decimal places on invoice totals; a value with more fails validation, and the error names the rule.
Purpose
purpose says what Complyance does with the document.
| Value | What happens | Response |
|---|---|---|
invoicing | Validates the document, stores it, produces the e-invoice and continues delivery. | 200 when it passed, 422 when it failed. See Response. |
validate | Runs the same checks as invoicing, including your mapping and the duplicate-number check, without storing or submitting anything. The document number is not used up. | 200 with valid set to true or false. |
mapping | Saves the payload as a sample in Payload Manager, so you can build a mapping from it. Nothing is validated or submitted. | 200 with a payloadId. |
Use validate to check a payload before you send it with invoicing.
{
"valid": false,
"documentNumber": "INV-2026-000184",
"message": "Your invoice could not be validated. Review the errors and try again.",
"errors": [
{
"code": "IBR-015",
"getsPath": "totals.amountDue",
"payloadPath": "totals.amountDue",
"message": "An Invoice MUST have the Amount due for payment (ibt-115).",
"severity": "error",
"ruleSet": "ae:tax_invoice"
}
],
"warnings": [],
"validationStage": "gets",
"limitations": "…"
}When the payload passes, valid is true, message is Your invoice was validated. and errors is empty. warnings lists findings that do not stop the document. limitations says in words what validate does not do, shortened here.
{
"payloadId": "01K5EXAMPLE0000000PAYLOAD01",
"message": "Payload saved for mapping."
}Response
For invoicing, 200 means the document passed validation and was stored.
{
"documentId": "01K5EXAMPLE00000000000142",
"documentNumber": "INV-2026-000184",
"message": "Your invoice was validated and is ready for submission.",
"Base64XML": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4…"
}| Field | Type | Description |
|---|---|---|
documentId | string | Keep it to look the document up later. |
documentNumber | string | The number from your payload. |
message | string | What happened, in words. |
Base64XML | string | The e-invoice XML, Base64-encoded. Empty for countries where it is not produced at this point. |
422 means the document failed validation. The body lists every problem in errors; see Errors.
Batch requests
To send several documents in one request, put them in an invoices array. Each item is a complete request body with its own country, environment, purpose, source, documentType and payload. Nothing is shared between items, and purpose is required on every item.
{
"invoices": [
{
"country": "AE",
"environment": "sandbox",
"purpose": "invoicing",
"source": "acme-erp:1.0.0",
"documentType": { "base": "tax_invoice", "modifiers": [] },
"payload": { "header": { "documentNumber": "INV-2026-000185" } }
},
{
"country": "AE",
"environment": "sandbox",
"purpose": "invoicing",
"source": "acme-erp:1.0.0",
"documentType": { "base": "tax_invoice", "modifiers": [] },
"payload": { "header": { "documentNumber": "INV-2026-000186" } }
}
]
}The payloads are shortened here. A batch holds 1 to 10 documents with invoicing or mapping, or 1 to 100 when every item uses validate. A batch that mixes validate with the other purposes, or holds more items than its limit, is refused with 400.
Every item is checked separately, so one failure does not stop the others. The response is HTTP 200 with a summary and one result per item, in the order you sent them:
{
"summary": { "total": 2, "succeeded": 1, "failed": 1 },
"results": [
{
"index": 0,
"success": true,
"result": {
"documentId": "01K5EXAMPLE00000000000143",
"documentNumber": "INV-2026-000185",
"message": "Your invoice was validated and is ready for submission.",
"Base64XML": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4…"
}
},
{
"index": 1,
"success": false,
"result": {
"documentId": "01K5EXAMPLE00000000000144",
"documentNumber": "INV-2026-000186",
"message": "Your invoice could not be validated. Review the errors and try again.",
"validationStage": "gets",
"errors": [
{
"code": "IBR-015",
"getsPath": "totals.amountDue",
"payloadPath": "totals.amountDue",
"message": "An Invoice MUST have the Amount due for payment (ibt-115).",
"severity": "error",
"ruleSet": "ae:tax_invoice"
}
]
}
}
]
}index is the item’s position in invoices, counting from 0. When an item was checked, result holds the same body a single request returns. When an item could not be processed at all, it has error with a code and message instead, such as invalid_argument for a missing source or failed_precondition for a mapping that is not ready. Always read success on each item: HTTP 200 only means the batch was read.
If any item’s environment does not match your key, the whole batch is refused with 403 and nothing is processed.
XML requests
If your system produces XML, send the request as XML instead of JSON. Use the same endpoint and Authorization header with Content-Type: application/xml (optionally with ; charset=utf-8). The new-api header is not needed: an XML body always uses this request format.
The XML has the same fields as the JSON body, as elements under a UnifyRequest root. List items go in item elements. The payload element holds your own invoice XML.
<?xml version="1.0" encoding="UTF-8"?>
<UnifyRequest>
<country>AE</country>
<environment>sandbox</environment>
<purpose>invoicing</purpose>
<source>acme-erp:1.0.0</source>
<documentType>
<base>tax_invoice</base>
<modifiers/>
</documentType>
<payload>
<Invoice>
<InvoiceNumber>INV-2026-000184</InvoiceNumber>
<IssueDate>2026-09-18</IssueDate>
<Currency>AED</Currency>
</Invoice>
</payload>
</UnifyRequest>For a batch, put each request in an item element inside <invoices>, and nothing else under the root. The batch limits are the same as for JSON.
- Your mapping reads the
payloadelement, so build it from an XML sample: send one withpurposeset tomapping, then map it in the Integration engine. - Values in
payloadare read as text. Your mapping decides how each one becomes a GETS field. - The envelope elements take no attributes. Elements inside
payloadcan have attributes. - The body must be UTF-8 and at most 8 MiB (8,388,608 bytes). A larger body is refused with
413. A different content type, or a compressed body, is refused with415. - DTDs and custom entities are not accepted. Use only the five standard XML entities, such as
&, or CDATA sections.
The responses are the same as for JSON. When an XML request is refused before it is checked, the body has a code such as invalid_argument and the message Something went wrong. Please contact support. If you cannot find the cause, email support@complyance.io with the time of the request.
Limits
| Limit | Value |
|---|---|
| Documents per batch | 1 to 10, or 1 to 100 when every item uses validate |
| XML body | 8 MiB |
| Request rate | Too many requests in a short time return 429. Wait for the number of seconds in the Retry-After header, then send again. |
Retries and duplicates
Complyance refuses a second document with the same documentNumber from your workspace in the same country and environment. That protects you when a request is repeated.
- The request timed out or the connection dropped. Do not assume the document was not stored. Send the same request again. If the first one was stored, you get
422withGETS-HEAD-010, and the message names the status the document has, such asCOMPLIANT. That422has nodocumentId: find the document in the Invoice Portal by searching for its number. - The document failed validation. A
422with validation errors stores the document as Needs Correction. Fix the data and send it again with the samedocumentNumberfrom the same source. Complyance updates the stored document and keeps itsdocumentId. - You meant to send a new document. Give it a number you have not used before.
- The document was already sent on. Once a document has passed validation, its number is used, whatever happens next. If the tax authority or the network rejected it, send the corrected document with a new number.
The rule is the same in every country. A document marked Needs Correction can only be corrected through the source and integration that sent it; the same number from another source is refused with GETS-HEAD-010.
A documentId field in the request does not make a retry safe; Complyance assigns its own. Use validate to check a document without using up its number.
Follow the document
Delivery to the tax authority or the Peppol network continues after the response. Follow it with the documentId:
curl "https://prod.gets.complyance.io/api/v3/documents/${DOCUMENT_ID}?type=sales" \
--header "Authorization: Bearer ${COMPLYANCE_API_KEY}" \
--header 'Accept: application/json'Read data.state with data.isTerminal. While isTerminal is false, wait before checking again, and make each wait longer than the last. Stop when isTerminal is true, or, in Belgium and Germany, when state is delivery_success or delivery_failed. No webhook reports the outcome: invoice.passed and invoice.failed only say whether the document passed validation when you sent it. Checking the status is how you learn the outcome. Every state and field is in Documents and status.
Errors
422 means the document failed validation, and the body lists every problem. Any other error status means the request itself was refused. What each one means, and what to do, is in Errors.
Last updated