Forecasts and Stress Tests API
Compute forecasts, save stress-test scenarios, and read runway through the public metrics and reports contract.
Summary
Compute monthly cash-flow or revenue forecasts, model a stress scenario, and request a current-cash runway estimate. These operations use /metrics and /reports relative to the public /v1 base.
Use POST for forecast computation. The registered GET forecast endpoints currently reject ordinary horizonMonths query values before computation. Saved stress-test retrieval also has limitations described below; keep the POST response and your input together.
Capabilities
| Method | Endpoint | Scope | Result |
|---|---|---|---|
| POST | /metrics/forecast/cash-flow | metrics.write | Computes and saves a monthly net-flow forecast; returns 200. |
| POST | /metrics/forecast/revenue | metrics.write | Computes and saves a monthly inflow forecast; returns 200. |
| GET | /metrics/forecast/cash-flow | metrics.read | Registered lookup/computation endpoint with a current query-validation limitation. |
| GET | /metrics/forecast/revenue | metrics.read | Registered lookup/computation endpoint with the same limitation. |
| POST | /metrics/stress-test | metrics.write | Computes and saves a stress-test result; returns 200 without its saved ID. |
| GET | /metrics/stress-test/{id} | metrics.read | Reads a saved result by known ID, subject to team ownership. |
| GET | /reports/runway | reports.read | Returns a JSON number of estimated months when a valid estimate is available. |
Legacy /forecast and /stress-tests paths are not public v1 operations. These endpoints do not expose a general scenarios collection or a public list of saved stress tests.
Prerequisites
Use an authenticated credential for the intended team with the operation's scope. Having metrics.read does not grant POST computation access. Keep the source date window, currency, drivers, and response with each analysis.
For forecast POST requests, send JSON with from, to, and integer horizonMonths from 1 to 60. Optional currency selects the reporting currency. Use valid YYYY-MM-DD dates in chronological order; the request schema accepts strings without enforcing date validity or ordering.
For stress tests, the public horizon is 1–24 months. Optional fields are name, currency, startingCash, and drivers, which defaults to an empty array. The larger horizons and extra driver types used by other planning features are not part of this REST request contract.
Concepts
What the estimates measure
A cash-flow forecast projects monthly net transactions, not an account balance. A revenue forecast uses transaction inflows. Both train on monthly history over the supplied window, expanded to month boundaries. When there is more than one history bucket and the last bucket is the current month or later, that last bucket is omitted from fitting. Forecast dates follow the historical buckets; inspect the returned dates rather than assuming the series always begins next month relative to today.
Forecast responses contain meta.type, currency, the requested period, and horizonMonths, plus series entries with date, value, ciLow, ciHigh, and currency. The bands are model estimates, not guaranteed bounds or actual receipts. An empty series is not a forecast of zero.
Stress tests return monthly inflow, outflow, net, and running balance, with optional balance bands. Summary fields include monthsOfRunway, minBalance, minBalanceMonth, breachDate, and optional probOfBreach. Runway counts to the first projected month with a negative balance; null means no modeled breach within the returned series, not unlimited funding.
Currency and starting cash
Forecast and stress computation prefer explicit currency, then the team's base currency, then the dominant transaction currency over the last 90 days, then the most common bank-account currency, then USD. Always inspect forecast response currency; computation failure can return an empty USD result even when another currency was requested.
Stress starting cash defaults to current enabled cash accounts, including depository and other-asset accounts and untyped manual accounts. It can use stored converted balances or available exchange rates; foreign balances without a usable conversion can be omitted. startingCash overrides today's cash, after which modeled remaining flows for the current month can adjust the opening projection balance. It does not set a historical bank balance or mutate an account.
Workflow
Compute a forecast
Send the same JSON shape to the cash-flow or revenue POST endpoint:
{
"from": "2026-01-01",
"to": "2026-06-30",
"horizonMonths": 6,
"currency": "USD"
}- Check that the team has appropriate transaction history for the selected months.
- Select cash flow for net movements or revenue for inflows.
- POST the request, optionally using a new
Idempotency-Keyfor this exact operation. - Check response status, returned currency, dates, nonempty series, and confidence bands.
- Store the response with the input; a successful response contains no forecast record ID.
Apply stress drivers
Each object needs its type discriminator. Use fractional changes: pct: -0.2 means a 20% revenue reduction; pct: 0.15 means a 15% expense increase. FX delta also uses a fraction, not whole percentage points.
| Driver | Fields | Effective behavior and limits |
|---|---|---|
RevenueShock | pct, positive integer months; optional nonnegative integer startOffset | Scales revenue by 1 + pct from the selected zero-based month, floored at zero. |
CollectionDelay | Integer days | Use 1–180. Zero passes the public schema but fails downstream validation. Delays are an aggregate monthly approximation, not a due-date rewrite. |
ExpenseSurge | pct; optional positive integer months, nonnegative integer startMonthOffset | Scales expenses by 1 + pct. The accepted startMonthOffset is currently discarded before execution, so the surge starts at the first month. |
OneTimeExpense | amount, date | Use a positive amount and YYYY-MM-DD. Adds expense only when that month exists in the projected series. Zero passes initial validation but fails downstream. |
HiringPlan | Integer headcountDelta, monthlyCostPerHead; optional nonnegative startMonthOffset, positive rampMonths | Use positive cost and integer offsets/ramp. Adds or removes monthly cost progressively; resulting expenses are floored at zero. Zero cost fails downstream validation. |
FxShock | pairs of { from, delta }; optional mode | Use a nonempty currency code. Exposure shares scale foreign flows; missing exposure means no change. Both accepted modes, spot and sticky, currently use the same calculation. |
For example, pass drivers: [{ "type": "RevenueShock", "pct": -0.2, "months": 3 }] with the required dates and horizon. Do not send a generic assumptions object or expect extra planning-workbench drivers to work through this endpoint.
Drivers apply in submitted order, so combining a fixed expense and a scaling change can produce different results when reordered. Collection delay moves a fraction of each month's revenue into the following month, including amounts previously shifted into that month; amounts moved beyond the horizon disappear from the displayed period. The fraction is capped at 30 days, so larger accepted values do not model a distinct multi-month delay.
Behavior specification
Forecast lookup and persistence limits
The GET routes require the forecast inputs as query parameters, but their initial validator expects a numeric horizonMonths while URL query values are strings. A normal request therefore returns 400 before reaching the handler. Use POST instead.
The lookup handler, when reached, selects the newest saved record matching team, type, horizon, dates, and currency. It has no age limit or transaction-change invalidation. A cache miss, invalid saved series, or refresh=true leads to computation and a new save; it is not a read-only lookup.
Forecast computation catches calculation/data-read errors and can return an empty series with USD metadata. A later save can succeed, leaving an HTTP 200 response that does not prove successful modeling. Persistence errors still fail the request. Reject empty or unexpected-currency results in your integration before presenting an estimate.
Stress results and retrieval limits
Stress computation uses a hybrid deterministic and probabilistic model with 2,000 simulations. Balance bands are the simulated P10/P90 range, and breach probability estimates the share of paths with a negative balance. Fresh computations can differ because the public endpoint does not expose a simulation seed. These fields do not describe a guaranteed future outcome.
POST saves a result but returns only series and summary: it does not return the saved ID, name, original drivers, or currency metadata. Retain your input. A known saved ID can be read only for its owning team; missing or foreign records return 404.
GET returns the saved result, not the original request or a new computation. It validates stored results before returning them, so incompatible or malformed stored data can fail. Do not build a POST-then-GET workflow that assumes the POST response supplies a saved ID.
Retrying computation
An optional Idempotency-Key of at most 128 characters supports replay of a successfully cached POST response for 24 hours. Identity, method, path, query, and key identify the replay; the JSON body is not compared. Use a new key whenever any input changes, or an earlier result can be replayed for a different body.
A concurrent request can receive 409, and an unavailable replay guard can return 503. Replay storage is best effort after execution, and locks have a finite lifetime; this is not permanent exactly-once computation. A request without a key, an expired replay, or an uncached result can produce another saved record. See Errors for retry handling.
Runway report
Provide from, to, and preferably explicit currency. This report uses current eligible cash balances divided by average monthly outgoing transaction amounts, rounded to whole months. It does not subtract revenue from expenses to calculate net burn. The expense window expands to complete months, includes zero-expense months in the average, and excludes internal, excluded-status, and excluded-category transactions.
The returned successful value is a number, without echoed currency or date metadata. Preserve the request separately. Cash can include converted balances; review missing conversion data before relying on the total.
A same-calendar-month range, missing base currency without an override, or nonpositive average burn produces no internal estimate. The current public response schema accepts only a number, so these cases can return 500 instead of a usable null. Do not interpret a failed response as zero months or infinite runway. This historical-expense ratio is different from the stress test's first projected cash-breach month.
Diagrams
Rendering diagram…
Screenshots
Not applicable: this page describes an API or a non-visual workflow rather than an application screen.
Verification
- Compute a forecast from known synthetic history. Confirm dates, returned currency, nonempty series, and the distinction between net flow and cash balance.
- Exercise no-history and invalid-date cases. Treat an empty series or unexpected USD fallback as unavailable evidence, even with a successful status.
- Confirm ordinary GET forecast queries encounter the documented validation limit; use POST for computation.
- Compare a baseline stress result with a fractional revenue shock. Preserve the input because the response omits the saved ID and currency.
- Check driver edge cases, including zero collection delay, delayed expense surge, and an expense dated outside the horizon. Do not infer effective behavior from accepted fields alone.
- Replay an unchanged POST with its key and inspect replay headers. Use a different key for changed inputs and allow fresh probabilistic results to differ.
- If testing saved stress retrieval, use an independently known same-team ID and compare the returned result with the saved response. Confirm a foreign-team ID is not returned.
- Check runway over multiple calendar months and a same-month range. Distinguish a usable number from response validation failure; compare the expense basis with the request's cash/currency context.