# .NET SDK

Send invoices from a .NET application, check their status and retrieve purchase invoices with the Complyance .NET SDK. You need a .NET project, a workspace and an API key to begin.

> **Before you start**
>
> - A project on .NET 6 or later. The package also targets .NET Standard 2.0 and 2.1, so .NET Framework 4.6.1 or later works too.
> - A Complyance workspace with access to the **Developer portal**.
> - An API key. See [API keys](https://docs.complyance.io/one-platform/developer-portal/api-keys/). Start with a sandbox key and move to production only after your invoices pass in sandbox.

## Install

The latest version is `3.0.36-beta`, a beta release. NuGet lists it as a prerelease, so name the version when you install it.

**.NET CLI**

```bash title="Install the SDK"
dotnet add package Complyance.SDK.dotNet --version 3.0.36-beta
```

**Package Manager Console**

```
Install-Package Complyance.SDK.dotNet -Version 3.0.36-beta
```

**Project file**

```xml title="Install the SDK"
<PackageReference Include="Complyance.SDK.dotNet" Version="3.0.36-beta" />
```

Some older guides name the package `Complyance.SDK`. That package does not exist on NuGet; install `Complyance.SDK.dotNet`.

## Configure the SDK

Configure the SDK once, when your application starts.

```c# title="Configure the SDK"
using System;
using System.Collections.Generic;
using Complyance.SDK;
using Complyance.SDK.Models;
using Environment = Complyance.SDK.Models.Environment;

var apiKey = System.Environment.GetEnvironmentVariable("COMPLYANCE_API_KEY")
    ?? throw new InvalidOperationException("Set COMPLYANCE_API_KEY.");

var sources = new List<Source>
{
    new Source("acme-erp", "1.0", SourceType.FirstParty)
};

var config = new SDKConfig(apiKey, Environment.Sandbox, sources);
GETSUnifySDK.Configure(config);
```

Change these parts:

- `COMPLYANCE_API_KEY` holds your API key. Never write the key into your code.
- `Environment.Sandbox` sends to the sandbox. Change it to `Environment.Production` when you go live, and use a production key with it. A sandbox key works only with `Environment.Sandbox`.
- `"acme-erp"` and `"1.0"` are the name and version of your source, the system that sends the invoices. Use the same pair in every call below. `SourceType.FirstParty` means the source is your own system.

The `using Environment = …` line avoids a clash with `System.Environment`.

### Retries

The SDK sends a failed request again on its own. To change how often it tries, give `SDKConfig` a retry configuration as its fourth argument, in place of the `config` line above:

```c# title="Choose a retry configuration"
var config = new SDKConfig(apiKey, Environment.Sandbox, sources, RetryConfig.ConservativeRetry());
```

The presets are `RetryConfig.DefaultConfig()`, `RetryConfig.ConservativeRetry()`, `RetryConfig.AggressiveRetry()` and `RetryConfig.NoRetry()`, which sends each request once.

## Send a sample payload for mapping

Before Complyance can convert your invoices, it needs to know how your fields match GETS, the Complyance standard invoice format. Send one sample payload with `Purpose.Mapping`. Complyance stores it so you can build the mapping from it in the Integration engine. 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).

The payload is your own invoice data, in your own field names. This example is shortened to show the shape: invoice data, seller, buyer and one line item.

```c# title="Send a sample payload"
var payload = new Dictionary<string, object>
{
    ["invoice_data"] = new Dictionary<string, object>
    {
        ["document_number"] = "INV-2026-0142",
        ["invoice_date"] = "2026-09-22",
        ["currency_code"] = "AED",
        ["total_amount"] = 5250.00
    },
    ["seller_info"] = new Dictionary<string, object>
    {
        ["seller_name"] = "Acme Trading LLC",
        ["vat_number"] = "100000000000003",
        ["country_code"] = "AE"
    },
    ["buyer_info"] = new Dictionary<string, object>
    {
        ["buyer_name"] = "Example Buyer LLC",
        ["buyer_vat_number"] = "100000000100003",
        ["buyer_country"] = "AE"
    },
    ["line_items"] = new List<Dictionary<string, object>>
    {
        new Dictionary<string, object>
        {
            ["line_id"] = "1",
            ["item_name"] = "Office chairs",
            ["quantity"] = 10.0,
            ["unit_price"] = 500.00,
            ["tax_rate"] = 5.0,
            ["line_total"] = 5250.00
        }
    }
};

var documentType = GetsDocumentType.NewBuilder()
    .Base(BASE.TAX_INVOICE)
    .Build();

var mapping = await GETSUnifySDK.PushToUnifyAsync(
    "acme-erp",
    "1.0",
    documentType,
    Country.AE,
    Operation.Single,
    Mode.Documents,
    Purpose.Mapping,
    payload);

Console.WriteLine($"{mapping.Status}: {mapping.Message}");
```

You should see a success status:

```json title="Sample payload stored"
{
  "status": "success",
  "message": "Unify request processed successfully"
}
```

Then, in the **Developer portal**:

1. [Create an integration](https://docs.complyance.io/one-platform/developer-portal/integration-engine/create/) for the same country and document type, and pick the payload you sent.
2. [Map your fields to GETS](https://docs.complyance.io/one-platform/developer-portal/integration-engine/gets-mapping/) until no mandatory field is left unmapped.
3. [Test your mapping](https://docs.complyance.io/one-platform/developer-portal/integration-engine/test/) in the testbed.
4. Link your source to the integration. See [Manage sources and their links](https://docs.complyance.io/one-platform/developer-portal/source-management/).

## Submit an invoice

Once your source is linked to a tested integration, send invoices with `Purpose.Invoicing`. Use the same source name and version, country and document type as the integration. Complyance converts the payload with your mapping, validates it and continues delivery.

```c# title="Submit an invoice"
var response = await GETSUnifySDK.PushToUnifyAsync(
    "acme-erp",
    "1.0",
    documentType,
    Country.AE,
    Operation.Single,
    Mode.Documents,
    Purpose.Invoicing,
    payload);

var validation = response.Data?.Validation;

if (validation?.Success == false)
{
    foreach (var error in validation.Errors ?? new List<ValidationError>())
    {
        Console.WriteLine($"{error.Code} at {string.Join(".", error.Path)}: {error.Message}");
    }
}
else if (response.IsSuccess)
{
    Console.WriteLine($"Document ID: {response.Data?.Document?.DocumentId}");
}
```

The document type has a base and optional modifiers. `BASE` holds the bases, such as `TAX_INVOICE`, `CREDIT_NOTE` and `DEBIT_NOTE`. `MODIFIER` holds the modifiers. For a UAE export invoice, add `.Modifier(MODIFIER.EXPORT)` before `.Build()`; add more than one with `.AddModifier(...)`. The document type decides which fields are required; the modifiers each country accepts are in [Base and modifiers](https://docs.complyance.io/api-reference/gets/base-and-modifiers/).

In sandbox and production the SDK sends invoices for `Country.SA`, `Country.MY` and `Country.AE`. It has no country value for Belgium, Germany or Oman.

The calls on this page send the earlier Unify request format, without the `new-api` header, so the answer comes back in the earlier response format described in [SDKs](https://docs.complyance.io/sdks/#response-formats). Read the result from `validation.Success`. A trimmed response for a document that passed validation:

```json title="Invoice accepted"
{
  "status": "success",
  "data": {
    "document": {
      "documentId": "01K5EXAMPLE00000000000142"
    },
    "validation": {
      "success": true,
      "errors": []
    }
  }
}
```

Keep the `documentId`. You need it to check the document’s status.

When validation fails, `success` is `false` and `errors` lists every problem. `path` points to the GETS field:

```json title="Invoice failed validation"
{
  "status": "success",
  "data": {
    "validation": {
      "success": false,
      "errors": [
        {
          "code": "GETS-LINE-005",
          "message": "Line item price is required",
          "path": ["lineItems", 0, "price", "amount"]
        }
      ]
    }
  }
}
```

`status` reports that the request was processed, not that the invoice is valid. Always read `validation`.

Run your invoices in sandbox, and through the [testbed](https://docs.complyance.io/one-platform/developer-portal/testbeds/), before you switch to `Environment.Production` with a production key.

## Check a document’s status

Delivery to the tax authority or the Peppol network continues after the response. Check it with the `documentId`.

```c# title="Check a document's status"
var status = await GETSUnifySDK.GetDocumentStatusAsync("01K5EXAMPLE00000000000142");

Console.WriteLine($"{status.Data?.DocumentNumber}: {status.Data?.State}");

if (status.Data is { IsTerminal: true } data)
{
    foreach (var error in data.Errors ?? new List<StatusError>())
    {
        Console.WriteLine($"{error.Code}: {error.Message}");
    }
}
```

| Property | What it tells you |
| - | - |
| `Data.State` | The document’s current state. |
| `Data.IsTerminal` | `true` once processing has finished. Stop checking then. |
| `Data.Errors` | Problems found during processing, each with `Code`, `Message` and `Path`. |
| `Data.LastUpdatedAt` | When the state last changed. |

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

## Retrieve purchase invoices

Purchase invoices are the invoices your suppliers send you. Your API key decides which workspace and environment they come from.

Get one by its document ID:

```c# title="Get a purchase invoice"
var invoice = await GETSUnifySDK.GetPurchaseInvoiceAsync("01JEXAMPLE0000000000000142");

Console.WriteLine($"{invoice.DocumentNumber} ({invoice.Country}): {invoice.State}");
```

`Invoice` holds the full invoice data and `Xml` the e-invoice XML, when available. The fields are described in [Get a purchase invoice](https://docs.complyance.io/api-reference/purchases/get/).

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

```c# title="List purchase invoices"
using System.Text.Json;

var filters = new Dictionary<string, string>
{
    ["from"] = "2026-09-01",
    ["to"] = "2026-09-30",
    ["limit"] = "100"
};

while (true)
{
    var page = await GETSUnifySDK.ListPurchaseInvoicesAsync(filters);
    var data = (JsonElement)page["data"];

    foreach (var item in data.GetProperty("items").EnumerateArray())
    {
        Console.WriteLine(item.GetProperty("documentId").GetString());
    }

    if (!data.GetProperty("hasMore").GetBoolean()) break;
    filters["cursor"] = data.GetProperty("nextCursor").GetString()!;
}
```

The method returns the response body as a dictionary. `from` is required and dates use `YYYY-MM-DD`. `limit` is 1 to 100. Keep the same dates and limit while you follow `nextCursor`, and never change the cursor. The parameters and response fields are in [List purchase invoices](https://docs.complyance.io/api-reference/purchases/list/).

## Handle errors

The SDK reports a problem in one of two ways: it throws an `SDKException`, or it returns a response with `Error` set. Handle both.

```c# title="Handle errors"
using Complyance.SDK.Exceptions;

try
{
    var response = await GETSUnifySDK.PushToUnifyAsync(
        "acme-erp", "1.0", documentType, Country.AE,
        Operation.Single, Mode.Documents, Purpose.Invoicing, payload);

    if (response.Error != null)
    {
        Console.WriteLine($"{response.Error.Code}: {response.Error.Message}");
        Console.WriteLine(response.Error.Suggestion);
    }
}
catch (SDKException ex)
{
    Console.WriteLine($"{ex.ErrorDetail?.Code}: {ex.Message}");
    Console.WriteLine(ex.ErrorDetail?.Suggestion);
    Console.WriteLine($"Request ID: {ex.RequestId}");
}
```

`Suggestion` says what to try. Keep the request ID when you contact support.

The errors people hit most often:

- **`AUTHENTICATION_FAILED`**: the API key is missing, invalid, revoked or expired, or belongs to the other environment. Check `COMPLYANCE_API_KEY`, and use a sandbox key with `Environment.Sandbox` and a production key with `Environment.Production`. See [Authentication and environments](https://docs.complyance.io/api-reference/authentication/).
- **Validation errors**: the invoice reached Complyance but a field or rule failed. Read `Data.Validation.Errors` as shown in [Submit an invoice](https://docs.complyance.io/sdks/dotnet/#submit-an-invoice), fix every error and send the invoice again. A missing field usually means it is not mapped yet: go back to [Map your fields to GETS](https://docs.complyance.io/one-platform/developer-portal/integration-engine/gets-mapping/).
- **SDK not configured**: you called the SDK before `GETSUnifySDK.Configure`. Configure it once when your application starts.

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

Next, read [Submit and retrieve](https://docs.complyance.io/api-reference/unify/submit-and-retrieve/) for the full request and response.

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