LedgerBee Developer
  • Getting started
  • Conventions
  • Products
  • Advisor firms
  • Configuration
  • API Reference
Subscriptions
Payment flowCard paymentsProducts & PricingDiscountsProduct Entitlements
Billing documents
Accounting
    Journal entriesSettlementsBank transactionsBudgets
WebhooksPartner-Hosted Checkout
Customer Portal
Accounting

Budgets

Read your company's budgets over the LedgerBee public API (base URL https://api.ledgerbee.com/api/v1), and overwrite a budget's figures from an edited copy of its Excel workbook. Budgets are created and structured in the LedgerBee app. Authenticate with the x-api-key header. There are two scopes:

  • budgets-read — every budget read, and the workbook export.
  • budgets-write — uploading a workbook, as a dry run or a publish.

The Budgets module is licensed: calls return 403 LICENSE.REQUIRED unless your company holds the Budgets license.

A budget plans one accounting year as a grid of amounts per ledger account and month. Every amount crosses the wire as a decimal string ("160000.00") in your company's base currency, which each response states as currency.

Operations

OperationEndpointScope
List budgetsGET /v1/budgetsbudgets-read
Runway headlineGET /v1/budgets/runwaybudgets-read
Get a budgetGET /v1/budgets/{budgetId}budgets-read
List scenariosGET /v1/budgets/{budgetId}/scenariosbudgets-read
Scenario gridGET /v1/budgets/{budgetId}/scenarios/{scenarioId}/gridbudgets-read
Budget vs actualsGET /v1/budgets/{budgetId}/comparisonbudgets-read
Variance vs frozen baselineGET /v1/budgets/{budgetId}/variancebudgets-read
List frozen baselinesGET /v1/budgets/{budgetId}/frozen-baselinesbudgets-read
Get a frozen baselineGET /v1/budgets/{budgetId}/frozen-baselines/{baselineId}budgets-read
Re-baseline proposalsGET /v1/budgets/{budgetId}/rebaseline-proposalsbudgets-read
Export the Excel workbookGET /v1/budgets/{budgetId}/excelbudgets-read
Upload a workbook (dry run or publish)POST /v1/budgets/{budgetId}/excelbudgets-write

The list is paginated (page, limit), filterable by status, accountingYearId, and search (prefix match on name), and sortable by name, createdAt, or updatedAt. Endpoint schemas are in the API Reference.

TerminalCode
curl 'https://api.ledgerbee.com/api/v1/budgets?status=ACTIVE' \ -H 'x-api-key: <your-key>'

The budget grid

GET /v1/budgets/{budgetId} returns the full grid: lines (one cell per account and period), the accounting year's periods, driverRows (non-monetary planning quantities such as headcount, with numeric values), and per-budget display metadata (rowOverrides, rowNumbers).

Cells in periods whose status is CLOSED carry the booked actuals from the ledger rather than the planned amount, marked source: "ACTUALS" — the same rolling-forecast view the app shows. A cell filled from the subscription forecast is marked source: "SUBSCRIPTION"; a cell with source: null is user-entered.

Cells can carry a formula (for example =baseline*1.05 or =#3*2); the resolved value is always cached in amount, so you never need to evaluate formulas. #N references resolve through the rowNumbers array. Pass ?includeBaseline=true to add each cell's prior-year realised amount as baseline.

TerminalCode
curl 'https://api.ledgerbee.com/api/v1/budgets/0197a943-2325-7829-b835-b6c71a293066?includeBaseline=true' \ -H 'x-api-key: <your-key>'

Scenarios

A scenario is an assumption overlay (driver overrides and line toggles) on top of the budget's base grid. The scenario flagged isBaseline: true is the base grid with no overlay; it is created the first time scenarios are used in the app, so a budget where they have never been opened returns an empty list — the base data is the budget detail itself.

GET /v1/budgets/{budgetId}/scenarios/{scenarioId}/grid returns the fully resolved grid with the scenario's assumptions applied. When the response has hasCycle: true, the overlay could not resolve because of a formula cycle; the offending cells carry error: "BUDGET_FORMULA_CYCLE" and amounts must not be trusted.

A driver row's cells are the budget's own driver cells with the scenario's values applied. When the scenario sets a month the budget left empty, no driver cell exists for it, and the month is listed on the row in scenarioOnlyMonths instead, with scenarioOverrideId, accountingPeriodId and value. scenarioOnlyMonths appears on scenario grids only, and its scenarioOverrideId identifies the scenario change, not a driver cell.

Comparison and variance

Two reports compare the budget against booked actuals, in opposite directions:

ReportComparesVariance
GET .../comparisonLive plan vs actuals, per account and periodbudget − actual
GET .../varianceActuals vs a frozen baseline, closed periods onlyactual − frozenBaseline

The comparison accepts viewMode=FISCAL_YEAR (the budget's accounting year, the default) or viewMode=ROLLING_12_MONTHS with an optional startMonth (YYYY-MM). Each entry's isOverBudget marks the unfavourable direction for that account type: overspend on expense accounts, shortfall on revenue accounts.

The variance report compares against a frozen baseline — an immutable snapshot of the resolved grid taken in the app. It defaults to the most recently frozen baseline; pass ?frozenBaselineId= to pick one from GET /v1/budgets/{budgetId}/frozen-baselines. A budget with no frozen baseline returns an empty data array. Each row's sentiment is unfavourable when spending exceeds the frozen plan (expense accounts) or revenue misses it (revenue accounts).

GET /v1/budgets/{budgetId}/rebaseline-proposals lists the re-baseline suggestions raised when accounting periods close, with their status (PROPOSED, CONFIRMED, or REJECTED). Proposals are confirmed or rejected in the app.

Runway

GET /v1/budgets/runway returns the company-wide cash headline: cash on hand (the sum of bank-account-linked ledger balances), the trailing 3-month burn, and monthsRemaining. Cash and burn are properties of the whole company, so the endpoint is not scoped under a budget id. Pass ?asOf=YYYY-MM-DD to compute as of a past date. When no bank account is linked to a ledger account, hasCashSource is false and status is UNKNOWN.

Updating figures from Excel

You change a budget's figures the way the app's Excel round trip does: download the budget's workbook, change figures in it, and upload it back. The workbook has one row per account (and dimension value) and one column per accounting period.

  1. GET /v1/budgets/{budgetId}/excel returns the workbook (.xlsx).
  2. Change figures in it, in Excel or in code. Keep its hidden sheet: it identifies the budget, its accounting year and the figures the file was exported with.
  3. POST /v1/budgets/{budgetId}/excel?dryRun=true, with the file as the multipart field file, checks the file and answers every figure it would change. Nothing is written.
  4. The same request without dryRun publishes the file.
TerminalCode
curl -o budget.xlsx 'https://api.ledgerbee.com/api/v1/budgets/0197a943-2325-7829-b835-b6c71a293066/excel' \ -H 'x-api-key: <your-key>' curl -X POST 'https://api.ledgerbee.com/api/v1/budgets/0197a943-2325-7829-b835-b6c71a293066/excel?dryRun=true' \ -H 'x-api-key: <your-key>' \ -F 'file=@budget.xlsx'

A dry run and a publish answer the same response. changes lists every figure that changes, with previousAmount and newAmount as decimal strings in currency. A change with origin: "FILE" was typed into the workbook; one with origin: "RECALCULATED" is a formula figure that moves because a figure it reads changed. refusedRows lists the rows that cannot be read against the budget, such as an unknown account number or an amount that is not a number. canPublish is true when no row is refused and at least one figure changes. After a publish, publication describes the upload, which the app keeps in the budget's upload history with its file.

Only the figures typed into the workbook are written. A figure the workbook changes loses its formula or live source; every other figure keeps its own. A figure that changed in LedgerBee after the workbook was exported carries changedSinceExport: true when the workbook overwrites it.

Holding a publish to its dry run

The dry run answers a previewToken. Pass it to the publish as ?previewToken= and the publish is refused with 409 BUDGET_EXCEL_PREVIEW_STALE if the budget changed between the two, so a publish writes only what its dry run showed. Run the dry run again and publish with the new token.

Retries

Send an x-api-idempotency-key header and a retry with the same key is answered with the original response instead of running again. Use a new key for each request: a dry run and the publish after it are two requests.

From an AI assistant

Over MCP an assistant reads and sets figures as data, with no workbook in between. The budgets toolset holds two tools, and is not among the toolsets a new connection enables, so tick it when you connect. Reading needs the budgets-read scope and setting figures needs budgets-write.

  • get_budget returns the budget's periods and one row per account (and dimension value) with a figure per period, in the company's base currency. accountNumbers narrows it to the accounts you name.
  • set_budget_figures takes a list of changes. Each names an accountNumber, a period (the period's id, its start date such as 2026-12-01, or the month it starts in such as 2026-12), a baseCurrencyAmount and, on a budget split by a dimension, a dimensionValueId. A required dryRun and an optional previewToken work as on the REST upload, and the changes go through the same checks and the same publish.

The answer refers to the changes by their position in the request: changes[].changeIndex for each figure a change moves (null for a formula figure that recalculates), refusedChanges for the changes that cannot be applied, and alreadyHeld for the changes whose figure the budget already holds. A publish is recorded in the upload history under the user who connected the assistant, and keeps the budget's workbook with the figures typed in.

Errors

ConditionError codeFix
Company does not hold the Budgets license403 LICENSE.REQUIREDActivate the Budgets module in LedgerBee.
Key lacks the scope403 INSUFFICIENT_PERMISSIONSMint a key with budgets-read, or budgets-write to upload a workbook.
Unknown or foreign budgetId404 BUDGET_NOT_FOUNDUse an id from GET /v1/budgets.
The upload carries no file part400 IMPORT_NO_FILES_PROVIDEDSend the workbook as the multipart field file.
The file is not a .xlsx, .xls or .csv400 IMPORT_FILE_TYPE_NOT_ALLOWEDUpload the .xlsx the export returned.
The file is over 5 MB413Remove rows and columns that are not part of the export.
The workbook holds more than 20,000 rows or 100 columns400 BUDGET_EXCEL_FILE_TOO_LARGERemove rows and columns that are not part of the export.
The file is not an exported budget workbook400 BUDGET_EXCEL_NOT_A_BUDGET_EXPORTStart from GET /v1/budgets/{budgetId}/excel and keep its hidden sheet.
The file was exported from another budget, year or currency400 BUDGET_EXCEL_WRONG_BUDGET, BUDGET_EXCEL_WRONG_YEAR, BUDGET_EXCEL_WRONG_CURRENCYExport this budget's workbook and edit that.
The budget's dimension changed after the export400 BUDGET_EXCEL_DIMENSION_MISMATCHExport the workbook again.
A publish with refused rows400 BUDGET_EXCEL_HAS_REFUSED_ROWSRead refusedRows from a dry run and fix those rows.
A publish that changes no figure400 BUDGET_EXCEL_NOTHING_TO_PUBLISHThe budget already holds every figure in the file.
The budget changed after the dry run409 BUDGET_EXCEL_PREVIEW_STALERun the dry run again and publish with its new previewToken.
Unknown scenarioId404 BUDGET_SCENARIO_NOT_FOUNDUse an id from the scenarios list.
Unknown baselineId404 BUDGET_FROZEN_BASELINE_NOT_FOUNDUse an id from the frozen-baselines list.
Invalid filter or query valueA 400 validation errorSee the API Reference for accepted values.
Last modified on November 6, 2026
Bank transactionsWebhooks
On this page
  • Operations
  • The budget grid
  • Scenarios
  • Comparison and variance
  • Runway
  • Updating figures from Excel
    • Holding a publish to its dry run
    • Retries
    • From an AI assistant
  • Errors