Go SDK

Send invoices to Complyance from a Go application, from a first sample payload to a submitted invoice in sandbox. You need Go 1.18 or later and an API key for your workspace.

Install

Install the SDKbash
go get github.com/complyance-io/complyance-go-sdk/v3@v3.0.3

The code lives in the module’s pkg package. The examples on this page import it as sdk:

Import the SDKgo
import sdk "github.com/complyance-io/complyance-go-sdk/v3/pkg"

Configure the SDK

Configure the SDK once, when your application starts. Every call after that uses the same key and environment.

Configure the SDKgo
package main

import (
	"log"
	"os"

	sdk "github.com/complyance-io/complyance-go-sdk/v3/pkg"
)

const (
	sourceName    = "acme-erp"
	sourceVersion = "1.0.0"
)

func main() {
	cfg := sdk.NewSDKConfig(
		os.Getenv("COMPLYANCE_API_KEY"),
		sdk.EnvironmentSandbox,
		nil, // sources
		nil, // retry settings: nil uses the defaults
	)
	if err := sdk.Configure(cfg); err != nil {
		log.Fatal(err)
	}
}
  • Read the key from the COMPLYANCE_API_KEY environment variable. Never write it in your code.
  • sdk.EnvironmentSandbox sends to sandbox. Use sdk.EnvironmentProduction with a production key when you go live. The key and the environment must match.
  • sourceName and sourceVersion name the source your invoices come from, as set up in your workspace. Every request sends them.

This version of the SDK keeps its configuration for the whole process, so its functions take no client value and no context.Context. Each call waits for the answer, for up to 30 seconds per attempt.

Send a sample payload for mapping

Before you can submit invoices, Complyance needs to know how your fields correspond to GETS, the Complyance standard invoice format. Send one invoice exactly as your system produces it, with the purpose set to mapping. Nothing is sent to a tax authority.

Send a sample payloadgo
payload := map[string]interface{}{
	"invoice_no":    "INV-2026-0142",
	"invoice_date":  "2026-09-18",
	"currency":      "AED",
	"seller_name":   "Acme Trading LLC",
	"customer_name": "Gulf Office Supplies LLC",
	"lines": []map[string]interface{}{
		{"description": "Office chairs", "qty": 4, "unit_price": 250.00, "vat_rate": 5},
	},
	"total": 1050.00,
}

resp, err := sdk.PushToUnifyV2(
	sourceName,
	sourceVersion,
	sdk.DocType.Of(sdk.BASE.TaxInvoice),
	sdk.CountryAE,
	sdk.OperationSingle,
	sdk.ModeDocuments,
	sdk.PurposeMapping,
	payload,
	nil, // destinations
)
if err != nil {
	log.Fatal(err)
}
log.Println("status:", resp.GetStatus())

A nil error means Complyance accepted the payload.

Next, open the Integration engine in the Developer portal. In Create an integration, pick the payload you sent at Payload Selection, then map your fields to GETS and test your mapping.

Submit an invoice

Once the integration is tested and linked to your source, send invoices from the same source with the purpose set to invoicing. Send the payload in the same format you used for mapping; Complyance converts it with your integration.

Submit an invoicego
docType := sdk.DocType.Of(sdk.BASE.TaxInvoice)

resp, err := sdk.PushToUnifyV2(
	sourceName,
	sourceVersion,
	docType,
	sdk.CountryAE,
	sdk.OperationSingle,
	sdk.ModeDocuments,
	sdk.PurposeInvoicing,
	payload,
	nil, // destinations
)
if err != nil {
	// See "Handle errors" below.
	log.Fatal(err)
}
log.Println("status:", resp.GetStatus())

The document type is a GETS base plus optional modifiers. The base is one of sdk.BASE.TaxInvoice, sdk.BASE.SimplifiedInvoice, sdk.BASE.CreditNote or sdk.BASE.DebitNote. Add modifiers after it for the kind of sale:

Add a modifiergo
docType := sdk.DocType.Of(sdk.BASE.TaxInvoice, sdk.MODIFIER.Export)

Which bases and modifiers a country accepts, and which fields each one requires, is in the field reference.

In sandbox and production, version v3.0.3 sends documents for Saudi Arabia (sdk.CountrySA), Malaysia (sdk.CountryMY) and the UAE (sdk.CountryAE). Any other country returns an INVALID_ARGUMENT error before the request is sent.

Run your invoices in sandbox first, and check them against the test cases in a testbed before you switch to production.

Check a document’s status

Version v3.0.3 has no function for a document’s status. Follow each document with webhooks, or with the request in Follow the document.

Retrieve purchase invoices

Purchase invoices are the invoices your suppliers send you. Get one by its documentId:

Get a purchase invoicego
invoice, err := sdk.GetPurchaseInvoice("01JEXAMPLE0000000000000142")
if err != nil {
	log.Fatal(err)
}
data, _ := invoice["data"].(map[string]interface{})
log.Println(data["documentNumber"], data["state"])

To list the invoices received in a date range, pass the query parameters as a map. When hasMore is true, send the same request again with cursor set to nextCursor:

List purchase invoicesgo
filters := map[string]string{
	"from":  "2026-09-01",
	"to":    "2026-09-30",
	"limit": "100",
}

for {
	page, err := sdk.ListPurchaseInvoices(filters)
	if err != nil {
		log.Fatal(err)
	}
	data, _ := page["data"].(map[string]interface{})
	items, _ := data["items"].([]interface{})
	for _, item := range items {
		inv, _ := item.(map[string]interface{})
		log.Println(inv["documentId"], inv["invoiceNumber"], inv["supplierName"])
	}

	hasMore, _ := data["hasMore"].(bool)
	next, _ := data["nextCursor"].(string)
	if !hasMore || next == "" {
		break
	}
	filters["cursor"] = next
}

Both functions return the response body as a map[string]interface{}. Every field is described in Get a purchase invoice and List purchase invoices.

Handle errors

Every function returns an error. When the SDK or the API refuses a request, the error is an *sdk.SDKError. Its ErrorDetail holds a Code, a Message, a Suggestion and, for validation failures, a list of ValidationErrors.

Handle errorsgo
resp, err := sdk.PushToUnifyV2(
	sourceName, sourceVersion,
	sdk.DocType.Of(sdk.BASE.TaxInvoice),
	sdk.CountryAE,
	sdk.OperationSingle, sdk.ModeDocuments, sdk.PurposeInvoicing,
	payload, nil,
)

var sdkErr *sdk.SDKError
if errors.As(err, &sdkErr) && sdkErr.ErrorDetail != nil && sdkErr.ErrorDetail.Code != nil {
	detail := sdkErr.ErrorDetail
	switch *detail.Code {
	case sdk.ErrorCodeAuthenticationFailed:
		log.Fatal("check COMPLYANCE_API_KEY and the environment")
	case sdk.ErrorCodeValidationFailed:
		for _, v := range detail.ValidationErrors {
			log.Printf("%s: %s (%s)", v["field"], v["message"], v["code"])
		}
		return
	default:
		log.Fatal(err)
	}
} else if err != nil {
	log.Fatal(err)
}

if resp.GetStatus() == "queued" {
	log.Println("the API was not reachable; the SDK will retry")
}
CodeWhat it meansWhat to do
AUTHENTICATION_FAILEDThe API key is missing, invalid, revoked or expired.Check that COMPLYANCE_API_KEY is set, and that the key belongs to the environment you configured.
VALIDATION_FAILEDThe invoice failed validation. Each entry in ValidationErrors names the field, the message and the rule code.Fix every listed field, then send the invoice again.
INVALID_ARGUMENTThe request is malformed, or the country is not available in this environment.Check the country, document type and payload.

When the API is busy or cannot be reached, the SDK retries the request. If it still fails, the SDK keeps the request and tries again later, and returns no error with the status queued.

What each error from the API means is in Errors.

Last updated