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. Install version 3.0.2b0, a beta release, by its exact version. Later releases on PyPI, from 3.0.3b0 to 3.0.11b0, raise a SyntaxError when you import them.
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. Some older guides name the package complyance-sdk. That package does not exist on PyPI; install io-complyance-unify-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.
Retries
The SDK tries a request up to five times when the network fails or the API answers 408, 429, 500, 502, 503 or 504. To change this, pass a retry configuration as retry_config:
from complyance_sdk import RetryConfig
config = SDKConfig(
api_key=api_key,
environment=Environment.SANDBOX,
sources=[Source("acme-erp", "1.0", SourceType.FIRST_PARTY)],
retry_config=RetryConfig.conservative_retry(),
)The presets are RetryConfig.default_config(), RetryConfig.conservative_retry(), RetryConfig.aggressive_retry() and RetryConfig.no_retry(), which sends each request once.
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. Complyance stores it as a sample payload that you map in the Developer portal; it is not validated or sent to a tax authority. The purposes are described in Submit and retrieve.
from complyance_sdk import (
BASE,
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).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 that apply. For a UAE export invoice, add.modifiers([MODIFIER.EXPORT])before.build(). The document type decides which fields are required; the modifiers each country accepts are in Base and modifiers.Country.AEis the country the invoice is issued in. In sandbox and production the SDK sends invoices forCountry.SA,Country.MY,Country.AE,Country.BEandCountry.DE. It has no country value for Oman.
This version of the SDK sends the earlier Unify request format, without the new-api header, so the answer comes back in the earlier response format described in SDKs. response.status is "success" when Complyance received the request, even if the invoice failed its checks. Always read response.data.validation as well; the SDK reads its success field into validation.overall_success. An invoice that failed shows each problem with its code:
{
"status": "success",
"data": {
"document": {
"documentId": "01K5EXAMPLE00000000000142"
},
"validation": {
"success": 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 request still fails after its retries, 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 state and field is described in the documents reference. Checking the status is how you learn whether the invoice was accepted.
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.