New

Our landmark annual report is here.Read the State of Payment Operations 2022

Journal|||November 11, 2022

How to Verify Prior Ledger States

Image of Eve Li
Eve LiEngineering

Introduction

Immutability is an important guarantee from a ledger. At Modern Treasury, we guarantee immutability by keeping track of the historical changes whenever a Ledger object is updated, which is backed by a series of immutable event logs. In this guide, we will walk through how to verify prior ledger states by finding the historical changes to a ledger.

Who This Guide is For

As a user who has integrated with Modern Treasuryโ€™s Ledger product, there may be situations where you need to find the prior states of a Ledger object. For example, you might need this when building a front-end UI for your end users to view the historical changes, generating an audit report, or manually investigating a production issue. Modern Treasury has made it more accessible to query the historical records.

This guide assumes that you are familiar with the core Ledger concepts and APIs, like creating a Ledger, adding Ledger Accounts, creating or updating a Ledger Transaction with Ledger Entries, and reading Account balances. If you are new to Ledgers, we highly recommend you to read the Accounting for Developers series or review the Ledgers API documentation first.

Ledger Transaction History

A Ledger Transaction and its Entries can be edited conditionally

Ledger Transactions are created to record money movement events that happened among multiple Ledger Accounts. A Ledger Transaction can be updated when the status is pending, but once it transitions to the final status posted or archived, it can no longer be changed.

For example, a transaction might be modified in pending status to reflect payments in transit or before they settle - such as in the processing window of an ACH credit. Once the transaction cleared they would be moved to posted. Throughout the life cycle of a Ledger Transaction, each modification generates a new Ledger Transaction Version with an incremental version number starting from zero.

Ledger Entries of a pending Ledger Transaction can also be updated. In order to guarantee immutability, the old Ledger Entries are discarded while new Ledger Entries are created. The Ledger Transaction Version object not only stores the top level Transaction attributes like description, status, metadata, it also captures the historical Ledger Entry values when the change happened.

Ledger Transaction update example

Past Transaction versions are used when trying to reconstruct the history of a particular money movement event that involves multiple changing Accounts. Consider a bill splitting app. A pending Transaction represents the bill when itโ€™s first created, and users can add themselves to subsequent versions of the bill before it is posted.

Alice starts by paying for the entire bill.

Alice pays the bill
Alice pays the bill

Bob splits the bill with Alice.

Bob splits the bill with Alice
Bob splits the bill with Alice

The bill is posted.

The bill posts

Querying Ledger Transaction Versions

The Versions of a Ledger Transaction can be retrieved by sending a request to the List Ledger Transaction Versions endpoint:

1curl --request GET \
2     --url 'https://app.moderntreasury.com/api/ledger_transactions/d376dd4e-1c4f-4ba2-9b13-db63c04471e0/versions'

The API response contains a list of historical values of the Ledger Transaction, sorted by version in a descending order. Each Version also includes the Ledger Entries attached to the Transaction at the time when the Version was created:

1[ 
2  { 
3    "id": "b3ea66d8-ffa9-44e6-96d2-591cdb998a5e", 
4    "ledger_transaction_id": "d376dd4e-1c4f-4ba2-9b13-db63c04471e0", 
5    "object": "ledger_transaction_version", 
6    "ledgerable_type": null, 
7    "ledgerable_id": null, 
8    "ledger_id": "a9d970da-207e-43da-b4d6-6e9ae01ba2cc", 
9    "description": "Restaurant bill", 
10    "status": "posted", 
11    "ledger_entries": [ 
12      { 
13        "id": "0bbdf6db-2378-449f-b731-0409c14f270c", 
14        "object": "ledger_entry", 
15        "amount": 100000, 
16        "direction": "credit", 
17        "ledger_account_id": "063da2e3-4a37-4abf-8626-57d25bd6d441", 
18        "ledger_account_currency": "USD", 
19        "ledger_account_currency_exponent": 2, 
20        "ledger_account_lock_version": 5, 
21        "ledger_transaction_id": "d376dd4e-1c4f-4ba2-9b1-db63c04471e0", 
22        "live_mode": true, 
23        "created_at": "2022-10-15T16:58:51Z", 
24      }, 
25      { 
26        "id": "5f6a2fe9-502c-439a-a475-2af09b8836ef", 
27        "object": "ledger_entry", 
28        "amount": 50000, 
29        "direction": "debit", 
30        "ledger_account_id": "09fa6c2e-ab3e-42c8-86dd-6afbe7ef05ae", 
31        "ledger_account_currency": "USD", 
32        "ledger_account_currency_exponent": 2, 
33        "ledger_account_lock_version": 5, 
34        "ledger_transaction_id": "d376dd4e-1c4f-4ba2-9b13-db63c04471e0", 
35        "live_mode": true, 
36        "created_at": "2022-10-15T16:58:51Z", 
37      }, 
38      { 
39        "id": "1afd291d-ce6c-477c-8d52-692fc554070", 
40        "object": "ledger_entry", 
41        "amount": 50000, 
42        "direction": "debit", 
43        "ledger_account_id": "6b85cfb0-2f59-4775-8207-aee8f5035610", 
44        "ledger_account_currency": "USD", 
45        "ledger_account_currency_exponent": 2, 
46        "ledger_account_lock_version": 5, 
47        "ledger_transaction_id": "d376dd4e-1c4f-4ba2-9b13-db63c04471e0", 
48        "live_mode": true, 
49        "created_at": "2022-10-15T16:58:51Z", 
50      } 
51     ], 
52     "posted_at": "2022-10-15T16:58:51Z", 
53     "effective_at": "2022-09-30T16:58:51Z", 
54     "metadata": {}, 
55     "live_mode": true, 
56     "created_at": "2022-10-15T16:58:51Z", 
57     "version": 2  
58    }, 
59    ... 
60  ]

Versions can be queried by their created_at timestamps. For example, to find the Ledger Transaction changes between 9am and 6pm UTC on Oct 21st, you would add query parameters created_at[gte]=2022-10-21T09:00:00Z&created_at[lte]=2022-10-21T18:00:00Z:

1curl --request GET \
2     --url <https://app.moderntreasury.com/api/ledger_transactions/d376dd4e-1c4f-4ba2-9b13-db63c04471e0/versions&created_at[gte]=2022-10-21T09:00:00Z&created_at[lte]=2022-10-21T18:00:00Z>

Versions can also be queried by a version number range. For example, if youโ€™d like to see what a Ledger Transaction looked like when it was first created, you would add query parameter version[eq]=0

1curl --request GET \
2     --url <https://app.moderntreasury.com/api/ledger_transactions/d376dd4e-1c4f-4ba2-9b13-db63c04471e0/versions&version[eq]=0>

Ledger Account Balance History

A Ledger account represents a balance tracked by the ledger. One transaction modifies at least two accounts. Each Ledger Account has a lock_version field which increments when the pending or posted balance of the Ledger Account changes. Notice that you can add Ledger Entries to a Ledger Account with the effective_at timestamp in any order, but the lock_version increases per edit, which is perpendicular to effective_at.

Querying Ledger Entries

In order to find which Ledger Entry contributed to a certain Ledger Account lock_version, you can send a request to the List Ledger Entries endpoint with ledger_account_id and ledger_account_lock_version query parameters. As mentioned earlier, in order to guarantee immutability, previous Ledger Entries are replaced with the new ones upon editing, which is why we need an extra query parameter show_deleted=true:

1curl --request GET \
2     --url '<https://app.moderntreasury.com/api/ledger_entries?ledger_account_id=063da2e3-4a37-4abf-8626-57d25bd6d441&ledger_account_lock_version=5&show_deleted=true>'

The API response contains the Ledger Entries of the ledger_account_lock_version:

1[ 
2  { 
3    "id": "b5cecf80-93ff-4e2a-8f37-fa5687a14837", 
4    "object": "ledger_entry", 
5    "live_mode": true, 
6    "amount": 6000, 
7    "direction": "credit", 
8    "ledger_account_id": "063da2e3-4a37-4abf-8626-57d25bd6d441", 
9    "ledger_account_currency": "USD", 
10    "ledger_account_currency_exponent": 2, 
11    "ledger_account_lock_version": 5, 
12    "ledger_transaction_id": "d376dd4e-1c4f-4ba2-9b13-db63c04471e0", 
13    "discarded_at": "2022-10-12T17:32:22Z", 
14    "created_at": "2022-10-11T19:40:59Z", 
15    "updated_at": "2022-10-12T17:32:22Z" 
16  } 
17]

Querying Account Balance

When a Ledger Entry with an effective_at timestamp is added to a Ledger Account, it affects the balance on and after the effective_at timestamp. For example, at 6pm UTC on Oct 1st, some Ledger Entries are added with effective_at as September 30th 6pm UTC. Then on Oct 15th, you would like see what September 30th 6pm UTCโ€™s balances are as of now, you can send a request to the Get Ledger Account endpoint with a query parameter balances[effective_at]:

1curl --request GET \
2     --url <https://app.moderntreasury.com/api/ledger_accounts/09fa6c2e-ab3e-42c8-86dd-6afbe7ef05ae?balances%5Beffective_at%5D=2022-09-30T18:00:00Z>

The balances in the response are the sum of all the Ledger Entries that are currently recorded in the system with effective_at less than or equal to 2022-09-30T18:00:00Z.

1{ 
2  "id": "09fa6c2e-ab3e-42c8-86dd-6afbe7ef05ae", 
3  "object": "ledger_account", 
4  "name": "Alice's account", 
5  "ledger_id": "a9d970da-207e-43da-b4d6-6e9ae01ba2cc", 
6  "description": "Alice's account", 
7  "lock_version": 5, 
8  "normal_balance": "credit", 
9  "balances": { 
10    "pending_balance": { 
11      "credits": 50000, 
12      "debits": 10000, 
13      "amount": 40000, 
14      "currency": "USD", 
15      "currency_exponent": 2 
16    }, 
17    "posted_balance": { 
18      "credits": 20000, 
19      "debits": 1000, 
20      "amount": 19000, 
21      "currency": "USD", 
22      "currency_exponent": 2 
23    }, 
24    "available_balance": { 
25      "credits": 20000, 
26      "debits": 10000, 
27      "amount": 10000, 
28      "currency": "USD", 
29      "currency_exponent": 2 
30    } 
31  }, 
32  "metadata": {}, 
33  "live_mode": true, 
34  "created_at": "2022-08-04T16:54:32Z", 
35  "updated_at": "2022-08-04T16:54:32Z" 
36}

Conclusion

Ledger transaction versions, account lock versions and effective_at queries are powerful ways for you to reconstruct the history of your ledger. If you are interested in building a transparent and auditable data store for your financial data, take a look at Modern Treasury Ledgers and reach out if we can be helpful.

Try Modern Treasury

See how smooth payment operations can be.

Talk to Us
Share

Copied!