Submit and retrieve

Submit a document to Unify, read the response, then follow the document until it is delivered.

Endpoint

Submit a documenthttp
POST https://prod.gets.complyance.io/api/v3/unify

One 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

HeaderValueRequired
AuthorizationBearer YOUR_API_KEY. See Authentication.Yes
new-apitrue. Selects this request format for a JSON body. Without it, a JSON request is read in the earlier format.Yes, for JSON
Content-Typeapplication/json, or application/xml for an XML requestYes
Acceptapplication/jsonNo

Request body

The envelope names the country and document type; payload carries the invoice data your source sends.

FieldTypeDescriptionRequired
countrystringISO 3166-1 alpha-2 country code for this document.Yes
environmentstringsandbox or production. Must match your key: a sandbox key for sandbox, a production key for production.Yes
purposestringinvoicing, validate or mapping. See Purpose.Yes
sourcestringYour source, as name:version.Yes, except with mapping
documentTypeobjectbase, modifiers[] and an optional variant. Determines which invoice fields the selected country requires. See document types.Yes
payloadobjectThe 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.

Submit a documentbash
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.json

A 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.

ValueWhat happensResponse
invoicingValidates the document, stores it, produces the e-invoice and continues delivery.200 when it passed, 422 when it failed. See Response.
validateRuns 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.
mappingSaves 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.

validate — HTTP 200json
{
  "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.

mapping — HTTP 200json
{
  "payloadId": "01K5EXAMPLE0000000PAYLOAD01",
  "message": "Payload saved for mapping."
}

Response

For invoicing, 200 means the document passed validation and was stored.

Document validated — HTTP 200json
{
  "documentId": "01K5EXAMPLE00000000000142",
  "documentNumber": "INV-2026-000184",
  "message": "Your invoice was validated and is ready for submission.",
  "Base64XML": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4…"
}
FieldTypeDescription
documentIdstringKeep it to look the document up later.
documentNumberstringThe number from your payload.
messagestringWhat happened, in words.
Base64XMLstringThe 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.

Batch requestjson
{
  "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:

Batch response — HTTP 200json
{
  "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 requestxml
<?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 payload element, so build it from an XML sample: send one with purpose set to mapping, then map it in the Integration engine.
  • Values in payload are read as text. Your mapping decides how each one becomes a GETS field.
  • The envelope elements take no attributes. Elements inside payload can 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 with 415.
  • 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

LimitValue
Documents per batch1 to 10, or 1 to 100 when every item uses validate
XML body8 MiB
Request rateToo 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 422 with GETS-HEAD-010, and the message names the status the document has, such as COMPLIANT. That 422 has no documentId: find the document in the Invoice Portal by searching for its number.
  • The document failed validation. A 422 with validation errors stores the document as Needs Correction. Fix the data and send it again with the same documentNumber from the same source. Complyance updates the stored document and keeps its documentId.
  • 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:

Check the statusbash
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