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

> **Before you start**
>
> - Go 1.18 or later, and a Go module to add the SDK to.
> - A Complyance workspace, with a source set up for the system that sends invoices.
> - An API key for that workspace. See [API keys](https://docs.complyance.io/one-platform/developer-portal/api-keys/).
> - Start with a sandbox key. Move to production only after your invoices pass in sandbox.

## Install

```bash title="Install the SDK"
go get github.com/complyance-io/complyance-go-sdk/v3@v3.0.5-beta
```

`v3.0.5-beta` is the latest version, and it is a beta release. The latest stable release, `v3.0.3`, does not have `SubmitJSONSingle` or `GetDocumentStatus`, which this page uses.

Some older guides use the module path `github.com/complyance/go-sdk`. That module does not exist. Use `github.com/complyance-io/complyance-go-sdk/v3`, with `/v3` at the end.

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

```go title="Import the SDK"
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.

```go title="main.go"
package main

import (
	"log"
	"os"

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

const (
	apiHost       = "https://prod.gets.complyance.io"
	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)
	}

	// The examples below go here.
}
```

- Read the key from the `COMPLYANCE_API_KEY` environment variable. Never write it in your code.
- `sdk.EnvironmentSandbox` is for a sandbox key. Use `sdk.EnvironmentProduction` with a production key when you go live.
- `apiHost` is the API’s address, for sandbox and production alike. The `environment` in each request, and the key, decide which one you reach.
- `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`.

### How this page sends requests

The SDK’s `PushToUnifyV2` function sends the earlier Unify request format, and in this version its response carries only a `status` and a `message`, not the document ID or the validation result. So this page sends invoices with `sdk.SubmitJSONSingle` instead. It sends a request body you build, in the current format described in [Submit and retrieve](https://docs.complyance.io/api-reference/unify/submit-and-retrieve/), with the `new-api: true` header, and returns the whole response as a `map[string]interface{}`.

`SubmitJSONSingle` sends each request once. It does not retry, and it does not check the country: the API accepts the countries listed in [Countries](https://docs.complyance.io/countries/). The SDK’s own functions, such as `PushToUnifyV2`, accept only Saudi Arabia (`sdk.CountrySA`), Malaysia (`sdk.CountryMY`) and the UAE (`sdk.CountryAE`) in sandbox and production, and have no country value for Oman.

## 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 `mapping`. It is not validated or sent to a tax authority. The purposes are described in [Submit and retrieve](https://docs.complyance.io/api-reference/unify/submit-and-retrieve/#purpose).

Add `"encoding/json"` to the imports in `main.go`, then:

```go title="Send a sample payload"
// Your own invoice data, in your system's format
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,
}

request := map[string]interface{}{
	"country":     "AE",
	"environment": "sandbox",
	"purpose":     "mapping",
	"source":      sourceName + ":" + sourceVersion,
	"documentType": map[string]interface{}{
		"base":      "tax_invoice",
		"modifiers": []string{},
	},
	"payload": payload,
}

body, err := json.Marshal(request)
if err != nil {
	log.Fatal(err)
}

result, err := sdk.SubmitJSONSingle(body, apiHost, true)
if err != nil {
	log.Fatal(err)
}
log.Println(result["message"])
```

You should see:

```
Payload saved for mapping.
```

Next, open the [Integration engine](https://docs.complyance.io/one-platform/developer-portal/integration-engine/) in the Developer portal. In [Create an integration](https://docs.complyance.io/one-platform/developer-portal/integration-engine/create/), pick the payload you sent at **Payload Selection**, then [map your fields to GETS](https://docs.complyance.io/one-platform/developer-portal/integration-engine/gets-mapping/) and [test your mapping](https://docs.complyance.io/one-platform/developer-portal/integration-engine/test/).

## Submit an invoice

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

```go title="Submit an invoice"
request["purpose"] = "invoicing"

body, err = json.Marshal(request)
if err != nil {
	log.Fatal(err)
}

result, err = sdk.SubmitJSONSingle(body, apiHost, true)
if err != nil {
	// See "Handle errors" below.
	log.Fatal(err)
}

if problems, _ := result["errors"].([]interface{}); len(problems) > 0 {
	// The invoice failed validation
	for _, item := range problems {
		problem, _ := item.(map[string]interface{})
		log.Printf("%v at %v: %v", problem["code"], problem["payloadPath"], problem["message"])
	}
} else {
	log.Println("Document ID:", result["documentId"])
}
```

`documentType` is the GETS document type: a `base`, such as `tax_invoice` or `credit_note`, and the `modifiers` that apply. For a UAE export invoice, use `"modifiers": []string{"export"}`. Which bases and modifiers a country accepts is in [Base and modifiers](https://docs.complyance.io/api-reference/gets/base-and-modifiers/), and which fields each one requires is in the [field reference](https://docs.complyance.io/api-reference/gets/fields/).

A validated invoice returns its `documentId`. Keep it to check the status later.

```json title="Invoice validated"
{
  "documentId": "01K5EXAMPLE00000000000142",
  "documentNumber": "INV-2026-0142",
  "message": "Your invoice was validated and is ready for submission.",
  "Base64XML": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4…"
}
```

An invoice that fails validation comes back as HTTP `422`. `SubmitJSONSingle` returns it as a result, not as an error, with `errors` listing every problem. Each one has a `code`, the GETS field in `getsPath`, the field in your payload in `payloadPath`, and a `message`. Fix every one, then send the invoice again.

Run your invoices in sandbox first, and check them against the test cases in a [testbed](https://docs.complyance.io/one-platform/developer-portal/testbeds/) before you switch to production. For production, use a production key, `sdk.EnvironmentProduction` and `"environment": "production"`.

## Check a document’s status

Delivery to the tax authority or the Peppol network continues after the response. Ask for a document’s current status with its `documentId`:

```go title="Check a document's status"
status, err := sdk.GetDocumentStatus("01K5EXAMPLE00000000000142")
if err != nil {
	log.Fatal(err)
}
data, _ := status["data"].(map[string]interface{})
log.Println(data["state"], data["isTerminal"])
```

Check again until `isTerminal` is `true`. Every state and field is described in the [documents reference](https://docs.complyance.io/api-reference/unify/documents/). Checking the status is how you learn whether the invoice was accepted.

## Retrieve purchase invoices

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

```go title="Get a purchase invoice"
invoice, err := sdk.GetPurchaseInvoice("01JEXAMPLE0000000000000142")
if err != nil {
	log.Fatal(err)
}
invoiceData, _ := invoice["data"].(map[string]interface{})
log.Println(invoiceData["documentNumber"], invoiceData["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`:

```go title="List purchase invoices"
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)
	}
	pageData, _ := page["data"].(map[string]interface{})
	items, _ := pageData["items"].([]interface{})
	for _, item := range items {
		inv, _ := item.(map[string]interface{})
		log.Println(inv["documentId"], inv["invoiceNumber"], inv["supplierName"])
	}

	hasMore, _ := pageData["hasMore"].(bool)
	next, _ := pageData["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](https://docs.complyance.io/api-reference/purchases/get/) and [List purchase invoices](https://docs.complyance.io/api-reference/purchases/list/).

## Handle errors

When the API refuses a request with any status other than `422`, `SubmitJSONSingle` returns an `*sdk.SDKError`. Its `ErrorDetail` holds the HTTP status as `httpStatus` and the response body as `responseBody`. Any other error means the request could not be sent. This function, in the same package as `main.go`, sends one invoice and reports each case:

```go title="errors.go"
package main

import (
	"errors"
	"fmt"
	"log"

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

// submitInvoice sends one request body and returns the document ID.
func submitInvoice(body []byte) (string, error) {
	result, err := sdk.SubmitJSONSingle(body, apiHost, true)

	var sdkErr *sdk.SDKError
	if errors.As(err, &sdkErr) && sdkErr.ErrorDetail != nil {
		detail := sdkErr.ErrorDetail
		return "", fmt.Errorf("refused with HTTP %v: %v",
			detail.GetContextValue("httpStatus"), detail.GetContextValue("responseBody"))
	}
	if err != nil {
		return "", fmt.Errorf("not sent: %w", err)
	}

	if problems, _ := result["errors"].([]interface{}); len(problems) > 0 {
		for _, item := range problems {
			problem, _ := item.(map[string]interface{})
			log.Printf("%v at %v: %v", problem["code"], problem["payloadPath"], problem["message"])
		}
		return "", errors.New("the invoice failed validation")
	}

	documentID, _ := result["documentId"].(string)
	return documentID, nil
}
```

| Status | What it means | What to do |
| - | - | - |
| `401` | The API key is missing, invalid, revoked or expired. | Check that `COMPLYANCE_API_KEY` is set where your application runs. |
| `403` | The key belongs to a different environment from `environment` in the request. | Use a sandbox key with `"environment": "sandbox"` and a production key with `"environment": "production"`. |
| `404` | The `source` is not in your workspace. | Check `sourceName` and `sourceVersion`. |
| `429` | Too many requests. | Wait, then send the request again. `SubmitJSONSingle` does not retry for you. |

What each error from the API means is in [Errors](https://docs.complyance.io/api-reference/unify/errors/).

> **Next: check your fields**
>
> Look up every GETS field a UAE tax invoice needs in the [field reference](https://docs.complyance.io/api-reference/gets/fields/).

Source: https://docs.complyance.io/sdks/go/
