# Sync transactions into accounting platforms with Bank Feeds

Bank feeds push transaction data from a fintech, corporate card, or payout platform straight into the customer's accounting system so reconciliation happens automatically against invoices and bills. Apideck exposes this through the [Accounting API](/apis/accounting/reference) with two unified resources, `bank-feed-accounts` and `bank-feed-statements`, covering Xero, QuickBooks Online, Sage Intacct, Sage Business Cloud Accounting, FreshBooks, Campfire, and Odoo. A working sample app, with institution discovery, simulated bank login, account selection, and a transactions dashboard, lives at [github.com/apideck-samples/bank-feeds-sync](https://github.com/apideck-samples/bank-feeds-sync). The sample currently exercises four of the seven supported connectors; the unified resources work the same way against the rest.

![The bank-feeds-sync sample app at bank-feeds.vercel.app](/guides/bank-feeds-sample.png)

## Why a Unified API

Each accounting platform exposes its own bank feeds surface with a different auth model, payload shape, and certification process. Building one feed is achievable. Building seven, plus keeping them current as the underlying APIs evolve, is a quarter of engineering work that does not differentiate your product.

- One request shape for creating feed accounts and posting statements across every supported connector
- Vault handles the OAuth flow and token refresh per consumer, so you never store accounting credentials
- Connector-specific quirks like balance validation and currency rules are normalized where the unified model can hide them
- New accounting connectors light up without code changes on your side

## Resource mapping

| Connector | Downstream object |
| --- | --- |
| Xero | Bank Feed Connection plus Statement (Bank Feeds API) |
| QuickBooks Online | Bank Feed Account plus Transaction Batch |
| Sage Intacct | Bank Account plus Imported Bank Transactions |
| Sage Business Cloud Accounting | Bank Feed Connection plus Statement |
| FreshBooks | Bank Connection plus Imported Transactions |
| Campfire | Bank Account plus Bank Transaction batch |
| Odoo | `account.journal` (bank) plus `account.bank.statement` |

## Walkthrough

The integration has three calls. Register the source account once per consumer and connector, then post statements as transactions clear on your side. Read back posted statements when you need to confirm what the accounting system has accepted.

### 1. Register a bank feed account

A feed account represents the source of funds on your platform, for example a corporate card program or a virtual account. Create it once per consumer and reuse the returned ID for every statement you post against that source.

```http
POST https://unify.apideck.com/accounting/bank-feed-accounts
Authorization: Bearer <APIDECK_API_KEY>
x-apideck-app-id: <APIDECK_APP_ID>
x-apideck-consumer-id: cons_01H8X9Y2A3B4C5D6E7F8G9H0J1
x-apideck-service-id: xero
Content-Type: application/json
```

```json
{
  "name": "Acme Corporate Card - USD",
  "currency": "USD",
  "account_number": "4242424242424242",
  "account_type": "credit_card",
  "balance": 12450.75,
  "balance_date": "2025-04-01",
  "status": "active"
}
```

Send this to [`POST /accounting/bank-feed-accounts`](/apis/accounting/reference#operation/bankFeedAccountsAdd). The response returns an `id` like `bfa_01H8X9Y2A3B4C5D6E7F8G9H0J1` that you store against the consumer. Most connectors then require the end user to link that feed account to a ledger account inside their accounting platform's UI before statements can be posted. The sample app surfaces this step explicitly so users know where to click.

### 2. Post a statement of cleared transactions

Once the feed account is linked downstream, post statements as transactions settle. A statement is a batch of transactions for a given period, with an opening and closing balance the accounting system uses to validate the math.

```json
{
  "bank_feed_account_id": "bfa_01H8X9Y2A3B4C5D6E7F8G9H0J1",
  "currency": "USD",
  "start_date": "2025-04-01",
  "end_date": "2025-04-07",
  "opening_balance": 12450.75,
  "closing_balance": 11892.40,
  "transactions": [
    {
      "transaction_id": "txn_01H9A2B3C4D5E6F7G8H9J0K1L2",
      "posted_date": "2025-04-02",
      "description": "AWS - April invoice",
      "amount": -312.18,
      "counterparty_name": "Amazon Web Services",
      "transaction_type": "debit"
    },
    {
      "transaction_id": "txn_01H9A2B3C4D5E6F7G8H9J0K1M3",
      "posted_date": "2025-04-04",
      "description": "Refund - Linear annual",
      "amount": 96.00,
      "counterparty_name": "Linear",
      "transaction_type": "credit"
    },
    {
      "transaction_id": "txn_01H9A2B3C4D5E6F7G8H9J0K1N4",
      "posted_date": "2025-04-06",
      "description": "Lunch - client meeting",
      "amount": -42.17,
      "counterparty_name": "Tartine Bakery",
      "transaction_type": "debit"
    }
  ]
}
```

Send this to [`POST /accounting/bank-feed-statements`](/apis/accounting/reference#operation/bankFeedStatementsAdd). Each `transaction_id` should be stable and unique on your side. Most downstream platforms reject duplicates, which makes that ID the safest deduplication key if you ever need to retry.

### 3. Read back posted statements

Use [`GET /accounting/bank-feed-statements`](/apis/accounting/reference#operation/bankFeedStatementsAll) filtered by `bank_feed_account_id` to confirm what the accounting system has accepted, paginate prior periods for backfill UIs, or build the kind of transactions dashboard the sample app ships with. Pair it with the standard `limit` and `cursor` query parameters for pagination.

```bash
curl -G https://unify.apideck.com/accounting/bank-feed-statements \
  -H "Authorization: Bearer <APIDECK_API_KEY>" \
  -H "x-apideck-app-id: <APIDECK_APP_ID>" \
  -H "x-apideck-consumer-id: cons_01H8X9Y2A3B4C5D6E7F8G9H0J1" \
  -H "x-apideck-service-id: xero" \
  --data-urlencode "filter[bank_feed_account_id]=bfa_01H8X9Y2A3B4C5D6E7F8G9H0J1" \
  --data-urlencode "limit=50"
```

## Connector-specific behavior

The table below covers every connector Apideck supports for Bank Feeds. The sample app at [bank-feeds.vercel.app](https://bank-feeds.vercel.app) currently demonstrates four of them (Xero, QuickBooks Online, Sage Business Cloud Accounting, FreshBooks); the unified resources behave the same way against the others.

| Connector | Notes |
| --- | --- |
| Xero | Feed account must be linked to a Xero bank account by the user before statements are accepted. Opening and closing balances must reconcile against the previous statement's closing balance. |
| QuickBooks Online | Requires a QuickBooks Online tier with bank feeds enabled on the customer's side. Currency on the feed account must match the connected QuickBooks account currency. |
| Sage Intacct | Standard mapping. No known quirks beyond the unified model. |
| Sage Business Cloud Accounting | Standard mapping. No known quirks beyond the unified model. |
| FreshBooks | Standard mapping. No known quirks beyond the unified model. |
| Campfire | Standard mapping. No known quirks beyond the unified model. |
| Odoo | Standard mapping. No known quirks beyond the unified model. |

### Xero

Xero validates the closing balance of each statement against the opening balance of the next one. If your platform produces gaps or restates a prior period, post a correcting statement rather than editing the original. The sample app demonstrates this by showing the running balance reported back from `bankFeedStatementsAll` next to your own ledger.

### QuickBooks Online

QuickBooks rejects statements posted to a feed account whose currency does not match the linked QuickBooks bank account. Check the currency on both sides before the first post, and create a separate `bank-feed-account` per currency you support.

## Next steps

- Connector-specific deep dives: [Xero Bank Feeds](/guides/bank-feeds-xero) and [NetSuite Bank Feeds](/guides/bank-feeds-netsuite)
- Reference: [Bank Feed Accounts](/apis/accounting/reference#operation/bankFeedAccountsAdd) and [Bank Feed Statements](/apis/accounting/reference#operation/bankFeedStatementsAdd)
- Worked example: [apideck-samples/bank-feeds-sync](https://github.com/apideck-samples/bank-feeds-sync), a Next.js app covering institution discovery, account selection, and live feed posting against four of the supported connectors
- Related accounting use cases: [Expense Management Integration](/guides/expense-management-integration) and [Mark Invoices as Paid](/guides/mark-invoices-as-paid)
