Posting a Ledger Entry to your Ledger is a two-step process:
Ledger Entries are defined in your Schema under the ledgerEntries.types
key. Every Ledger Entry has a type
that must be unique within the Schema.
{
"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
and the required parameters
.
{
"ik": "add-ledger-entry",
"entry": {
"ledger": {
"ik": "quickstart-ledger"
},
"type": "user_funds_account",
"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.
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.
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.
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"
}
]
}
}