# Ruby SDK

Send invoices to Complyance from a Ruby application: install the gem, send a sample payload for mapping, then submit invoices and follow them. You need Ruby 2.6 or later and a sandbox API key.

> **Before you start**
>
> - Ruby 2.6 or later.
> - A One Platform workspace.
> - A sandbox [API key](https://docs.complyance.io/one-platform/developer-portal/api-keys/), stored in the `COMPLYANCE_API_KEY` environment variable. Never write the key into your code.
> - Work in sandbox first. Switch to production only after your invoices pass there.

## Install

The gem is `io-complyance-unify-sdk`. The latest version is 3.0.12, a stable release. Some older guides name the gem `complyance-sdk`. That gem does not exist on RubyGems; install `io-complyance-unify-sdk`.

**Bundler**

```ruby title="Gemfile"
gem "io-complyance-unify-sdk", "~> 3.0"
```

Then run `bundle install`.

**gem**

```bash title="Install the gem"
gem install io-complyance-unify-sdk -v 3.0.12
```

In your code, load it with `require "complyance_sdk"`.

## Configure the SDK

Configure the SDK once, when your application starts.

```ruby title="Configure the SDK"
require "complyance_sdk"

config = ComplyanceSDK::Config::SDKConfig.new(
  api_key: ENV.fetch("COMPLYANCE_API_KEY"),
  environment: ComplyanceSDK::Models::Environment::SANDBOX
)

ComplyanceSDK.configure(config)

SOURCE_NAME = "acme-erp"
SOURCE_VERSION = "1"
```

- `environment` is `Environment::SANDBOX` for testing and `Environment::PRODUCTION` for live invoices. Use a sandbox key with sandbox and a production key with production.
- `SOURCE_NAME` and `SOURCE_VERSION` name the system that sends the invoices, such as your ERP. Use the same pair for mapping and for invoicing, because Complyance uses it to find the mapping for your invoices.

### Retries

The SDK tries a request up to three times when the network fails or the API answers `408`, `429`, `500`, `502`, `503` or `504`. To send each request only once, pass `RetryConfig.none` as `retry_config`:

```ruby title="Choose a retry configuration"
config = ComplyanceSDK::Config::SDKConfig.new(
  api_key: ENV.fetch("COMPLYANCE_API_KEY"),
  environment: ComplyanceSDK::Models::Environment::SANDBOX,
  retry_config: ComplyanceSDK::Config::RetryConfig.none
)
```

## 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 exactly as your system produces it, with the purpose set to 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).

```ruby title="Send a sample payload for mapping"
payload = {
  invoice_number: "INV-2026-0142",
  issue_date: "2026-09-22",
  currency: "AED",
  seller: { name: "Acme Trading LLC", trn: "100000000000003" },
  buyer: { name: "Gulf Retail LLC", trn: "100000000000011" },
  lines: [
    { description: "Office chairs", quantity: 4, unit_price: 250.0, vat_rate: 5 }
  ],
  total: 1050.0
}

document_type = ComplyanceSDK::Models::GetsDocumentType.builder
  .base(ComplyanceSDK::Models::BASE::TAX_INVOICE)
  .build

raw = ComplyanceSDK.push_to_unify_v2(
  SOURCE_NAME,
  SOURCE_VERSION,
  document_type,
  ComplyanceSDK::Models::Country::AE,
  ComplyanceSDK::Models::Operation::SINGLE,
  ComplyanceSDK::Models::Mode::DOCUMENTS,
  ComplyanceSDK::Models::Purpose::MAPPING,
  payload
)

result = ComplyanceSDK::Models::UnifyResponse.from_h(raw)
puts result.status
```

You should see `success`. The payload is now stored in your workspace.

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

## Submit an invoice

Once the mapping is linked to your source, send invoices with the purpose set to invoicing. Use the same source name and version as for mapping.

The document type is a GETS document type: a base, such as `BASE::TAX_INVOICE` or `BASE::CREDIT_NOTE`, plus any modifiers that apply. For a UAE export invoice, add `.modifiers([ComplyanceSDK::Models::MODIFIER::EXPORT])` before `.build`. It decides which fields the invoice must contain; 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.

```ruby title="Submit an invoice"
document_type = ComplyanceSDK::Models::GetsDocumentType.builder
  .base(ComplyanceSDK::Models::BASE::TAX_INVOICE)
  .build

raw = ComplyanceSDK.push_to_unify_v2(
  SOURCE_NAME,
  SOURCE_VERSION,
  document_type,
  ComplyanceSDK::Models::Country::AE,
  ComplyanceSDK::Models::Operation::SINGLE,
  ComplyanceSDK::Models::Mode::DOCUMENTS,
  ComplyanceSDK::Models::Purpose::INVOICING,
  payload
)

result = ComplyanceSDK::Models::UnifyResponse.from_h(raw)

if result.success?
  document_id = result.data.dig("document", "documentId")
  puts "Accepted: #{document_id}"
else
  result.validation_errors.each { |error| puts error }
end
```

The gem 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):

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

`success?` is `true` only when the status is `success` and `data.validation.errors` is empty. Keep the `documentId` to follow the invoice.

If the request still fails after its retries, the SDK keeps the invoice and sends it again later. The status is then `queued`.

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

## Check a document’s status

Delivery to the tax authority continues after the response. Look up the document with its `documentId`.

```ruby title="Check a document's status"
status = ComplyanceSDK.get_document_status("01K5EXAMPLE00000000000142")
data = status["data"]

puts data["state"]
puts data["isTerminal"]
```

```json title="Invoice accepted as compliant"
{
  "success": true,
  "data": {
    "documentId": "01K5EXAMPLE00000000000142",
    "documentNumber": "INV-2026-0142",
    "country": "AE",
    "environment": "sandbox",
    "state": "final_valid",
    "isTerminal": true,
    "lastUpdatedAt": "2026-09-22T10:00:04.000Z"
  }
}
```

While `isTerminal` is `false`, the invoice is still being processed, so check again later. `final_valid` means it was accepted as compliant. 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. The gem reads them only when `client_id` is set to your workspace ID; without it, it raises `client_id is required for purchase invoice APIs`. Ask <support@complyance.io> for your workspace ID. Your API key still decides which workspace and environment the invoices come from.

```ruby title="Configure for purchase invoices"
config = ComplyanceSDK::Config::SDKConfig.new(
  api_key: ENV.fetch("COMPLYANCE_API_KEY"),
  environment: ComplyanceSDK::Models::Environment::SANDBOX,
  client_id: ENV.fetch("COMPLYANCE_CLIENT_ID")
)

ComplyanceSDK.configure(config)
```

List the purchase invoices received in a date range. Each page holds up to `limit` invoices. While `hasMore` is `true`, ask for the next page with `nextCursor`, keeping the same dates and limit.

```ruby title="List purchase invoices"
cursor = nil

loop do
  page = ComplyanceSDK.list_purchase_invoices(
    from: "2026-09-01",
    to: "2026-09-30",
    limit: 100,
    cursor: cursor
  )["data"]

  page["items"].each do |item|
    puts "#{item["invoiceNumber"]} from #{item["supplierName"]}"
  end

  break unless page["hasMore"]
  cursor = page["nextCursor"]
end
```

Get one purchase invoice by its `documentId`:

```ruby title="Get a purchase invoice"
invoice = ComplyanceSDK.get_purchase_invoice("01JEXAMPLE0000000000000142")

puts invoice.document_number
puts invoice.state
puts invoice.is_terminal
```

Every field is described in [List purchase invoices](https://docs.complyance.io/api-reference/purchases/list/) and [Get a purchase invoice](https://docs.complyance.io/api-reference/purchases/get/).

## Handle errors

Every error the SDK raises is a `ComplyanceSDK::Exceptions::SDKException`, with a `message` and, where there is one, a `suggestion`.

```ruby title="Handle errors"
begin
  raw = ComplyanceSDK.push_to_unify_v2(
    SOURCE_NAME, SOURCE_VERSION, document_type,
    ComplyanceSDK::Models::Country::AE,
    ComplyanceSDK::Models::Operation::SINGLE,
    ComplyanceSDK::Models::Mode::DOCUMENTS,
    ComplyanceSDK::Models::Purpose::INVOICING,
    payload
  )
rescue ComplyanceSDK::Exceptions::APIError => e
  puts "#{e.status_code}: #{e.message}"
  puts e.context[:response_body]
rescue ComplyanceSDK::Exceptions::SDKException => e
  puts e.message
  puts e.suggestion if e.suggestion
end
```

| Error | What it means | What to do |
| - | - | - |
| `APIError` with `status_code` `401` | The API key is missing, invalid, revoked or expired. | Check `COMPLYANCE_API_KEY`, or create a new [API key](https://docs.complyance.io/one-platform/developer-portal/api-keys/). |
| `APIError` with `status_code` `403` | The key belongs to a different environment from the one you configured. | Use a sandbox key with `Environment::SANDBOX` and a production key with `Environment::PRODUCTION`. |
| `ValidationError`: `Country not allowed for environment` | The SDK refused the country before sending anything. | Use a country the SDK accepts for this environment, such as `Country::AE`. |
| `ConfigurationError`: `SDK must be configured before making API calls` | `ComplyanceSDK.configure` was not called, or the key or environment is missing. | Configure the SDK before the first call. |

`NetworkError` means the SDK could not reach Complyance. What each status code from the API 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 every field of the request and response.

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