Python SDK

Send invoices to Complyance from a Python application with the Python SDK, from a sample payload for mapping to a submitted invoice in sandbox. You need Python 3.7 or later and a sandbox API key.

Install

The package is io-complyance-unify-sdk on PyPI. Version 3.0.2b0 is a prerelease, so install it by its exact version.

Install the SDKbash
pip install io-complyance-unify-sdk==3.0.2b0

You import it as complyance_sdk.

Configure the SDK

Set your key as an environment variable, so it never appears in your code:

Set your keybash
export COMPLYANCE_API_KEY='paste-your-sandbox-key'

Then configure the SDK once, when your application starts:

Configure the SDKpython
import os

from complyance_sdk import Environment, GETSUnifySDK, SDKConfig, Source, SourceType

api_key = os.environ.get("COMPLYANCE_API_KEY")
if not api_key:
    raise RuntimeError("Set COMPLYANCE_API_KEY before you start.")

config = SDKConfig(
    api_key=api_key,
    environment=Environment.SANDBOX,
    sources=[Source("acme-erp", "1.0", SourceType.FIRST_PARTY)],
)

GETSUnifySDK.configure(config)
  • Environment.SANDBOX sends requests to sandbox. Use Environment.PRODUCTION with a production key when you go live. A key works only in the environment it was created for.
  • Source names the system your invoices come from, such as your ERP, by a name and a version. Use the same name and version in every request from that system.

The SDK is synchronous: each call returns when the response arrives. The examples below run after this configuration, in the same module.

Send a sample payload for mapping

Before you can submit invoices, Complyance needs to know how your fields match GETS, the Complyance standard invoice format. Send one invoice in your own format with the purpose Purpose.MAPPING. It is stored as a sample payload that you map in the Developer portal; it is not submitted to a tax authority.

Send a sample payloadpython
from complyance_sdk import (
    BASE,
    MODIFIER,
    Country,
    GETSUnifySDK,
    GetsDocumentType,
    Mode,
    Operation,
    Purpose,
)

payload = {
    "invoice_data": {
        "document_number": "INV-2026-0142",
        "invoice_date": "2026-09-18",
        "currency_code": "AED",
        "total_amount": 10500.0,
    },
    "seller_info": {"seller_name": "Acme Trading LLC", "country_code": "AE"},
    "buyer_info": {"buyer_name": "Gulf Retail LLC", "buyer_country": "AE"},
    "line_items": [
        {"line_id": "1", "item_name": "Office chair", "quantity": 10, "unit_price": 1000.0},
    ],
}

document_type = (
    GetsDocumentType.builder()
    .base(BASE.TAX_INVOICE)
    .modifiers([MODIFIER.B2B])
    .build()
)

mapping = GETSUnifySDK.push_to_unify_v2(
    source_name="acme-erp",
    source_version="1.0",
    document_type_v2=document_type,
    country=Country.AE,
    operation=Operation.SINGLE,
    mode=Mode.DOCUMENTS,
    purpose=Purpose.MAPPING,
    payload=payload,
)

print(mapping.status, mapping.data.payload.payload_id)

Send a payload with every field your system can produce, so you can map all of them. The SDK reads the response into an object; this is the response it reads, shortened:

Sample payload receivedjson
{
  "status": "success",
  "message": "Unify request processed successfully",
  "data": {
    "processing": {
      "status": "completed"
    }
  }
}

In Python, the same values are mapping.status, mapping.data.payload.payload_id and mapping.data.processing.status.

Next, in the Developer portal:

  1. Create an integration and pick this sample payload.
  2. Map your fields to GETS until the required fields are covered.
  3. Link your source (acme-erp, version 1.0) to the integration.

Submit an invoice

Once your source is linked to an integration, send invoices from the same source with the purpose Purpose.INVOICING. Complyance applies your mapping, checks the invoice and continues delivery for its country.

Submit an invoicepython
response = GETSUnifySDK.push_to_unify_v2(
    source_name="acme-erp",
    source_version="1.0",
    document_type_v2=document_type,
    country=Country.AE,
    operation=Operation.SINGLE,
    mode=Mode.DOCUMENTS,
    purpose=Purpose.INVOICING,
    payload=payload,
)

validation = response.data.validation if response.data else None
if validation and validation.overall_success is False:
    for error in validation.errors:
        print(error.code, error.path, error.message)
elif response.data and response.data.document:
    print("Document ID:", response.data.document.document_id)
  • The source name and version must match the source you linked to the integration.
  • document_type comes from the mapping step. The builder sets the GETS document type: a base, such as BASE.TAX_INVOICE or BASE.CREDIT_NOTE, and any modifiers, such as MODIFIER.B2B or MODIFIER.EXPORT. The document type decides which fields are required.
  • Country.AE is the country the invoice is issued in. The SDK accepts Country.SA, Country.MY, Country.AE, Country.BE and Country.DE.

response.status is "success" when Complyance received the request, even if the invoice failed its checks. Always read response.data.validation as well. An invoice that failed shows each problem with its code:

Invoice failed validationjson
{
  "status": "success",
  "data": {
    "document": {
      "documentId": "01K5EXAMPLE00000000000142"
    },
    "validation": {
      "overallSuccess": false,
      "errors": [
        {
          "code": "GETS-LINE-005",
          "message": "Line item price is required",
          "path": ["lineItems", 0, "price", "amount"]
        }
      ]
    }
  }
}

path points to the GETS field. Fix the field in your payload or in your mapping, then send the invoice again. Keep the documentId to check the document later.

If the service is unavailable, response.status is "queued". The SDK keeps the request and sends it again itself, so do not send that invoice again.

Run your mapping against test cases in a testbed before you switch to production.

Check a document’s status

Delivery to the tax authority or the Peppol network continues after the response. This version of the SDK has no method to read a document, so ask the API for it with the documentId. The example uses httpx, which is installed with the SDK:

Check a document's statuspython
import os

import httpx

document_id = "01K5EXAMPLE00000000000142"

result = httpx.get(
    f"https://prod.gets.complyance.io/api/v3/documents/{document_id}",
    params={"type": "sales"},
    headers={
        "Authorization": f"Bearer {os.environ['COMPLYANCE_API_KEY']}",
        "Accept": "application/json",
    },
)
result.raise_for_status()
print(result.json())

Every field of the answer is in Get a document in the API playground. To be told when the status changes instead of asking, use webhooks.

Retrieve purchase invoices

Purchase invoices are the invoices your suppliers send you. This version of the SDK does not retrieve them, so call the API in the same way. Your API key decides the workspace and environment.

List the purchase invoices received in a date range, one page at a time:

List purchase invoicespython
import os

import httpx

headers = {
    "Authorization": f"Bearer {os.environ['COMPLYANCE_API_KEY']}",
    "Accept": "application/json",
}
params = {"type": "purchases", "from": "2026-09-01", "to": "2026-09-30", "limit": 100}

with httpx.Client(base_url="https://prod.gets.complyance.io", headers=headers) as client:
    while True:
        page = client.get("/api/v3/documents", params=params)
        page.raise_for_status()
        data = page.json()["data"]
        for item in data["items"]:
            print(item["documentId"], item["invoiceNumber"], item["supplierName"])
        if not data["hasMore"]:
            break
        params["cursor"] = data["nextCursor"]

Keep the same dates and limit for every page, and pass nextCursor back unchanged. Then get one invoice by its documentId:

Get a purchase invoicepython
invoice = httpx.get(
    "https://prod.gets.complyance.io/api/v3/documents/01JEXAMPLE0000000000000142",
    params={"type": "purchases"},
    headers=headers,
)
invoice.raise_for_status()
print(invoice.json()["data"]["invoice"])

The fields are described in List purchase invoices and Get a purchase invoice.

Handle errors

The SDK raises an SDKException when it cannot send a request or the API refuses it. Its error_detail holds the code, a message and a suggestion.

Handle errorspython
from complyance_sdk import SDKException

try:
    response = GETSUnifySDK.push_to_unify_v2(
        source_name="acme-erp",
        source_version="1.0",
        document_type_v2=document_type,
        country=Country.AE,
        operation=Operation.SINGLE,
        mode=Mode.DOCUMENTS,
        purpose=Purpose.INVOICING,
        payload=payload,
    )
    print(response.status)
except SDKException as exc:
    detail = exc.error_detail
    print(detail.code, detail.message, detail.suggestion)
    print(detail.context.get("originalError"))

When the API refuses a request, detail.code is ErrorCode.MAX_RETRIES_EXCEEDED, and detail.context["originalError"] names the reason. The errors people hit most:

  • AUTHENTICATION_FAILED in originalError: the key was refused because it is invalid, revoked or expired. Check that COMPLYANCE_API_KEY is set where your application runs, and use a sandbox key with Environment.SANDBOX and a production key with Environment.PRODUCTION.
  • MISSING_FIELD with Source name is required or Source version is required: an invoicing request was sent without a source. Pass the source name and version you linked to your integration.
  • INVALID_ARGUMENT with Country not allowed for production environment: the country is not one the SDK sends to. Use one of the countries listed in Submit an invoice.

Validation problems in the invoice itself do not raise an exception; they come back in response.data.validation, as shown in Submit an invoice. What each error means, and what to do, is in Unify errors.

Next, try a request without writing code in the API playground.

Last updated