.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.35-beta. NuGet lists it as a prerelease, so name the version when you install it.
dotnet add package Complyance.SDK.dotNet --version 3.0.35-betaInstall-Package Complyance.SDK.dotNet -Version 3.0.35-beta<PackageReference Include="Complyance.SDK.dotNet" Version="3.0.35-beta" />Configure the SDK
Configure the SDK once, when your application starts.
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_KEYholds your API key. Never write the key into your code.Environment.Sandboxsends to the sandbox. Change it toEnvironment.Productionwhen you go live, and use a production key with it. A sandbox key works only withEnvironment.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.FirstPartymeans the source is your own system.
The using Environment = … line avoids a clash with System.Environment.
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. Nothing is sent to a tax authority.
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.
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)
.Modifier(MODIFIER.B2B)
.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:
{
"status": "success",
"message": "Unify request processed successfully"
}Then, in the Developer portal:
- Create an integration for the same country and document type, and pick the payload you sent.
- Map your fields to GETS until no mandatory field is left unmapped.
- Test your mapping in the testbed.
- 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.
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 != null && !validation.OverallSuccess)
{
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, such as B2B, B2C, EXPORT and REVERSE_CHARGE. Add more than one with .AddModifier(...). The document type decides which fields are required.
A trimmed response for a document that passed validation:
{
"status": "success",
"data": {
"document": {
"documentId": "01K5EXAMPLE00000000000142"
},
"validation": {
"overallSuccess": true,
"errors": []
}
}
}Keep the documentId. You need it to check the document’s status.
When validation fails, overallSuccess is false and errors lists every problem. path points to the GETS field:
{
"status": "success",
"data": {
"validation": {
"overallSuccess": 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.
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. |
To be told about changes instead of asking, use webhooks.
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:
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:
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.
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. CheckCOMPLYANCE_API_KEY, and use a sandbox key withEnvironment.Sandboxand a production key withEnvironment.Production. See Authentication and environments.- Validation errors: the invoice reached Complyance but a field or rule failed. Read
Data.Validation.Errorsas 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.
Last updated