Ledger SDK
Ruby SDK for the Ledger API, routed through the Fluence API gateway. The SDK is mostly read-only — browsing containers, accounts, positions, inventories, and balances. Its write surface is narrow and explicit: containers (create, and updating their characteristics), fees/fee-exclusions management (list, create, update, destroy), and imports (create, plus replaying an import or one of its lines) — the only way to write positions and movements.
Installation
Add the gem and its fluence-gateway-client runtime dependency to your application's Gemfile (both are hosted on GitHub Packages):
source 'https://rubygems.pkg.github.com/fluence-eu' do
gem 'fluence-gateway-client'
gem 'ledger-sdk'
end
Then run:
bundle install
Configuration
Credentials and gateway URLs are managed by fluence-gateway-client. Set APPCENTER_CLIENT_ID and APPCENTER_CLIENT_SECRET and you're done — the SDK targets the ledger service on the Fluence gateway out of the box (paths are prefixed with /backend/ledger/).
For overrides (URL, profile, SSL, etc.), see the fluence-gateway-client README.
Usage
Containers
# List all containers
containers = Ledger::Sdk.Container.list
# Create a container — for a portfolio built by hand rather than fed by a bank.
# reference (unique) and primary_currency are required; manager_id, custodian_id,
# account_keeper_id, management_mode and portfolio_manager_name are optional.
container = Ledger::Sdk.Container.create(reference: 'MODEL.RESILIENCE', primary_currency: 'EUR')
# Get a specific container (includes its accounts)
container = Ledger::Sdk.Container(42).data
Accounts
# List accounts for a container
accounts = Ledger::Sdk.Container(42).accounts.list
# Get a specific account
account = Ledger::Sdk.Container(42).accounts(7).data
Balances
Balances are accessed by name from a container or an account. Known names: official, official_with_updated_price, transparent, transparent_with_updated_price. Names are not validated by the SDK — an unknown name yields an empty list.
# Recent official balances of a container
balances = Ledger::Sdk.Container(42).balances('official').list
# Recent transparent balances of an account
balances = Ledger::Sdk.Container(42).accounts(7).balances('transparent').list
balances.first.data
# => { 'effective_date' => '2026-06-10', 'currency' => 'EUR', 'value' => 1250000.0 }
Charge breakdown
# Per-label ventilation of a container's charges, read as of an instant:
# as_of bounds the window (ledger defaults to today), from is optional
# (inception when omitted)
breakdown = Ledger::Sdk.Container(42).charge_breakdown(as_of: '2026-06-30', from: '2026-01-01')
breakdown
# => { 'currency' => 'EUR', 'total' => 12.5, 'labels' => [...] }
Performance
container = Ledger::Sdk.Container(42)
container.performance(from: '2026-01-01', to: '2026-08-07', granularity: 'month')
# => { 'performance' => { 'value_in_percentage' => 3.0, ... },
# 'contribution' => { 'market' => 4.5, 'fx' => -1.5, 'total' => 3.0, 'average_weight' => 100.0 },
# 'contribution_series' => [{ 'from' => '2026-01-01', 'to' => '2026-01-31', ... }],
# 'accounts' => [{ ..., 'positions' => [{ ..., 'contribution' => { ... } }] }] }
container.accounts(7).performance(from: '2026-01-01', to: '2026-08-07')
contribution is null whenever there is nothing to decompose — a cash line, or a window
ledger cannot chain — so read it as "no answer", never as zero. contribution_series is
absent altogether unless granularity asked for it, which is what tells "not asked" apart
from "nothing to show".
Fees, fee exclusions and fee kinds
Fees and fee exclusions are attached to a container. Unlike most of the SDK they are writable: they can be listed, created, updated and destroyed.
# List fees for a container
fees = Ledger::Sdk.Container(42).fees.list
# Filter fees by kind and as-of date
fees = Ledger::Sdk.Container(42).fees.list { { filters: { kind: 'financial_management', as_of: '2026-01-01' } } }
# Create a fee
fee = Ledger::Sdk.Container(42).fees.create(kind: 'financial_management', rate: 0.01)
# Update a fee
fee = Ledger::Sdk.Container(42).fees(5)
fee.update(rate: 0.02)
# Destroy a fee (the instance must not be reused afterwards)
fee.destroy
Fee exclusions work the same way, and additionally support a code filter:
# List fee exclusions for a container
exclusions = Ledger::Sdk.Container(42).fee_exclusions.list
# Filter fee exclusions by kind, code and as-of date
exclusions = Ledger::Sdk.Container(42).fee_exclusions.list do
{ filters: { kind: 'financial_management', code: 'FM-1', as_of: '2026-01-01' } }
end
# Create, update and destroy work the same way as fees
exclusion = Ledger::Sdk.Container(42).fee_exclusions.create(kind: 'financial_management')
exclusion.update(code: 'FM-1')
exclusion.destroy
FeeKind is a global, read-only registry of every fee kind the Ledger API
understands. It is not nested under a container, has no show endpoint, and
the /fee_kinds endpoint ignores query params — list always returns the
full registry:
Ledger::Sdk.FeeKind.list
Positions
# List positions of an account
positions = Ledger::Sdk.Container(42).accounts(7).positions.list
positions.first.data
# => { 'id' => 3, 'currency' => 'USD', 'quantity' => 100.0, 'primary_code' => 'US0378331005', ... }
# List every position of a container in one call
positions = Ledger::Sdk.Container(42).positions.list
# Look up positions across every container — filters[code] is required,
# the root endpoint returns [] without it
positions = Ledger::Sdk.Position.list { { filters: { code: 'FR0000120271' } } }
Inventories
# List inventories of a position
inventories = Ledger::Sdk.Container(42).accounts(7).positions(3).inventories.list
Imports
Positions and movements are read-only over REST, so imports are the only way to write them. An import also creates the container and accounts its lines name, on first sight, by natural key — there is no separate container-creation call.
# Push lines. The batch is processed asynchronously: a fresh import is `pending`.
import = Ledger::Sdk.Import.create(
filename: 'model-resilience-2026-08-05.json',
positions: [position_line],
movements: [movement_line]
)
import.data['status'] # => 'pending'
# Read the outcome: status, total_count, imported_count, failure_count and lines
Ledger::Sdk.Import(42).data
# List imports, filtered by status
Ledger::Sdk.Import.list { { filters: { status: 'failure' } } }
# Replay a whole import, or just one of its failed lines
Ledger::Sdk.Import(42).retry
Ledger::Sdk.Import(42).lines(7).retry
The body is posted flat, unlike fees, which wrap their attributes under a fee key. Line shapes are validated by Ledger, not by the SDK: posting two empty lists answers 422 no_lines_provided, replaying a line that did not fail answers 422 line_not_failure.
Import lines have no index endpoint — they come embedded in their import's payload — so lines requires an id and returns a single line.
provider_id is optional: it is stored as the producer's Mosaic id, and Ledger records the calling gateway client on every import regardless.
Chaining associations
Models support association chaining from container down to inventory:
# Container -> Accounts -> Positions -> Inventories
Ledger::Sdk.Container(42).accounts(7).positions(3).inventories.list
Positions, inventories, and balances have no individual show endpoint: their data is returned by list (calling data on a bare chained instance raises NotImplementedError).
Error handling
The SDK raises the fluence-gateway-client errors as-is:
begin
Ledger::Sdk.Container.list
rescue Fluence::Gateway::AuthenticationError
# OAuth2 credentials invalid — check APPCENTER_CLIENT_ID / APPCENTER_CLIENT_SECRET
rescue Fluence::Gateway::ConnectionError
# Gateway unreachable — safe to retry with backoff
rescue Fluence::Gateway::RequestError => e
# Ledger responded 4xx/5xx — e.status, e.body, e.headers are available
end
Development
After checking out the repo, run bin/setup to install dependencies.
Use bin/console for an interactive prompt with the SDK pre-loaded. Set the APPCENTER_CLIENT_ID and APPCENTER_CLIENT_SECRET environment variables (read directly by fluence-gateway-client) before launching:
APPCENTER_CLIENT_ID=your_id APPCENTER_CLIENT_SECRET=your_secret bin/console
Run the full catalog (lint + security + codequality + tests) with:
bundle exec fluence-ci all
License
The gem is available as open source under the terms of the MIT License.