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.

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.

Gemfileruby
gem "io-complyance-unify-sdk", "~> 3.0"

Then run bundle install.

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

Configure the SDK

Configure the SDK once, when your application starts.

Configure the SDKruby
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:

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

Send a sample payload for mappingruby
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 in the Developer portal. In Payload Selection, choose the payload you sent, then map your fields to GETS 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.

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.

Submit an invoiceruby
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:

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

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

puts data["state"]
puts data["isTerminal"]
Invoice accepted as compliantjson
{
  "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.

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.

Configure for purchase invoicesruby
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.

List purchase invoicesruby
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:

Get a purchase invoiceruby
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 and Get a purchase invoice.

Handle errors

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

Handle errorsruby
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
ErrorWhat it meansWhat to do
APIError with status_code 401The API key is missing, invalid, revoked or expired.Check COMPLYANCE_API_KEY, or create a new API key.
APIError with status_code 403The 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 environmentThe 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 callsComplyanceSDK.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.