Skip to content
Bkper Bkper

Exchange Bot

by Bkper

The Exchange Bot automatically mirrors transactions across books in different currencies, converting amounts using exchange rates for the transaction date. It also calculates unrealized FX gains and losses, giving you a consolidated multi-currency view without manual replication.

Each currency lives in its own book within the same collection, with exc_code set on each book. When you post a transaction in one book, the bot records the equivalent transaction in every other currency book — unless your collection uses base books (see exc_base below). When base books exist, transactions are always mirrored to base books, while other books only receive transactions whose accounts match that book's currency via group name or group exc_code.

How it works

The Exchange Bot listens for transaction events across all books in a collection. When a transaction is posted, it fetches the exchange rate for that date and records a converted copy in every other currency book.

You post in the USD book:

15/03  1,000.00  Product  >>  Citi Bank  Invoice #1042

The bot records in the EUR book (at a rate of 0.92):

15/03    920.00  Product  >>  Citi Bank  Invoice #1042

The full amount is mirrored - only the currency changes.

sequenceDiagram participant Source as USD Book participant Bot as Exchange Bot participant Rates as Exchange Rates participant Target as EUR Book Source->>Bot: Transaction posted Bot->>Rates: Fetch rate for date Rates-->>Bot: USD/EUR 0.92 Bot->>Target: Record converted transaction

Mirroring a transaction

You sell a product for 1,000 USD. The customer pays into your US bank account. You have two books in a collection - one for USD and one for EUR.

flowchart LR subgraph USD["USD Book"] P1["Product"]:::incoming -- "1,000" --> B1["Citi Bank"]:::asset end USD -. "rate 0.92" .-> EUR subgraph EUR["EUR Book"] P2["Product"]:::incoming -- "920" --> B2["Citi Bank"]:::asset end
# Book Amount From To Description
You USD 1,000 Product >> Citi Bank Invoice #1042
Bot EUR 920 Product >> Citi Bank Invoice #1042

Result: Product 1,000 in USD / 920 in EUR, Citi Bank +1,000 in USD / +920 in EUR

Book properties on each book:

YAML
exc_code: USD
YAML
exc_code: EUR

The chart of accounts is replicated across all books in the collection, using the same account and group names in each book.

What stays in sync
  • checked, updated, deleted, and restored transactions stay synchronized across books
  • account and group creates, updates, and deletions are propagated across books
  • selected book settings and shared Exchange Bot properties are copied across connected books
  • missing accounts are automatically created when mirroring a transaction if they do not yet exist in the target book

International wire transfer

When transferring between currencies, the actual rate often differs from the market rate due to spreads and fees. Use transaction properties to specify the exact converted amount.

You post in the EUR book:

20/03  5,000.00  Bank of Europe  >>  Citi Bank  Wire transfer
exc_amount: 5,408.75
exc_code: USD

The bot records in the USD book (using your specified amount instead of the market rate):

20/03  5,408.75  Bank of Europe  >>  Citi Bank  Wire transfer
flowchart LR subgraph EUR["EUR Book"] BE1["Bank of Europe"]:::asset -- "5,000" --> CB1["Citi Bank"]:::asset end EUR -. "exc_amount: 5,408.75" .-> USD subgraph USD["USD Book"] BE2["Bank of Europe"]:::asset -- "5,408.75" --> CB2["Citi Bank"]:::asset end
# Book Amount From To Description
You EUR 5,000 Bank of Europe >> Citi Bank Wire transfer
Bot USD 5,408.75 Bank of Europe >> Citi Bank Wire transfer

Result: Bank of Europe -5,000 EUR / -5,408.75 USD, Citi Bank +5,000 EUR / +5,408.75 USD

Transaction properties on the posted transaction:

YAML
exc_amount: 5,408.75
exc_code: USD

FX gains and losses

Over time, exchange rate fluctuations change the value of balances held in foreign currencies. The Exchange Bot calculates these unrealized gains and losses on demand.

Open any book in the collection and select More > Exchange Bot. Choose a date and click Gain/Loss. The bot adjusts each account's balance using the selected rates and records the difference in automatically created exchange accounts (suffixed with EXC).

In the Gain/Loss view, the bot loads exchange rates for the selected date and displays them as editable fields, so you can adjust them before running the update. If one or more books in the collection are marked with exc_base, clicking Gain/Loss updates all base books. If no base books are configured, the update applies across all connected exchange books.

By default, the bot creates a separate exchange account per account (suffixed with EXC). When a book has exc_aggregate: true, it uses a single Exchange_XXX account per foreign currency instead.

The action is available only when the user has the required permissions and there are no pending bot tasks or bot errors in the connected books.

Example: Your EUR book holds a Citi Bank balance of 920. The original rate was 0.92 but the current rate is 0.94 - a gain of 20.

flowchart LR EXC["Citi Bank EXC"]:::liability -- "20" --> CB["Citi Bank"]:::asset
# Amount From To Description
Bot 20 Citi Bank EXC >> Citi Bank #exchange_gain

Result: Citi Bank 940, Citi Bank EXC -20

Configuration

Book properties

Set these on each book in the collection.

Property Required Description
exc_code Yes The book's currency code (e.g. USD, EUR, JPY). Also accepts the legacy key exchange_code
exc_rates_url No Custom exchange rates endpoint URL. Default: Open Exchange Rates. Also accepts the legacy key exchange_rates_url
exc_rates_cache No Cache duration in seconds for exchange rate responses. Default: 3600 when using Open Exchange Rates, minimum 300
exc_on_check No Set to true to delay normal mirroring of an unchecked transaction until it is checked. Later transaction events, such as updates, may still synchronize related changes. Default: false
exc_base No Marks this book as a base book. When at least one base book exists in the collection, transactions are always mirrored to base books, while other books only receive transactions whose accounts match that book's currency via group name or group exc_code
exc_historical No Set to true to consider balances since the beginning of the book. Default: uses balances after the closing date
exc_aggregate No Set to true to use a single Exchange_XXX account per currency instead of per-account EXC accounts

Example:

YAML
exc_code: USD
Group properties

Group properties control which accounts participate in multi-currency mirroring. The bot matches accounts by group name against exc_code from associated books, or by the exc_code property set on the group.

Property Description
exc_code The currency code of the accounts in this group
exc_account Optional - name of the exchange account to use for gain/loss
stock_exc_code Set to any value to indicate stock accounts. Changes the gain/loss account name from {Account} EXC to {Account} Unrealized EXC
Account properties
Property Description
exc_account Optional - name of the exchange account to use for gain/loss

By default, an account with suffix EXC is created for each account (e.g. Citi Bank EXC). Set exc_account on an account or its group to override the default.

Example:

YAML
exc_account: Assets_Exchange

The first exc_account found is used. Avoid setting it on multiple groups for the same account.

Transaction properties

Transaction properties can override how the bot determines the converted amount for a specific transaction.

Property Description
exc_code Identifies which target currency the exc_amount or exc_rate override applies to
exc_amount The exact amount to use in the target currency instead of converting by market rate. Only applies when exc_code matches the target book or the transaction accounts match that book's currency
exc_rate The exact exchange rate to use instead of fetching one. Also honored when mirroring to a base book, even if exc_code is omitted or does not match
exc_date Overrides the date used to look up the exchange rate. Must match the book's date format. Future dates are silently clamped to today

Use these properties when the actual conversion should differ from the default rate lookup - for example, in wire transfers, negotiated conversions, or settlements using a different effective date.

The bot also records these properties on mirrored transactions for traceability:

Property Description
exc_code The reference currency used by the mirrored transaction
exc_amount The original amount from the source transaction
exc_rate The exchange rate used for conversion

Example - use a different date for the exchange rate lookup:

YAML
exc_date: 15/03/2026

Example - wire transfer with a known converted amount:

YAML
exc_code: UYU
exc_amount: 1256.43

Example - wire transfer with a known exchange rate:

YAML
exc_code: USD
exc_rate: 1.08175

Implicit amount override via description

As a convenience, the bot also scans the transaction description for a token starting with the target currency code. If you include the converted amount directly in the description, the bot uses it automatically.

20/03  5,000.00  Bank of Europe  >>  Citi Bank  Wire to USD5408.75

When mirroring to the USD book, the bot uses 5408.75 instead of the market rate. The token is replaced in the mirrored description with the source amount and currency (e.g. EUR5000).

Custom exchange rates endpoint

By default, the bot uses Open Exchange Rates. You can use any provider - Fixer, a custom service, or your own endpoint.

Set the exc_rates_url book property:

YAML
exc_rates_url: https://data.fixer.io/api/${date}?access_key=*****

Supported expressions:

Expression Description
${date} Transaction date in ISO format yyyy-mm-dd
${agent} Request source: app for menu-triggered, bot for transaction-triggered

The endpoint must return JSON in this format:

JSON
{
  "base": "EUR",
  "date": "2020-05-29",
  "rates": {
    "CAD": 1.565,
    "CHF": 1.1798,
    "GBP": 0.87295,
    "SEK": 10.2983,
    "USD": 1.2234
  }
}
Events handled

The bot responds to the following Bkper events:

Event Behavior
TRANSACTION_POSTED Mirrors the transaction to connected currency books (filtered by base-book rules when exc_base is used). Skipped when exc_on_check: true and the transaction is not checked
TRANSACTION_CHECKED Mirrors or updates the transaction on connected books. When the amount has changed due to rate differences, the connected transaction is updated and re-checked
TRANSACTION_UPDATED Updates the mirrored transaction on connected books (amount, accounts, description, properties). Deletes the mirror if the converted amount is zero
TRANSACTION_DELETED Deletes the mirrored transaction on connected books
TRANSACTION_RESTORED Restores the mirrored transaction on connected books
ACCOUNT_CREATED Creates the account on all connected books with the same name, type, groups, and properties
ACCOUNT_UPDATED Updates the account on all connected books
ACCOUNT_DELETED Deletes the account on all connected books
GROUP_CREATED Creates the group on all connected books
GROUP_UPDATED Updates the group on all connected books
GROUP_DELETED Deletes the group on all connected books
BOOK_UPDATED Syncs book settings across connected books: page size, period, lock date, closing date, period start month, exc_rates_url, exc_rates_cache, exc_on_check, exc_aggregate

The bot skips its own transactions to prevent loops (except for deletions, which are always propagated).

Learn more