Build Forecast
POST/api/inventory-forecasting/build
Build a replenishment forecast for one or more suppliers and return recommended order quantities per supplier, with a full per-line breakdown of how each number was derived.
purchase-orders:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token.
FORECAST TYPES (forecast_type)
- with_sales_history: derive demand from historical sales velocity over the chosen window. The most common mode.
- target_stock_level: order up to a fixed target quantity per product (target_quantity), or up to each product's minimum stock level when use_min_stock_level_as_target is true. Velocity is not used to set the target.
- fill_backorders: order only what is needed to clear outstanding backorders. Baseline and trend settings do not apply.
BASELINE METHODS (baseline_method) - how the daily velocity baseline is derived from sales history (applies to with_sales_history)
- flat (default): plain mean over the window (total units / days covered). No trend. Legacy behaviour - stable and predictable. Use when demand is roughly level.
- recency_weighted: weights recent history more heavily than older history, so a recent shift in demand is reflected faster without projecting a hard trend line. Use for choppy demand that has recently stepped up or down. Tune with trend_half_life_days.
- damped_trend: fits the recent growth (or decline) trend and projects it forward, attenuated by trend_damping_factor so a short surge does not overshoot into over-ordering. Use for a SKU on a sustained growth or decline curve.
DAMPED-TREND CONTROLS
- trend_damping_factor (0..1, default 0.85): the damping factor phi. Each period further out keeps only phi of the previous period's growth, so the projected trend flattens instead of compounding forever. Lower = more damping (closer to flat mean); 1.0 = undamped.
- trend_max_growth_pct (>= 0, default 100): caps how far the trend may lift the baseline above the flat mean. 100 means the trend may at most double the flat mean; 0 disables any lift. When the cap binds, the line reports trend_capped: true.
- trend_half_life_days (>= 1, nullable, default 21): reserved tuning for recency_weighted - the age at which a day's weight halves.
SPARSE-HISTORY FALLBACK A trend needs enough signal to be trustworthy. If a product has fewer than roughly 8 weeks of history or fewer than 8 orders in the window, the trend is skipped and the baseline falls back to the flat mean for that product (trend_factor 1.0), regardless of baseline_method. This stops a couple of early sales from projecting a runaway trend.
ANOMALY CLEANSING (exclude_detected_anomalies, default false) When true, days already resolved as excluded or smoothed anomalies for a product are dropped (excluded) or replaced with their expected value (smoothed) before the baseline is computed. This de-contaminates one-off promo spikes and stockout gaps so they do not distort ordinary velocity. Detect and resolve anomalies first (see the Anomalies folder). Each line reports how many days were affected via inputs.anomaly_days_excluded and inputs.anomaly_days_smoothed.
DEMAND MODIFIERS (demand_modifiers[]) An array of adjustments layered onto the baseline. Each entry has a type:
- percentage: scale demand by (1 + value/100), e.g. value 10 = +10%.
- seasonal / event: a bounded uplift or reduction over a date range (start_date, end_date) for a known seasonal peak or one-time event; value is the percent change.
- override: replace the daily average outright with value.
- promo_lift: re-apply a previously measured promotion. Set promo_window_id to a promo window whose lift has been measured; the server hydrates that window's live measured lift and applies it as a MULTIPLICATIVE factor on the current baseline. Because it multiplies the CURRENT baseline (not a stored absolute number), the same promo automatically re-scales as the business grows. Send value 0 - it is ignored and hydrated server-side. Optional per-modifier fields: apply_to (all or a scope key), name (label), period_key, start_date, end_date, product_id (limit to one product), promo_window_id (promo_lift only).
FILTERS (product_filters, sales_filters) Both accept a filter_groups tree: a conjunction (and / or) plus a children array of conditions and nested groups. Each condition has column, operator, and value. product_filters narrows which products are forecast; sales_filters narrows which historical orders feed the velocity baseline. A legacy filters.filterSet shape is also accepted for older saved configurations. Operators: is, is_not, contains, does_not_contain, starts_with, ends_with, is_empty, is_not_empty, is_one_of, greater_than, less_than, between. Example: {"filter_groups": {"conjunction": "and", "children": [{"type": "condition", "condition": {"column": "order_status", "operator": "is_one_of", "value": ["shipped", "fulfilled"]}}]}}
DECOMPOSITION RETURNED PER LINE (inputs object) Each forecast line's inputs object exposes the multiplicative decomposition:
- base_flat: the flat mean before any trend.
- trend_factor: the multiplicative trend baked into the baseline (1.0 = none).
- baseline_method: which method produced the baseline.
- trend_capped: true if trend_max_growth_pct clamped the trend.
- anomaly_days_excluded / anomaly_days_smoothed: how many history days were dropped / replaced by anomaly cleansing.
- promo_factor: the multiplicative promo lift applied (null when no promo_lift modifier applied).
- promo_window_name: the promo window that supplied the lift (null if none).
- promo_current_baseline: the baseline the promo factor multiplied. The effective daily_average is base_flat x trend_factor x (promo_factor or 1), then any percentage/event/override demand modifiers.
SCENARIOS (copy a body variation for each)
- Flat baseline (default / legacy): omit baseline_method or send "flat". Velocity is the plain window mean; inputs.trend_factor is 1.0 and inputs.base_flat equals daily_average.
- Damped-trend for a growing SKU: baseline_method "damped_trend", trend_damping_factor 0.85, trend_max_growth_pct 100. Recent growth is projected forward but damped and capped at 2x the flat mean; inputs.trend_factor rises above 1.0 and inputs.trend_capped reports whether the cap bound.
- Recency-weighted: baseline_method "recency_weighted", trend_half_life_days 21. Recent days count more; no hard trend line is projected.
- Anomaly-cleansed baseline: exclude_detected_anomalies true (after resolving anomalies as excluded/smoothed). Promo spikes and stockout gaps are removed before the mean is taken; inputs.anomaly_days_excluded / inputs.anomaly_days_smoothed report the count.
- Re-apply a past promotion: add a demand_modifiers entry of type "promo_lift" with promo_window_id set to a measured promo window and value 0. The window's live lift multiplies the current baseline, so it auto-scales with growth; inputs.promo_factor, inputs.promo_window_name, and inputs.promo_current_baseline are populated.
- Target stock level / fill backorders: forecast_type "target_stock_level" (with target_quantity, or use_min_stock_level_as_target true) orders up to a fixed target; forecast_type "fill_backorders" clears backorders only. Baseline and trend settings do not apply to fill_backorders.
MINIMUMS enforce_minimum_order_value / enforce_minimum_order_quantity: when true, the per-supplier minimums block flags whether the forecast totals fall below the supplier's minimum_purchase_order / minimum_purchase_order_quantity. Display-only here; enforcement is applied at PO creation time (POST /api/inventory-forecasting/purchase-orders).
Each line also carries a confidence object (tier high/medium/low/new, 0-100 score, drivers, reasons, actions), a prediction_interval (point/lower/upper at a service level), and a plain-language rationale.
Returns 422 if too many products are found for any supplier.
PER-LINE FIELDS OF NOTE
- is_bom_component (boolean): true when the product is a component line on a live bill of materials. Such products have no sales of their own — their demand is driven by finished-goods production — so a 'nothing to order' verdict for them means demand planning is the wrong tool, not that they are well stocked. Plan component requirements through manufacturing planning (MRP) instead.
UNIFIED MAKE / BUY / TRANSFER PLAN Send warehouse_ids (or omit supplier_ids, or set include_make=true) to build one plan for everything you sell instead of a per-supplier purchase forecast. Suppliers become an optional filter on purchased products. Products with an active BOM are planned as builds (Make), their ingredients and packaging are sized from those builds (BOM explosion with unit conversion, scrap and yield), warehouses fed by the distribution network get transfers, and products with no BOM or supplier are shown as unsourced. The plan is built by a background job: the response is 202 with run_id and tracked_job_log_id — poll Get Plan for the result.
- warehouse_ids (integer[]): warehouses to plan. Required when no supplier_ids are sent.
- horizon_days (integer 7–365, default 90): how far ahead the plan's forecast looks.
- include_make (boolean): plan products with an active BOM as builds. Omit to decide from whether any active BOM exists. A request with supplier_ids and no warehouse_ids (and include_make not true) runs the per-supplier forecast exactly as before. Each line of that forecast now carries has_active_bom (true when the product is made in-house).
FINAL QUANTITY AND DATED EVENTS
final_quantity on a line is a whole number for products ordered in whole units, and a decimal (for example 2.7) when the configuration's rounding keeps decimals. inputs.event_units (number, present when dated events apply) is the extra units dated events — a promo window's next occurrence or a manual event — add on the event days inside the order's cover; they are added once to the order instead of lifting the everyday daily average.
Request
Responses
- 200
- 202
- 401
- 403
- 422
- 429
OK
Response Headers
Accepted
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden — the token lacks a required scope, the endpoint is not available to API tokens, or the user behind the token lacks the permission. A human must adjust the token scopes or user permissions; do not retry.
Unprocessable Entity
Response Headers
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.