D Post Ledger Entries#

Posting a Ledger Entry to your Ledger is a two-step process:

  1. Define the structure of the Ledger Entry in your Schema.
  2. Post the Ledger Entry using the API.

a. Schema

#

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.

Ledger Entry definition
{
  "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 -):

Ledger Entry definition
{
  "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.

b. Post to the API

#

Call the addLedgerEntry mutation to post a Ledger Entry:

addLedgerEntry mutation
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.

addLedgerEntry variables
{
  "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"
    }
  }
}
Minor Units#

All numbers in FRAGMENT are integers representing the smallest unit, encoded as strings. For example, USD $2.50 is provided as "250".

Tags#

You can specify tags on a Ledger Entry to store arbitrary key-value pairs. See Store metadata.

Idempotency#

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.

Timestamps#

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.

c. Linking Ledger Accounts

#

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.

Linked Ledger Account Entry
{
  "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:

reconcileTx query
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.

reconcileTx variables
{
  "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.

d. Currencies

#

If a Ledger Account is in currencyMode: multi, you must specify the currency of the Ledger Lines posted to it.

Ledger Entry definition
{
  "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.

e. Using conditions

#

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:

Ledger Entry Template with conditions
{
  "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.

f. Using Groups

#

Ledger Entry Groups provide a way to tie together related Ledger Entries. You can configure them on a Ledger Entry Type in the Schema.

Ledger Entry Type with Groups
{
  "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.

g. Runtime 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.

Runtime-defined Ledger Entry

{
  "type": "runtime_entry",
  "description": "Runtime-defined ledger entry"
}

Then, set the lines field when posting the Ledger Entry using addLedgerEntry or reconcileTx:

AddRuntimeLedgerEntry query
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
    }
  }
}
 
AddRuntimeLedgerEntry variables
{
  "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"
        }

    ]
  }
}