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

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.

Install the SDKbash
dotnet add package 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.

Configure the SDKc#
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:

Choose a retry configurationc#
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.

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.

Send a sample payloadc#
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:

Sample payload storedjson
{
  "status": "success",
  "message": "Unify request processed successfully"
}

Then, in the Developer portal:

  1. Create an integration for the same country and document type, and pick the payload you sent.
  2. Map your fields to GETS until no mandatory field is left unmapped.
  3. Test your mapping in the testbed.
  4. Link your source to the integration. See Manage sources and their links.

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.

Submit an invoicec#
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.

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. Read the result from validation.Success. A trimmed response for a document that passed validation:

Invoice acceptedjson
{
  "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:

Invoice failed validationjson
{
  "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, 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.

Check a document's statusc#
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}");
    }
}
PropertyWhat it tells you
Data.StateThe document’s current state.
Data.IsTerminaltrue once processing has finished. Stop checking then.
Data.ErrorsProblems found during processing, each with Code, Message and Path.
Data.LastUpdatedAtWhen 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.

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:

Get a purchase invoicec#
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.

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

List purchase invoicesc#
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.

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.

Handle errorsc#
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.
  • Validation errors: the invoice reached Complyance but a field or rule failed. Read Data.Validation.Errors as shown in 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.
  • 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.

Next, read Submit and retrieve for the full request and response.