forecast.run
WHAT-IF / scenario lane: runs monthly data YOU supply through a seeded 5,000-path simulation of monthly revenue and costs; bands are simulation outputs, not calibrated probabilities. The paths are clustered into 3-7 outcome bands for the next 6 months, with per-month revenue and cost percentiles and deathRisk, the share of paths the engine marks closed (a distress signal, not a cash-out date). Amounts are in currency (major units, default USD): 1500 or "1500.00" - never cents. currency is one of 16 codes (USD, EUR, GBP, JPY, CNY, AUD, HKD, INR, SGD, MYR, IDR, THB, PHP, VND, BND, MMK); leave it out and USD is assumed, and the output says currency_assumed: true. The engine runs in USD; every output figure converts back into currency at fx_rate_to_usd (USD per 1 unit), named in the top-level currency field. Send cash_balance (the bank balance, negative for an overdraft) with cash_as_of (its date, YYYY-MM-DD, within 45 days) and the cash block adds runway, cash-out share, lowest balance and health from that balance plus each path's revenue minus costs (balance_source 'request'); without them it carries net-flow figures only and its next_step says what to send. Send declared_totals {revenue, costs}, the totals your own source shows for the months sent, and the sums are checked on the server; totals_check echoes them. history_months, shallow (true under 5 months) and blend {yours_pct, peers_pct} (null when shallow) say how much of the result is your own history. Earlier releases removed the per-band cash range and the per-month cash percentiles (they started from an invented balance) and interpreted_money.unit (the top-level currency field replaces it). Refuses with VALIDATION_ERROR and a fix per field: fewer than 2 or more than 120 rows, a date that is not YYYY-MM, revenue or costs sent in cents or as text that is not a number (an exponent such as 1e5, letters other than one currency code or symbol, or a comma that does not group thousands), an amount whose code or symbol names another currency than the one it is read in (currency; cash_balance is read in cash_currency when sent: send that currency, or the amount in it; never converted), no month with revenue, or a currency outside the list; SIMPLER_FORECAST_CASH_WITHOUT_DATE when cash_balance comes without cash_as_of (send both); SIMPLER_FORECAST_CASH_AS_OF_INVALID when cash_as_of is not a real YYYY-MM-DD date or comes without cash_balance; SIMPLER_FORECAST_GRID_TOTALS_MISMATCH when a declared total and the months' sum are more than 0.5% apart (correct the months or the total and resend). Refuses with SERVICE_UNAVAILABLE SIMPLER_FORECAST_FX_UNAVAILABLE when no rate for a non-USD currency is available: retry shortly, or pass USD amounts. Send drivers (at most 20) to ask what if: each is {kind, start, end?, amount?, pct?}, kind one of revenue_pct, cost_pct, revenue_step, cost_step, hire, one_off_in, one_off_out, lose_customer, loan_drawdown, loan_repayment, festive_month. start and end are YYYY-MM (the whole month) or YYYY-MM-DD (that day); revenue_pct, cost_pct, lose_customer and festive_month take months only; start must fall inside the forecast months, and end may be left out to run to the last one. amount is in currency (major units): per month for revenue_step and cost_step (negative for a cut), hire (the full monthly cost), loan_repayment and lose_customer (that customer's average monthly receipts; forecast.run reads no records, so customer_ref is refused here), and the whole sum for one_off_in, one_off_out and loan_drawdown, which hit one month or one day and take no end. In text, commas only group thousands ("4,000.00"); a currency code or symbol may lead or follow ("SGD 4,000") only when it is the currency the amount is read in, and one naming another currency is refused, never converted; no exponent or other letters. The same applies to every amount this tool takes. pct is -100 to 500, not zero, for revenue_pct, cost_pct and festive_month (a revenue_pct on one month you pick: your assumption, not learned seasonality). loan_drawdown and loan_repayment move the balance only, never revenue, costs or net. With drivers the answer adds base and scenario, each with monthly revenue, costs, net, financing and balance percentiles and a cash block, on the same simulated paths; drivers change money on those paths, not growth or closures (no knock-on effects). Refuses with VALIDATION_ERROR and simpler_code SIMPLER_FORECAST_DRIVER_INVALID (fix the field as it says; a start outside the 6 months after the last month sent refuses before anything runs or is counted), SIMPLER_FORECAST_DRIVER_LIMIT (more than 20 drivers: send fewer) or SIMPLER_FORECAST_DRIVER_UNKNOWN_CUSTOMER (customer_ref sent: send amount instead, or use forecast.read view scenarios). No forecast and nothing you send is stored, and your business records are NOT read (the one setting read is the workspace's time zone, to know today; like every tool call, the call itself is counted in usage): for YOUR stored forecast call forecast.run_stored; read the last stored result with forecast.read. cash_currency (optional, one of the same codes) names the currency of cash_balance when it is not currency; the balance converts into currency at the cached rate, and interpreted_money.cash_balance echoes both. Your own accounting system's rows (for example from a Xero, QuickBooks or Zoho connection) add the next 13 weeks: send any of open_receivables (at most 2,000: {client_ref?, issued_at?, due_date, amount, currency?}), paid_history (at most 5,000: {client_ref?, due_date, paid_at, amount?}), open_payables (at most 2,000: {vendor_ref?, due_date, amount, currency?}) and recurring (at most 100: {direction in or out, amount, currency?, cadence weekly, monthly or quarterly, next_date}); an empty list counts as sent. Dates are YYYY-MM-DD; amounts are major units of the row's currency (default currency), converted into currency at the cached rates; client_ref and vendor_ref are your own ids, at most 60 characters, and nothing is looked up by them. The answer then adds weekly, shaped like forecast.read view weekly: Monday to Sunday weeks in weekly.bucket_timezone (the workspace's time zone, the one setting read, to know today), 2000 seeded draws (the same rows give the same answer all week), receipts, payments and net at p10, p50 and p90, top_contributors, payables_counted and accounting_inputs (rows used per list), from those rows alone: no typical weekly sales or spend are drawn, so send regular money as recurring items. Open receivables are paid per their receipt timing, and receipt_timing lists the 50 largest with source, median_payment_date, paid_in_13_weeks_pct, paid_after_13_weeks_pct and never_paid_pct, plus sources and learned_from: the timing is learned from paid_history (source client or tenant), else it is the labelled prior (source prior). recurring in items fall due on each date and are paid on the same curve; open payables and recurring out items leave on their date. With cash_balance and cash_as_of within 31 days the weeks carry balance bands, below_zero_pct and lowest_day, and cash_on (YYYY-MM-DD inside the 13 weeks) adds weekly.cash_on: that day's balance at p10, p50 and p90 and the dated items due by then. drivers apply to the monthly forecast only. These refuse before anything runs: PAYLOAD_TOO_LARGE SIMPLER_FORECAST_AGENT_INPUT_TOO_LARGE when a list is over its cap (send the largest amounts, or the most recent paid_history, up to the cap); VALIDATION_ERROR SIMPLER_FORECAST_AGENT_INPUT_INVALID naming each wrong field (a date that is not YYYY-MM-DD, an amount not above zero, a ref over 60 characters, a key the row does not take, an amount whose code or symbol names another currency than the row's currency (or currency when the row sends none), issued_at after due_date or after today, paid_at after today, a recurring next_date before this week's Monday): fix each as it says; VALIDATION_ERROR naming cash_on when none of the four lists is sent, or cash_currency when cash_balance is not; SIMPLER_FORECAST_CASH_ON_OUT_OF_RANGE when cash_on is outside the 13 weeks (send a date inside them); SIMPLER_FORECAST_WC_NEEDS_CASH when cash_on comes without a cash_balance dated within 31 days (send one); SERVICE_UNAVAILABLE SIMPLER_FORECAST_FX_UNAVAILABLE when a row's currency or cash_currency has no rate (retry shortly, or send it in currency). Every call counts toward forecast.run's own 60 engine runs an hour per workspace, apart from forecast.read's views and the Forecast page: one for the monthly run and one more when the 13 weeks are drawn. Past it the call refuses with RATE_LIMITED, simpler_code SIMPLER_FORECAST_READ_COMPUTE_LIMITED and a next_step: try again within the hour. Free internal compute, ~50ms without the lists. Streaming-capable: send Accept: text/event-stream for phase progress (started/fitted/simulated/clustered/complete).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| cash_on | No | Optional, with any of open_receivables, paid_history, open_payables or recurring: a date YYYY-MM-DD inside the 13 weeks; needs cash_balance and cash_as_of within 31 days. The answer adds that day's end-of-day balance. | |
| drivers | No | Optional: at most 20 what-if drivers, applied to the same simulated paths as the base. | |
| currency | No | The currency of every amount sent, one of 16 codes. Leave it out for USD; the output then says currency_assumed: true. | |
| recurring | No | Optional: repeating money, at most 100 rows (more refuses with SIMPLER_FORECAST_AGENT_INPUT_TOO_LARGE). direction out leaves on each date; direction in falls due on each date and is paid on the receipt-timing curve, like a receivable. | |
| cash_as_of | No | Optional: the date of cash_balance as YYYY-MM-DD, within the last 45 days. Send it with cash_balance. | |
| monthlyData | Yes | 2-120 monthly rows, oldest first: { date: 'YYYY-MM', revenue, costs, customers? }. revenue and costs are amounts in currency (major units, default USD): 1500 or "1500.00" - never cents. | |
| cash_balance | No | Optional: the bank balance on cash_as_of in currency (major units, negative for an overdraft). Send it with cash_as_of. | |
| paid_history | No | Optional: invoices already paid, at most 5,000 rows (more refuses with SIMPLER_FORECAST_AGENT_INPUT_TOO_LARGE). The receipt-timing curve learns from the days between due_date and paid_at; with no paid history at all the labelled prior times every receivable (source 'prior'). | |
| cash_currency | No | Optional: the currency of cash_balance when it is not currency. Send it with cash_balance and cash_as_of. | |
| open_payables | No | Optional: bills you owe, at most 2,000 rows (more refuses with SIMPLER_FORECAST_AGENT_INPUT_TOO_LARGE). Each is paid on its due date (one already due is paid on the first modelled day). | |
| declared_totals | No | Optional: the revenue and costs totals your own source shows for the months sent, in currency. A total more than 0.5% away from the months' sum refuses with SIMPLER_FORECAST_GRID_TOTALS_MISMATCH. | |
| open_receivables | No | Optional: invoices your own accounting system shows unpaid, at most 2,000 rows (more refuses with SIMPLER_FORECAST_AGENT_INPUT_TOO_LARGE). Each is paid on the receipt-timing curve from its due date. |