Marketplace
Ledgers tutorial for marketplaces. Sign up for an account to start using the API.
Use Case Overview
For this tutorial, we will design and create a ledger for a fictional company called Rarebnb. Rarebnb is a marketplace that connects homeowners looking to rent out unique properties with travelers seeking accommodations.
We will carry out the ledger implementation in four phases:
- Define the Account and Transactions
- Create the Ledger Objects
- Write the Ledger Transactions
- Read the Ledger Account Balances
Demo of Tutorial
You can click through a demo of this Tutorial below:
Step 1. Define the Account and Transactions
Before we implement our ledger, we need to design our accounts and transactions.
Ledger Accounts represent the distinct balances that Rarebnb will want to track over time. They act as records of activity for specific aspects of the business, such as assets, liabilities, revenue, and expenses.
As a property rental marketplace, there are four fundamental Ledger Accounts Rarebnb will track.
| Ledger Account Name | Normality | Purpose |
|---|---|---|
| Cash | Debit | Tracks total funds held in the Rarebnb operating account. |
| Guest Receivable | Debit | Tracks the amount Rarebnb needs to collect from a certain guest. Each guest in the marketplace will have their own receivable. |
| Owner Payable | Credit | Tracks the amount Rarebnb owes to a certain property owner. Each owner in the maketplace will have their own payable. |
| Revenue | Credit | Tracks revenue generated by Rarebnb’s marketplace. |
Next, we will determine the Ledger Transactions needed to support our marketplace ledger. Ledger Transactions are tied to the events that happen during end-user workflows. For the purposes of this example, we will demonstrate a simple workflow with just three events.
| Ledger Transaction | Debited Accounts | Credited Accounts | Notes |
|---|---|---|---|
| Guest Booking | Guest Receivable account(increase) | Owner Payable account (increase); Revenue account (increase) | A guest has booked a stay with a property owner. |
| Guest Payment | Cash account (increase) | Guest Receivable account (decrease) | A guest pays the amount owed for their stay. |
| Owner Payout | Owner Payable account(decrease) | Cash account (decrease) | A property owner is paid out for a guest’s stay. |
Step 2. Create the Ledger Objects
Now that our Ledger is designed, we can begin implementing it. The first step is to instantiate the Ledger object.
Create a Ledger using the Create Ledger endpoint (POST /ledgers).
curl --request POST \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledgers \
-H 'Content-Type: application/json' \
-d '{
"name": "Rarebnb Ledger",
"description": "Represents our funds and user balances"
}'Modern Treasury will return a Ledger object with an id to be used in subsequent steps.
{
"id": "<ledger_id>",
"object": "ledger",
"name": "Rarebnb Ledger",
"description": "Represents our funds and user balances",
"metadata": {},
"live_mode": true,
"created_at": "2025-03-18T10:32:04Z",
"updated_at": "2025-03-18T10:32:04Z""
}Next, create the three Ledger Accounts from the previous section using the Create Ledger Account endpoint (POST /ledger_accounts). For our example, we will use Guest X and Owner Y as the guest and owner, respectively, involved in the booking.
curl --request POST \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_accounts \
-H 'Content-Type: application/json' \
-d '{
"name": "Cash Account",
"description": "Tracks Rarebnb cash",
"normal_balance": "debit",
"currency": "USD",
"ledger_id": "<ledger_id>"
}'
curl --request POST \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_accounts \
-H 'Content-Type: application/json' \
-d '{
"name": "Revenue",
"description": "Tracks Rarebnb revenue",
"normal_balance": "credit",
"currency": "USD",
"ledger_id": "<ledger_id>"
}'
curl --request POST \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_accounts \
-H 'Content-Type: application/json' \
-d '{
"name": "Guest X Receivable",
"description": "Tracks fees to be collected from Guest X for a stay",
"normal_balance": "debit",
"currency": "USD",
"ledger_id": "<ledger_id>"
}'
curl --request POST \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_accounts \
-H 'Content-Type: application/json' \
-d '{
"name": "Owner Y Payable",
"description": "Tracks amount owed to Owner Y for hosting a guest",
"normal_balance": "credit",
"currency": "USD",
"ledger_id": "<ledger_id>"
}'Modern Treasury will return the three Ledger Accounts that were created. Each has its own id that can be later used in Ledger Transactions.
{
"id": "<cash_account_id>",
"object": "ledger_account",
"name": "Cash Account",
"description": "Tracks Rarebnb cash",
"ledger_id": "<ledger_id>",
"normal_balance": "debit",
"live_mode": true,
"lock_version": 0,
"balances": {<balance_array>},
"ledgerable_type": null,
"ledgerable_id": null,
"external_id": null,
"metadata": {},
"created_at": "2025-03-18T12:09:24Z",
"updated_at": "2025-03-18T12:09:24Z"
}
{
"id": "<revenue_account_id>",
"object": "ledger_account",
"name": "Revenue",
"description": "Tracks Rarebnb revenue",
"ledger_id": "<ledger_id>",
"normal_balance": "credit",
"live_mode": true,
"lock_version": 0,
"balances": {<balance_array>},
"ledgerable_type": null,
"ledgerable_id": null,
"external_id": null,
"metadata": {},
"created_at": "2025-03-18T12:09:24Z",
"updated_at": "2025-03-18T12:09:24Z"
}
{
"id": "<guest_x_account_id>",
"object": "ledger_account",
"name": "Guest X Receivable",
"description": "Tracks fees to be collected from Guest X for a stay",
"ledger_id": "<ledger_id>",
"normal_balance": "debit",
"live_mode": true,
"lock_version": 0,
"balances": {<balance_array>},
"ledgerable_type": null,
"ledgerable_id": null,
"external_id": null,
"metadata": {},
"created_at": "2025-03-18T12:09:24Z",
"updated_at": "2025-03-18T12:09:24Z"
}
{
"id": "<owner_y_account_id>",
"object": "ledger_account",
"name": "Owner Y Payable",
"description": "Tracks amount owed to Owner Y for hosting a guest",
"ledger_id": "<ledger_id>",
"normal_balance": "credit",
"live_mode": true,
"lock_version": 0,
"balances": {<balance_array>},
"ledgerable_type": null,
"ledgerable_id": null,
"external_id": null,
"metadata": {},
"created_at": "2025-03-18T12:09:24Z",
"updated_at": "2025-03-18T12:09:24Z"
}Step 3. Write the Ledger Transactions
With the Ledger and Ledger Accounts created, we can begin recording activity by writing Ledger Transactions.
First, create a Ledger Transaction using the Create Ledger Transaction endpoint (POST /ledger_transactions) to record that Guest X has booked a stay at Owner Y’s property. Rarebnb charges a 5% commission fee to the property owner. So, the Ledger Transaction will reflect that Guest X will owe us $200, of which $10 is revenue for Rarebnb and the remaining $190 is owed to Owner Y.
💡 Notice that the Ledger Transaction records revenue before payment has been received. This is because accrual accounting is used, which records revenue when it is earned (i.e. when a booking is made). The alternative, cash accounting, records revenue only when money moves (i.e. payment is received from the guest or payment is sent to the owner).
curl --request POST \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_transactions \
-H 'Content-Type: application/json' \
-d '{
"description": "Guest X Booking",
"status": "pending",
"external_id": "<booking_id>",
"ledger_entries": [
{
"amount": 20000,
"direction": "debit",
"ledger_account_id": "<guest_x_account_id>"
},
{
"amount": 1000,
"direction": "credit",
"ledger_account_id": "<revenue_account_id>"
}
{
"amount": 19000,
"direction": "credit",
"ledger_account_id": "<owner_y_account_id>"
}
]
}'This Ledger Transaction is created in a pending state. When the stay actually occurs, the Ledger Transaction status is updated to posted.
curl --request PATCH \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_transactions/<ledger_transaction_id> \
-H 'Content-Type: application/json' \
-d '{
"description": "Updating Guest X Booking to posted",
"status": "posted"
}'Once Guest X completes their stay, Rarebnb charges them and the incoming payment is represented by a pending Ledger Transaction that zeros out Guest X's due balance.
curl --request POST \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_transactions \
-H 'Content-Type: application/json' \
-d '{
"description": "Guest X Payment",
"status": "pending",
"external_id": "<guest_payment_id>",
"ledger_entries": [
{
"amount": 20000,
"direction": "debit",
"ledger_account_id": "<cash_account_id>"
},
{
"amount": 20000,
"direction": "credit",
"ledger_account_id": "<guest_x_account_id>"
}
]
}'Once the transaction has posted to Rarebnb's Cash account, the Ledger Transaction can be updated to posted.
curl --request PATCH \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_transactions/<ledger_transaction_id> \
-H 'Content-Type: application/json' \
-d '{
"description": "Updating Guest X Payment to posted",
"status": "posted"
}'💡 A common pattern for any real-world money movement is to start the associated Ledger Transaction in
pendingand only update it topostedonce the bank transaction has posted or settled at the originating bank. This pattern is automatically handled when Ledger Transactions are linked to Modern Treasury Payments.
Finally, create a pending Ledger Transaction to record the payment to Owner Y. This zeros out Owner Y’s balance and records funds leaving the Rarebnb Cash account.
curl --request POST \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_transactions \
-H 'Content-Type: application/json' \
-d '{
"description": "Owner Y Payout",
"status": "pending",
"external_id": "<owner_payout_id>",
"ledger_entries": [
{
"amount": 19000,
"direction": "debit",
"ledger_account_id": "<owner_y_account_id>"
},
{
"amount": 19000,
"direction": "credit",
"ledger_account_id": "<cash_account_id>"
}
]
}'Once the transaction has posted to Rarebnb's Cash account, the Ledger Transaction can be updated to posted.
curl --request PATCH \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_transactions/<ledger_transaction_id> \
-H 'Content-Type: application/json' \
-d '{
"description": "Updating Owner Y Payout to posted",
"status": "posted"
}'Step 4. Read the Ledger Account Balances
Since activity is ledgered as it takes place, Rarebnb can query for various balance information at anytime. If, for example, Rarebnb needs to display Guest X’s pending due amount after booking in the app, Rarebnb can query the Guest X Ledger Account using the Get Ledger Account endpoint (GET /ledger_accounts/<guest_x_account_id>).
curl --request GET \
-u ORGANIZATION_ID:API_KEY \
--url https://app.moderntreasury.com/api/ledger_accounts/<guest_x_account_id>This would return the current balance for Guest X.
{
"id":"<guest_x_account_id>",
"object":"ledger_account",
"name":"Employer X Receivable",
"description": "Tracks fees to be collected from Guest X for a stay",
"ledger_id": "<ledger_id>",
"normal_balance":"debit",
"live_mode":true,
"lock_version":1,
"balances":{
"pending_balance":{
"credits":0
"debits":20000
"amount":20000
"currency":"USD",
"currency_exponent":2
},
"posted_balance":{
"credits":0
"debits":0
"amount":0
"currency":"USD",
"currency_exponent":2
},
"available_balance":{
"credits":0
"debits":0
"amount":0
"currency":"USD",
"currency_exponent":2
}
},
"ledgerable_type": null,
"ledgerable_id": null,
"external_id": null,
"metadata":{},
"created_at": "2025-03-18T12:45:39Z",
"updated_at": "2025-03-18T12:45:39Z"
}Notice that only the pending balance reflects the pending Ledger Transaction that was created for Guest X’s Booking. See Ledger Balances for additional details on the types of balances.
Similarly, if the amount owed to Owner Y needs to be displayed after Guest X completes their stay, the Owner Y Ledger Account could be queried, producing the following result.
{
"id":"<owner_y_account_id>",
"object":"ledger_account",
"name":"Owner Y Payable",
"description": "Tracks amount owed to Owner Y for hosting a guest",
"ledger_id": "<ledger_id>",
"normal_balance":"credit",
"live_mode":true,
"lock_version":1,
"balances":{
"pending_balance":{
"credits":19000
"debits":0
"amount":19000
"currency":"USD",
"currency_exponent":2
},
"posted_balance":{
"credits":19000
"debits":0
"amount":19000
"currency":"USD",
"currency_exponent":2
},
"available_balance":{
"credits":19000
"debits":0
"amount":19000
"currency":"USD",
"currency_exponent":2
}
},
"ledgerable_type": null,
"ledgerable_id": null,
"external_id": null,
"metadata":{},
"created_at": "2025-03-18T12:45:39Z",
"updated_at": "2025-03-18T12:45:39Z"
}Notice that all three balances reflect the amount owed since the Guest X Booking Ledger Transaction has been updated to posted at this point.
Next Steps: Advanced Topics
Up to this point, we’ve covered the basics of creating Ledger Accounts and Ledger Transactions to record user actions in the Ledger, but we’re just scratching the surface of what it takes to build a Production-ready application ledger.
Below are some more Advanced Topics you should explore as you build out your ledger design.
Attaching Metadata
Modern Treasury Ledgers supports attaching free-form metadata to most objects, in the form of key-value pairs. Here are some metadata ideas that a marketplace company like Rarebnb could use:
| Object | Metadata |
|---|---|
| Ledger | productID |
| Ledger Account | accountType; guestName; guestId; ownerName; ownerId |
| Ledger Transaction | transactionType; guestId; ownerId; bookingId |
Modern Treasury Ledgers allows you to query based on metadata. For example, the List Ledger Transactions endpoint can be used with both ledger_account_id and metadata parameters to find all Ledger Transactions for a specific Ledger Account that match certain metadata criteria.
Defining Ledger Account Categories
Oftentimes, we need to retrieve a real-time balance that is the sum of all Ledger Accounts of a particular type.
For example, Rarebnb will often want to review the sum of all Owner Payables to understand their outstanding liabilities.
Ledger Account Categories are the solution for this. They enable Rarebnb to easily “roll up” balances that comprise many Ledger Accounts, providing real-time access to aggregate balances to meet UI or reporting needs. Below are some LACs that may be useful for a marketplace.
| Ledger Account Category | Normality | Description | Contains |
|---|---|---|---|
| Total Receivables | debit | Sums balances across all Guest Receivable Ledger Accounts. Represents the total amount Rarebnb must collect. | Guest X Receivable balance; Guest Y Receivable balance; Guest Z Receivable balance; etc. |
| Total Payables | credit | Sums balances across all Owner Payable Ledger Accounts. Represents the total amount Rarebnb must pay out. | Owner A Payable balance; Owner B Payable balance; Owner C Payable balance; etc. |
Updated 12 days ago