Unified.to
All articles

How to Build Budgets and Forecast Runway


January 27, 2026

Most budgeting and runway features break long before the math.

They break when products treat accounting reports as inputs rather than outputs. Profit & Loss, Cashflow, and Balance Sheet reports look authoritative, but they are reconstructed views—shaped by provider rules, timing differences, and fields that may change or be deprecated over time.

For PMs and backend teams, this creates real uncertainty:

  • Are budgets reacting to real spend, or delayed summaries?
  • Is burn calculated from posted activity, or inferred from reports?
  • Can runway be trusted when providers compute reports differently?

Teams often discover too late that report-driven logic doesn't generalize across accounting systems. Numbers drift. Assumptions leak into code. Validation becomes difficult.

Unified's Accounting API is designed to avoid this trap. It exposes transaction-level accounting data as the stable foundation for financial analysis—every debit and credit, already classified, timestamped, and incrementally syncable. Reports remain useful, but only as validation layers, not sources of truth.

This guide shows how to build operating budgets and forecast cash runway directly from normalized transaction data—using documented, non-deprecated fields only—so your calculations stay consistent across accounting providers and defensible as your product scales.

Why transactions—not reports—are the source of truth

Accounting reports (Profit & Loss, Cashflow, Balance Sheet) are derived snapshots. They are useful for validation, but they:

  • vary by provider,
  • may lag behind real activity,
  • and include deprecated fields you should not depend on.

Transactions, on the other hand, represent posted financial reality:

  • every debit and credit,
  • already classified to accounts,
  • timestamped,
  • and incrementally syncable.

For budgets and runway forecasting:

Transactions are the canonical input. Reports are optional validation.

What this guide covers

  • Fetching and incrementally syncing transactions
  • Interpreting debit/credit semantics correctly
  • Classifying transactions using the chart of accounts
  • Building monthly budgets from historical spend
  • Calculating burn rate
  • Forecasting runway
  • Validating results using non-deprecated report totals (optional)

Prerequisites

  • Node.js 18+
  • A Unified workspace
  • An Accounting integration authorized for a customer
  • A valid connectionId
  • Unified API key

Step 1: Initialize the SDK

import { UnifiedTo } from '@unified-api/typescript-sdk';

const sdk = new UnifiedTo({
  security: {
    jwt: process.env.UNIFIED_API_KEY!,
  },
});

All requests are scoped by connectionId, which represents one authorized customer integration.

Step 2: Understand the Transaction data model (critical)

Unified exposes transactions via AccountingTransaction.

Key fields you must understand:

  • total_amount
    • positive = DEBIT
    • negative = CREDIT
  • account_idAccountingAccount
  • type → provider-specific transaction type (Invoice, Bill, Transfer, etc.)
  • created_at / updated_at
  • lineitems[] (optional)
  • currency

This sign convention is the foundation of every calculation that follows.

Step 3: Fetch transactions incrementally

Use listAccountingTransactions to sync transactions.

const transactions = await sdk.accounting.listAccountingTransactions({
  connectionId,
  limit: 50,
  offset: 0,
  updated_gte: '2026-01-22T22:41:23.849Z',
  sort: 'updated_at',
  order: 'asc',
  query: '',
  contact_id: '',
  fields: '',
  raw: '',
});

Pagination rules

  • Default limit: 100
  • Maximum limit: 100 on most endpoints
  • Pagination uses offset
  • Stop when results.length < limit

Incremental sync

Use updated_gte to fetch only transactions updated since your last checkpoint. This is the recommended polling strategy.

Step 4: Load the chart of accounts

Accounts tell you what kind of transaction you're looking at.

const accounts = await sdk.accounting.listAccountingAccounts({
  connectionId,
  limit: 50,
  offset: 0,
  updated_gte: '2026-01-22T22:41:23.849Z',
  sort: 'updated_at',
  order: 'asc',
  query: '',
  org_id: '',
  fields: '',
  raw: '',
});

Key account types:

'EXPENSE'
'REVENUE'
'BANK'
'CREDIT_CARD'
'LIABILITY'
'EQUITY'
'FIXED_ASSET'

For runway forecasting, you will primarily use:

  • EXPENSE accounts (burn)
  • BANK / CREDIT_CARD accounts (cash movement)
  • REVENUE (optional, for net burn)

Step 5: Filter to 'true operating spend'

Not every transaction should count toward burn.

  1. Account type
    • Include: EXPENSE
    • Exclude: EQUITY, LIABILITY, FIXED_ASSET
  2. Sign
    • Expenses are positive debits
    • Refunds/credits appear as negative values
  3. Transaction type
    • Exclude internal transfers (Transfer, Deposit)
    • Exclude balance-only movements

Example filter logic:

const expenseTransactions = transactions.filter(tx => {
  const account = accountMap[tx.account_id];
  return (
    account?.type === 'EXPENSE' &&
    tx.total_amount > 0
  );
});

Step 6: Bucket spend by month (budget building)

Group transactions into calendar months.

function monthKey(date: string) {
  return date.slice(0, 7); // YYYY-MM
}

const monthlySpend = {};

for (const tx of expenseTransactions) {
  const month = monthKey(tx.created_at);
  monthlySpend[month] = (monthlySpend[month] || 0) + tx.total_amount;
}

This gives you actual historical spend, not estimates.

Step 7: Build category-level budgets (optional)

If categories are present, you can budget by function.

const categories = await sdk.accounting.listAccountingCategories({
  connectionId,
  limit: 50,
  offset: 0,
  updated_gte: '2026-01-22T22:41:23.849Z',
  sort: 'updated_at',
  order: 'asc',
  query: '',
  parent_id: '',
  fields: '',
  raw: '',
});

Aggregate spend by category_id across transactions to produce budgets like:

  • Marketing
  • R&D
  • G&A

Note: Category availability varies by provider. Your code must tolerate missing categories.

Step 8: Calculate burn rate

Burn rate is typically computed as:

averageMonthlyBurn =
  sum(lastNMonthsSpend) / N;

Example (3-month burn):

const recentMonths = Object.values(monthlySpend).slice(-3);
const burnRate =
  recentMonths.reduce((a, b) => a + b, 0) / recentMonths.length;

You now have empirical burn, derived directly from transactions.

Step 9: Forecast runway

Runway is:

runwayMonths =
  currentCash / monthlyBurn;

Getting current cash (two options)

Option A (preferred):

  • Sum BANK and CREDIT_CARD account balances.
const cash = accounts
  .filter(a => a.type === 'BANK' || a.type === 'CREDIT_CARD')
  .reduce((sum, a) => sum + (a.balance || 0), 0);

Option B (validation):

  • Use the Cashflow report (see below).

Step 10: Validate with non-deprecated reports (optional)

Profit & Loss (validation only)

Retrieve a P&L report by ID:

const pl = await sdk.accounting.getAccountingProfitloss({
  connectionId,
  id: profitlossId,
  fields: '',
  raw: '',
});

Use only:

  • income_total_amount
  • expenses_total_amount
  • net_income_amount
  • expenses_sections[].accounts[].transaction_ids

Do not use deprecated fields.

Balance Sheet (cash anchor)

const bs = await sdk.accounting.getAccountingBalancesheet({
  connectionId,
  id: balancesheetId,
  fields: '',
  raw: '',
});

Useful fields:

  • net_assets_amount
  • assets[]
  • liabilities[]

Cashflow (cross-check)

const cf = await sdk.accounting.getAccountingCashflow({
  connectionId,
  id: cashflowId,
  fields: '',
  raw: '',
});

Use:

  • cash_beginning_amount
  • cash_ending_amount
  • net_change_in_cash_amount

Reports should confirm, not drive, your forecast.

Pagination, rate limits, and operations

  • All list endpoints use limit / offset
  • Default and max page size is 100
  • Rate limits come from:
    • the underlying provider
    • Unified's fairness limits
  • Rate limit errors return HTTP 429
  • Exponential backoff
  • Add jitter
  • Cap retries
  • Prefer webhooks for continuous sync

Summary

Using Unified's Accounting API, you can:

  • Build budgets from real posted transactions
  • Compute burn rate empirically
  • Forecast runway with defensible assumptions
  • Validate results using non-deprecated financial reports
  • Avoid provider-specific reporting quirks

The key principle is simple:

Transactions first. Reports second. Assumptions last.

Start your 30-day free trial

Book a demo

All articles