Exchange Bot
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.
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.
| # | Book | Amount | From | To | Description | |
|---|---|---|---|---|---|---|
| You | USD | 1,000 | Product Incoming | >> | Citi Bank Asset | Invoice #1042 |
| Bot | EUR | 920 | Product Incoming | >> | Citi Bank Asset | 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:
exc_code: USD
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
| # | Book | Amount | From | To | Description | |
|---|---|---|---|---|---|---|
| You | EUR | 5,000 | Bank of Europe Asset | >> | Citi Bank Asset | Wire transfer |
| Bot | USD | 5,408.75 | Bank of Europe Asset | >> | Citi Bank Asset | 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:
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.
| # | Amount | From | To | Description | |
|---|---|---|---|---|---|
| Bot | 20 | Citi Bank EXC Liability | >> | Citi Bank Asset | #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:
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:
exc_account: Assets_Exchange
The first
exc_accountfound 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:
exc_date: 15/03/2026
Example - wire transfer with a known converted amount:
exc_code: UYU
exc_amount: 1256.43
Example - wire transfer with a known exchange rate:
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:
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:
{
"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
- Multiple currencies - conceptual guide on multi-currency accounting in Bkper
- Structuring Books & Collections - how bots connect books for consolidated reporting