# Documentation
> Complete documentation for Large Language Models
---
## Document: /mcp
Connect an MCP client to your Retrocast™ workspace to ingest datasets, run forecasts and backtests, score results and open runs in the app.
URL: /mcp
# Retrocast MCP
The Retrocast MCP server exposes your
[Retrocast](https://app.retrocast.com) workspace as tools, so an MCP client can
ingest a dataset, inspect its columns, run forecasts and backtests, score the
results and open the run in the app — without you writing any HTTP calls.
It is a different server from the [Docs MCP](https://docs.retrocast.com/mcp/docs/docs-mcp),
which only searches this documentation and needs no account. This one works
against your own data, so it signs you in first.
## Who can connect
| | |
| --- | --- |
| Endpoint | `https://retrocast-backend-prod.fly.dev/mcp` |
| Transport | Streamable HTTP (stateless) |
| Authentication | OAuth 2.0 — your MCP client runs the flow for you |
| Requires | A [Retrocast account](https://app.retrocast.com). Free accounts get 20 forecasts a day; backtesting is available on paid plans |
Clients discover the authorization server from
`/.well-known/oauth-protected-resource/mcp`; there is nothing to register or
paste by hand.
No account yet? [Sign up for Retrocast](https://app.retrocast.com). The free tier
covers 20 forecasts a day, which is enough to connect a client and work through
the dataset and forecasting tools below. Backtesting is available on paid plans.
## Connect a client
**Claude, Codex and Cursor are the supported clients today.** Other MCP clients
aren't tested against this server yet — [tell us which one you need](#getting-help)
and we'll look at it.
Claude Code:
```bash
claude mcp add --transport http retrocast https://retrocast-backend-prod.fly.dev/mcp
```
Codex CLI:
```bash
codex mcp add --transport http retrocast https://retrocast-backend-prod.fly.dev/mcp
```
Claude Desktop and the Codex app read a JSON config instead:
```json
{
"mcpServers": {
"retrocast": {
"url": "https://retrocast-backend-prod.fly.dev/mcp"
}
}
}
```
Cursor needs the OAuth client spelled out in `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"retrocast-mcp": {
"url": "https://retrocast-backend-prod.fly.dev/mcp",
"auth": {
"CLIENT_ID": "PJdmQfpYABFfWspX"
}
}
}
}
```
The first call opens a browser window for the OAuth consent step. After that the
client refreshes the token on its own.
Accounts belonging to more than one organisation aren't supported yet — a
connection resolves against your own. If you need multi-organisation access,
[get in touch](#getting-help).
## Tools
Everything below runs against your own organisation's data. Read-only tools are
marked as such by the server, so clients that ask before mutating only prompt on
the ones that write.
### Datasets
| Tool | Purpose |
| --- | --- |
| `upload_dataset` | Ingest a CSV or Parquet file. Returns a presigned upload target — PUT the bytes, then call `confirm_dataset_upload`. |
| `confirm_dataset_upload` | Finalise an upload: analyse, commit column roles, poll until the dataset is `ready`. |
| `create_dataset_from_link` | Ingest a presigned Parquet URL in one call, no upload round-trip. |
| `list_datasets` | Browse your datasets, newest first, paginated. |
| `get_dataset` | One dataset's status, column roles and readiness. |
| `get_dataset_columns` | Columns with types, flags, and the index/target suggested at analyse time. |
| `update_dataset_defaults` | Correct the default index or target column on a ready dataset. |
| `list_example_datasets` | Curated example datasets (`ex_…`) to try the workflow on. |
| `detect_hierarchy` | Infer categorical nesting (for example region → store → SKU). |
| `column_values` | Distinct values of one column. |
| `search_dataset_values` | Find which column holds a label, by searching every text column. |
| `query_timeseries` | The gap-filled target series as rows. |
`upload_dataset` never streams file bytes through the model or the server: it
hands back a self-signed storage URL that you PUT to directly. Uploads store no
default index or target, so pass the suggestion you want as an explicit
`datetime_column` / `target_column` — or set them once with
`update_dataset_defaults`.
### Covariates
| Tool | Purpose |
| --- | --- |
| `detect_covariate_roles` | Suggest how a covariate dataset's columns map onto a target. |
| `create_covariate_attachment` | Attach a covariate dataset to a target with column roles. |
| `list_covariate_attachments` | List attachments, optionally filtered by target. |
Uploading a *covariate* dataset is out of scope for now — attach one that is
already in the workspace.
### Forecasting and backtesting
| Tool | Purpose |
| --- | --- |
| `create_forecast` | Synchronous forecast. Accepts attached, inline and built-in covariates. |
| `run_backtest` | Synchronous backtest over historical cutoffs. Returns run metadata and pooled metrics. |
| `list_backtest_runs` | Browse runs with per-run metrics merged in. |
| `get_backtest_run` | One run's metadata and scalar metrics. |
| `list_backtest_series_metrics` | Per-series metrics — worst series by WAPE, or metrics rolled up by dimension. |
| `list_backtest_cutoff_metrics` | Per-cutoff WAPE/MAE/MAPE, sortable and filterable by date window. |
| `list_backtest_cutoff_results` | Per-cutoff actuals, forecasts and quantile bands, paged. |
| `export_backtest_csv` | Full-run predictions as CSV, via a short-lived download URL. |
| `compare_backtest_runs` | Deterministic comparison of 2–6 single-series runs, joined on forecast creation date. |
The backtest tools are available on paid plans. `create_forecast` is on every plan.
A grouped run returns pooled metrics only. Per-member figures live in
`list_backtest_series_metrics` — which also supplies the `series_id` the
cutoff-grain tools need. Metrics are fractions, not percentages.
### Scoring, charts and sharing
| Tool | Purpose |
| --- | --- |
| `score_forecast` | Score a forecast against actuals over an evaluation frame. |
| `score_forecast_arrays` | Score aligned `{forecasts, actuals, index}` arrays over an optional date range. |
| `plot_time_series` | Render a brand-styled chart PNG. Returns the image plus a short-lived download URL. |
| `create_share_url` | Build a link that opens a dataset in [Retrocast](https://app.retrocast.com) at a given mode, model, scope, horizon and cutoff. |
`create_forecast`, `run_backtest` and `get_backtest_run` lead with a
plain-language reading of the result — horizon, interval, which level to trust —
and attach it as structured content, with the compacted raw payload after it.
The backtest tools include an `app_url` that opens the run focused in the app.
## Guided workflows
Two prompts encode the method rather than leaving it to the model:
- `forecast_workflow` — detect the hierarchy, backtest each level, forecast at
the level that backtests best, and recommend it citing that evidence.
- `forecast_report` — profile the data, forecast at your current operating
points, score every forecast identically, and write a like-for-like accuracy
comparison.
Both take an optional `dataset_id`.
## Interactive forecast viewer
Clients that support MCP Apps render `create_forecast` results as a live chart
instead of JSON. Every other client gets the same summary and payload as text —
nothing is hidden behind the viewer.
## Errors
| Status | Meaning |
| --- | --- |
| `401` | Missing or invalid token. Reconnect so the client can run the OAuth flow again. |
| `403` | Authenticated, but this account isn't allowed to use the server. |
| `404` | Unknown dataset, run or attachment. |
| `422` | Invalid tool arguments. |
| `429` | Rate limited, or the day's free-tier forecasts are used up. |
## Getting help
Ask in the chat inside [Retrocast](https://app.retrocast.com), or email
`support@theforecastingcompany.com`.
---
## Document: /documentation
Quick start, authentication, available models and supported frequencies for The Forecasting Company API.
URL: /documentation
# Overview
Welcome to The Forecasting Company API. We expose our own, as well as open-source foundation models,
behind a unified REST API at `https://api.retrocast.com`.
This page covers the quick start, authentication, the available models and the
supported frequencies. The rest of the documentation:
- [Endpoints](https://docs.retrocast.com/documentation/endpoints) — which endpoint to reach for, and how to
read a forecast, poll a job and run a backtest. Parameters, schemas and worked
examples live in the [API Reference](https://docs.retrocast.com/api/endpoints).
- [Covariates](https://docs.retrocast.com/documentation/covariates) — exogenous variables you provide, and
holidays, events and temporal features we provide.
- [Model-specific configuration](https://docs.retrocast.com/documentation/model-config) — the `model_config`
overrides accepted by Auto-ARIMA, Auto-ETS and Chronos-2.
- [Python SDK](https://docs.retrocast.com/documentation/python-sdk) — the higher-level
`theforecastingcompany` client.
- [Error handling](https://docs.retrocast.com/documentation/errors) — status codes and how to reach us.
## Quick start
```bash
curl -X POST "https://api.retrocast.com/forecast?model=tabpfn-ts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"series": [{
"index": ["2024-01-01","2024-01-02","2024-01-03","2024-01-04","2024-01-05"],
"target": [125, 120, 140, 135, 133]
}],
"horizon": 5,
"freq": "D",
"quantiles": [0.1, 0.5, 0.9]
}'
```
Or with the **Python SDK**:
```bash
pip install theforecastingcompany
```
```python
from theforecastingcompany import TFCClient, TFCModels
client = TFCClient() # reads TFC_API_KEY from environment
forecast_df = client.forecast(
train_df,
model=TFCModels.TabPFN_TS,
horizon=12,
freq="W",
quantiles=[0.1, 0.5, 0.9],
)
```
## Authentication
All API requests require a Bearer token. Obtain your API key at
[https://docs.retrocast.com/settings/api-keys](https://docs.retrocast.com/settings/api-keys).
Include it in every request header:
```
Authorization: Bearer YOUR_API_KEY
```
## Available models
`t0-gamma` is the latest model and the default selection.
| Model | Description | Covariates? | Forecast? | Backtest? |
| -------------- | ---------------------------------------- | ---------------------- | --------- | --------- |
| `t0-gamma` | The Forecasting Company's latest model | ✅ | ✅ | ✅ |
| `t0-beta` | The Forecasting Company's previous model | ✅ | ✅ | ✅ |
| `t0-alpha` | The Forecasting Company's model | ✅ | ✅ | ✅ |
| `tfc-global` | Global model for time series | ✅ | ✅ | ✅ |
| `tabpfn-ts` | Foundation model from Uni. of Freiburg | ✅ | ✅ | ✅ |
| `moirai-2` | Salesforce's Moirai v.2 | ✅ | ✅ | ✅ |
| `timesfm-2` | Google's TimesFM 2.0 | Future covariates only | ✅ | ✅ |
| `timesfm-2p5` | Google's TimesFM 2.5 | Future covariates only | ✅ | ✅ |
| `chronos-2` | Amazon's Chronos-2 | ✅ | ✅ | ✅ |
| `tirex` | NXAI's model based on xLSTMs | ❌ | ✅ | ✅ |
| `tirex-2` | NXAI's successor to `tirex` | Historical & future | ✅ | ✅ |
| `aarima` | Auto-ARIMA | Soon | ✅ | ✅ |
| `aets` | Auto-ETS | Soon | ✅ | ✅ |
| `toto-2` | Datadog's Toto v.2 | ❌ | ✅ | ✅ |
### Models that support exogenous variables (covariates)
`t0-gamma`, `t0-beta`, `t0-alpha`, `tabpfn-ts`, `moirai-2`, and `chronos-2` support full exogenous variables.
`timesfm-2` and `timesfm-2p5` support future covariates only.
`tirex-2` supports historical and future covariates, but not static variables.
`tfc-global` supports all covariate types including string covariates and static variables.
### Models that support string covariates
Only `tabpfn-ts` and `tfc-global` support non-numeric (string) covariates. All other models require numeric covariates only.
## Supported frequencies
The `freq` field must be one of:
| Value | Meaning |
| ------- | ------------- |
| `10S` | 10 seconds |
| `min` | 1 minute |
| `5min` | 5 minutes |
| `10min` | 10 minutes |
| `15min` | 15 minutes |
| `30min` | 30 minutes |
| `H` | Hourly |
| `D` | Daily |
| `W` | Weekly |
| `M` | Monthly |
| `Q` | Quarterly |
| `Y` | Yearly |
---
## Document: /documentation/python-sdk
Install the theforecastingcompany Python SDK and run forecasts, forecasts with exogenous variables, and cross-validation backtests.
URL: /documentation/python-sdk
# Python SDK
For a higher-level interface, use the official Python SDK: [`theforecastingcompany`](https://pypi.org/project/theforecastingcompany/).
```bash
pip install theforecastingcompany
```
```python
from theforecastingcompany import TFCClient, TFCModels
client = TFCClient() # reads TFC_API_KEY env var
```
## Simple forecast
```python
import pandas as pd
from theforecastingcompany import TFCClient, TFCModels
client = TFCClient()
train_df = pd.DataFrame({
"unique_id": ["store_1"] * 100,
"ds": pd.date_range("2024-01-01", periods=100, freq="D"),
"target": range(100),
})
forecast_df = client.forecast(
train_df,
model=TFCModels.TabPFN_TS,
horizon=14,
freq="D",
quantiles=[0.1, 0.5, 0.9],
)
```
## Forecast with exogenous variables
```python
forecast_df = client.forecast(
train_df,
future_df=future_df,
model=TFCModels.TFCGlobal,
horizon=12,
freq="W",
static_variables=["region", "store_type"],
future_variables=["price", "promotion"],
add_holidays=True,
country_isocode="US",
)
```
## Cross-validation (backtesting)
```python
cv_df = client.cross_validate(
train_df,
model=TFCModels.TabPFN_TS,
horizon=12,
freq="W",
fcds=[pd.Timestamp("2024-06-01"), pd.Timestamp("2024-07-01"), pd.Timestamp("2024-08-01")],
quantiles=[0.1, 0.5, 0.9],
)
```
## Custom column names
```python
forecast_df = client.forecast(
my_data,
model=TFCModels.TimesFM_2,
horizon=7,
freq="D",
id_col="item_id",
date_col="date",
target_col="sales",
)
```
## Available TFCModels enum values
- `TFCModels.TFCGlobal` → `"tfc-global"`
- `TFCModels.TabPFN_TS` → `"tabpfn-ts"`
- `TFCModels.Moirai_2` → `"moirai-2"`
- `TFCModels.TimesFM_2` → `"timesfm-2"`
- `TFCModels.TimesFM_2p5` → `"timesfm-2p5"`
- `TFCModels.Chronos_2` → `"chronos-2"`
- `TFCModels.Tirex` → `"tirex"`
- `TFCModels.AutoARIMA` → `"aarima"`
- `TFCModels.AutoETS` → `"aets"`
## Data structure requirements
DataFrames must have:
- **`unique_id`** (or custom `id_col`): unique identifier for each time series
- **`ds`** (or custom `date_col`): datetime column
- **`target`** (or custom `target_col`): numeric values to forecast
## Batch size
Models like `chronos-2` and `moirai-2` support batching. Control with `batch_size` (default 256). Increase for speed, decrease if you encounter timeouts.
---
## Document: /documentation/model-config
Per-model options in model_config for Auto-ARIMA (aarima), Auto-ETS (aets) and Chronos-2 (chronos-2).
URL: /documentation/model-config
# Model-specific configuration
Some models accept additional configuration via `model_config`:
## Auto-ARIMA (`aarima`)
```json
{
"model_config": {
"model": "aarima",
"config": { "season_length": 7 }
}
}
```
- `season_length` (integer or null): Override automatic seasonal period detection.
## Auto-ETS (`aets`)
```json
{
"model_config": {
"model": "aets",
"config": { "season_length": 12 }
}
}
```
- `season_length` (integer or null): Override automatic seasonal period detection.
## Chronos-2 (`chronos-2`)
```json
{
"model_config": {
"model": "chronos-2",
"config": { "is_global": true }
}
}
```
- `is_global` (boolean, default false): Enable influence across the time-series — useful if all passed-in time-series are related (e.g., same product, different stores).
---
## Document: /documentation/errors
HTTP status codes the forecasting API returns (401, 403, 404, 422, 429, 500), what each one means, and how to contact support.
URL: /documentation/errors
# Error handling
| HTTP Status | Description |
| ----------- | ----------- |
| `200` | Success |
| `401` | Unauthorized — invalid or missing API key |
| `403` | Forbidden — no access to this resource |
| `404` | Not found — resource does not exist |
| `422` | Unprocessable entity — invalid request body or parameters |
| `429` | Rate limited — too many requests |
| `500` | Internal server error |
## Support and feedback
Please reach out at `support@theforecastingcompany.com`
---
## Document: /documentation/endpoints
The five forecasting API endpoints: when to use sync /forecast or async /forecast-jobs, how to read a forecast, poll a job and backtest on past dates.
URL: /documentation/endpoints
# Endpoints
The [API Reference](https://docs.retrocast.com/api/endpoints) is the source of truth for this API: it is
generated from the OpenAPI spec the gateway actually serves, and carries the full
request and response schemas, every query parameter, worked request examples and
a console you can call the API from. The spec itself is at
[https://api.retrocast.com/openapi](https://api.retrocast.com/openapi), no
authentication required.
This page covers what the reference can't: which endpoint to reach for, and how
to read what comes back.
## Choosing an endpoint
| Endpoint | Reach for it when |
| --- | --- |
| `POST /forecast` | You want forecasts in the response. The default path. |
| `POST /forecast-jobs` | The work is long-running — backtesting across many forecast creation dates, or a slow statistical model. Returns a `job_id` instead of a forecast. |
| `GET /forecast-jobs/{job_id}` | Polling a submitted job. |
| `GET /warmup` | Pre-loading a model. Cold models cost a few seconds on the first inference call; warming up hides that from your users. |
| `GET /openapi` | Fetching the spec, unauthenticated. |
Both forecast endpoints take the same request body, so moving a request from one
to the other is a change of URL, not of payload.
## Reading a forecast
`series` is a nested array: `series[i][j]` is the prediction for the i-th input
series at the j-th forecast creation date. A request with one series and no
`fcds` gets back `series[0][0]`.
Each prediction carries:
- `prediction` — a map of quantile → values. Always includes `mean`; the other
keys are the `quantiles` you asked for.
- `index` — the dates the forecast covers.
- `metrics` — accuracy scores, present only when the true target is known (so:
during backtesting). `mae`, `mape`, `crps`, `wape` and `scaled_bias`, each
nullable.
## Polling a job
`GET /forecast-jobs/{job_id}` returns one of three shapes, and you switch on
`status`:
- `in_progress` — nothing else in the body. Poll again.
- `completed` — carries `series`, in the same shape as a synchronous forecast.
- `failed` — carries `error` with a machine-readable `code` and a human-readable
`message`. The job failed permanently; don't retry it.
## Backtesting with forecast creation dates
`fcds` is a list of indexes into `target`, each marking a point in history to
forecast *from*. One request with several `fcds` returns one forecast per date —
that's a rolling-origin backtest, and because the true values are known, each
prediction comes back with `metrics`.
This is the usual reason to prefer `POST /forecast-jobs`: a backtest over many
dates is exactly the long-running case the synchronous endpoint isn't meant for.
## Series that only lend context
Setting `only_as_context: true` on a series feeds it to the model without
forecasting it. Use it to let a global model see related history — sibling stores,
earlier product generations — without paying for predictions you'll discard. It
applies to global models only.
## Pinning cloud and region
`/forecast` and `/warmup` accept optional `cloud` (`gcp` or `aws`) and `region`
(`us` or `eu`) query parameters. Leave them unset and any available combination
may serve the request; set them when residency or locality matters.
---
## Document: /documentation/covariates
Pass your own historical, future and static covariates, and use the holiday, event and temporal features the forecasting API adds for you.
URL: /documentation/covariates
# Covariates
Covariates are additional variables that can improve forecast accuracy. They come in two forms:
## User-provided covariates
Pass these directly in the `series` objects:
- **`hist_variables`** — variables with values only for the historical period (same length as `target`). Example: recorded temperature.
- **`future_variables`** — variables with values for both history and forecast period. Length = history + horizon. Example: planned promotions, weather forecasts.
- **`static_variables`** — variables that don't change over time. Single numeric value per variable. Example: population, store size.
## TFC-provided covariates
Use the `covariates` array in the request body to automatically add covariates managed by TFC:
### Holidays
```json
{
"type": "holidays",
"config": {
"country": "US",
"smoothing_method": "auto",
"window_size": 15,
"sigma": 7.0,
"ramp_days": 14
}
}
```
- `country` (required): 2-character ISO country code (e.g., `US`, `GB`, `FR`, `DE`)
- `smoothing_method`: `auto` (default), `none`, `gaussian`, `exponential`, `ramp`, `flat`
- `window_size`: 1–15 (default 15), should be odd
- `sigma`: standard deviation for Gaussian kernel (default 7.0)
- `ramp_days`: days before/after for ramp (1–35, default 14)
### Events
```json
{
"type": "events",
"config": {
"country": "US"
}
}
```
- `country` (required): ISO country code or `"Global"`
### Temporal features
```json
{
"type": "temporal_features",
"config": {}
}
```
Automatically adds time-based features (day of week, month, etc.).
---
## Document: /api-v1/contents/intro
URL: /api-v1/contents/intro
# Describe your data, don't reshape it
Most forecasting APIs ask you to flatten your world. You take whatever lives in
your warehouse — stores, SKUs, regions, daily and weekly grains, gaps and all —
collapse it into a positional array, throw away the timestamps, and re-upload
the whole thing on every call:
```json
{
"values": [142, 137, 145, 139, ..., 90, 84, 88],
"series_lengths": [730, 365],
"horizon": 28
}
```
Every time-series is concatenated into that one array; `series_lengths` is all
that marks where one ends and the next begins. Your data has structure — and
it's the first thing this shape throws away, leaving you to rebuild it by hand,
in glue code, before every request.
Our beta API inverts that. **You describe your data; we do the reshaping.** You
hand us a schema that says what your columns *mean*, and a selector that says
which slice you want to forecast. Everything else — aggregating to the right
grain, filling gaps, rolling up a hierarchy, running a backtest across dozens of
dates — falls out of that one description, automatically handled by our
time-series engine.
## Describe a family once
A real dataset isn't one time-series. It's a *family* of them — every store,
every SKU, recorded at whatever grain the source happens to use. Here's the kind
of raw, long-format table that lands in your lake:
You don't reshape it — you describe it. Assign each column a role, say how each
signal behaves when it's combined, and declare how your identifiers roll up:
```json
{
"input": {
"source": { "uri": "s3://acme/retail/sales/*.parquet" },
"columns": {
"date": { "kind": "time", "frequency": "1d" },
"sku": "identifier",
"color": "identifier",
"product_family": "identifier",
"store": "identifier",
"city": "identifier",
"sales": { "kind": "target", "aggregate": "sum", "impute": { "const": 0.0 } }
},
"hierarchies": {
"sku": [["sku", "color"], ["sku", "product_family"]],
"store": [["store", "city"]]
}
},
"selector": { "product_family": "any", "city": "any" },
"prediction_length": "3w"
}
```
Read the `hierarchies` block as roll-up paths. These paths tell the API which
identifiers can be aggregated together. Individual SKUs can be grouped by color
or by product_family; individual stores can be grouped by city. The `selector`
is the dial: it picks which level you want to forecast. Click around and watch the
request change:
_Pick a level for each dimension, then refine its values — the request updates
live. With `sku` rolled up to `product_family` and `store` rolled up to `city`,
the selector above gives one forecast per (day, `product_family`, `city`)._
Time rolls up the same way. Daily data comes with `weekly`, `monthly`,
`quarterly` and `yearly` levels: pick `weekly` above and each day's sales are
summed into its week before the forecast runs. A range such as
`{ "from": "2024-04-01" }` keeps only part of the history. See
[Forecast weekly or monthly totals](https://docs.retrocast.com/api-v1/contents/cookbook/time-rollups).
Roll SKUs up to families, stores into cities, days into weeks or months — and
you never hand-roll that `GROUP BY` again. Missing days, mixed grains, ragged
history: you declared how to handle them once, in the schema above.
## Point at the data where it lives
Your data already lives somewhere. There's no reason to serialize megabytes into
a request body to forecast it. Point the API at the source:
```json
"source": { "uri": "s3://acme/retail/sales/*.parquet" }
```
Inline arrays, parquet on object storage, a glob across many files — the same
schema describes all of them. Your lake stays the source of truth, and more
connectors (warehouses and databases) are on the way.
## One query, every forecast — and every backtest
Because you described a *family*, a single query fans out into a forecast for
every time-series it contains — every family, every city, in one request.
Once the API understands your data's shape, backtesting is another parameter,
not another pipeline. A backtest is just that forecast re-run from many points
in time, so say so:
```json
{
"input": {
"source": { "uri": "s3://acme/retail/sales/*.parquet" },
"columns": {
"date": { "kind": "time", "frequency": "1d" },
// rest of the columns...
}
},
"selector": { "product_family": "any", "city": "any" },
"context": "90d",
"prediction_length": "28d",
"cutoff": [{ "every": "2w" }],
"compute_metrics": true,
"quantiles": [0.1, 0.5, 0.9]
}
```
`"cutoff": [{ "every": "2w" }]` re-runs the forecast from a cutoff every two
weeks, across every time-series, with 90 days of context each time. With
`compute_metrics`, you get back quantiles *and* accuracy metrics for every
series and cutoff — a full rolling-origin evaluation from the request you were
already going to send. Pass a list of explicit dates
instead, and you control exactly when each forecast is made.
Each cutoff reuses the same request shape: historical cutoffs become backtests,
and future-facing cutoffs become forecasts. Expand the controls to see how
`cutoff`, `lead_time`, `span`, and `prediction_length` carve up the timeline:
_One query, re-run from many cutoffs. Historical cutoffs become backtests,
scored against history; future-facing cutoffs project forecasts forward. The
timeline plots actuals per day against the forecast per span, from each cutoff,
for one of the many series in the family._
## Forecast among peers
Much of the signal for forecasting one time-series comes from its neighbors. A
newly launched product has no history of its own — but its predecessors do. So
instead of forecasting every time-series in isolation, you can pair each target
with the peers that should inform it — a small map from one selector to another.
Forecasting `{"sku": ["phone10"]}{:json}`? Let it borrow from `{"sku": ["phone8", "phone9"]}{:json}`.
A fresh store leans on the established ones nearby; this season's launch on the
last two generations. Each target points at its own hand-picked peers, and the
model sees them alongside the target at request time — no fine-tuning, no
separate training job. The forecast simply borrows the patterns it needs.
## Describe once, forecast anything
That's the whole idea. Describe your data where it lives, once — its grains, its
hierarchy, how its signals combine — then reuse that same description on every
request, changing only what you ask for: a slice, a different granularity, a
longer horizon, a year of backtests, a forecast leaning on its neighbors. The
shape of the request barely changes; the leverage is enormous.
This API is in beta. Grab an [API key](https://docs.retrocast.com/settings/api-keys), point it at the data
you already have, describe it once, and forecast any slice of it you can name.
Questions or feedback? Reach us at support@theforecastingcompany.com.
---
## Document: /api-v1/contents/cookbook/time-rollups
URL: /api-v1/contents/cookbook/time-rollups
# Forecast weekly or monthly totals
Your table is one row per day, but you plan by the week. Name a coarser time
level in `selector`, and every day is added into its week before the forecast
runs:
```json
{
"input": {
"source": {
"inline": {
"date": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04", "2024-01-05", "2024-01-06", "2024-01-07",
"2024-01-08", "2024-01-09", "2024-01-10", "2024-01-11", "2024-01-12", "2024-01-13", "2024-01-14"],
"sales": [142, 137, 145, 139, 160, 188, 95,
150, 141, 139, 144, 158, 191, 99]
}
},
"columns": {
"date": { "kind": "time", "frequency": "daily" },
"sales": { "kind": "target", "aggregate": "sum" }
}
},
"selector": { "weekly": "any" },
"prediction_length": 4
}
```
Post it to `https://api.retrocast.com/v1-beta/forecast?model=t0-alpha`.
`model` names the forecasting model and is required — without it the
request is rejected with `missing field "model"`.
What each part does:
- `"weekly"` is a time level the API adds for you. Daily data gets `weekly`,
`monthly`, `quarterly` and `yearly`; monthly data gets `quarterly` and
`yearly`. Weeks run Monday to Sunday.
- `"aggregate": "sum"` says how days combine into a week, so this forecasts
weekly totals. `"mean"` would forecast the average day of each week instead.
Every variable column needs an `aggregate` once you roll up — the request is
rejected without one.
- `"prediction_length": 4` is four weeks: once the forecast is weekly, a step
is a week. `"4w"` and `"28d"` mean the same; `"1mo"` doesn't fit a whole
number of weeks and is rejected.
Weeks your data only partly covers, at either end, are left out, so no total
is missing days. The history above starts on a Monday and ends on a Sunday,
so it makes exactly two weeks.
## Per city, per week
Time combines with the other levels of a selector. Add an identifier level to
get one weekly series per city:
```json
"selector": { "city": "any", "weekly": "any" }
```
Leave time out of the selector and the forecast stays at the time column's
own frequency — daily here.
## Only part of the history
A range keeps the weeks inside it and drops the rest:
```json
"selector": { "weekly": { "from": "2024-04-01" } }
```
## Your own time levels
The default levels cover most calendars. Declare others under the time
column's `partitions`, keyed by the name you'll use in `selector`:
```json
"date": {
"kind": "time",
"frequency": "daily",
"partitions": {
"fortnightly": { "interval": "2w", "anchor": "2024-01-01" },
"weekly": { "anchor": "sunday" }
}
}
```
`fortnightly` is a new level of two-week periods starting on 1 January 2024.
`weekly` replaces the default weekly level with weeks starting on Sunday.
An anchor names where the periods start: a date, or a weekday for weekly
periods.
## What comes back
The same shape as any forecast, one row per week: `time` is the Monday each
week starts on, and `span` is `P7D`. `lead_time` counts up a week per row:
`PT0S`, `P7D`, `P14D`, `P21D`.
---
## Document: /api-v1/contents/cookbook/parquet-sources
URL: /api-v1/contents/cookbook/parquet-sources
# Send a parquet file
Inline rows travel inside the JSON body, which is fine for a few hundred rows
and wasteful for a real table. Upload the file instead: send the request as
`multipart/form-data`, with the query in a part named `query` and the data in
a part of its own.
This is the one case where the request isn't plain JSON, so here is the whole
`curl` call:
```bash
curl -X POST "https://api.retrocast.com/v1-beta/forecast?model=t0-alpha" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "query=@query.json;type=application/json" \
-F "sales=@sales.parquet"
```
`query.json` is the same request body you would have posted as JSON, except
that `source` points at the uploaded part with a `uri` instead of carrying
rows:
```json
{
"input": {
"source": { "uri": "inline://sales" },
"columns": {
"date": { "kind": "time", "frequency": "1d" },
"store": "identifier",
"sales": { "kind": "target", "aggregate": "sum" }
}
},
"prediction_length": "2w"
}
```
`model` names the forecasting model and is required on every call to this
endpoint.
The part name is yours to choose. `inline://sales` means the part sent as
`-F "sales=@sales.parquet"` — the name in the URL is the part name, not a
file name, so `inline://sales.parquet` would not resolve.
Two errors tell you the two halves are out of step: `the query reads
inline://x, but no part was uploaded under that name`, and `part x is not
read by the query`.
## One part per source
A `uri` names a single part; it does not take a list. To forecast several
files as one table, concatenate them before uploading.
Several parts are for several *sources*. A covariate has a source of its own,
so it can come from its own part:
```bash
curl -X POST "https://api.retrocast.com/v1-beta/forecast?model=t0-alpha" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "query=@query.json;type=application/json" \
-F "sales=@sales.parquet" \
-F "weather=@weather.parquet"
```
```json
"covariates": [
{
"schema": {
"version": "0.3",
"source": { "uri": "inline://weather" },
"columns": {
"date": { "kind": "time", "frequency": "1d" },
"temperature": "historical"
}
},
"on": [{ "base": "date", "attached": "date" }]
}
]
```
Parts can be Parquet or Arrow IPC files. Write Arrow uncompressed — an
LZ4-compressed Arrow file is rejected with `lz4 IPC decompression requires
the lz4 feature`.
## What comes back
The same response as any other request. One row per (store, time step), since
`store` is an identifier and nothing aggregates it away:
```json
{
"data": {
"time": ["2024-02-10T00:00:00Z", "2024-02-11T00:00:00Z"],
"cutoff": ["2024-02-09T00:00:00Z", "2024-02-09T00:00:00Z"],
"lead_time": ["PT0S", "P1D"],
"span": ["P1D", "P1D"],
"identifiers": { "store": ["S1", "S1"] },
"targets": {
"sales": {
"quantiles": {
"0.1": [143.5, 144.1],
"0.5": [144.8, 145.7],
"0.9": [145.8, 146.7],
"mean": [144.7, 145.6]
}
}
}
}
}
```
It is JSON either way — uploading a file changes how the input arrives, not
how the forecast comes back.
---
## Document: /api-v1/contents/cookbook/multiple-targets
URL: /api-v1/contents/cookbook/multiple-targets
# Forecast several columns at once
You have more than one number to predict per series — sales and returns, for
example. Give each one the `"target"` kind and both are forecast in the same
request:
```json
{
"input": {
"source": {
"inline": {
"date": ["2024-01-01", "2024-01-02", "2024-01-03"],
"store": ["S1", "S1", "S1"],
"sales": [142, 137, 145],
"returns": [6, 4, 9]
}
},
"columns": {
"date": { "kind": "time", "frequency": "1d" },
"store": "identifier",
"sales": { "kind": "target", "aggregate": "sum" },
"returns": { "kind": "target", "aggregate": "sum" }
}
},
"prediction_length": "2w",
"quantiles": [0.1, 0.5, 0.9]
}
```
Post it to `https://api.retrocast.com/v1-beta/forecast?model=t0-alpha`.
`model` names the forecasting model and is required — without it the
request is rejected with `missing field "model"`.
There is no `targets` field in this request. Leave it out and every column
with the `"target"` kind is forecast.
## Forecast only some of them
To forecast a subset without editing the schema, list the columns you want in
`targets`:
```json
"targets": ["sales"]
```
The names must be columns you declared as targets. `returns` stays in the
data as history the model can use, but no forecast is returned for it.
## What comes back
`data.targets` is keyed by column name, one entry per forecast column. The
arrays below are trimmed to the first two of the 14 steps:
```json
{
"data": {
"time": ["2024-01-04T00:00:00Z", "2024-01-05T00:00:00Z"],
"cutoff": ["2024-01-03T00:00:00Z", "2024-01-03T00:00:00Z"],
"lead_time": ["PT0S", "P1D"],
"span": ["P1D", "P1D"],
"identifiers": { "store": ["S1", "S1"] },
"targets": {
"sales": { "quantiles": { "0.1": [137.7, 137.5], "0.5": [141.0, 140.5], "0.9": [147.4, 146.2] } },
"returns": { "quantiles": { "0.1": [4.1, 4.1], "0.5": [6.2, 5.8], "0.9": [10.4, 9.5] } }
}
}
}
```
Every target shares one time axis and one set of levels: `prediction_length`,
`cutoff`, `lead_time` and `quantiles` are set once for the request and apply
to all of them. With `compute_metrics: true`, each entry in `metrics` is
keyed by target column the same way, so you get separate scores per column.
---
## Document: /api-v1/contents/cookbook/hierarchy-rollups
URL: /api-v1/contents/cookbook/hierarchy-rollups
# Group rows before forecasting
Your table is one row per store per day, but you want a forecast per city —
every store in that city summed together. Declare the grouping in
`hierarchies`, then pick the level you want with `selector`:
```json
{
"input": {
"source": {
"inline": {
"date": ["2024-01-01", "2024-01-01", "2024-01-01", "2024-01-02", "2024-01-02", "2024-01-02"],
"store": ["S1", "S2", "S1", "S1", "S2", "S1"],
"city": ["Paris", "Lyon", "Paris", "Paris", "Lyon", "Paris"],
"channel": ["online", "online", "in_store", "online", "online", "in_store"],
"sales": [42, 30, 58, 40, 28, 62]
}
},
"columns": {
"date": { "kind": "time", "frequency": "1d" },
"store": "identifier",
"city": "identifier",
"channel": "identifier",
"sales": { "kind": "target", "aggregate": "sum" }
},
"hierarchies": {
"store": [["store", "city"]]
}
},
"selector": { "city": "any" },
"prediction_length": "1w"
}
```
Post it to `https://api.retrocast.com/v1-beta/forecast?model=t0-alpha`.
`model` names the forecasting model and is required — without it the
request is rejected with `missing field "model"`.
What each part does:
- `hierarchies` says `store` sits inside `city`.
- `selector: { "city": "any" }` asks for one forecast per city. Every store
in a city is summed into it.
- `channel` appears in neither `hierarchies` nor `selector`, so it is summed
over too — `online` and `in_store` are added together.
To get fewer series back, name the values instead of `"any"`:
`{ "city": ["Paris"] }` returns Paris only, still summed across its stores
and channels.
You don't need to reshape your table or keep a separate rollup table. Send
the rows you have and pick the level at request time.
## What comes back
One row per (city, time step) — two cities over seven days, so 14 rows,
trimmed here to the first two:
```json
{
"data": {
"time": ["2024-01-03T00:00:00Z", "2024-01-04T00:00:00Z"],
"cutoff": ["2024-01-02T00:00:00Z", "2024-01-02T00:00:00Z"],
"lead_time": ["PT0S", "P1D"],
"span": ["P1D", "P1D"],
"identifiers": { "city": ["Lyon", "Lyon"] },
"targets": {
"sales": {
"quantiles": {
"0.1": [26.64, 26.68],
"0.5": [28.48, 28.71],
"0.9": [31.47, 32.40],
"mean": [28.68, 29.00]
}
}
}
}
}
```
`identifiers` carries only the dimensions that identify a row — here `city`
alone. `store` and `channel` are absent: those rows were summed together, so
there is no single value to report.
The request above sets no `quantiles`, so the response comes back with a
default spread that includes `mean`. Set `quantiles` yourself and you get
exactly the levels you asked for.
---
## Document: /api-v1/contents/cookbook/covariates
URL: /api-v1/contents/cookbook/covariates
# Add extra data like weather or prices
Other series affect your target: weather, promotions, prices. They usually
live in a separate table. `covariates` attaches that table to your input and
matches its rows to yours by key, so you don't have to join them yourself:
```json
{
"input": {
"source": {
"inline": {
"date": ["2024-01-01", "2024-01-02", "2024-01-03"],
"sales": [142, 137, 145]
}
},
"columns": {
"date": { "kind": "time", "frequency": "1d" },
"sales": { "kind": "target" }
},
"covariates": [
{
"schema": {
"version": "0.3",
"source": {
"inline": {
"date": ["2024-01-01", "2024-01-02", "2024-01-03"],
"temperature": [4.5, 3.1, 6.0]
}
},
"columns": {
"date": { "kind": "time", "frequency": "1d" },
"temperature": "historical"
}
},
"on": [{ "base": "date", "attached": "date" }]
}
]
},
"prediction_length": "1w"
}
```
Post it to `https://api.retrocast.com/v1-beta/forecast?model=t0-alpha`.
`model` names the forecasting model and is required — without it the
request is rejected with `missing field "model"`.
`on` lists the columns to match on — here the base `date` against the
attached `date`. `temperature` then behaves like a column of the base
series.
Two things to watch:
- `on: []` matches every attached row to every base row.
- Leaving `on` out is not the same as `on: []`. The request is rejected, so
always list the pairs.
Use `only` to keep a subset of the attached columns, or `rename` to rename
them.
Each extra table stays where it is. Attach weather, prices and promotions
separately instead of maintaining one wide pre-joined table.
## What comes back
Only your targets. The attached columns change the forecast but are not
echoed back:
```json
{
"data": {
"time": ["2024-01-04T00:00:00Z", "2024-01-05T00:00:00Z"],
"cutoff": ["2024-01-03T00:00:00Z", "2024-01-03T00:00:00Z"],
"lead_time": ["PT0S", "P1D"],
"span": ["P1D", "P1D"],
"identifiers": {},
"targets": {
"sales": {
"quantiles": {
"0.1": [137.6, 137.5],
"0.5": [141.0, 140.5],
"0.9": [147.4, 146.1],
"mean": [141.6, 141.0]
}
}
}
}
}
```
There is no `temperature` entry — it was an input, not something to
forecast. To get a forecast for it too, make it a column of your base series
with the `"target"` kind instead. See
[Forecast several columns at once](https://docs.retrocast.com/api-v1/contents/cookbook/multiple-targets).
---
## Document: /api-v1/contents/cookbook/backtesting
URL: /api-v1/contents/cookbook/backtesting
# Backtest on past dates
One `cutoff` gives you one forecast. A list of cutoffs re-runs the same
forecast from several points in the past, so you can compare predictions to
what actually happened:
```json
{
"input": {
"source": {
"inline": {
"date": ["2024-01-01", "2024-01-02", "2024-01-03"],
"sales": [142, 137, 145]
}
},
"columns": {
"date": { "kind": "time", "frequency": "1d" },
"sales": { "kind": "target" }
}
},
"context": "90d",
"prediction_length": "28d",
"cutoff": [{ "every": "2w" }],
"compute_metrics": true,
"quantiles": [0.1, 0.5, 0.9]
}
```
Post it to `https://api.retrocast.com/v1-beta/forecast?model=t0-alpha`.
`model` names the forecasting model and is required — without it the
request is rejected with `missing field "model"`.
What each part does:
- `"cutoff": [{ "every": "2w" }]` runs the forecast again every two weeks.
At each cutoff the model sees `context` of history and predicts
`prediction_length` ahead.
- `"compute_metrics": true` scores every cutoff where the actual values are
already in your data.
If you leave `cutoff` out, it defaults to `["latest"]`: a single forecast
starting at the most recent date in your data. That's what you want for a
normal forward-looking forecast.
## What comes back
One row per (cutoff, step). The `cutoff` column tells you which origin each
prediction came from, so a backtest and a plain forecast have the same shape.
How many cutoffs you get depends on how much history you send: the three days
above are enough to read, but they only fit one. The response below is the
same request over 200 days of daily sales, which fits 15 — 420 rows, trimmed
here to the first two:
```json
{
"data": {
"time": ["2024-01-05T00:00:00Z", "2024-01-06T00:00:00Z"],
"cutoff": ["2024-01-04T00:00:00Z", "2024-01-04T00:00:00Z"],
"lead_time": ["PT0S", "P1D"],
"span": ["P1D", "P1D"],
"identifiers": {},
"targets": {
"sales": {
"quantiles": {
"0.1": [136.9, 136.8],
"0.5": [139.9, 139.5],
"0.9": [147.7, 147.2],
"mean": [140.8, 140.5]
}
}
}
},
"metrics": [
{
"identifiers": {},
"cutoff": "2024-01-04T00:00:00Z",
"targets": {
"sales": {
"crps": 1.623,
"scaled_crps": 0.0116,
"mae": 2.522,
"mape": 0.0180,
"wape": 0.0180,
"scaled_bias": -0.00036
}
}
}
]
}
```
`identifiers` is empty here because this request declares no identifier
columns — one series, so there is nothing to name it by.
`metrics` appears only when you set `compute_metrics: true`. There is one
entry **per series and cutoff**, trimmed above to the first: each scores the
forecast made from its `cutoff` against the actual values in its forecast
window. A cutoff is scored when that window lies inside your data and holds
at least one actual value. Cutoffs that run past the end of your data are
still predicted, they just aren't scored.
`mean` is in the response even though the request asked for three quantiles:
some of the scores are computed on it, so `compute_metrics` forecasts it too.
A metric is `null` where it doesn't apply — `mape` when every actual in the
window is zero, for instance. `mape` and `wape` are fractions, not
percentages.
---
## Document: /api-v1/contents/cookbook
URL: /api-v1/contents/cookbook
# Cookbook
Worked examples of common requests. Each one is a complete request body for
the beta endpoint,
`POST https://api.retrocast.com/v1-beta/forecast?model=t0-alpha`, using the
same request body as the [Introduction](https://docs.retrocast.com/api-v1/contents/intro) — only the
fields each example needs change. The `model` query parameter is required on
every call. Every page ends with a "What comes back" section showing the
response.
Lengths of time — `prediction_length`, `context`, `lead_time`, the `every` of
a backtest — take either a number of steps of your data's time grid (`14`) or
a duration string: `"28d"`, `"2w"`, `"1mo"`, `"1d 12h"`. The units are `y`,
`q`, `mo`, `w`, `d`, `h`, `m` (minutes), `s` and `us`; ISO 8601 (`"P2W"`) works
too. A duration must be a whole number of steps, so `"1mo"` doesn't fit daily
data. Responses always write durations in ISO 8601: `P1D` for a day, `PT0S`
for none.
- [Forecast several columns at once](https://docs.retrocast.com/api-v1/contents/cookbook/multiple-targets) —
predict sales and returns in the same request.
- [Group rows before forecasting](https://docs.retrocast.com/api-v1/contents/cookbook/hierarchy-rollups) —
your data is per store, you want a forecast per city.
- [Forecast weekly or monthly totals](https://docs.retrocast.com/api-v1/contents/cookbook/time-rollups) —
your data is daily, you want a forecast per week.
- [Backtest on past dates](https://docs.retrocast.com/api-v1/contents/cookbook/backtesting) —
re-run the forecast from several points in the past and score it.
- [Send a parquet file](https://docs.retrocast.com/api-v1/contents/cookbook/parquet-sources) —
upload the data instead of putting rows in the request body.
- [Add extra data like weather or prices](https://docs.retrocast.com/api-v1/contents/cookbook/covariates) —
attach a second table and match it to yours by key.
---
Every field used here — and the ones these examples leave out, like
hierarchies of covariates — is described in the
[API Reference](https://docs.retrocast.com/api-v1/contents/api-reference). If something doesn't work
the way these examples suggest, email support@theforecastingcompany.com.