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.
pip install io-complyance-unify-sdk==3.0.2b0poetry add io-complyance-unify-sdk==3.0.2b0uv add io-complyance-unify-sdk==3.0.2b0You import it as complyance_sdk.
Configure the SDK
Set your key as an environment variable, so it never appears in your code:
export COMPLYANCE_API_KEY='paste-your-sandbox-key'Then configure the SDK once, when your application starts:
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.SANDBOXsends requests to sandbox. UseEnvironment.PRODUCTIONwith a production key when you go live. A key works only in the environment it was created for.Sourcenames 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.
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:
{
"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:
- Create an integration and pick this sample payload.
- Map your fields to GETS until the required fields are covered.
- Link your source (
acme-erp, version1.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.
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_typecomes from the mapping step. The builder sets the GETS document type: a base, such asBASE.TAX_INVOICEorBASE.CREDIT_NOTE, and any modifiers, such asMODIFIER.B2BorMODIFIER.EXPORT. The document type decides which fields are required.Country.AEis the country the invoice is issued in. The SDK acceptsCountry.SA,Country.MY,Country.AE,Country.BEandCountry.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:
{
"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:
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:
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:
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.
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_FAILEDinoriginalError: the key was refused because it is invalid, revoked or expired. Check thatCOMPLYANCE_API_KEYis set where your application runs, and use a sandbox key withEnvironment.SANDBOXand a production key withEnvironment.PRODUCTION.MISSING_FIELDwithSource name is requiredorSource version is required: an invoicing request was sent without a source. Pass the source name and version you linked to your integration.INVALID_ARGUMENTwithCountry 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