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.
gem "io-complyance-unify-sdk", "~> 3.0"Then run bundle install.
gem install io-complyance-unify-sdk -v 3.0.12In your code, load it with require "complyance_sdk".
Configure the SDK
Configure the SDK once, when your application starts.
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"environmentisEnvironment::SANDBOXfor testing andEnvironment::PRODUCTIONfor live invoices. Use a sandbox key with sandbox and a production key with production.SOURCE_NAMEandSOURCE_VERSIONname 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:
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.
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.statusYou 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.
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 }
endThe 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:
{
"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.
status = ComplyanceSDK.get_document_status("01K5EXAMPLE00000000000142")
data = status["data"]
puts data["state"]
puts data["isTerminal"]{
"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.
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.
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"]
endGet one purchase invoice by its documentId:
invoice = ComplyanceSDK.get_purchase_invoice("01JEXAMPLE0000000000000142")
puts invoice.document_number
puts invoice.state
puts invoice.is_terminalEvery 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.
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. |
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.