Ruby SDK
Official Ruby SDK for thelawin.dev.
Requires Ruby 3.0+
Installation
Add to your Gemfile:
ruby
gem 'thelawin', '~> 0.2'Then:
bash
bundle installOr install directly:
bash
gem install thelawinQuick Start
ruby
require 'thelawin'
client = Thelawin::Client.new(api_key: 'env_sandbox_xxx')
result = client.invoice
.number('2026-001')
.date('2026-01-15')
.seller(name: 'Acme GmbH', vat_id: 'DE123456789', city: 'Berlin', country: 'DE')
.buyer(name: 'Customer AG', city: 'München', country: 'DE')
.add_item(description: 'Consulting Services', quantity: 8, unit: 'HUR', unit_price: 150, vat_rate: 19)
.template('minimal')
.generate
if result.success?
result.save_pdf('./invoice.pdf')
puts "Generated: #{result.filename}"
puts "Format: #{result.format.format_used}"
else
result.errors.each { |e| puts "#{e[:path]}: #{e[:message]}" }
endClient Options
ruby
client = Thelawin::Client.new(
api_key: 'env_sandbox_xxx',
environment: :production, # :production or :preview
timeout: 30 # optional, seconds
)Environments
| Environment | URL | Description |
|---|---|---|
:production | https://api.thelawin.dev | Production API (default) |
:preview | https://api.preview.thelawin.dev:3080 | Preview/staging API |
ruby
# Global configuration
Thelawin.configure do |config|
config.api_key = 'env_sandbox_xxx'
config.environment = :preview
end
# Or per-client
client = Thelawin::Client.new(api_key: 'env_sandbox_xxx', environment: :preview)
# Check environment
client.preview? # => true
client.production? # => falseSupported Formats
| Format | Description | Output |
|---|---|---|
auto | Auto-detect based on countries (default) | PDF or XML |
zugferd | ZUGFeRD 2.3 (Germany/EU) | PDF/A-3 + CII XML |
facturx | Factur-X 1.0 (France) | PDF/A-3 + CII XML |
xrechnung | XRechnung 3.0 (German B2G) | PDF/A-3 + UBL XML |
pdf | Plain PDF without XML | |
ubl | UBL 2.1 Invoice | XML only |
cii | UN/CEFACT CII | XML only |
peppol | Peppol BIS Billing 3.0 | XML only |
fatturapa | FatturaPA 1.2.1 (Italy) | XML only |
Builder API
Invoice Details
ruby
client.invoice
.number('2026-001') # Required
.date('2026-01-15') # Required, String or Date
.due_date('2026-02-15') # Optional
.currency('EUR') # Default: 'EUR'
.notes('Thank you!') # OptionalFormat & Profile
ruby
.format('zugferd') # 'auto', 'zugferd', 'facturx', 'xrechnung', 'pdf', 'ubl', 'cii', 'peppol', 'fatturapa'
.profile('en16931') # 'minimum', 'basic_wl', 'basic', 'en16931', 'extended'Parties
ruby
.seller(
name: 'Acme GmbH', # Required
vat_id: 'DE123456789', # Required for ZUGFeRD
street: 'Hauptstraße 1',
city: 'Berlin', # Required
postal_code: '10115',
country: 'DE', # Required
email: 'invoice@acme.de', # Required for XRechnung
peppol_id: '0088:123...', # For Peppol
codice_fiscale: '...', # For FatturaPA
)
.buyer(
name: 'Customer AG', # Required
vat_id: 'DE987654321',
city: 'München', # Required
country: 'DE', # Required
codice_destinatario: '...', # For FatturaPA
pec: 'cliente@pec.it' # For FatturaPA
)Line Items
ruby
.add_item(
description: 'Consulting', # Required
quantity: 8, # Required
unit: 'HUR', # Required (C62=piece, HUR=hour, DAY=day)
unit_price: 150.00, # Required
vat_rate: 19.0, # Required
natura: 'N2.2' # For FatturaPA (VAT exemption)
)Format-Specific Fields
ruby
.leitweg_id('04011000-12345-67') # XRechnung: German B2G routing
.buyer_reference('PO-12345') # Peppol: Purchase order reference
.tipo_documento('TD01') # FatturaPA: Document type (TD01=invoice)Customization
ruby
.template('minimal') # 'minimal', 'classic', 'compact'
.locale('de') # 'en', 'de', 'fr', 'es', 'it'
.accent_color('#8b5cf6')
.footer_text('Thank you!')Logo
ruby
# From file (auto Base64)
.logo_file('./logo.png')
.logo_file('./logo.png', width_mm: 30)
# From Base64
.logo_base64('iVBORw0KGgo...', width_mm: 30)Result Handling
ruby
result = builder.generate
if result.success?
puts result.filename # 'invoice-2026-001.pdf' or '.xml'
puts result.format.format_used # 'zugferd', 'fatturapa', etc.
puts result.format.profile # 'EN16931'
puts result.format.version # '2.3'
# Save to file
result.save_pdf('./invoice.pdf') # or result.save('./invoice.xml')
# Get bytes
pdf_bytes = result.to_bytes
# Get data URL
data_url = result.to_data_url
# Check if XML-only output
if result.xml_only?
# Handle XML-only formats (UBL, Peppol, FatturaPA, etc.)
end
# Legal warnings
result.warnings.each do |warning|
puts "#{warning.code}: #{warning.message}"
end
else
result.errors.each do |error|
puts "#{error[:path]}: #{error[:message]}"
end
endPre-Validation (Dry-Run)
Validate invoice data without generating PDF:
ruby
result = client.invoice
.number('2026-001')
.date('2026-01-15')
.seller(name: 'Acme', country: 'DE')
.buyer(name: 'Customer', country: 'IT')
.add_item(description: 'Service', quantity: 1, unit_price: 100)
.format('fatturapa')
.validate # Returns DryRunResult
if result.valid?
puts "Valid! Would generate: #{result.format.format_used}"
else
result.errors.each { |e| puts e }
endError Handling
ruby
begin
result = client.invoice.generate
rescue Thelawin::QuotaExceededError
puts 'Quota exceeded, upgrade your plan'
rescue Thelawin::NetworkError => e
puts "Network error: #{e.message}"
rescue Thelawin::ApiError => e
puts "API error #{e.status_code}: #{e.message}"
endFormat-Specific Examples
XRechnung (German B2G)
ruby
result = client.invoice
.format('xrechnung')
.leitweg_id('04011000-12345-67')
.seller(
name: 'Acme GmbH',
vat_id: 'DE123456789',
email: 'invoice@acme.de', # Required
street: 'Hauptstraße 1',
city: 'Berlin',
postal_code: '10115',
country: 'DE'
)
# ... rest of invoice
.generatePeppol
ruby
result = client.invoice
.format('peppol')
.buyer_reference('PO-12345')
.seller(
name: 'Acme Ltd',
vat_id: 'GB123456789',
peppol_id: '0088:1234567890123',
city: 'London', country: 'GB'
)
.buyer(
name: 'Customer BV',
peppol_id: '0106:NL123456789B01',
city: 'Amsterdam', country: 'NL'
)
.generateFatturaPA (Italy)
ruby
result = client.invoice
.format('fatturapa')
.tipo_documento('TD01')
.seller(
name: 'Acme S.r.l.',
vat_id: 'IT12345678901',
codice_fiscale: '12345678901',
city: 'Milano', country: 'IT'
)
.buyer(
name: 'Cliente S.p.A.',
vat_id: 'IT98765432109',
codice_destinatario: 'ABCDEFG', # or pec: 'cliente@pec.it'
city: 'Roma', country: 'IT'
)
.add_item(description: 'Consulenza', quantity: 10, unit_price: 100, vat_rate: 22.0)
.generate
# FatturaPA returns XML only
result.save('./fattura.xml')Rails Integration
ruby
# config/initializers/thelawin.rb
Thelawin.configure do |config|
config.api_key = Rails.application.credentials.thelawin_api_key
config.environment = Rails.env.production? ? :production : :preview
end
# In your controller/service
class InvoiceService
def generate_invoice(order)
Thelawin.client.invoice
.number(order.invoice_number)
.date(order.created_at.to_date)
.seller(company_details)
.buyer(customer_party(order.customer))
.items(order.line_items.map { |li| line_item_attrs(li) })
.generate
end
end