Endpoints.
This page is the complete reference for all ForecastAPI endpoints, parameters, and response formats.
https://forecastapi.com/v2Bearer YOUR_API_KEYEndpoints
This endpoint generates forecasts for your time series data and selects the model automatically.
Request Body
{
"identifier": "SKU-12345",
"data": [
{"date": "2024-01-01", "value": 120},
{"date": "2024-02-01", "value": 135},
{"date": "2024-03-01", "value": 155}
],
"periods": 6,
"frequency": "M",
"data_type": "sales",
"confidence_level": 0.80
}
Parameters
identifier. The same identifier under two different values makes two independent series. See Patterns & Segmentation.standard widens further. See the note below.[0.1, 0.5, 0.9]. This adds a quantiles object to each forecast row.{"min": 0, "max": 100}. Either side is optional. The API forecasts the series on a transformed scale, so the forecast, both bounds, and every quantile stay inside the range. See forecasting rates and percentages.adjusted block beside result.accumulated block.model: multivariate accepts these. Every other model rejects the request instead of forecasting without them. See Covariates.current_period block beside result. See Current-Period Completion.auto (default), combined (0.6·MASE + 0.4·sMAPE), mase, and smape. auto uses combined for demand, sales, and inventory, and sMAPE for other data types.
quantiles, value_bounds, adjustments, and accumulate also work on
POST /v2/batch/forecast. You set each one once for the whole request, or per series. See
Batch Processing for how they scope and where the results land.
current_period and covariates are the exceptions. They work on
POST /v2/forecast only, and batch and grouped requests reject them instead of ignoring
them silently.
Data Types
These types support intermittent demand patterns:
"sales"- Sales data"demand"- Demand forecasting"inventory"- Inventory levels
These types use standard forecasting methods:
"web_traffic"- Website analytics"cpu_usage"- System metrics"revenue"- Financial data
Choosing the winning model (selection_metric)
For every request, the API back-tests a set of candidate models on your own history. It keeps the
winning model, which is the one with the smallest error. selection_metric controls
which error metric decides the winning model. A lower value is always better.
combined for sales, demand, and inventory, and sMAPE for every other data type. It keeps the historical default behavior.- This applies to the standard model. The advanced models (
advanced-quantized,advanced-patched) honormaseandsmape, butcombinedandautouse their own default metric. On single-model foundation forecasts, the API records the value but does not change the output. - Model comparison runs only when there is enough history to back-test. This needs at least 6 data points and
periodsgreater than 1. With less data, no comparison runs and the field has no effect. - A change to the metric changes which model wins. This changes the forecast values themselves, not just how the API scores them. The winning model under
smapeis often not the winning model undercombined. - The API does not offer MAPE. MAPE is undefined at zero and unstable near it, so the API ranks on sMAPE and MASE instead.
Response
{
"result": {
"identifier": "SKU-12345",
"tenant_context": null,
"forecasts": [
{ "period": 1, "date": "2024-04-01", "forecast": 168.5, "lower": 162.3, "upper": 174.7 },
{ "period": 2, "date": "2024-05-01", "forecast": 175.2, "lower": 168.1, "upper": 182.3 }
],
"model_info": {
"best_model": "AutoETS",
"models_evaluated": ["AutoETS", "AutoARIMA", "AutoTheta", "SeasonalNaive"],
"selection_metric": "smape",
"interval_source": "conformal",
"validation_performed": true,
"smape": { "AutoETS": 6.1, "AutoARIMA": 7.4 },
"mase": { "AutoETS": 0.82, "AutoARIMA": 0.95 }
}
},
"meta": {
"selection_metric": "smape",
"timing": {
"validation": 8.2,
"selection": 45.6,
"forecasting": 72.1,
"total": 125.9
}
}
}
Response Fields
identifier from your request.period, date, forecast (the point value), and lower/upper prediction bounds.best_model), the models evaluated, the interval source, and the per-model back-testing scores (smape/mape/mase) when validation runs.adjustments. It is a sibling of result, not a field inside it. It carries the scenario path and stored: false.accumulate. It is also a sibling of result. It carries total, by_period, and the stated assumptions.current_period: true. It is also a sibling of result. It projects how the current period finishes, from your actuals plus the forecast remainder.Quantile Fan (quantiles)
quantiles asks for specific points of the predictive distribution per period, instead
of just the two band edges. Use it to draw a fan chart, or to pick a service level directly (order
to the 0.9 quantile rather than to the mean).
{
"identifier": "SKU-12345",
"data": [{"date": "2024-01-01", "value": 120}, {"date": "2024-02-01", "value": 135}],
"frequency": "M",
"periods": 3,
"quantiles": [0.1, 0.5, 0.9]
}
Each forecast row gains a quantiles object. The API does not change the existing
lower and upper fields:
{
"period": 1,
"date": "2024-03-01",
"forecast": 168.5,
"lower": 162.3,
"upper": 174.7,
"quantiles": { "0.1": 151.2, "0.5": 168.5, "0.9": 186.0 }
}
-
Accepted values are the nine deciles
0.1, 0.2, … 0.9. The API rejects anything else with a 422 rather than approximate it. The foundation models have exactly nine native decile heads. An interpolated value under a0.95label would be mislabeling, not extra precision. -
A fan request does not change the point forecast.
advanced-quantizedalways fits the full decile grid, regardless of the levels you request. So a fan request and a band-only request return the same point forecast. -
model_info.quantile_crossings_fixedreports any case where a raw model returned a higher quantile below a lower one. The API repairs it by sorting. The API reports the repair rather than hide it.
What-If Adjustments (adjustments)
adjustments applies your own assumptions on top of the forecast. Examples are "assume we
lose the enterprise deal" or "assume the promo lifts Q4 by 20%". This is the deliberate half of
scenario planning. The probabilistic half is the band and the quantile fan.
{
"adjustments": [
{"type": "multiplier", "value": 0.8, "from_period": 3},
{"type": "level_shift", "value": -500, "from_period": 6, "to_period": 9}
]
}
multiplier (scales the value) or level_shift (adds a constant)periods.×2 then +50 gives
250. +50 then ×2 gives 300. The response echoes both in
adjusted.adjustments_applied.adjusted arrives as a sibling of result and carries
"stored": false. The API persists and scores only the baseline forecast for accuracy.
So your scenarios can never pollute your accuracy history. Every adjustment moves the point value,
both bounds, and the whole quantile fan together. So the band always still brackets its own point.Cumulative Totals (accumulate)
accumulate collapses the forecast path into a single total:
Σ (value × survival × discount). The same operation covers cumulative demand over a
lead time (safety stock), customer lifetime value, total cost of ownership, and net present value.
{
"accumulate": {
"decay": 0.95,
"discount_rate": 0.10,
"correlation": 0.5
}
}
0.95 means 5% attrition each period. Default 1.0 (no decay).1.10-1/12, not by 1.10. Default 0.
Period 1 is undiscounted and at full survival. by_period carries a running
cumulative plus cumulative_lower/cumulative_upper, which is
the number a safety-stock calculation actually reads.
A sum of each period's lower value does not give a lower bound for the
total. It assumes every period misses low at the same time. Instead, the API states the joint
assumption explicitly as one correlation parameter. The response echoes this
parameter in accumulated.assumptions:
0— independent periods (the textbook √L rule). This is the narrowest band, and it is measurably too narrow. It covers only about 33–51% of outcomes against a nominal 80%, because a level error persists across a horizon.1— perfectly correlated, exactly the same as a sum of the bounds. This is a legitimate conservative envelope, as long as you label it as one.0.5(default) — measured across the benchmark suite as the tightest value that still covers at the stated rate on all three backends.
assumptions.measured tells you whether the correlation in force is the measured
default or one you supplied. A value you pass is your assumption, and the API never labels it
measured. When a model's band coverage is unknown, or any period lacks a band,
lower and upper return null with an
interval_reason. The API does not assemble a total from a partial set of variances.
Current-Period Completion (current_period)
Your data can end mid-period, for example daily sales sent on the 20th and forecast monthly. In
that case, the API excludes the incomplete period from the model's history, because it would
otherwise look like a sudden collapse. The forecast horizon then starts at that period
with a full-period forecast. The API reports the exclusion in
result.model_info.partial_period_dropped.
A full-period forecast is not the number you pace against a target. Part of the period has already
happened. current_period: true adds the projection that accounts for this. The formula
is projected_total = actual_to_date + full_period_forecast × (1 − elapsed_fraction):
{
"result": { "forecasts": [ { "period": 1, "date": "2024-04-30", "forecast": 300.0, "...": "..." } ] },
"current_period": {
"applied": true,
"period_start": "2024-04-01",
"period_end": "2024-04-30",
"last_actual_date": "2024-04-20",
"elapsed_fraction": 0.6667,
"actual_to_date": 200.0,
"full_period_forecast": 300.0,
"forecast_remainder": 100.0,
"projected_total": 300.0,
"lower": 283.3,
"upper": 316.7,
"assumptions": { "intraperiod_distribution": "uniform" },
"stored": false
}
}
assumptions.false. The projection mixes observed and forecast values, so the API never persists it or counts it toward forecast accuracy.
The projection needs data finer than the forecast frequency. With one point per period, there
is no way to tell a partial period from a complete one. If your data ends exactly on a period
boundary, there is nothing to complete. In those cases, the block returns
applied: false and a reason instead of a prorated guess.
current_period works on POST /v2/forecast only. Batch and grouped
requests reject it.
Covariates (covariates)
A covariate is a series that influences your data without being forecast itself. Promotions,
public holidays, price changes, and upstream traffic are all covariates. You send them beside
data, in the same shape, and the forecast is conditioned on them.
Covariates need "model": "multivariate". Every other model rejects the request. This
is deliberate: the alternative is a forecast that looks conditioned on your promotion calendar
while ignoring it, which is worse than a clear error.
{
"identifier": "SKU-12345",
"frequency": "M",
"periods": 6,
"model": "multivariate",
"data": [
{"date": "2024-01-01", "value": 100},
{"date": "2024-02-01", "value": 180}
],
"covariates": [
{
"name": "promo",
"known_future": true,
"data": [
{"date": "2024-01-01", "value": 0},
{"date": "2024-02-01", "value": 1},
{"date": "2024-03-01", "value": 0}
]
},
{
"name": "web_sessions",
"data": [
{"date": "2024-01-01", "value": 1200},
{"date": "2024-02-01", "value": 1450}
]
}
]
}
known_future
known_future says whether you know the covariate's future values. It decides how far
the covariate has to reach:
known_future |
Example | Must cover |
|---|---|---|
false (default) |
Web sessions, competitor stock levels | Your whole history |
true |
A planned promotion, a holiday calendar | Your whole history and every forecast period |
A covariate that falls short of its range is rejected. A known_future covariate that
stops at the end of your history is not known-future, and padding it would condition the forecast
on values nobody supplied.
A covariate recorded more finely than your target frequency is summed into each period, the same way your own data is. That is right for counts, such as promotion days per month or sessions per week.
It is wrong for levels, such as a price or a temperature, where summing daily values into a month is meaningless. Send those already at the frequency you forecast at, where nothing is summed.
The response reports which covariates were used in
result.model_info.covariates, so you can confirm the forecast was conditioned rather
than take it on trust. Up to 20 covariates per request.
covariates works on POST /v2/forecast only. Batch and grouped requests
reject it.
Model Options
Choose the right forecasting model based on your accuracy requirements and budget. All models use the same automatic algorithm selection, but differ in their underlying implementation and computational complexity.
Available Models
Standard Model (Default)
This is the default forecasting model, and it balances accuracy and speed. It suits most use cases and gives reliable forecasts across a wide range of data patterns.
Best for:
- General-purpose forecasting
- High-volume batch processing
- Real-time forecasting needs
- Cost-sensitive applications
{
"data": [...],
"periods": 6,
"frequency": "M"
// model defaults to "standard" if not specified
}
Advanced Models (+25% cost)
These forecasting models aim for higher accuracy. They work well for new identifiers, smaller datasets, and complex patterns. Two variants exist:
advanced-quantizedis the most thorough advanced variant. It evaluates and ensembles multiple candidate models per request for the highest single-model accuracy, at the cost of slower processing.advanced-patchedis a patch-based advanced variant. Its response times are comparable to the standard model. Choose it when you want advanced-level accuracy for real-time forecasting.
The API keeps the advanced value for backward compatibility. It behaves the same as
advanced-quantized.
advanced-quantized variant processes slower than the standard model. Do not use it
for real-time forecasting with large datasets. Use advanced-patched when response time
matters.Best for:
- New products or identifiers with limited history
- Small to medium datasets (5-50 datapoints)
- Critical forecasts where accuracy matters most
- Complex or irregular patterns
{
"data": [...],
"periods": 6,
"frequency": "M",
"model": "advanced-quantized"
}
// or for faster responses:
{
"data": [...],
"periods": 6,
"frequency": "M",
"model": "advanced-patched"
}
Multivariate Model (Covariates, +25% cost)
multivariate is the only model that reads covariates. Pick it when
something outside the series drives it and you can measure that thing: a promotion calendar, a
holiday flag, a price change, or upstream web sessions.
Without covariates it is an ordinary foundation model, and the other variants are the better tested choice. The reason to reach for it is the extra input, not extra accuracy on its own.
multivariate forecasts at most 320 periods per request, where the other models go to
365. Ask for more and the request is rejected. It is never quietly shortened, so a response always
covers the horizon you asked for.{
"data": [...],
"periods": 6,
"frequency": "M",
"model": "multivariate",
"covariates": [
{
"name": "promo",
"known_future": true,
"data": [
{"date": "2024-01-01", "value": 0},
{"date": "2024-02-01", "value": 1}
]
}
]
}
Ensemble Model (Best Accuracy, +25% cost)
This is the most accurate model. It combines the standard model and both advanced variants into a single forecast. It smooths extreme outliers for more reliable forecasts.
Best for:
- Mission-critical forecasts requiring highest accuracy
- Data with extreme outliers or volatility
- Strategic planning and long-term projections
- Batch processing where speed is less critical
{
"data": [...],
"periods": 6,
"frequency": "M",
"model": "ensemble"
}
Auto Model (Learns Per Series, +25% cost)
auto routes each identifier to whichever model has proven most accurate on that
series. A new identifier starts on the ensemble. As you send fresh actuals, the API scores
every past forecast against what actually happened. It does this for the ensemble and for each
member model individually. Once one model beats every alternative by a clear margin across enough
matured forecasts, auto switches that identifier to it. While the evidence is thin, or
the models are too close to call, the identifier stays on the ensemble.
The response always says which model answered and why, in
model_info.auto_selection. It gives the selected model, the reason
(no_history, insufficient_evidence, no_clear_winner, or
scored_winner), and the per-model accuracy scores behind the decision.
identifier and tenant_context as it becomes
available. A series that you forecast once and never update stays on the ensemble.Best for:
- Long-lived identifiers you forecast repeatedly (SKUs, tenants, metrics)
- Catalogs where the best model differs per series
- Integrations that should not hand-tune model choice per series
{
"data": [...],
"periods": 6,
"frequency": "M",
"identifier": "SKU-12345",
"model": "auto"
}
API Usage Calculation
The advanced variants (advanced, advanced-quantized,
advanced-patched), the multivariate model, the ensemble
model and auto consume 25% more API usage than the standard model. This is how it
works:
Example Calculation
/forecast, /batch, /traffic-forecasting, and
/inventory-planning. In batches, the API applies the multiplier per series and honors
per-series model overrides. The one exception is auto on
/forecast/grouped. The API reconciles a hierarchy under one explicitly chosen model, so
grouped requests do not accept it.Quick Model Selection Guide
| Use Case | Recommended Model | Reason |
|---|---|---|
| Recurring forecasts of long-lived series | auto |
It learns the best model per series from realized accuracy |
| High-volume real-time forecasting | standard |
It processes fast at a low cost |
| New product launch forecasting | advanced |
It works better with limited historical data |
| Strategic planning & budgeting | ensemble |
It gives the highest accuracy for critical decisions |
| Overnight batch processing | ensemble |
Time matters less, and accuracy is key |
| Volatile or erratic data | ensemble |
It smooths extreme outliers |
| Promotions, holidays or price changes drive the series | multivariate |
It is the only model that reads covariates |
| Cost-sensitive applications | standard |
It adds no usage cost |
Error Responses
HTTP Status Codes
Error Response Format
{
"error": {
"code": "invalid_data_format",
"message": "Data array must contain at least 3 datapoints",
"details": {
"received_points": 2,
"minimum_required": 3
}
}
}