# Business Lending with the Accounting API

By the end of this page you'll have a borrower's balance sheet, profit and loss, and receivables aging landing in your underwriting model straight out of their own ledger, whether that is QuickBooks, Xero, NetSuite, or Sage Intacct. One integration against the unified [Accounting API](/apis/accounting/reference), five read calls, no per-ERP ingestion pipeline.

_The borrower's ledger, whether QuickBooks, Xero or NetSuite, sends its balance sheet, profit and loss and receivables aging through the Apideck Accounting API into your underwriting product, which re-pulls on a schedule to monitor the facility._

[Vault](/guides/vault) carries the borrower's OAuth and token refresh for every supported ERP, the report endpoints return the same JSON shape whichever connector answers, each aging bucket comes back with its own `start_date`, `end_date`, and the transactions inside it so you never recompute aging from invoice dates, and `filter[updated_since]` keeps post-funding monitoring incremental rather than a full refresh.

## Resource mapping

| Underwriting input | Apideck resource | Endpoint |
| --- | --- | --- |
| Balance sheet | Balance Sheet | [`GET /accounting/balance-sheet`](/apis/accounting/reference#operation/balanceSheetOne) |
| Profit and loss | Profit and Loss | [`GET /accounting/profit-and-loss`](/apis/accounting/reference#operation/profitAndLossOne) |
| Aged receivables | Aged Debtors | [`GET /accounting/aged-debtors`](/apis/accounting/reference#operation/agedDebtorsOne) |
| Aged payables | Aged Creditors | [`GET /accounting/aged-creditors`](/apis/accounting/reference#operation/agedCreditorsOne) |
| Operating cash movement | Payments + Bill Payments | [`GET /accounting/payments`](/apis/accounting/reference#operation/paymentsAll), [`GET /accounting/bill-payments`](/apis/accounting/reference#operation/billPaymentsAll) |
| Revenue line detail | Invoices | [`GET /accounting/invoices`](/apis/accounting/reference#operation/invoicesAll) |
| Cost detail | Bills | [`GET /accounting/bills`](/apis/accounting/reference#operation/billsAll) |
| Trial balance fallback | Journal Entries + Ledger Accounts | [`GET /accounting/journal-entries`](/apis/accounting/reference#operation/journalEntriesAll) |

>
> There is no unified cash flow statement endpoint. For most lending models, the operating cash signal comes from `payments` and `bill-payments` against the company's bank ledger accounts, plus the period-over-period change in cash on the balance sheet. Pull the dedicated reports first, then fall back to transaction-level data when the connector does not expose them.

## Walkthrough

The flow below assumes the borrower has already connected their accounting system through Apideck Vault and that you have stored the resulting `consumer_id`. Replace the service ID in `x-apideck-service-id` with the connector the borrower selected. The dedicated report endpoints in this walkthrough are read-only across every connector that exposes them, which is the right shape for an underwriting pull.

Each step below shows the raw request first, then the same call through the [`@apideck/unify`](/sdks/node) SDK. The SDK takes camelCase and handles serialization, so pick whichever fits your stack. Every snippet is verified against `@apideck/unify` v0.46.0.

```ts
import { Apideck } from '@apideck/unify'

const apideck = new Apideck({
  apiKey: process.env.API_KEY!,
  appId: process.env.APP_ID!,
  consumerId: 'borrower_01H8X9Y2A3K4M5N6P7Q8R9S0TU'
})
```

One naming detail catches people coming from the CRUD resources: a report is a single document per connector, so the report namespaces expose `get()` and nothing else. There is no `apideck.accounting.balanceSheet.list()`, and the same is true of `profitAndLoss`, `agedDebtors`, and `agedCreditors`. Only the transaction resources such as `invoices` and `payments` have `list()`.

### 1. Pull the balance sheet

The balance sheet anchors leverage and liquidity ratios: current assets versus current liabilities, total debt versus equity, and the asset base available as collateral. Hit the dedicated endpoint rather than re-aggregating ledger accounts so the response reflects the source system's own classification of current versus non-current.

Send this to [`GET /accounting/balance-sheet`](/apis/accounting/reference#operation/balanceSheetOne).

```bash
curl -G https://unify.apideck.com/accounting/balance-sheet \
  -H "Authorization: Bearer ${APIDECK_API_KEY}" \
  -H "x-apideck-app-id: ${APIDECK_APP_ID}" \
  -H "x-apideck-consumer-id: borrower_01H8X9Y2A3K4M5N6P7Q8R9S0TU" \
  -H "x-apideck-service-id: quickbooks" \
  --data-urlencode "filter[start_date]=2024-01-01" \
  --data-urlencode "filter[end_date]=2024-12-31"
```

A trimmed response looks like this. Note the shape: `data.reports` is an array, because a single request can return a run of periods, and `assets`, `liabilities`, and `equity` are each one account node carrying a `value` and a recursive `items` list. There is no `current_assets` or `fixed_assets` key, and no `total`: the current-versus-non-current split arrives as the first level of `items`, named by the source system.

```json
{
  "data": {
    "reports": [
      {
        "id": "bs_2024_fy",
        "report_name": "BalanceSheet",
        "start_date": "2024-01-01",
        "end_date": "2024-12-31",
        "currency": "USD",
        "assets": {
          "account_id": "acct_01H8X9Y2A3ASSETS",
          "name": "Assets",
          "value": 1842500.42,
          "items": [
            {
              "account_id": "acct_01H8X9Y2A3CA",
              "code": "1000",
              "name": "Current Assets",
              "value": 612300.18,
              "items": [
                { "account_id": "acct_01H8X9Y2A3CASH", "code": "1010", "name": "Operating Checking", "value": 184200.55 },
                { "account_id": "acct_01H8X9Y2A3AR", "code": "1200", "name": "Accounts Receivable", "value": 348100.00 },
                { "account_id": "acct_01H8X9Y2A3INV", "code": "1300", "name": "Inventory", "value": 79999.63 }
              ]
            },
            {
              "account_id": "acct_01H8X9Y2A3FA",
              "code": "1500",
              "name": "Fixed Assets",
              "value": 1230200.24,
              "items": [
                { "account_id": "acct_01H8X9Y2A3PPE", "code": "1510", "name": "Equipment, net", "value": 1230200.24 }
              ]
            }
          ]
        },
        "liabilities": {
          "account_id": "acct_01H8X9Y2A3LIAB",
          "name": "Liabilities",
          "value": 985400.10,
          "items": [
            {
              "account_id": "acct_01H8X9Y2A3CL",
              "code": "2000",
              "name": "Current Liabilities",
              "value": 312800.10,
              "items": [
                { "account_id": "acct_01H8X9Y2A3AP", "code": "2010", "name": "Accounts Payable", "value": 198500.00 },
                { "account_id": "acct_01H8X9Y2A3CC", "code": "2020", "name": "Credit Card Payable", "value": 114300.10 }
              ]
            },
            {
              "account_id": "acct_01H8X9Y2A3LTL",
              "code": "2500",
              "name": "Long Term Liabilities",
              "value": 672600.00,
              "items": [
                { "account_id": "acct_01H8X9Y2A3LOAN", "code": "2510", "name": "SBA Term Loan", "value": 672600.00 }
              ]
            }
          ]
        },
        "equity": {
          "account_id": "acct_01H8X9Y2A3EQ",
          "name": "Equity",
          "value": 857100.32,
          "items": [
            { "account_id": "acct_01H8X9Y2A3RE", "code": "3200", "name": "Retained Earnings", "value": 612400.32 },
            { "account_id": "acct_01H8X9Y2A3CS", "code": "3000", "name": "Common Stock", "value": 244700.00 }
          ]
        },
        "net_assets": 857100.32
      }
    ]
  }
}
```

Or through the SDK, taking the last report in the run so the same code works whether the connector returned one period or twelve:

```ts
const { getBalanceSheetResponse } = await apideck.accounting.balanceSheet.get({
  serviceId: 'quickbooks',
  filter: { startDate: '2024-01-01', endDate: '2024-12-31', accountingMethod: 'accrual' }
})

const reports = getBalanceSheetResponse?.data.reports ?? []
const latest = reports.at(-1)
if (!latest) throw new Error('Connector returned no balance sheet for that window')

const debtToEquity = (latest.liabilities.value ?? 0) / (latest.equity.value ?? 1)
void debtToEquity
```

`filter[accounting_method]` accepts `cash` or `accrual`, and it is worth setting explicitly: an accrual balance sheet and a cash-basis one give different working capital for the same borrower. Not every connector honors it, which is covered below.

### 2. Pull the profit and loss

The P&L drives revenue trend, gross margin, and EBITDA approximations. Pull it on the same period as the balance sheet plus the prior comparable period, so the model has year-over-year deltas.

Send this to [`GET /accounting/profit-and-loss`](/apis/accounting/reference#operation/profitAndLossOne).

```bash
curl -G https://unify.apideck.com/accounting/profit-and-loss \
  -H "Authorization: Bearer ${APIDECK_API_KEY}" \
  -H "x-apideck-app-id: ${APIDECK_APP_ID}" \
  -H "x-apideck-consumer-id: borrower_01H8X9Y2A3K4M5N6P7Q8R9S0TU" \
  -H "x-apideck-service-id: xero" \
  --data-urlencode "filter[start_date]=2024-01-01" \
  --data-urlencode "filter[end_date]=2024-12-31"
```

Trimmed response. Three field names differ from the balance sheet: sections carry `total` rather than `value`, their children live under `records` rather than `items`, and each record is titled with `title` rather than `name` and keyed with `id` rather than `account_id`. `gross_profit`, `net_operating_income`, and `net_income` are objects wrapping a single `total`, not bare numbers.

```json
{
  "data": {
    "id": "pnl_2024_fy",
    "report_name": "ProfitAndLoss",
    "start_date": "2024-01-01",
    "end_date": "2024-12-31",
    "currency": "USD",
    "income": {
      "code": "4000",
      "title": "Income",
      "type": "Section",
      "total": 4820100.00,
      "records": [
        { "id": "acct_01H8X9Y2A3REV", "code": "4010", "title": "Product Revenue", "type": "Record", "total": 4120100.00 },
        { "id": "acct_01H8X9Y2A3SVC", "code": "4020", "title": "Service Revenue", "type": "Record", "total": 700000.00 }
      ]
    },
    "cost_of_goods_sold": {
      "code": "5000",
      "title": "Cost of Goods Sold",
      "type": "Section",
      "total": 2410050.00,
      "records": [
        { "id": "acct_01H8X9Y2A3COGS", "code": "5010", "title": "Cost of Goods Sold", "type": "Record", "total": 2410050.00 }
      ]
    },
    "gross_profit": { "total": 2410050.00 },
    "expenses": {
      "code": "6000",
      "title": "Expenses",
      "type": "Section",
      "total": 1689400.00,
      "records": [
        { "id": "acct_01H8X9Y2A3PAY", "code": "6010", "title": "Payroll", "type": "Record", "total": 1240000.00 },
        { "id": "acct_01H8X9Y2A3RNT", "code": "6020", "title": "Rent", "type": "Record", "total": 240000.00 },
        { "id": "acct_01H8X9Y2A3MKT", "code": "6030", "title": "Marketing", "type": "Record", "total": 209400.00 }
      ]
    },
    "net_operating_income": { "total": 720650.00 },
    "net_income": { "total": 720650.00 }
  }
}
```

Or through the SDK. Unlike the balance sheet, the profit and loss response is a single report rather than an array:

```ts
const { getProfitAndLossResponse } = await apideck.accounting.profitAndLoss.get({
  serviceId: 'xero',
  filter: { startDate: '2024-01-01', endDate: '2024-12-31', accountingMethod: 'accrual' }
})

const pnl = getProfitAndLossResponse?.data
if (!pnl) throw new Error('Connector returned no profit and loss for that window')

const revenue = pnl.income.total ?? 0
const grossMargin = (pnl.grossProfit?.total ?? 0) / (revenue || 1)
void grossMargin
```

`income.total` and `expenses.total` are typed nullable, so coalesce before dividing. A connector that reports a section with no accounts returns `null`, not `0`, and an uncoalesced `null` quietly becomes `0` in arithmetic while breaking any ratio that divides by it.

### 3. Pull aged receivables and payables

Aging buckets show how reliably the borrower collects and pays. You define the buckets on the request, with `filter[report_as_of_date]` for the cutoff and `filter[period_count]` and `filter[period_length]` for how many buckets of how many days, and the response labels each bucket with its own `start_date` and `end_date` rather than a text label such as "1 to 30". Every bucket can also carry the individual transactions inside it under `balances_by_transaction`, so the underwriting model never has to recompute aging from invoice dates.

Send this to [`GET /accounting/aged-debtors`](/apis/accounting/reference#operation/agedDebtorsOne).

```json
{
  "data": {
    "report_generated_at": "2025-01-02T09:15:00.000Z",
    "report_as_of_date": "2024-12-31",
    "period_count": 4,
    "period_length": 30,
    "outstanding_balances": [
      {
        "customer_id": "cust_01H8X9Y2A3ACME",
        "customer_name": "Acme Robotics LLC",
        "outstanding_balances_by_currency": [
          {
            "currency": "USD",
            "total_amount": 25600.00,
            "balances_by_period": [
              {
                "start_date": "2024-12-01",
                "end_date": "2024-12-31",
                "total_amount": 18400.00,
                "balances_by_transaction": [
                  {
                    "transaction_id": "inv_01H8YBZ3C4D5E6F7G8H9J0K1L2",
                    "transaction_number": "INV-4471",
                    "transaction_type": "invoice",
                    "transaction_date": "2024-12-18",
                    "due_date": "2025-01-17",
                    "original_amount": 18400.00,
                    "outstanding_balance": 18400.00
                  }
                ]
              },
              {
                "start_date": "2024-11-01",
                "end_date": "2024-11-30",
                "total_amount": 7200.00,
                "balances_by_transaction": [
                  {
                    "transaction_id": "inv_01H8YBZ3C4D5E6F7G8H9J0K1M3",
                    "transaction_number": "INV-4402",
                    "transaction_type": "invoice",
                    "transaction_date": "2024-11-14",
                    "due_date": "2024-12-14",
                    "original_amount": 9700.00,
                    "outstanding_balance": 7200.00
                  }
                ]
              }
            ]
          }
        ]
      },
      {
        "customer_id": "cust_01H8X9Y2A3OASIS",
        "customer_name": "Oasis Distributing Co.",
        "outstanding_balances_by_currency": [
          {
            "currency": "USD",
            "total_amount": 31300.00,
            "balances_by_period": [
              { "start_date": "2024-11-01", "end_date": "2024-11-30", "total_amount": 12500.00 },
              { "start_date": "2024-10-01", "end_date": "2024-10-31", "total_amount": 12500.00 },
              { "start_date": "2024-09-01", "end_date": "2024-09-30", "total_amount": 6300.00 }
            ]
          }
        ]
      }
    ]
  }
}
```

Or through the SDK. Debtor concentration, the single ratio most underwriters want out of this report, is one reduce over the currency totals:

```ts
const { getAgedDebtorsResponse } = await apideck.accounting.agedDebtors.get({
  serviceId: 'quickbooks',
  filter: { reportAsOfDate: '2024-12-31', periodCount: 4, periodLength: 30 }
})

const byCustomer = (getAgedDebtorsResponse?.data.outstandingBalances ?? []).map((debtor) => ({
  name: debtor.customerName,
  outstanding: (debtor.outstandingBalancesByCurrency ?? []).reduce(
    (sum, bucket) => sum + (bucket.totalAmount ?? 0),
    0
  )
}))

const total = byCustomer.reduce((sum, c) => sum + c.outstanding, 0)
byCustomer.sort((a, b) => b.outstanding - a.outstanding)
const topDebtorShare = (byCustomer[0]?.outstanding ?? 0) / (total || 1)
void topDebtorShare
```

The outer loop over `outstanding_balances_by_currency` is not optional. A borrower invoicing in more than one currency returns one entry per currency and no converted grand total, so summing only the first entry understates receivables silently.

The mirror call for payables is [`GET /accounting/aged-creditors`](/apis/accounting/reference#operation/agedCreditorsOne), reached as `apideck.accounting.agedCreditors.get()`, and returns the same shape with `supplier_id` and `supplier_name` in place of the customer keys. Both reports are thinly covered: only QuickBooks, Xero, Sage Intacct, Intuit Enterprise Suite, and AFAS expose aged debtors, and aged creditors drops Sage Intacct from even that short list. For every other connector, derive aging from invoices and bills as in step 5.

### 4. Reconstruct cash movement

There is no unified cash flow statement, so for connectors that need it derived, pull customer receipts and supplier disbursements over the underwriting window and net them against the cash and credit-card ledger accounts.

```bash
curl -G https://unify.apideck.com/accounting/payments \
  -H "Authorization: Bearer ${APIDECK_API_KEY}" \
  -H "x-apideck-app-id: ${APIDECK_APP_ID}" \
  -H "x-apideck-consumer-id: borrower_01H8X9Y2A3K4M5N6P7Q8R9S0TU" \
  -H "x-apideck-service-id: xero" \
  --data-urlencode "filter[updated_since]=2024-01-01T00:00:00Z"
```

Or through the SDK. `list()` returns an async iterator, so paging is a `for await` rather than a manual cursor loop:

```ts
const paymentPages = await apideck.accounting.payments.list({
  serviceId: 'xero',
  filter: { updatedSince: new Date('2024-01-01T00:00:00Z') },
  limit: 200
})

let cashIn = 0
for await (const page of paymentPages) {
  for (const payment of page.getPaymentsResponse?.data ?? []) {
    cashIn += payment.totalAmount ?? 0
  }
}
void cashIn
```

`limit` caps at 200, so a year of receipts is many pages. Iterate all of them: stopping after the first page and reading the running total is the quiet way to underwrite against a fraction of the borrower's cash in.

Pair this with [`GET /accounting/bill-payments`](/apis/accounting/reference#operation/billPaymentsAll) to capture supplier cash out, and use the cash and credit-card balances from the balance sheet at period start and period end to validate the net movement.

### 5. Pull line-level detail when needed

After the dedicated reports, drill into [`GET /accounting/invoices`](/apis/accounting/reference#operation/invoicesAll) and [`GET /accounting/bills`](/apis/accounting/reference#operation/billsAll) for line-item detail (customer concentration, COGS structure, recurring revenue identification). Page with `limit` and `cursor` and filter with `filter[updated_since]` to keep the post-funding monitoring sync incremental.

This is also where outstanding receivables come from on any connector without `aged-debtors`. Note what the invoice filters do not offer: there is no status filter and no invoice-date or due-date range on any of the major ledgers, so the only way to scope the pull is `updated_since`, and open versus settled has to be decided per record once the page is in hand:

```ts
const invoicePages = await apideck.accounting.invoices.list({
  serviceId: 'netsuite',
  filter: { updatedSince: new Date('2024-01-01T00:00:00Z') },
  limit: 200
})

let receivables = 0
for await (const page of invoicePages) {
  for (const invoice of page.getInvoicesResponse?.data ?? []) {
    receivables += invoice.balance ?? 0
  }
}
void receivables
```

Sum `balance`, not `total`. `total` is the face value of the invoice and never moves, so totalling it counts every invoice the borrower has ever raised as still owing. `balance` is what remains after allocations, which is the figure a facility is advanced against. It is also the only reading available on QuickBooks, for the reason in the next section.

## What actually bites people

Three constraints on the connectors most lending products start with, each verified against the live coverage matrix rather than the unified schema.

**QuickBooks Online.** Invoices do not carry `status` in QuickBooks coverage at all, so you cannot classify open against settled receivables from the field the unified model gives you. Compare `balance` against `total` per invoice instead. In exchange, it is the only one of these three whose profit and loss accepts `filter[customer_id]`, so revenue concentration can be pushed down to the connector rather than reassembled locally.

**Xero.** The balance sheet has no `start_date` filter. Xero accepts `end_date` plus `period_count` and `period_type`, so a year-over-year comparison is one request for a run of periods rather than two requests with explicit windows: ask for the run, then read the last entry of `data.reports` for the closing position. Xero invoices also support only `updated_since` and `number`, with no `customer_id` and no `created_since`, so per-borrower-customer receivables have to be indexed on your side after a full window sync.

**NetSuite.** NetSuite is in coverage for neither `aged-debtors` nor `aged-creditors`, so both aging profiles have to be built from invoices and bills as in step 5. Its balance sheet and profit and loss accept only `start_date` and `end_date`: no `accounting_method`, so you cannot force a cash-basis or accrual-basis run and take whatever the account is configured for, and no `location_id`. Invoice paging offers `id_since` where the others offer `created_since`.

## Connector-specific behavior

The notes below reflect each connector's coverage of the resources most relevant to underwriting (balance sheet, profit and loss, invoices, bills, payments, journal entries, ledger accounts). Where the dedicated reports are not exposed, derive the equivalent figures from journal entries and ledger accounts only when those are themselves in coverage; otherwise, mark the connector as a partial input and pair it with another data source.

| Connector | Notes |
| --- | --- |
| `access-financials` | No balance sheet or P&L endpoint. Invoices, credit notes, payments, suppliers, and customers are available; ledger accounts are read-only. Derive financial position from ledger account balances rather than journal entries. |
| `acumatica` | No balance sheet or P&L endpoint. Strong transaction coverage (bills, invoices, journal entries, ledger accounts, payments, purchase orders). Derive reports from journals. |
| `banqup` | Read-only on customers and invoices; bills, payments, journal entries, and ledger accounts are not in coverage. Insufficient on its own for full underwriting; combine with bank statement data. |
| `campfire` | Balance sheet and P&L exposed read-only. Bill payments and payments coverage is limited, so cash movement is best reconstructed from journal entries (read+write) and ledger accounts. |
| `clearbooks-uk` | No balance sheet or P&L endpoint, and journal entries are not in coverage. Bills, credit notes, customers, invoices, and suppliers are read-only. Limited utility for full underwriting; verify against the coverage matrix before relying on it. |
| `digits` | Balance sheet and P&L are read-only and well-supported. Transaction-level resources are largely read-only or absent, so this connector is best used for report ingestion only. |
| `dualentry` | No balance sheet or P&L endpoint. Broad coverage including bills, invoices, payments, journal entries, and credit notes. Derive reports from journals. |
| `exact-online` | Balance sheet and P&L are read-only. Bills, invoices, journal entries, and payments are supported. Reports are returned in the entity's reporting currency. |
| `exact-online-nl` | Same coverage as `exact-online`. Tax rate detail follows Dutch BTW conventions. |
| `exact-online-uk` | Same report coverage as `exact-online`. Bills are read-only on this variant, so payable aging should come from `aged-creditors` rather than aggregating bills. |
| `freeagent` | Balance sheet and P&L exposed read-only. Bills are read-only, payments are not in coverage, and journal entries are read-only. Suitable for read-only underwriting flows. |
| `freshbooks` | Balance sheet and P&L are read-only. Strong invoice, bill, payment, and supplier coverage. Common for service-business borrowers. |
| `intuit-enterprise-suite` | Balance sheet and P&L are read-only and the broader resource surface (bills, invoices, journal entries, departments, locations, tracking categories) is read+write. Use multi-dimensional filters to underwrite specific business units. |
| `kashflow` | No balance sheet or P&L endpoint. Most resources are read-only, including journal entries and ledger accounts. Derive aggregates from those reads. |
| `microsoft-dynamics-365-business-central` | No balance sheet or P&L endpoint. Strong write coverage on bills, invoices, journal entries, and purchase orders; ledger accounts and payments are read-only. Derive reports from journal entries grouped by ledger account. |
| `moneybird` | No balance sheet or P&L endpoint. Bills, invoices, journal entries, and tracking categories are supported. Derive aggregates from journals. |
| `mrisoftware` | No balance sheet or P&L endpoint. Bills, journal entries, and suppliers are read+write; customers, ledger accounts, and most other underwriting resources are read-only or not in coverage. Common in real-estate and property-management lending. |
| `myob` | No balance sheet or P&L endpoint, and journal entries and bills are not in coverage. Customers, invoices, and ledger accounts are read+write. Coverage of underwriting resources is limited; expect to fall back to ledger accounts. |
| `myob-acumatica` | No balance sheet or P&L endpoint. Strong coverage across bills, invoices, credit notes, journal entries, and purchase orders. Derive reports from journals. |
| `netsuite` | Balance sheet and P&L are read-only and accept only the `start_date` and `end_date` filters. Subsidiaries are read+write; departments, classes, and locations are read-only. Scope reports to a single legal entity via `pass_through`, not `filter`. Aged debtors and aged creditors are not in coverage. |
| `odoo` | No balance sheet or P&L endpoint. Bills, invoices, and journal entries are read+write; ledger accounts are read-only. Derive reports from journals. |
| `pennylane` | No balance sheet or P&L endpoint. Bills, invoices, and journal entries are supported; bill-payments are not in coverage. Common for French SMB borrowers. |
| `procountor-fi` | No balance sheet or P&L endpoint. Bills, invoices, journal entries, and purchase orders are supported; payments are read-only. Reports must be derived from ledger postings. |
| `quickbooks` | Balance sheet and P&L are read-only. The most common borrower system in the US SMB segment. Departments, locations, and tracking categories (classes) are available read+write for segment-level underwriting. |
| `rillet` | Balance sheet and P&L are read-only. Bills, invoices, and journal entries are read+write; ledger accounts are read-only. |
| `sage-business-cloud-accounting` | Balance sheet and P&L are read-only. Journal entries are read-only, so cash flow reconstruction relies on `payments` and `bill-payments`. |
| `sage-intacct` | Balance sheet and P&L are read-only. Tracking categories are read+write; subsidiaries, departments, and locations are read-only. Useful for multi-entity underwriting. |
| `sage-intacct-rest` | Limited or no coverage across underwriting resources; verify in the coverage matrix. Use `sage-intacct` where possible for borrower onboarding. |
| `stripe` | No balance sheet or P&L endpoint. Useful as a revenue and customer-payment source for borrowers whose primary book of record lives in Stripe (e.g. SaaS), but combine with a true ledger system for full underwriting. |
| `visma-netvisor` | No balance sheet or P&L endpoint. Bills, invoices, payments, and purchase orders are supported; journal entries are read-only and ledger accounts are not in coverage. |
| `wave` | Balance sheet and P&L are read-only. Invoices and ledger accounts are supported; bills and payments are not in coverage. |
| `workday` | No balance sheet or P&L endpoint. Strong coverage of bills, invoices, journal entries, expenses, and payments. Derive reports from journals; common for larger borrowers. |
| `xero` | Balance sheet and P&L are read-only. Tracking categories (read-only) carry departmental detail. The balance sheet takes `end_date` with `period_count` and `period_type` and has no `start_date` filter. Aged debtors and aged creditors are both in coverage, bucketed by the `period_count` and `period_length` you request. |
| `yuki` | No balance sheet or P&L endpoint. Invoices and journal entries are supported; ledger accounts are read-only. Derive aggregates from journals. |
| `zoho-books` | No balance sheet or P&L endpoint. Strong coverage across bills, invoices, journal entries, payments, and purchase orders. Derive reports from journal entries. |

### NetSuite

For multi-subsidiary borrowers, request the report once per subsidiary identified through [`GET /accounting/subsidiaries`](/apis/accounting/reference#operation/subsidiariesAll) and consolidate on your side, or rely on the parent's consolidation if the borrower already books at the parent level. NetSuite's department, class, and location dimensions can be used to scope the P&L to a specific operating segment when the loan is asset-backed against that segment.

### Sage Intacct

Sage Intacct supports the full report set as read-only and exposes subsidiaries, departments, and locations (read-only) along with tracking categories (read+write). For entity-level underwriting in multi-entity tenants, scope each report request to a single entity rather than relying on consolidations that may include intercompany eliminations the model does not need to see.

### QuickBooks and Intuit Enterprise Suite

Both connectors expose departments, locations, and classes via tracking categories. For franchise or multi-location borrowers, segment the P&L by location to underwrite a single store rather than the whole company.

### Connectors without dedicated reports

For connectors without `balance-sheet` and `profit-and-loss`, build a derivation step:

1. Pull [`GET /accounting/ledger-accounts`](/apis/accounting/reference#operation/ledgerAccountsAll) to get the chart of accounts and each account's `classification` (asset, liability, equity, income, expense).
2. Pull [`GET /accounting/journal-entries`](/apis/accounting/reference#operation/journalEntriesAll) for the period, summing debits and credits per account.
3. Group by classification to produce a balance sheet at period end and a P&L over the period.

The trade-off: derived reports do not always match the borrower's own report run inside the ERP, because the source system may apply elimination or rounding rules the unified model does not surface. When the ERP exposes its own report endpoint, prefer it. When journal entries or ledger accounts are themselves not in coverage for a given connector, this derivation is not viable and the connector should be paired with an external data source.

## Visit the demo

The lending demo runs both halves of this against a connected ledger: it finances unpaid invoices by reading the borrower's receivables and advancing against them, and it underwrites an SME term facility from that borrower's live balance sheet and profit and loss, then monitors it after funding. It ships on synthetic sample data so you can walk the whole flow with nothing to set up, and runs against a real borrower ledger once you drop in your own credentials.

## Next steps

- [Mark Invoices and Bills as Paid](/guides/mark-invoices-as-paid) for understanding how settled status flows back into the receivables and payables you pull.
- [Handling Bills and Expenses](/guides/expenses-bills) for cost-side data modeling.
- [Accounting API reference](/apis/accounting/reference) for the full resource and filter surface.
- [Webhooks reference](/apis/webhook/reference) to keep post-funding monitoring sync incremental rather than full-refresh.
