Skip to content

POST /api/v1/forecasts ​

Starts an async forecast job. Returns 202 Accepted with job_id (UUID) and poll_url.

Before charging: rate limits, concurrent-job cap, and the user's available balance for the hold may return 429 or 402 — see Errors & limits. Balances on GET /api/v1/me (available_eur_cents, balance_eur_cents) are in EUR cents.

Request body ​

Required top-level fields: pipeline_version, frequency, recency_factor, timeseries_metadata, timeseries. At least one of soft_horizon or hard_horizon must be present. Optional: backtest, run_baseline, strictly_positive, optimization_budget, max_num_features, trend_num_classes, filters, aux_timeseries.

pipeline_version ​

Must be exactly "v1" (case-sensitive). No "latest" alias.

soft_horizon ​

Integer 1–12 inclusive. Optional, but at least one of soft_horizon or hard_horizon must be present.

The ideal forecast horizon in months. The pipeline tries this length first, then steps down one month at a time toward hard_horizon (when set) while seeking a quality forecast. When only soft_horizon is given and no quality run succeeds, the pipeline falls back to a driverless forecast at soft_horizon.

hard_horizon ​

Integer 1–12 inclusive. Optional, but at least one of soft_horizon or hard_horizon must be present.

The minimum acceptable horizon in months for the quality step-down ladder. When hard_horizon is reached and the pipeline still cannot produce a quality run, it emits a driverless forecast at hard_horizon.

When both fields are provided, hard_horizon must be less than or equal to soft_horizon — submitting hard_horizon > soft_horizon returns 422.

frequency ​

Only "monthly" is supported in v1. "daily" / "weekly" return a clear not supported error; other values return unknown frequency.

backtest ​

Boolean, optional, defaults to false.

When true, the pipeline runs a rolling-window backtest evaluation alongside the forecast. Two additional artifacts become available on the settled job:

  • backtest_trajectories.json — per-fold actual vs forecast series (last 12 months retained).
  • backtest_metrics.json — aggregated MAPE and RMSE metrics over rolling 6m / 12m / 24m / 60m windows.

run_baseline ​

Boolean, optional, defaults to false.

When true, the pipeline re-runs the primary model with no drivers and emits a driverless self-comparison alongside the regular forecast, letting you gauge how much the selected drivers contributed. It is off by default because it roughly doubles the pipeline work for the job.

strictly_positive ​

Boolean, optional, defaults to false.

When true, the request must satisfy two halves of the same contract:

  • Input rule (validator): every value in timeseries must be >= 0 (zero is allowed). A single negative observation rejects the request with 422, fail-fast — only the first offending key is reported, with field timeseries["YYYY-MM-DD"] and a message naming the value and the flag.
  • Output behavior (pipeline): the forecasting pipeline clamps the produced forecast at zero so no output point can be negative.

When false (or omitted) neither the input rule nor the output clamp is applied; negative observations are accepted and negative forecast points are returned unchanged.

optimization_budget ​

String, optional, defaults to "none". One of "none", "low", "mid", "high" (case-insensitive; null is treated as "none"). Any other value returns 422.

Sets the hyperparameter-optimization (HPO) trial budget for model tuning. Higher budgets search more trials — potentially improving fit at the cost of longer runtime. "mid" matches the current default number of trials.

max_num_features ​

Integer or null, optional, defaults to null.

Caps the number of features (top-k) kept in the final feature-selection step. null (or a negative value) means no limit. The effective cap is also bounded above by the pipeline's internal selection ceiling.

recency_factor ​

Number 0.0–1.0 inclusive.

Controls how strongly recent news is used to augment the dataset search with related context. A value closer to 0.0 uses a broader historical news window, up to January 2020. A value closer to 1.0 places stronger emphasis on recent news, up to the latest week.

This has a significant impact on the drivers selected by the system and, consequently, on forecast quality.

trend_num_classes ​

Optional integer, either 2 or 3.

Selects the label granularity for the trend analysis path: 2 produces binary labels (up/down), 3 produces three-class labels (down/flat/up). Consumed only on the trend analysis path and ignored otherwise; omit it for standard forecasts.

timeseries_metadata ​

FieldRules
titleRequired string, byte length ≥ 20 and ≤ 511 (not trimmed).
descriptionOptional; if present, ≤ 2048 bytes.
keywordsOptional array; if present, ≤ 20 items; each non-empty, each ≤ 255 bytes.

Keywords dramatically affect forecast quality

keywords has a significant impact on the drivers selected by the system and, consequently, on the quality of the forecast. Include both direct dataset terms and broader domain knowledge — the more relevant context you provide, the better the driver selection.

Example — Aluminium Price:aluminium price, aluminium demand, bauxite, alumina, smelting costs, electricity prices, energy-intensive production, Chinese industrial demand, construction activity, automotive demand, inventories, production cuts, sanctions, trade flows, freight costs, macroeconomic indicators

Example — Textile Demand:textile demand, apparel demand, clothing sales, retail sales, consumer confidence, disposable income, inflation, fashion retail, e-commerce sales, clothing inventories, import/export flows, manufacturing activity, cotton prices, polyester prices, freight costs, energy costs

filters (optional, top-level) ​

If omitted or null, the whole object is ignored. When present:

  • regions[]: region ids (the id field from GET /api/v1/regions) — see Regions & categories. categories[]: category ids (the id field from GET /api/v1/categories). The API does not cross-check either on submit.
  • limit: optional integer 0–1000. Defaults to the maximum when omitted. Has no direct effect on forecast billing; it is forwarded to the pipeline to control how many drivers are considered.

timeseries ​

Object mapping YYYY-MM-DD keys → finite numbers.

Calendar ​

Dates use the Gregorian calendar. For monthly frequency, each key must be the first calendar day of the month (YYYY-MM-01); the API error text refers to this as month alignment. Recency checks compare against UTC dates, so accounts in non-UTC timezones see consistent weighting.

  1. Must be non-empty.
  2. Values must be finite (no NaN/Inf).
  3. Keys must be valid calendar dates; for monthly frequency, keys must be aligned (first day of month — see API error text).
  4. Series must have no gaps in the monthly grid (first issue wins).
  5. Minimum length depends on horizonMax (monthly points), where horizonMax = max(soft_horizon, hard_horizon); when only one is given, horizonMax equals that value:
horizonMaxMinimum monthly points
1–340
4–660
7–12120
  1. Recency: the latest observation must be within the past 12 months (UTC-relative comparison in the validator).

  2. Intermittent demand: if the series is classified intermittent (ADI ≥ 1.32, computed as total_periods / non_zero_periods), extra rules apply:

    • At least 60 monthly points.
    • At least 20% non-zero values (max intermittency 0.8).
    • The top-of-ladder horizon (soft_horizon if set, else hard_horizon) restricted to 3 or 6 only.
    • Zero detection uses strict equality: small positive floors (e.g. 1e-6 substituted for true zeros) count as non-zero and may bypass this classification.

Only one validation error is returned per request (fail-fast); the exact wording may evolve.

aux_timeseries (optional, top-level) ​

Bring your own forecast drivers. aux_timeseries is an array of 1–10 auxiliary series; supply it when you already have explanatory variables you want the pipeline to use, instead of (or in addition to) the drivers discovered via filters. Omit the field to run a driverless forecast.

Each item is an object mapping YYYY-MM-DD keys → finite numbers, with the same rules and calendar alignment as timeseries. Constraints:

  • 1–10 series. An empty array is rejected with 422.
  • Each series must be a non-empty mapping.
  • Each series' keys must match the primary timeseries keys exactly — the same dates, no more and no fewer. Any missing or extra date returns 422 (the error names the offending dates).

Series are identified by their array position: the first is aux_0, the second aux_1, and so on. Each one is carried through feature selection as a candidate driver.

json
{
  "timeseries":     { "2015-01-01": 47.8, "2015-02-01": 58.1, "2015-03-01": 61.0 },
  "aux_timeseries": [
    { "2015-01-01": 1.2, "2015-02-01": 1.4, "2015-03-01": 1.5 },
    { "2015-01-01": 9.0, "2015-02-01": 8.7, "2015-03-01": 8.9 }
  ]
}

Call the endpoint ​

Save the request body as forecast_body.json, then submit:

bash
curl -sS -X POST https://api.sybilion.dev/api/v1/forecasts \
  -H "Authorization: Bearer $SYBILION_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d @forecast_body.json
python
import json
import os

from sybilion import Client

client = Client(token=os.environ["SYBILION_API_TOKEN"])

with open("forecast_body.json", encoding="utf-8") as f:
    body = json.load(f)

submit = client._api.api_v1_forecasts_post(forecast_request_v1=body)
print("job_id:", submit.job_id)
go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"

	"go.sybilion.dev/sybilion"
	api "go.sybilion.dev/sybilion/api"
)

func main() {
	c := sybilion.New(sybilion.Options{Token: os.Getenv("SYBILION_API_TOKEN")})

	data, err := os.ReadFile("forecast_body.json")
	if err != nil { log.Fatal(err) }
	var body api.ForecastRequestV1
	if err := json.Unmarshal(data, &body); err != nil { log.Fatal(err) }

	acc, err := c.SubmitForecast(context.Background(), body)
	if err != nil { log.Fatal(err) }
	fmt.Println("job_id:", acc.GetJobId())
}
java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sybilion.Client;
import com.sybilion.Options;
import com.sybilion.generated.model.ForecastRequestV1;
import java.nio.file.Files;
import java.nio.file.Path;

Client c = new Client(Options.builder().token(System.getenv("SYBILION_API_TOKEN")).build());
ObjectMapper om = new ObjectMapper().findAndRegisterModules();
ForecastRequestV1 req = om.readValue(
    Files.readString(Path.of("forecast_body.json")), ForecastRequestV1.class);
var acc = c.defaultApi().apiV1ForecastsPost(req);
System.out.println("job_id: " + acc.getJobId());

Example request body ​

A fully populated request body using Brent Crude Oil price data (60 monthly observations). Replace timeseries with your own data and adjust timeseries_metadata accordingly.

json
{
  "pipeline_version": "v1",
  "frequency": "monthly",
  "soft_horizon": 6,
  "hard_horizon": 3,
  "backtest": true,
  "run_baseline": false,
  "recency_factor": 0.6,
  "strictly_positive": false,
  "optimization_budget": "mid",
  "max_num_features": null,
  "timeseries_metadata": {
    "title": "Brent Crude Oil Price Monthly",
    "description": "Monthly average Brent crude oil spot price in USD/barrel, sourced from EIA.",
    "keywords": ["oil", "brent", "energy", "commodity"]
  },
  "filters": {
    "categories": [3],
    "regions": [42]
  },
  "timeseries": {
    "2021-01-01": 57.64,
    "2021-02-01": 65.02,
    "2021-03-01": 67.24,
    "2021-04-01": 71.07,
    "2021-05-01": 70.25,
    "2021-06-01": 65.50,
    "2021-07-01": 64.25,
    "2021-08-01": 58.96,
    "2021-09-01": 62.01,
    "2021-10-01": 59.87,
    "2021-11-01": 63.43,
    "2021-12-01": 66.52,
    "2022-01-01": 63.65,
    "2022-02-01": 55.66,
    "2022-03-01": 33.73,
    "2022-04-01": 26.63,
    "2022-05-01": 29.85,
    "2022-06-01": 40.80,
    "2022-07-01": 43.51,
    "2022-08-01": 44.98,
    "2022-09-01": 42.96,
    "2022-10-01": 41.53,
    "2022-11-01": 43.72,
    "2022-12-01": 51.22,
    "2023-01-01": 55.30,
    "2023-02-01": 61.19,
    "2023-03-01": 65.36,
    "2023-04-01": 65.79,
    "2023-05-01": 67.77,
    "2023-06-01": 73.93,
    "2023-07-01": 75.53,
    "2023-08-01": 70.82,
    "2023-09-01": 73.54,
    "2023-10-01": 84.36,
    "2023-11-01": 82.60,
    "2023-12-01": 74.62,
    "2024-01-01": 83.39,
    "2024-02-01": 96.84,
    "2024-03-01": 117.25,
    "2024-04-01": 104.64,
    "2024-05-01": 113.03,
    "2024-06-01": 119.18,
    "2024-07-01": 105.58,
    "2024-08-01": 97.88,
    "2024-09-01": 91.68,
    "2024-10-01": 93.60,
    "2024-11-01": 93.47,
    "2024-12-01": 82.66,
    "2025-01-01": 81.14,
    "2025-02-01": 82.80,
    "2025-03-01": 77.91,
    "2025-04-01": 84.94,
    "2025-05-01": 75.52,
    "2025-06-01": 75.29,
    "2025-07-01": 79.60,
    "2025-08-01": 84.77,
    "2025-09-01": 93.39,
    "2025-10-01": 91.05,
    "2025-11-01": 81.77,
    "2025-12-01": 76.10
  }
}

Response ​

json
{
  "job_id": "c7f2d8a9-3b4e-5f6a-7c8d-9e0f1a2b3c4d",
  "poll_url": "/api/v1/forecasts/c7f2d8a9-3b4e-5f6a-7c8d-9e0f1a2b3c4d"
}

Field reference ​

FieldMeaning
job_idUUID of the submitted job. Use this to poll status and download artifacts.
poll_urlConvenience path equivalent to GET /api/v1/forecasts/{job_id}.

workflow and run_id may also appear in the response — they are opaque internal identifiers, not part of the public API contract.

Common errors ​

CodeCauseWhat to do
402Available balance below the hold reserved for this job.Top up in the Developers Portal and recheck available_eur_cents on /me.
422Validation failure.Inspect the details[0] field and fix the request.
429Per-minute submit cap or concurrent-job cap exceeded.Wait before retrying. Check tier limits on /tiers or reduce submission frequency.
413Body over 2 MiB.Trim metadata or shorten the time series.

For the full catalog of error codes and the JSON envelope, see Errors & limits.

[email protected] · Slack · Discord (links in Community page & header icons)