# FP&A: Budgeting, Forecasting, and Variance Reporting with the Accounting API

FP&A platforms need clean period-aligned actuals to drive budget-vs-actuals dashboards, rolling forecasts, and scenario models. This guide describes how to pull historical actuals out of a customer's accounting system on a recurring schedule and reshape them into the period balance sheets, P&L by period, journal-level segment data, and aged AR/AP feeds that a planning product consumes. All paths use Apideck's [Accounting API](/apis/accounting/reference).

## Why a Unified API

FP&A ingestion is read-heavy and breadth-heavy: a single planning customer typically connects two or three accounting systems across legal entities, and each system has its own report shape, period semantics, and dimension model. A Unified API absorbs that surface area so the planning team can focus on modeling rather than connector plumbing.

- No per-connector report parsers, period-end conventions, or pagination quirks
- One incremental-sync pattern (`updated_since`) across every entity in the customer's stack
- One dimension model (`tracking_categories`) covering classes, locations, departments, and projects
- Consistent currency, tax, and journal shapes across consolidations

## Resource mapping

The dedicated reporting endpoints map onto the closest native report in each system. Use them for aggregates; drop down to journal entries and invoices only when you need line-level detail. Coverage of each dedicated report endpoint varies by connector. Verify per-resource support in the live coverage matrix before relying on a specific endpoint in production.

| Connector | Downstream report object |
| --- | --- |
| QuickBooks | `BalanceSheet`, `ProfitAndLoss`, `AgedReceivables`, `AgedPayables` reports |
| Xero | `Reports/BalanceSheet`, `Reports/ProfitAndLoss`, `Reports/AgedReceivablesByContact` |
| NetSuite | `Financial Report` (Balance Sheet, Income Statement) saved searches |
| Sage Intacct | `gltransaction` and `genledger` reports with dimension structures |
| Microsoft Dynamics 365 Business Central | `accountSchedules`, `generalLedgerEntries` |
| Exact Online | `BalanceLines` and `ProfitLossOverview` queries |
| Odoo | `account.move`, `account.account` aggregations |
| FreshBooks | `reports/accounting/profitloss_entity`, `reports/accounting/balance_sheet` |
| Sage Business Cloud Accounting | `journals`, `aged_debtors`, `aged_creditors` |
| Zoho Books | `reports/balancesheet`, `reports/profitandloss` |

## Walkthrough

The pipeline has four phases: an initial backfill, an incremental loop, a segment enrichment pass, and a working-capital read. Run all four against each connected entity, then consolidate in your warehouse.

### Pull the period P&L

Start with the income statement at the period grain the planning model expects (monthly is the common default). The dedicated endpoint returns the downstream system's native bucketing, which preserves the customer's accounting calendar without you reimplementing fiscal-year offsets.

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

```
GET /accounting/profit-and-loss?filter[start_date]=2024-01-01&filter[end_date]=2024-12-31&filter[period_count]=12&filter[period_type]=month
Authorization: Bearer sk_live_E7y2W...
x-apideck-app-id: app_01H9Q0K2X8V3...
x-apideck-consumer-id: cust_acme_holdings_us
x-apideck-service-id: quickbooks
```

Each row in the response carries an `account_id`, an `account_name`, a `classification` (revenue, expense, cogs), and an array of period values. Persist that shape directly: it is the natural input for variance calculations.

### Pull period balance sheets

Pair the P&L with snapshot balance sheets at each period close. Use [`GET /accounting/balance-sheet`](/apis/accounting/reference#operation/balanceSheetOne) with an end date per period rather than recomputing balances from journal entries.

```json
{
  "filter": {
    "start_date": "2024-12-31",
    "end_date": "2024-12-31",
    "period_count": 1,
    "period_type": "month"
  }
}
```

Loop the request across the period grid you maintain in the planning warehouse. For systems that close mid-month, request the customer's actual period boundary rather than the calendar boundary.

### Run the incremental sync

After the initial backfill, switch to incremental pulls keyed off `updated_since`. The journal-entries feed is the canonical change stream for actuals: any posting that affects the P&L or balance sheet shows up here.

Send this to [`GET /accounting/journal-entries`](/apis/accounting/reference#operation/journalEntriesAll).

```
GET /accounting/journal-entries?filter[updated_since]=2025-01-15T00:00:00Z&limit=200
Authorization: Bearer sk_live_E7y2W...
x-apideck-app-id: app_01H9Q0K2X8V3...
x-apideck-consumer-id: cust_acme_holdings_us
x-apideck-service-id: xero
```

Persist the high-water mark per `(consumer_id, service_id)` pair after each successful page. On the next run, advance `updated_since` to the largest `updated_at` you saw, minus a small overlap window (15 minutes is a safe default) to absorb out-of-order writes.

When a journal entry lands in a closed period, recompute the P&L and balance sheet for that period rather than trusting your incremental cache. Closed-period adjustments are a known source of variance drift.

### Map tracking_categories to segments

Segment reporting (revenue by department, opex by location, gross margin by project) hinges on the `tracking_categories` array that appears on journal-entry lines, invoices, and bills. List the available categories first, then store the customer's mapping from category to planning dimension.

Send this to [`GET /accounting/tracking-categories`](/apis/accounting/reference#operation/trackingCategoriesAll).

```json
{
  "data": [
    {
      "id": "tc_dept_01HZ8K3W9XQ7M2N6P4",
      "parent_id": null,
      "name": "Department",
      "code": "DEPT",
      "status": "active"
    },
    {
      "id": "tc_dept_eng_01HZ8K3W9XQ7M2N6P5",
      "parent_id": "tc_dept_01HZ8K3W9XQ7M2N6P4",
      "name": "Engineering",
      "code": "DEPT-ENG",
      "status": "active"
    }
  ]
}
```

Within journal entries, each line carries its own `tracking_categories[]`. Index your warehouse by `(period, account_id, tracking_category_ids[])` so the planning model can slice across any dimension without re-querying the source system.

### Consolidate across multiple entities

Multi-entity customers connect each legal entity as a separate Vault connection. Iterate over consumer connections and tag each row with the originating `service_id` and `consumer_id`. Use the Vault [`GET /vault/connections`](/apis/vault/reference#operation/connectionsAll) endpoint with the consumer header to enumerate active connections, and filter to `state: callable`.

Apply consolidation rules in your warehouse, not at read time:

- Translate non-base-currency entities using period-end rates for balance sheet accounts and period-average rates for P&L accounts
- Eliminate inter-company balances using a tracking category or account-code convention agreed with the customer
- Roll up segment dimensions when the planning chart of accounts is shallower than the source

### Pull aged AR and AP for working capital

Working-capital projections need aging buckets, not raw invoice lists. Use the dedicated endpoints so the buckets line up with whatever the source system reports natively.

Send these to [`GET /accounting/aged-debtors`](/apis/accounting/reference#operation/agedDebtorsOne) and [`GET /accounting/aged-creditors`](/apis/accounting/reference#operation/agedCreditorsOne).

```
GET /accounting/aged-debtors?filter[report_as_of_date]=2025-01-31
GET /accounting/aged-creditors?filter[report_as_of_date]=2025-01-31
```

Only fall back to [`GET /accounting/invoices`](/apis/accounting/reference#operation/invoicesAll) and [`GET /accounting/bills`](/apis/accounting/reference#operation/billsAll) when the planning model needs invoice-level detail (for example, to model individual large customer DSO).

### Build period-over-period comparisons

The planning UI typically shows three columns: this period, prior period, and same period last year. Compute these in the warehouse from the same canonical fact table, keyed on `(entity_id, period_end_date, account_id, tracking_category_ids[])`:

- **This period**: latest closed period from the incremental sync
- **Prior period**: the period immediately before, from the same fact table
- **Same period last year**: 12-month offset on `period_end_date`, accounting for fiscal calendars that are not calendar-aligned

Store the source `currency` and the FX rate used for translation alongside each row. When users drill into a variance, surfacing the underlying journal entries via `journal_entry_id` gives them the audit trail without an extra round-trip to the source system.

### When to drop down to the Proxy API

The unified endpoints cover the report shapes most FP&A use cases need. Reach for the [Proxy API](/apis/proxy/reference) only when the customer asks for a downstream-native artifact the unified model deliberately does not expose, for example NetSuite saved searches with custom formulas, Xero's budget endpoint, or Sage Intacct's `query` engine for ad-hoc dimension combinations. Treat Proxy responses as connector-specific by definition: they do not flow through the unified mapping layer, so they should land in a separate, connector-tagged table in your warehouse rather than the canonical fact table.

## Connector-specific behavior

Coverage of the dedicated balance-sheet, P&L, journal-entries, tracking-categories, and aged report endpoints varies by connector. For any row below where the guide depends on a report endpoint that is limited or out of coverage, fall back to journal entries plus account classifications, or use the Proxy API for a downstream-native report. Verify per-resource support in the live coverage matrix before relying on a specific endpoint in production. Quirks below are noted only where there is real signal; rows that do not call out a quirk should be treated as standard mapping with coverage to be verified.

| Connector | Notes |
| --- | --- |
| `access-financials` | Standard mapping. Verify report-endpoint coverage in the matrix before relying on dedicated balance-sheet and P&L pulls. |
| `acumatica` | Branches and subaccounts surface as `tracking_categories` where in coverage. Period rollups follow the financial period structure rather than calendar months. |
| `banqup` | AP-leaning scope. Verify report-endpoint coverage in the matrix; journal entries and invoices are the safer baseline. |
| `campfire` | Standard mapping. No known quirks beyond the unified model; verify coverage before relying on any specific report endpoint. |
| `clearbooks-uk` | UK-style VAT periods. Verify coverage of the aged report endpoints before using their bucket structure directly. |
| `digits` | Standard mapping. No known quirks beyond the unified model; verify coverage before relying on any specific report endpoint. |
| `dualentry` | Standard mapping. No known quirks beyond the unified model; verify coverage before relying on any specific report endpoint. |
| `exact-online` | Cost Centers and Cost Units map to `tracking_categories` where in coverage. Closed-period postings appear via `updated_since`. |
| `exact-online-nl` | Same as `exact-online` with NL-specific VAT codes on journal lines. |
| `exact-online-uk` | Same as `exact-online` with UK VAT codes. |
| `freeagent` | SMB scope. Project tracking maps to `tracking_categories` where in coverage; deeper segment dimensions are not modeled downstream. |
| `freshbooks` | SMB scope. Class and department dimensions are not modeled. Verify which dedicated report endpoints are in coverage; otherwise reconstruct from journal entries and account classifications. |
| `intuit-enterprise-suite` | Classes, locations, and departments surface as `tracking_categories` where in coverage. Multi-entity consolidation is native to the underlying suite. |
| `kashflow` | UK SMB scope. Project codes map to `tracking_categories` where in coverage. |
| `microsoft-dynamics-365-business-central` | Global Dimension 1, Global Dimension 2, and shortcut dimensions all map to `tracking_categories` where in coverage. Account schedules drive the native report shape. |
| `moneybird` | NL SMB scope. Cost centers map to `tracking_categories` where in coverage. |
| `mrisoftware` | Property-management scope. Property-level dimensions map to `tracking_categories` where in coverage. Period boundaries follow the property accounting calendar. |
| `myob` | Categories and jobs map to `tracking_categories` where in coverage. |
| `myob-acumatica` | Branches and subaccounts surface as `tracking_categories`, matching the underlying Acumatica engine. |
| `netsuite` | Subsidiaries, departments, classes, and locations all map to `tracking_categories` where in coverage. Multi-book accounting is supported, with the primary book exposed by default. |
| `odoo` | Analytic accounts map to `tracking_categories` where in coverage. The analytic dimension is the primary segment lever. |
| `pennylane` | French chart of accounts. Analytic codes map to `tracking_categories` where in coverage. |
| `procountor-fi` | Finnish chart of accounts and statutory periods. Cost centers map to `tracking_categories` where in coverage. |
| `quickbooks` | Classes and locations map to `tracking_categories` where in coverage. Sub-accounts roll up under their parent in the P&L unless the customer opts out. |
| `rillet` | Standard mapping. No known quirks beyond the unified model; verify coverage before relying on any specific report endpoint. |
| `sage-business-cloud-accounting` | Analysis types map to `tracking_categories` where in coverage. Verify aged report coverage before using its bucket structure directly. |
| `sage-intacct` | Native dimensions (Department, Location, Project, Class, plus user-defined dimensions) surface as `tracking_categories` where in coverage. Use the Proxy API for ad-hoc multi-dimension queries the unified report shape cannot express. |
| `sage-intacct-rest` | REST variant of `sage-intacct`. Same dimension model; verify per-resource coverage separately. |
| `stripe` | Payment-processor scope, not a general ledger. The dedicated balance-sheet, P&L, and aged report endpoints are not meaningful here. Treat as a transactional source for revenue and fees and reconcile in your warehouse. |
| `visma-netvisor` | Finnish chart of accounts. Cost centers map to `tracking_categories` where in coverage. |
| `wave` | SMB scope. Limited segment dimensions; rely on account-code conventions for grouping. Verify report-endpoint coverage in the matrix. |
| `workday` | Workday Financial Management exposes worktags as `tracking_categories` where in coverage. Consolidation hierarchy mirrors Workday's organizational structure. |
| `xero` | Up to two tracking categories per line, surfaced as `tracking_categories`. The native Xero reports endpoint backs the unified P&L and balance sheet where in coverage. |
| `yuki` | NL SMB scope. Cost centers map to `tracking_categories` where in coverage. |
| `zoho-books` | Classes and projects map to `tracking_categories` where in coverage. |

### NetSuite

NetSuite is the most segment-rich connector in the catalog. A single journal line can carry subsidiary, department, class, location, and a stack of custom segments, all of which can surface in `tracking_categories`. For consolidation, the subsidiary value is what tells you which legal entity the row belongs to; do not rely on the connection alone if the customer uses OneWorld with multiple subsidiaries on a single connection.

### Sage Intacct

Intacct's dimension model is the closest analog to a planning tool's own dimension model, but customers commonly add user-defined dimensions that the unified shape may flatten. When a customer reports that a slice in their FP&A platform doesn't match Intacct, check whether the slice is a UDD before assuming a sync issue, and use the Proxy API to pull it directly if needed.

### QuickBooks

QuickBooks sub-accounts are a common source of variance complaints: the P&L endpoint rolls them up by default, but the journal entries feed exposes them at the leaf level. Pick one shape and stick with it across the warehouse.

### Stripe

Stripe is a payment processor, not a general ledger. Connect it as a complementary feed to the customer's accounting system, not as a substitute. Use it to reconcile revenue at the transaction grain when the GL only has summary deposits.

### Workday

Workday's worktag model is multi-valued per line and hierarchical. Persist the full worktag set per journal line rather than the lowest-level value, so the planning UI can roll up flexibly.

## Next steps

- [Mark Invoices and Bills as Paid](/guides/mark-invoices-as-paid) for write-back flows from the planning tool
- [Handling Bills and Expenses](/guides/expenses-bills) for AP-side detail beyond the aged report
- [Accounting API reference](/apis/accounting/reference)
- [Proxy API reference](/apis/proxy/reference) for downstream-native reports the unified model does not cover
