Patterns & Segmentation
Some of the most common things people want — a forecast per customer segment, a revenue total split
into its parts, a churn rate projection — don't need a special endpoint. This page shows how to build
each one with /v2/forecast and
/v2/batch/forecast.
Forecast each segment separately
Use tenant_context to split one metric
across a dimension. A series is identified by the pair
identifier +
tenant_context, so
mrr on
plan-pro and
mrr on
plan-free are two independent series,
each with its own history and its own accuracy tracking.
Despite the name, it isn't only for multi-tenancy. Use it for any dimension you want to forecast along: plan, region, store, channel, cohort, acquisition month, device type.
Batch accepts up to 100,000 series per call on a paid plan (10 on the free tier), so a whole segmentation fits in one request:
curl -X POST "https://forecastapi.com/v2/batch/forecast" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"series": [
{
"identifier": "mrr",
"tenant_context": "plan-pro",
"data": [{"date": "2024-01-01", "value": 41200}, {"date": "2024-02-01", "value": 43900}]
},
{
"identifier": "mrr",
"tenant_context": "plan-free",
"data": [{"date": "2024-01-01", "value": 2100}, {"date": "2024-02-01", "value": 2450}]
},
{
"identifier": "mrr",
"tenant_context": "plan-enterprise",
"data": [{"date": "2024-01-01", "value": 88000}, {"date": "2024-02-01", "value": 91500}]
}
],
"frequency": "M",
"periods": 6,
"data_type": "revenue"
}'
Results come back in results.data, keyed
by entity ID rather than by your identifier — the same identifier can appear under several segments in
one batch, so it wouldn't be unique. Each result echoes its own
identifier and
tenant_context, so match on that pair.
Choose your segments before you send history
Each segment builds up its own history and accuracy record. Re-cutting the same data under new
tenant_context values later creates
new series that start from scratch — it won't relabel the ones you already have.
Break a total into components
To forecast the parts of a total — new, expansion, contraction and churned revenue, or a split by category, channel or region — send each part as its own series and add them up on your side. You get a forecast per component instead of one aggregate number.
{
"series": [
{"identifier": "revenue-new",
"data": [{"date": "2024-01-01", "value": 12000}, {"date": "2024-02-01", "value": 13100}]},
{"identifier": "revenue-expansion",
"data": [{"date": "2024-01-01", "value": 4300}, {"date": "2024-02-01", "value": 4650}]},
{"identifier": "revenue-contraction",
"data": [{"date": "2024-01-01", "value": -1800}, {"date": "2024-02-01", "value": -1720}]},
{"identifier": "revenue-churned",
"data": [{"date": "2024-01-01", "value": -3100}, {"date": "2024-02-01", "value": -2950}]}
],
"frequency": "M",
"periods": 6,
"data_type": "revenue"
}
If you also forecast the total as its own series, the components won't add up to it. Each series is fitted independently and picks its own model and trend, so the two numbers will disagree. When the roll-up itself is the deliverable, use grouped forecasting instead — it reconciles the whole hierarchy so the parts sum to the whole by construction. If you stay on batch, pick one published figure — either the sum of the parts or the standalone total — rather than showing both.
Don't add up the confidence bounds
Summing each component's lower value
doesn't give you a lower bound for the sum — it assumes every component misses low at the same
time, which is far more pessimistic than reality. Add up the point forecasts and treat the total's
uncertainty as an open question — or let
grouped
forecasting build the aggregate band under a stated correlation assumption. For the equivalent
problem across periods rather than across series,
accumulate
handles it explicitly.
Forecast rates and percentages
Churn rate, conversion rate, utilisation and margin are all ordinary time series and forecast normally. Send them in whatever unit you already use — 0–1 or 0–100 — as long as you're consistent.
Tell the API the range your series can't leave with
value_bounds. Either side is optional —
a rate has both, a backlog only has a floor:
{
"identifier": "churn-rate",
"data": [{"date": "2024-01-01", "value": 2.4}, {"date": "2024-02-01", "value": 3.1}],
"frequency": "M",
"periods": 12,
"value_bounds": {"min": 0, "max": 100}
}
The series is then forecast on a transformed scale and mapped back, so the forecast, both confidence bounds and every quantile land inside the range. The band also stops being symmetric: on a rate sitting near zero it has more room above than below, which is the honest shape for a number that can't go negative.
Why not just clamp the bounds yourself?
Because clamping cuts off probability without redistributing it, so a clamped band no longer covers
at its stated confidence level — you asked for 80% and got less. It also can't fix the shape of the
band inside the range, only chop off the ends.
value_bounds changes what's forecast
rather than what's displayed, so the stated coverage still holds.
Bounds have to match your data
If the history you send already falls outside the range you declared, the bounds are reported back
as not applied and the series is forecast normally — rather than squeezing real observations into a
range they contradict. Check
model_info.bounds_transform in the
response, which says whether the transform ran and why not if it didn't. The usual cause is a unit
mismatch: sending 0–1 rates while declaring
{"min": 0, "max": 100} works, but the
reverse doesn't.
Build a fan chart
A fan chart needs several quantiles per period. Request them together with the
quantiles parameter and each forecast row
comes back with a quantiles object
alongside the usual bounds:
{
"identifier": "SKU-12345",
"data": [{"date": "2024-01-01", "value": 120}, {"date": "2024-02-01", "value": 135}],
"frequency": "M",
"periods": 6,
"quantiles": [0.1, 0.3, 0.5, 0.7, 0.9]
}
Why not call the same series at several confidence levels?
It's the intuitive approach, but it costs more and gives you less. Results aren't cached, so five confidence levels bill as five forecasts.
More importantly, 0.80 is the widest band the foundation models produce natively.
advanced-quantized,
advanced-patched and
ensemble return the same 80% band
whether you ask for 0.85 or 0.95, so those lines land on top of each other. One
quantiles request gives you genuinely
distinct levels, read from each model's own quantile heads, for a single charge.
What's not directly supported
Per-customer churn scoring and per-customer LTV. Answering "which of these customers will churn, and what is each one worth" means classifying customers by their attributes — plan, tenure, usage, support history — which is a different kind of model from time series forecasting, and needs data this API doesn't take in.
The aggregate version does work well: forecast a churn rate or revenue series per cohort with
tenant_context, then use
accumulate to turn that forecast into a
discounted lifetime total. That tells you what a segment is worth, rather than what one customer is
worth.