Normalize account and transaction data from Plaid, MX, and Finicity into a single, consistent schema you can query in one place — no more per-provider field-name spaghetti in your business logic.
Drop this module into any Xano workspace. It ships two tables and a small public function surface; you feed it raw provider payloads (from your existing Plaid/MX/Finicity calls) and it stores canonical, de-duplicated rows.
Tables
| Table | Purpose |
|---|---|
oba_account |
One canonical row per account, keyed by provider:external_account_id (unique). Normalized type (depository/credit/loan/investment/other), balances, currency. |
oba_transaction |
One canonical row per transaction, keyed by provider:external_transaction_id (unique). Signed provider amounts are split into a positive amount + direction (debit/credit). |
Public function surface (call from any XanoScript via function.run)
| Function | What it does |
|---|---|
oba_normalize_account |
Pure transform: raw provider account → canonical account object. |
oba_normalize_transaction |
Pure transform: raw provider transaction → canonical transaction object. |
oba_ingest_accounts |
Normalize + idempotently upsert a batch of accounts. |
oba_ingest_transactions |
Normalize + idempotently upsert a batch of transactions, linking each to its account. |
oba_list_transactions |
Unified, filtered, paginated query across all providers. |
HTTP endpoints (API group open-banking-aggregator)
| Method | Path | Wraps |
|---|---|---|
POST |
/accounts/ingest |
oba_ingest_accounts |
GET |
/accounts |
list oba_account |
POST |
/transactions/ingest |
oba_ingest_transactions |
GET |
/transactions |
oba_list_transactions |
With the Xano MCP enabled, paste:
Install the module at https://github.com/xano-community/open-banking-aggregator into my Xano workspace.
git clone https://github.com/xano-community/open-banking-aggregator.git
cd open-banking-aggregator
xano workspace push backend -w <your-workspace-id>Normalization is provider-aware. You pass the raw payload exactly as the provider returns it plus a provider discriminator:
// After you fetch accounts from Plaid (or MX / Finicity):
function.run "oba_ingest_accounts" {
input = {
provider: "plaid",
accounts: $plaid_accounts_get.accounts, // the raw array from /accounts/get
item_id: $plaid_item_id,
owner_ref: $auth.id
}
} as $ingest
// Later, query everything in one normalized shape:
function.run "oba_list_transactions" {
input = { direction: "debit", from: $start, to: $end }
} as $spending| Canonical | Plaid | MX | Finicity |
|---|---|---|---|
external_account_id |
account_id |
guid |
id |
current_balance |
balances.current |
balance |
balance |
available_balance |
balances.available |
available_balance |
— |
transaction amount/direction |
sign of amount (+ = debit) |
type (DEBIT/CREDIT) |
sign of amount (− = debit) |
transaction posted_at |
date |
date |
postedDate (epoch s) |
This module is self-contained — it only needs the raw provider JSON. If you also installed the Xano community integrations, they're natural feeders:
integration-plaid-banking→plaid_get_accounts→oba_ingest_accountsintegration-mx-banking→mx_list_member_accounts/mx_list_transactionsintegration-finicity-open-banking→finicity_get_customer_accounts/finicity_get_transactions
If your normalized data already lives in an external SQL store, you can skip the HTTP ingest and feed rows from a direct query, then pass them to the normalize/ingest functions:
db.external.postgres.direct_query {
connection_string = $env.WAREHOUSE_PG_URL
sql = "SELECT raw_json FROM plaid_accounts WHERE synced = false"
response_type = "list"
} as $rowsMIT — see LICENSE.