Moloni
A modern Ruby wrapper for the Moloni API. Works with Ruby 3.2+ and Rails 8.
Installation
Add this line to your application's Gemfile:
gem 'moloni'
And then execute:
$ bundle
Or install it yourself as:
$ gem install moloni
Setup
In a Rails app
Create config/initializers/moloni.rb:
Moloni.configure do |config|
config.developer_id = ENV.fetch('MOLONI_DEVELOPER_ID')
config.redirect_uri = ENV.fetch('MOLONI_REDIRECT_URI')
config.client_secret = ENV.fetch('MOLONI_CLIENT_SECRET')
config.access_token = ENV.fetch('MOLONI_ACCESS_TOKEN')
config.refresh_token = ENV.fetch('MOLONI_REFRESH_TOKEN')
config.company_id = ENV.fetch('MOLONI_COMPANY_ID', 0)
end
Environment Variables
| Variable | Purpose |
|---|---|
MOLONI_DEVELOPER_ID |
OAuth client_id from Moloni developer area |
MOLONI_REDIRECT_URI |
OAuth callback URL |
MOLONI_CLIENT_SECRET |
OAuth client_secret |
MOLONI_ACCESS_TOKEN |
Short-lived API token (1h) |
MOLONI_REFRESH_TOKEN |
Long-lived refresh token (14 days) |
MOLONI_COMPANY_ID |
Default company for API calls |
Authentication
The gem supports OAuth 2.0. Use the built-in CLI tool to obtain tokens:
bin/auth
This starts a local callback server and opens the browser for Moloni authorization. After approval, tokens are printed to the console.
Token auto-refresh: The gem automatically refreshes expired access_tokens using the refresh_token before each API call (with a 5-minute safety margin). If the refresh token also expires, a Moloni::TokenExpiredError is raised.
Usage
Basic API calls
All API calls use POST under the hood, including reads. The gem automatically injects company_id, access_token, json=true, and human_errors=true.
# List all products
products = Moloni::Product.getAll
# Find one product
product = Moloni::Product.getOne(product_id: 123)
# Search by EAN (or any future method)
product = Moloni::Product.getByEAN(ean: '5601234567890')
# Snake_case also works
product = Moloni::Product.get_by_ean(ean: '5601234567890')
Dynamic dispatch
Any method name not explicitly defined on a model is automatically translated to a Moloni API endpoint. This means the gem supports new Moloni methods as soon as they are released, without code changes.
# These all work immediately, even if not explicitly defined:
Moloni::Product.getByReference(reference: 'ABC-123')
Moloni::Product.get_by_reference(reference: 'ABC-123')
Moloni::Invoice.getByNumber(number: 'FT 2025/001')
Customers
# Find by VAT, email, or customer number
customer = Moloni::Customer.getByVat(vat: '123456789')
customer = Moloni::Customer.find_by_vat(vat: '123456789')
# Create a customer
customer = Moloni::Customer.insert(
name: 'Acme Corp',
vat: '123456789',
number: 'C001',
language_id: 1,
address: 'Main St 1',
city: 'Lisbon',
country_id: 1,
maturity_date_id: Moloni::MaturityDate.pronto_pagamento,
payment_method_id: Moloni::PaymentMethod.numerario
)
Invoices and Documents
# Create an invoice
invoice = Moloni::Invoice.insert(
document_set_id: 332_429,
date: Date.today,
expiration_date: Date.today + 30,
customer_id: 38_096_359,
products: [
{
product_id: 70_241_498,
name: 'Consulting',
qty: 1,
price: 100.00,
taxes: [{ tax_id: Moloni::Tax.iva_normal_id }]
}
]
)
# Get PDF link
pdf_url = Moloni::Document.getPDFLink(document_id: invoice[:document_id])
Taxes and Settings
# Get hardcoded tax IDs
normal_iva = Moloni::Tax.iva_normal_id # 2072734
intermedio = Moloni::Tax.iva_intermedio_id # 2072748
reduzido = Moloni::Tax.iva_reduzido_id # 2072741
# Get full tax objects
normal_tax = Moloni::Tax.iva_normal
Error Handling
The gem raises specific exceptions for different failure modes:
begin
Moloni::Product.getOne(product_id: 999_999)
rescue Moloni::APIKeyError => e
# HTTP 401 — access token invalid or expired
rescue Moloni::APIError => e
# Validation errors (missing fields, invalid format, etc.)
e.errors.each do |err|
puts "#{err[:field]}: #{err[:message]}"
end
rescue Moloni::TokenExpiredError => e
# Both access and refresh tokens expired — re-authentication needed
end
Breaking Changes (0.4 → 0.5)
- Ruby requirement: Now requires Ruby 3.2 or later.
- HTTP method: All API calls now use
POST(previously some reads usedGET). - Removed methods: Redundant explicit methods (
all,count,find,create) were removed from models where they offered no custom logic. Use dynamic dispatch (getAll,getOne,insert) instead. - Auth URL fix:
Moloni::Auth.auth_urlnow correctly points tohttps://www.moloni.pt/ac/root/oauth/. - Multi-json removed: Replaced
multi_jsonwith standard libraryJSON.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release.
License
The gem is available as open source under the terms of the MIT License.