# TypeScript SDK

Send invoices to Complyance from a Node.js application with the TypeScript SDK, from a sample payload for mapping to a submitted invoice in sandbox. You need Node.js 18 or later and a sandbox API key.

> **Before you start**
>
> - Node.js 18 or later, in a project that uses ES modules (`"type": "module"` in `package.json`, or TypeScript run with a tool such as `tsx`). The examples use top-level `await`.
> - A Complyance workspace, with access to the **Developer portal**.
> - A sandbox API key. See [API keys](https://docs.complyance.io/one-platform/developer-portal/api-keys/) and [Authentication and environments](https://docs.complyance.io/api-reference/authentication/).
> - Work in sandbox until your invoices pass. Switch to production only when you go live.

## Install

The package is `unify-sdk` on npm. The latest version, `3.0.21-beta`, is a beta release, so install it by its exact version.

**npm**

```bash title="Install the SDK"
npm install unify-sdk@3.0.21-beta
```

**pnpm**

```bash title="Install the SDK"
pnpm add unify-sdk@3.0.21-beta
```

**yarn**

```bash title="Install the SDK"
yarn add unify-sdk@3.0.21-beta
```

**bun**

```bash title="Install the SDK"
bun add unify-sdk@3.0.21-beta
```

Some older guides name the package `@complyance/sdk`. That package does not exist on npm; install `unify-sdk`.

## Configure the SDK

Set your key as an environment variable, so it never appears in your code:

```bash title="Set your key"
export COMPLYANCE_API_KEY='paste-your-sandbox-key'
```

Then configure the SDK once, when your application starts:

```typescript title="Configure the SDK"
import {
  Environment,
  GETSUnifySDK,
  SDKConfig,
  Source,
  SourceType,
} from "unify-sdk";

const apiKey = process.env.COMPLYANCE_API_KEY;
if (!apiKey) {
  throw new Error("Set COMPLYANCE_API_KEY before you start.");
}

const config = new SDKConfig(apiKey, Environment.SANDBOX, [
  new Source("acme-erp", "1.0", SourceType.FIRST_PARTY),
]);

GETSUnifySDK.configure(config);
```

- `Environment.SANDBOX` sends requests to sandbox. Use `Environment.PRODUCTION` with a production key when you go live. A key works only in the environment it was created for.
- `Source` names the system your invoices come from, such as your ERP, by a name and a version. Use the same name and version in every request from that system.

The examples below run after this configuration, in the same module.

The SDK keeps a background task running after it is configured, so a script does not exit on its own. At the end of a script, call `GETSUnifySDK.stopAutoRetry()`. A long-running server does not need it.

### Retries

The SDK tries a request up to five times when the network fails or the API answers `408`, `429`, `500`, `502`, `503` or `504`. To change this, give `SDKConfig` a retry preset as its fourth argument, in place of the `config` line above:

```typescript title="Choose a retry preset"
import { RetryPresets } from "unify-sdk";

const config = new SDKConfig(
  apiKey,
  Environment.SANDBOX,
  [new Source("acme-erp", "1.0", SourceType.FIRST_PARTY)],
  RetryPresets.CONSERVATIVE,
);
```

The presets are `RetryPresets.DEFAULT`, `RetryPresets.CONSERVATIVE`, `RetryPresets.AGGRESSIVE` and `RetryPresets.NONE`, which sends each request once.

## Send a sample payload for mapping

Before you can submit invoices, Complyance needs to know how your fields match GETS, the Complyance standard invoice format. Send one invoice in your own format with the purpose `Purpose.MAPPING`. Complyance stores it as a sample payload that you map in the Developer portal; 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).

```typescript title="Send a sample payload"
import {
  BASE,
  Country,
  DocType,
  GETSUnifySDK,
  Mode,
  Operation,
  Purpose,
} from "unify-sdk";

const payload = {
  invoice_data: {
    document_number: "INV-2026-0142",
    invoice_date: "2026-09-18",
    currency_code: "AED",
    total_amount: 10500.0,
  },
  seller_info: { seller_name: "Acme Trading LLC", country_code: "AE" },
  buyer_info: { buyer_name: "Gulf Retail LLC", buyer_country: "AE" },
  line_items: [
    { line_id: "1", item_name: "Office chair", quantity: 10, unit_price: 1000.0 },
  ],
};

const documentType = DocType.of(BASE.TAX_INVOICE);

const mapping = await GETSUnifySDK.pushToUnifyWithDocumentType(
  "acme-erp",
  "1.0",
  documentType,
  Country.AE,
  Operation.SINGLE,
  Mode.DOCUMENTS,
  Purpose.MAPPING,
  payload,
);

console.log(mapping.status);
```

Send a payload with every field your system can produce, so you can map all of them. A successful response looks like this, shortened:

```json title="Sample payload received"
{
  "status": "success",
  "message": "Unify request processed successfully",
  "data": {
    "processing": {
      "status": "completed"
    }
  }
}
```

Next, in the Developer portal:

1. [Create an integration](https://docs.complyance.io/one-platform/developer-portal/integration-engine/create/) and pick this sample payload.
2. [Map your fields to GETS](https://docs.complyance.io/one-platform/developer-portal/integration-engine/gets-mapping/) until the required fields are covered.
3. [Link your source](https://docs.complyance.io/one-platform/developer-portal/source-management/) (`acme-erp`, version `1.0`) to the integration.

## Submit an invoice

Once your source is linked to an integration, send invoices from the same source with the purpose `Purpose.INVOICING`. Complyance applies your mapping, checks the invoice and continues delivery for its country.

```typescript title="Submit an invoice"
const response = await GETSUnifySDK.pushToUnifyWithDocumentType(
  "acme-erp",
  "1.0",
  documentType,
  Country.AE,
  Operation.SINGLE,
  Mode.DOCUMENTS,
  Purpose.INVOICING,
  payload,
);

const validation = response.data?.validation;
if (validation?.success === false) {
  for (const error of validation.errors ?? []) {
    console.error(error.code, error.message);
  }
} else {
  console.log("Document ID:", response.data?.document?.documentId);
}
```

- The source name and version must match the source you linked to the integration.
- `documentType` comes from the mapping step. `DocType.of` sets the GETS document type: a base, such as `BASE.TAX_INVOICE` or `BASE.CREDIT_NOTE`, followed by any modifiers that apply. For a UAE export invoice, use `DocType.of(BASE.TAX_INVOICE, MODIFIER.EXPORT)`. 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/).
- `Country.AE` is the country the invoice is issued in. In sandbox and production the SDK sends invoices for `Country.SA`, `Country.MY`, `Country.AE`, `Country.BE` and `Country.DE`. It has no country value for Oman.

`pushToUnifyWithDocumentType` sends 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). `status` is `"success"` when Complyance received the request, even if the invoice failed its checks. Always read `data.validation` as well. An invoice that failed shows each problem with its code:

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

`path` points to the GETS field. Fix the field in your payload or in your mapping, then send the invoice again. Keep the `documentId` to check the document later.

If the request still fails after its retries, with a server error or a rate limit, the SDK saves it on disk and sends it again in the background. The response then has no `document`, and its `message` starts with `Request failed but has been queued for retry`. Do not send that invoice again.

Run your mapping against test cases in a [testbed](https://docs.complyance.io/one-platform/developer-portal/testbeds/) before you switch to 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`:

```typescript title="Check a document's status"
const status = await GETSUnifySDK.getDocumentStatus("01K5EXAMPLE00000000000142");
console.log(status);
```

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. Get one by the `documentId` from a [purchase invoice webhook](https://docs.complyance.io/one-platform/developer-portal/webhooks/purchase-invoices/):

```typescript title="Get a purchase invoice"
const invoice = await GETSUnifySDK.getPurchaseInvoice("01JEXAMPLE0000000000000142");
console.log(invoice);
```

The fields of the result are described in [Get a purchase invoice](https://docs.complyance.io/api-reference/purchases/get/). To list the purchase invoices received in a date range, use [List purchase invoices](https://docs.complyance.io/api-reference/purchases/list/) in the API.

## Handle errors

When a request is refused, the SDK throws an `SDKException`. Its `code` says what went wrong, `message` describes it and `suggestion` says what to try.

```typescript title="Handle errors"
import { SDKException } from "unify-sdk";

try {
  const response = await GETSUnifySDK.pushToUnifyWithDocumentType(
    "acme-erp",
    "1.0",
    documentType,
    Country.AE,
    Operation.SINGLE,
    Mode.DOCUMENTS,
    Purpose.INVOICING,
    payload,
  );
  console.log(response.status);
} catch (error) {
  if (error instanceof SDKException) {
    console.error(error.code, error.message, error.suggestion);
  } else {
    throw error;
  }
}
```

The errors people hit most:

- **`API key is required`**, thrown by `new SDKConfig(...)`: the key is empty. Check that `COMPLYANCE_API_KEY` is set in the environment your application runs in.
- **`API_ERROR`** on your first request: most often the key was refused because it is invalid, revoked or expired, or belongs to the other environment. Use a sandbox key with `Environment.SANDBOX` and a production key with `Environment.PRODUCTION`. The `message` carries the reason the request was refused.
- **`MISSING_FIELD`** with `Source name is required` or `Source version is required`: an invoicing request was sent without a source. Pass the source name and version you linked to your integration.

Validation problems in the invoice itself do not throw; they come back in `data.validation`, as shown in [Submit an invoice](https://docs.complyance.io/sdks/typescript/#submit-an-invoice). What each error means, and what to do, is in [Unify errors](https://docs.complyance.io/api-reference/unify/errors/).

Next, try a request without writing code in the [API playground](https://docs.complyance.io/api/?operation=submitDocument).

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