# Building Accounting Integrations for Expense Management Platforms

One integration against Apideck's unified [Accounting API](/apis/accounting/reference) pushes a card transaction from your product into a customer's QuickBooks, Xero, NetSuite, or Exact Online as a coded expense or bill, with its receipt attached and its payment reconciled. Verify the required operations and fields for each connector before choosing an expense or bill workflow; payment and attachment support require separate checks.

## Explore the demo

**Expense management demo**

Explore category mapping, coded expenses, vendor bills, and card feeds.

Choose Mock for synthetic sample data. Live mode requires your own credentials and an authorized connection; requests can read or write real records. Check connector coverage before building.

[Open demo](https://demos.apideck.com/expense-management) · [API endpoints](/apis/accounting/reference?useCase=expenses)

_Your product sends coded expenses and bills to the Apideck Accounting API, which writes them into whichever ledger your customer uses and returns the ledger accounts and tax rates you map your categories against._

Account mapping is what decides whether a transaction lands on the right ledger account, [Vault](/guides/vault) handles the OAuth flow and token refresh for each provider, and supported [webhook events](/guides/webhooks) trigger reconciliation. Inspect `entity.state` on `vault.connection.updated` for connection-state changes, and retain a periodic reconciliation path for missed or unsupported events.

---

## Integration Architecture Overview

The integration has three layers:

1. **Your application**: expense tracking, mapping settings, and export engine
2. **Apideck Unified API**: Vault (auth), Accounting (CRUD), Webhooks (sync)
3. **Downstream providers**: QuickBooks, Xero, Exact Online, NetSuite, Sage, Business Central

Your app talks only to Apideck. Apideck handles provider-specific differences, auth token management, and data mapping.

The export phases below (3 onward) show the raw request body first, then the same call through the [`@apideck/unify`](/sdks/node) SDK; phases 1 and 2 are SDK-only setup steps with no single API call to mirror. The SDK takes camelCase and handles serialization, so pick whichever fits your stack. Every snippet is verified against `@apideck/unify` v0.52.2.

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

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

---

## Phase 1: Connection Setup

### Embed Apideck Vault

[Vault](/guides/vault) handles the OAuth flow with each accounting provider. Embed it in your application's settings or onboarding flow:

```ts
const { createSessionResponse } = await apideck.vault.sessions.create({
  consumerId: 'cons_01H8X9Y2A3K4M5N6P7Q8R9S0T1',
  session: {
    consumerMetadata: {
      accountName: 'Beachside Surf Co.',
      email: 'ops@beachsidesurf.com'
    },
    settings: { unifiedApis: ['accounting'] }
  }
})

const sessionUri = createSessionResponse?.data.sessionUri
if (!sessionUri) throw new Error('Vault session was not created')
```

Redirect the user to `sessionUri`, or pass `createSessionResponse?.data.sessionToken` as the embedded Vault JS `token` option. Sessions expire on the `settings.sessionLength` you set (up to one week), so mint one per visit rather than caching it.

### Monitor Connection State

Use [webhooks](/guides/webhooks) to track when connections become active or need re-authorization:

```javascript
// Key events to handle
'vault.connection.callable' // Connection ready, start syncing
'vault.connection.updated' // Inspect entity.state; reconnect only when required
'vault.connection.revoked' // User disconnected, stop syncing
```

See the [Connection States guide](/guides/connection-states) for the full state machine.

---

## Phase 2: Account Mapping

This is the most critical UX step. Your users need to map their expense categories to the correct ledger accounts in their accounting system.

### What to Map

| Your Side                  | Accounting Side                 | API                                                                                         |
| -------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------- |
| Expense categories         | Ledger accounts (type: expense) | [`GET /accounting/ledger-accounts`](/apis/accounting/reference#tag/Ledger-Accounts)         |
| Payment methods / cards    | Bank accounts (type: bank)      | [`GET /accounting/ledger-accounts`](/apis/accounting/reference#tag/Ledger-Accounts)         |
| Merchants / counterparties | Suppliers / Vendors             | [`GET /accounting/suppliers`](/apis/accounting/reference#tag/Suppliers)                     |
| Tax categories             | Tax rates                       | [`GET /accounting/tax-rates`](/apis/accounting/reference#tag/Tax-Rates)                     |
| Departments / cost centers | Tracking categories             | [`GET /accounting/tracking-categories`](/apis/accounting/reference#tag/Tracking-Categories) |

Pull the two lists that drive the mapping screen once per connection and cache them. `limit` caps at 200, so paginate through `meta.cursors.next` on a real chart of accounts rather than assuming one page holds everything:

```ts
const expenseAccounts = []
for await (const page of await apideck.accounting.ledgerAccounts.list({
  serviceId: 'quickbooks',
  filter: { classification: 'expense' },
  limit: 200
})) {
  expenseAccounts.push(...(page.getLedgerAccountsResponse?.data ?? []))
}

const taxRates = []
for await (const page of await apideck.accounting.taxRates.list({
  serviceId: 'quickbooks',
  limit: 200
})) {
  taxRates.push(...(page.getTaxRatesResponse?.data ?? []))
}
```

### Building the Mapping UI

Present a two-column interface where users match their categories to accounting accounts:

| Your Category            | Accounting Account            |
| ------------------------ | ----------------------------- |
| Travel                   | 6200 - Travel & Entertainment |
| Office Supplies          | 6100 - Office Expenses        |
| Software                 | 6350 - IT Costs               |
| Meals                    | 6200 - Travel & Entertainment |
| **Default bank account** | 1100 - Business Account       |
| **Default tax rate**     | 21% VAT                       |

For detailed implementation guidance, see the [Ledger Account Mapping guide](/guides/ledger-account-mapping).

---

## Phase 3: Exporting Expenses

### Choose the Right Resource

| Scenario                                | Resource          | Why                                                           |
| --------------------------------------- | ----------------- | ------------------------------------------------------------- |
| Expense already paid (card transaction) | **Expense**       | Records the payment and categorization in one step            |
| Expense awaiting reimbursement/approval | **Bill**          | Creates an AP entry that can be paid later                    |
| Exact Online (any scenario)             | **Bill**          | Expenses not supported, use Bills with `due_date = bill_date` |
| Need full debit/credit control          | **Journal Entry** | For complex multi-account transactions                        |

See [When to Use Bills vs. Expenses](/guides/expenses-bills#when-to-use-bills-vs-expenses) for the detailed decision matrix.

### Create an Expense (for QuickBooks, Xero, NetSuite)

The header `account` is the bank or card the money left, and each line item's `account` is the expense category it gets coded to. Both take a linked reference object rather than a bare id:

```json
{
  "transaction_date": "2025-03-15T12:00:00.000Z",
  "account": {
    "id": "1100"
  },
  "supplier": {
    "id": "supp_01HZ9Q2K4M5N6P7Q8R9S0T1U2V"
  },
  "currency": "EUR",
  "payment_type": "credit_card",
  "memo": "Business lunch, client meeting",
  "line_items": [
    {
      "account": {
        "id": "6200"
      },
      "description": "Business lunch",
      "total_amount": 45.5,
      "tax_rate": {
        "id": "VAT21"
      },
      "tracking_categories": [
        {
          "id": "dept_sales"
        }
      ]
    }
  ],
  "total_amount": 45.5
}
```

Send this to [`POST /accounting/expenses`](/apis/accounting/reference#operation/expensesAdd). The tax reference on a line is `tax_rate` holding an object, not a flat `tax_rate_id`: the flat form does not exist on the expense line item model and is dropped without an error.

Or through the SDK:

```ts
const mapping = {
  bankAccountId: '1100',
  categoryAccountId: '6200',
  categoryAccountCode: '6200',
  supplierId: 'supp_01HZ9Q2K4M5N6P7Q8R9S0T1U2V',
  taxRateId: 'VAT21',
  departmentId: 'dept_sales'
}

const { createExpenseResponse } = await apideck.accounting.expenses.create({
  serviceId: 'quickbooks',
  expense: {
    transactionDate: new Date('2025-03-15T12:00:00.000Z'),
    account: { id: mapping.bankAccountId },
    supplier: { id: mapping.supplierId },
    currency: 'EUR',
    paymentType: 'credit_card',
    memo: 'Business lunch, client meeting',
    lineItems: [
      {
        account: { id: mapping.categoryAccountId },
        description: 'Business lunch',
        totalAmount: 45.5,
        taxRate: { id: mapping.taxRateId },
        trackingCategories: [{ id: mapping.departmentId }]
      }
    ],
    totalAmount: 45.5
  }
})

const expenseId = createExpenseResponse?.data?.id
if (!expenseId) throw new Error('Expense was not created')
```

### Create a Bill (for Exact Online and reimbursements)

```json
{
  "bill_number": "EXP-4821",
  "supplier": {
    "id": "supp_01HZ9Q2K4M5N6P7Q8R9S0T1U2V"
  },
  "bill_date": "2025-03-15",
  "due_date": "2025-03-15",
  "currency": "EUR",
  "line_items": [
    {
      "description": "Business lunch, client meeting",
      "ledger_account": {
        "id": "6200"
      },
      "total_amount": 45.5,
      "tax_amount": 7.9,
      "tax_rate": {
        "id": "VAT21"
      }
    }
  ],
  "total": 45.5,
  "status": "authorised"
}
```

Send this to [`POST /accounting/bills`](/apis/accounting/reference#operation/billsAdd). Three things in that body are easy to get wrong:

- The supplier goes in `supplier` as an object. `supplier_id` is not a field on the bill model at all, unlike on the expense model where it survives as a deprecated alias.
- The line account is `ledger_account`, not `account_id`. Exact Online is the exception worth knowing about: its bill line coverage does not include `ledger_account`, so category coding there runs through the referenced `item` instead.
- Keep `total` gross. If you send the pre-tax subtotal while the line carries a `tax_rate`, the downstream bill posts a higher gross than you paid, your payment underpays it, and the balance never reaches zero. The alternative is `tax_inclusive: true`, which Xero and QuickBooks accept on bills but Exact Online and NetSuite do not.

Send `status: "authorised"` rather than `"draft"`. A draft bill is unposted and will reject the payment allocation in the next phase.

Or through the SDK, running against Xero because its bill lines carry `ledger_account` and it also accepts the `tax_amount` and `status` this bill sends. Note Xero exposes only `code` on a bill line's ledger account, not `id`, so the GL account is referenced by nominal code here. QuickBooks is the mirror image: it takes `ledger_account.id` but carries neither `tax_amount` nor `status` on a bill.

```ts
const { createBillResponse } = await apideck.accounting.bills.create({
  serviceId: 'xero',
  bill: {
    billNumber: 'EXP-4821',
    supplier: { id: mapping.supplierId },
    billDate: new Date('2025-03-15'),
    dueDate: new Date('2025-03-15'),
    currency: 'EUR',
    lineItems: [
      {
        description: 'Business lunch, client meeting',
        ledgerAccount: { code: mapping.categoryAccountCode },
        totalAmount: 45.5,
        taxAmount: 7.9,
        taxRate: { id: mapping.taxRateId }
      }
    ],
    total: 45.5,
    status: 'authorised'
  }
})

const billId = createBillResponse?.data?.id
if (!billId) throw new Error('Bill was not created')
```

Date-only fields such as `billDate` and `dueDate` are plain `Date` values in the SDK, not the `RFCDate` helper some other resources use for their own date-only fields.

---

## Phase 4: Payment Reconciliation

After creating bills, you need to record the payment to mark them as paid. This is the reconciliation step.

### Create a Bill Payment

```json
{
  "supplier": {
    "id": "supp_01HZ9Q2K4M5N6P7Q8R9S0T1U2V"
  },
  "total_amount": 45.5,
  "transaction_date": "2025-03-18T09:30:00.000Z",
  "account": {
    "id": "1100"
  },
  "allocations": [
    {
      "id": "bill_01HZ9R4M5N6P7Q8R9S0T1U2V3W",
      "type": "bill",
      "amount": 45.5
    }
  ],
  "status": "authorised",
  "type": "accounts_payable",
  "currency": "EUR"
}
```

Send this to [`POST /accounting/bill-payments`](/apis/accounting/reference#operation/billPaymentsAdd). `total_amount` and `transaction_date` are both required, and the supplier is again an object rather than a `supplier_id`.

Or through the SDK:

```ts
await apideck.accounting.billPayments.create({
  serviceId: 'xero',
  billPayment: {
    supplier: { id: mapping.supplierId },
    totalAmount: 45.5,
    transactionDate: new Date('2025-03-18T09:30:00.000Z'),
    account: { id: mapping.bankAccountId },
    allocations: [{ id: billId, type: 'bill', amount: 45.5 }],
    status: 'authorised',
    type: 'accounts_payable',
    currency: 'EUR'
  }
})
```

The accounting system automatically updates the bill status to **paid** once the full amount is allocated. Omit `allocations` and the cash posts against the supplier while the bill stays open, which is the single most common reconciliation bug.

For more details, see the [Mark Invoices as Paid guide](/guides/mark-invoices-as-paid).

>
> The example above runs against Xero. If you post bill payments to Exact Online instead, they use
> the XML API under the hood. This is handled transparently by Apideck, so your API calls remain the
> same REST format.

---

## Phase 5: Attachments & Receipts

Attach receipt images to expenses or bills for audit compliance:

```javascript
const formData = new FormData()
formData.append('file', receiptFile)

await fetch(`https://unify.apideck.com/accounting/attachments/bill/${billId}`, {
  method: 'POST',
  headers: {
    'x-apideck-consumer-id': consumerId,
    'x-apideck-app-id': appId,
    'x-apideck-service-id': serviceId,
    Authorization: `Bearer ${apiKey}`,
    'x-apideck-metadata': JSON.stringify({
      name: receiptFile.name,
      description: 'Expense receipt'
    })
  },
  body: formData
})
```

Or through the SDK. `requestBody` accepts a `ReadableStream`, a `Blob`, an `ArrayBuffer`, or a `Uint8Array`, and the per-file metadata rides along as a JSON string header:

```ts
// receiptBytes is the raw file, for example from fs.readFile or a multipart upload
const receiptBytes = new Uint8Array([37, 80, 68, 70])

await apideck.accounting.attachments.upload({
  serviceId: 'xero',
  referenceType: 'bill',
  referenceId: billId,
  xApideckMetadata: JSON.stringify({
    name: 'lunch-receipt.pdf',
    description: 'Expense receipt'
  }),
  requestBody: receiptBytes
})
```

For an expense management integration, `referenceType` is `bill` or `expense`, matching the two resources this guide covers. See [`POST /accounting/attachments/{reference_type}/{reference_id}`](/apis/accounting/reference#operation/attachmentsUpload) for the full contract.

---

## Phase 6: Error Handling & Monitoring

### Handle Common Errors

Inspect the error type, detail and downstream errors before choosing a recovery action. A 401 can indicate an incorrect Apideck API key or app/consumer context as well as a downstream authorization problem; do not send every customer through reauthorization automatically.

| Failure | Next step |
| --- | --- |
| 401 | Verify your server credentials and customer context. If the error identifies downstream credentials, inspect the Vault connection and reconnect as needed. |
| 403 | Confirm account permissions and required scopes for the intended company and operation. |
| 400 or 422 | Correct the reported field, type or account reference. For a connector mapping error, inspect child requests and check support rather than repeatedly changing the payload. |
| 429 | Honor retry timing when supplied and use bounded backoff with jitter. |
| Timeout or 5xx during a write | Reconcile whether the record was created before repeating the write. |

See [error types](/errors) and [downstream request debugging](/logs#follow-parent-and-child-requests). Your application's retry policy must preserve the source expense and any returned record IDs.

### Monitor with Webhooks

Follow the [webhook verification and delivery guide](/guides/webhooks) before processing an event. Verify its signature against the raw body, reject an invalid signature, deduplicate deliveries and durably enqueue work before acknowledging it.

For Vault events, the connection is in `entity`, not a generic `payload` property. On `vault.connection.updated`, inspect `entity.state`, `entity.consumer_id` and `entity.service_id` before changing the matching customer's connection status. An update does not automatically mean authorization failed.

For supported accounting events, fetch the affected record and reconcile it with the source expense and stored downstream ID. Do not mark an expense exported solely because a notification arrived: the event may be repeated or may refer to a different accounting operation. Confirm event availability for the connector and keep periodic reconciliation for gaps.

---

## What actually bites people

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

**QuickBooks Online.** Expenses map to Purchases, and QuickBooks is the only one of the three that reports a header-level `tax_rate` and supports custom fields on the resource. It does not report `status` on an expense, so you cannot read back whether a purchase posted cleanly: record your own sync state per transaction and drive retries off that. Its only expense filter is `updated_since`.

**Xero.** Expenses map to Bank Transactions, and the line item is thin: beyond the ids, coverage lists only `description`, `total_amount`, `account`, `tax_rate`, and `tracking_categories`. `quantity`, `unit_price`, `customer`, `department`, and `location` are not there, so per-unit pricing silently collapses into the line total. Xero also drops `payment_type`, `sub_total`, and `status` from the expense, and its journal entries ignore `limit`, so page them with `meta.cursors.next` instead of asking for a page size.

**NetSuite.** Expenses map to Credit Charges and support no filters at all, not even `updated_since`. There is no incremental resync to lean on: persist the last exported transaction on your side and replay from there. Tax lives only on the line, since the NetSuite expense has no header `tax_rate` or `tax_inclusive`, and NetSuite journal entry lines carry no `tax_rate`, `tax_amount`, or `tracking_categories` at all, so a tax-bearing correction cannot be expressed as a journal entry there.

---

## Development Strategy

### Start with one verified workflow

1. Choose the connector your first customer needs and obtain a suitable test company.
2. Verify a read, resolve ledger references, then export one approved expense or bill using supported operations.
3. Read it back and reconcile the result before adding batch export, payments or attachments.
4. Introduce recovery, duplicate prevention and monitoring before production use; they are not a final optional phase.
5. Repeat the same acceptance checks for each additional connector. Shared models do not guarantee identical fields, permissions or accounting behavior.

### Review each provider's accounting model

Confirm the supported resource and field mappings using [expense coverage](/apis/accounting/coverage?resources=expenses,bills,bill-payments,attachments) and the connector's setup and gotchas. Check tax treatment, tracking dimensions, subsidiaries, payment allocation and attachment support independently. Choose an alternative supported workflow only after agreeing its accounting treatment with the ledger owner.

---

## Verify one expense before exporting a batch

Start with one approved 48.00 expense in a test company's currency. Record whether it was paid by the company or remains payable to an employee; that decision determines the supported accounting resource and payment treatment. This example excludes tax and exchange differences.

| Stage | What to verify | Recovery boundary |
| --- | --- | --- |
| Resolve coding | Correct tenant, company, ledger account and any required tax or tracking references | A reference from another customer's connection must never be reused. |
| Export | Supported expense or bill operation; returned record ID saved against the source expense | A rejected account mapping leaves the source expense unexported. |
| Read back | Amount, currency, posting date, account coding and payee match the approved record | Missing fields remain unknown until verified; do not silently infer success. |
| Attach receipt | Attachment references the existing accounting record, where supported | Retry the attachment without creating another expense or bill. |
| Reconcile payment | Company-paid versus reimbursable treatment agreed with the ledger owner | Record settlement only when it happened; creating a bill does not reimburse an employee. |

For a batch of ten expenses, retain a separate result for every source ID. If eight exports succeed and two fail validation, repair the two failures without replaying the eight successes. A timeout is an unresolved outcome, not an automatic failure: inspect the request and reconcile the provider result before retrying a write.

Acceptance checks: reject an invalid ledger reference, recover from receipt failure after export, process a repeated job without duplication, and keep one customer's account IDs out of another customer's payload. Use [field comparison in Logs](/logs#compare-downstream-data-with-the-unified-model) to check what reached the provider.

## Complete Integration Checklist

| Area               | Requirement                                                          |
| ------------------ | -------------------------------------------------------------------- |
| **Connection**     | Embed Vault for OAuth connection flow                                |
|                    | Handle connection state webhooks (callable, invalid, revoked)        |
|                    | Support multiple accounting connections per user                     |
| **Mapping**        | Fetch and display ledger accounts for mapping                        |
|                    | Map expense categories to expense ledger accounts                    |
|                    | Map payment methods to bank/card ledger accounts                     |
|                    | Map merchants to suppliers (with option to create new)               |
|                    | Map tax categories to tax rates                                      |
|                    | Optional: map departments to tracking categories                     |
|                    | Implement auto-match by label similarity                             |
|                    | Persist mappings per user per connection                             |
| **Export**         | Create Bills for AP / reimbursement expenses                         |
|                    | Create Expenses for already-paid transactions (where supported)      |
|                    | Handle line items with correct account, tax, and tracking references |
|                    | Support multi-currency with exchange rates                           |
|                    | Upload receipt attachments                                           |
| **Reconciliation** | Create Bill Payments to mark bills as paid                           |
|                    | Handle partial payments and overpayments via allocations             |
|                    | Verify bill status updates after payment creation                    |
| **Production**     | Error handling for auth failures, validation errors, rate limits     |
|                    | Webhook monitoring for connection health                             |
|                    | Retry logic with exponential backoff                                 |
|                    | Logging for debugging sync failures                                  |
|                    | User-facing sync status UI                                           |

---

## AI Agent Prompt

Use this prompt with your AI coding assistant (Claude, Cursor, Copilot, etc.) to scaffold the integration.

```text
Build an accounting integration for an expense management platform using the
Apideck unified Accounting API. The integration should:

1. VAULT CONNECTION
   - Embed Apideck Vault for OAuth connection flow
   - Use x-apideck-consumer-id, x-apideck-app-id, and Bearer token auth
   - Handle vault.connection.callable, vault.connection.updated (inspect entity.state), and
     vault.connection.revoked webhook events
   - Base URL: https://unify.apideck.com

2. LEDGER ACCOUNT MAPPING
   - Fetch ledger accounts: GET /accounting/ledger-accounts
     (filter by classification only where supported)
   - Fetch suppliers: GET /accounting/suppliers
   - Fetch tax rates: GET /accounting/tax-rates
   - Build a settings UI where users map their expense categories to
     ledger accounts, payment methods to bank accounts, and merchants
     to suppliers
   - Store mappings per consumer_id + service_id
   - limit caps at 200, so paginate via meta.cursors.next

3. EXPENSE EXPORT
   - For already-paid expenses: POST /accounting/expenses
     (QuickBooks, Xero, NetSuite only, NOT Exact Online)
   - For AP / reimbursements: POST /accounting/bills
     (only after verifying bill creation for the selected connector)
   - Expense line items reference: account { id } (or the deprecated
     account_id), tax_rate { id }, and optionally tracking_categories
   - Bill line items reference: ledger_account { id } and tax_rate { id }
   - Suppliers are always objects: supplier { id }, never supplier_id
   - Keep the header total gross, or set tax_inclusive where supported
   - Support multi-currency via currency and currency_rate fields

4. PAYMENT RECONCILIATION
   - Create bills with status "authorised", not "draft", or the
     allocation will be rejected
   - After creating a bill, reconcile with: POST /accounting/bill-payments
   - total_amount and transaction_date are required
   - Link payment to bill via allocations[].id = bill.id, type "bill"
   - Bill status automatically updates to "paid"

5. ATTACHMENTS
   - Upload receipts: POST /accounting/attachments/{reference_type}/{reference_id}
   - reference_type is "bill" or "expense" for this integration
   - Either multipart/form-data over HTTP or accounting.attachments.upload
     in the @apideck/unify SDK

6. ERROR HANDLING
   - 401: redirect to Vault re-auth
   - 422: log validation error details
   - 429: implement exponential backoff using retry-after header

Provider differences (from the live coverage matrix):
- Exact Online: Only Bills (no Expenses), bill payments use XML API
  (transparent via Apideck), bill lines have no ledger_account
- QuickBooks: Expenses map to Purchases, no status on the expense,
  updated_since is the only filter
- Xero: Expenses map to Bank Transactions, line items carry no quantity
  or unit_price, journal entries ignore limit
- NetSuite: Expenses map to Credit Charges and support no filters,
  tax is line-level only

Reference: https://developers.apideck.com/guides/expense-management-integration
API docs: https://developers.apideck.com/apis/accounting/reference
```

---

## Related Guides

- [Ledger Account Mapping](/guides/ledger-account-mapping) for building the mapping UI
- [Integrating Expenses and Bills](/guides/expenses-bills) for the detailed Bills versus Expenses decision
- [Mark Invoices as Paid](/guides/mark-invoices-as-paid) for payment allocation
- [Accounting Data Model](/guides/accounting-data-model) for entity relationships
- [Vault](/guides/vault) for embedding the connection UI
- [Webhooks](/guides/webhooks) for real-time event monitoring
- [Tracking Dimensions](/guides/locations-subsidiaries-departments) for departments and locations

## Related reading

From Apideck.

- [Accounting integrations for SaaS: guides, use cases and tools](https://www.apideck.com/accounting-integrations): Explore common accounting integration patterns and how connected financial data fits into your product.
- [Accounting & ERP Partnership Guide](https://www.apideck.com/blog/accounting-and-erp-partnership-guide): Understand the partnership and production-access requirements to investigate when planning an accounting connector launch.
