Posting a Ledger Entry to your Ledger is a two-step process:
Ledger Entries are defined in your Schema under the ledgerEntries.types key.
{
"key": "schema-key",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account",
"description": "Fund {{user_id}} for {{funding_amount}}",
"lines": [
{
"account": {
"path": "assets/banks/user-cash"
},
"key": "funds_arrive_in_bank",
"amount": "{{funding_amount}}"
},
{
"account": {
"path": "liabilities/users:{{user_id}}/available"
},
"key": "increase_user_balance",
"amount": "{{funding_amount}}"
}
]
}
]
}
}The amounts of a Ledger Line can be parameterized using {{handlebar}} syntax and can contain basic arithmetic (+ or -):
{
"key": "schema-key",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account_with_fee",
"description": "Fund {{user_id}} for {{funding_amount}} with {{fee_amount}} fee",
"lines": [
{
"account": {
"path": "assets/banks/user-cash"
},
"key": "funds_arrive_in_bank",
"amount": "{{funding_amount}}"
},
{
"account": {
"path": "liabilities/users:{{user_id}}/available"
},
"key": "increase_user_balance",
"amount": "{{funding_amount}} - {{fee_amount}}"
},
{
"account": { "path": "income/funding-fees" },
"key": "take_fee",
"amount": "{{fee_amount}}"
}
]
}
]
}
}Ledger Entries must be balanced by the Accounting Equation. If they are not, the Ledger designer throws an error.
Call the addLedgerEntry mutation to post a Ledger Entry:
mutation AddLedgerEntry(
$ik: SafeString!
$entry: LedgerEntryInput!
) {
addLedgerEntry(
ik: $ik,
entry: $entry
) {
__typename
... on AddLedgerEntryResult {
entry {
type
created
posted
}
lines {
amount
key
description
account {
path
}
}
}
... on Error {
code
message
}
}
}Set the Ledger Entry's type, typeVersion (optional), and the required parameters. If not provided typeVersion will default to 1.
{
"ik": "add-ledger-entry",
"entry": {
"ledger": {
"ik": "quickstart-ledger"
},
"type": "user_funds_account",
"typeVersion": 1,
"posted": "1234-01-01T01:01:01",
"parameters": {
"user_id": "testing-user",
"funding_amount": "200"
}
}
}All numbers in FRAGMENT are integers representing the smallest unit, encoded as strings. For example, USD $2.50 is provided as "250".
To ensure a Ledger Entry is only posted once, provide an Idempotency Key ik to the addLedgerEntry mutation. This identifies the Ledger Entry and lets you safely retry the API call. See Integrate the API.
Ledger Entries have two timestamps:
posted, the time the money movement event happened. You provide this to the addLedgerEntry API call.created, the time at which the Ledger Entry was posted to the API. FRAGMENT auto-generates this value.You can post entries with a posted timestamp in the past or future. Posting a Ledger Entry updates all balances from the posted time into the future.
By default lines are aggregated and filtered before posting:
This is controlled by the postLinesAs field on a Ledger Entry type. The available values are:
net_amounts -- Lines targeting the same account, currency, and tx are aggregated into a single line with the summed amount. Lines that sum to zero are skipped unless all lines sum to zero.skip_zero_lines -- Lines are posted as-is without aggregation, but lines with a zero amount are skipped unless all lines are zero.raw_lines -- Lines are posted as-is without aggregation or filtering.Ledger Accounts can be linked to external financial systems to reconcile your Ledger with payments.
To define a Ledger Entry to a Linked Ledger Account, you must specify the tx of the Ledger Line. This is the ID of the transaction at the external system.
{
"key": "linked-ledger-account-schema",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "reconciliation-type",
"lines": [
{
"key": "reconciliation-line-key",
"account": { "path": "bank" },
"amount": "{{some_amount}}",
"tx": {
"externalId": "{{bank_transaction_id}}"
}
},
{...other line}
]
}
]
}
}To post Ledger Entries to a Linked Ledger Account, call reconcileTx instead of addLedgerEntry:
mutation ReconcileTx(
$entry: LedgerEntryInput!
) {
reconcileTx(entry: $entry) {
__typename
... on ReconcileTxResult {
entry {
ik
posted
created
}
isIkReplay
lines {
amount
currency {
code
}
}
}
... on Error {
code
message
}
}
}The query variables are the same as addLedgerEntry, except posted and ik are omitted. They are inferred from the external Tx.
{
"entry": {
"type": "reconciliation-type",
"ledger": {
"ik": "quickstart-ledger"
},
"parameters": {
"bank_transaction_id": "tx_1234"
}
}
}Read more about creating and using linked Ledger Accounts in Reconcile payments.
If a Ledger Account is in currencyMode: multi, you must specify the currency of the Ledger Lines posted to it.
{
"key": "schema-key",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account",
"description": "Funding {{user_id}} for {{funding_amount}}",
"lines": [
{
"account": { "path": "assets/banks/user-cash" },
"key": "funds_arrive_in_bank",
"amount": "{{funding_amount}}",
"currency": {
"code": "USD"
}
},
{
"account": { "path": "liabilities/users:{{user_id}}/available" },
"key": "increase_user_balance",
"amount": "{{funding_amount}}",
"currency": {
"code": "USD"
}
}
]
}
]
}
}Read more about how to implement a product that handles multiple currencies in Handle currencies.
Repeated lines allow you to post a variable number of Ledger Lines. For example, a batch payment that funds N wallets in a single entry, where N is determined at runtime.
To specify the parameter used as the source array, set repeated.key:
{
"type": "fund_wallets",
"description": "Fund wallets from bank",
"lines": [
{
"key": "record_incoming_top_ups",
"account": { "path": "assets/receivables/top_up:{{top_up_id}}" },
"amount": "{{amount}}",
"repeated": { "key": "top_ups" }
},
{
"key": "top_up_wallet",
"account": { "path": "liabilities/wallet:{{wallet_id}}" },
"amount": "{{amount}}",
"repeated": { "key": "top_ups" }
}
]
}In this example, the record_incoming_top_ups and top_up_wallet lines will be posted once for each element in the top_ups array:
{
"ik": "batch-fund-wallets",
"entry": {
"ledger": { "ik": "my-ledger" },
"type": "fund_wallets",
"parameters": {
"top_ups": [
{ "wallet_id": "user-a", "top_up_id": "t-1", "amount": "100" },
{ "wallet_id": "user-b", "top_up_id": "t-2", "amount": "200" }
]
}
}
}This produces four Ledger Lines: two repeated lines for each of the two items.
When repeated lines expand, multiple lines may target the same Ledger Account. For example, one line posts to a unique template account per item while the other posts to a shared account:
{
"type": "batch_payout",
"description": "Batch payout",
"postLinesAs": "net_amounts",
"lines": [
{
"key": "decrease_pool",
"account": { "path": "assets/pool" },
"amount": "-{{amount}}",
"repeated": { "key": "payouts" }
},
{
"key": "increase_recipient_balance",
"account": { "path": "liabilities/recipients:{{recipient_id}}" },
"amount": "{{amount}}",
"repeated": { "key": "payouts" }
}
]
}With postLinesAs: raw_lines or skip_zero_lines, this would produce four lines. With net_amounts, lines targeting the same account are combined, so only three lines are posted.
Restrictions:
Ledger Entry conditions are rules defined in your Schema used to manage concurrency and enforce correctness within your Ledger. If a condition is not met, the Ledger Entry is not posted and the mutation throws a BadRequestError.
Conditions are defined in the Schema:
{
"type": "user_withdraws_funds",
"description": "{{user_id}} withdraws for {{withdraw_amount}}",
"lines": [
{
"account": { "path": "assets/banks/user-cash" },
"key": "funds_leave_bank",
"amount": "-{{withdraw_amount}}"
},
{
"account": { "path": "liabilities/users:{{user_id}}/available" },
"key": "decrease_user_balance",
"amount": "-{{withdraw_amount}}"
}
],
"conditions": [
{
"account": {
"path": "liabilities/users:{{user_id}}/available"
},
"postcondition": {
"ownBalance": {
"gte": "0"
}
}
}
]
}Read more about using Ledger Entry Conditions in Configure consistency.
Ledger Entry Groups provide a way to tie together related Ledger Entries. You can configure them on a Ledger Entry Type in the Schema.
{
"type": "user_initiates_withdrawal",
"description": "{{user_id}} initiates withdrawal",
"lines": [
{
"account": {
"path": "liabilities/users:{{user_id}}/available"
},
"key": "decrease_user_balance",
"amount": "-{{withdraw_amount}}"
},
{...other line}
],
"groups": [
{
"key": "withdrawal",
"value": "{{withdrawal_id}}"
}
]
}Read more about using Ledger Entry Groups in Group Ledger Entries.
There are two ways to update a Ledger Entry after it has been posted:
updateLedgerEntry to modify tags and groupsTo update a Ledger Entry's tags and groups use the updateLedgerEntry mutation. This is useful for adding metadata or associating entries with groups after they've been posted.
mutation UpdateLedgerEntry(
$ledgerEntry: LedgerEntryMatchInput!
$update: UpdateLedgerEntryInput!
) {
updateLedgerEntry(
ledgerEntry: $ledgerEntry,
update: $update
) {
__typename
... on UpdateLedgerEntryResult {
entry {
type
ik
tags {
key
value
}
groups {
key
value
}
}
}
... on Error {
code
message
}
}
}Read more about updating tags and updating groups.
To correct mistakes or change other attributes of a Ledger Entry (like amounts, accounts, or timestamps), you can reverse the entry and post a new one.
mutation ReverseLedgerEntry(
$id: ID!
) {
reverseLedgerEntry(
id: $id
) {
__typename
... on ReverseLedgerEntryResult {
reversingLedgerEntry {
ik
id
type
posted
created
}
reversedLedgerEntry {
ik
id
type
posted
created
reversedAt
}
}
... on Error {
code
message
}
}
}Read more about reversing entries.
You can define a Ledger Entry whose Ledger Lines are defined at runtime.
This can be useful if you're building a product where your end user, not you, defines the Ledger Entry structure. This is common for FRAGMENT users offering accounting services to their users.
To support runtime-defined Ledger Entries, omit the lines field in the Schema.
{
"type": "runtime_entry",
"description": "Runtime-defined ledger entry"
}Then, set the lines field when posting the Ledger Entry using addLedgerEntry or reconcileTx:
mutation AddRuntimeLedgerEntry(
$ik: SafeString!
$entry: LedgerEntryInput!
) {
addLedgerEntry(
ik: $ik,
entry: $entry
) {
... on AddLedgerEntryResult {
entry {
type
created
posted
}
lines {
amount
key
description
account {
path
}
}
}
... on Error {
code
message
}
}
}{
"ik": "add-arbitrary-ledger-entry",
"entry": {
"type": "runtime_entry",
"lines": [
{
"account": { "path": "assets/banks/user-cash" },
"key": "funds_arrive_in_bank",
"amount": "100"
},
{
"account": { "path": "liabilities/users:test-user/available" },
"key": "increase_user_balance",
"amount": "100"
}
]
}
}