Forecasts
Async forecasts run on the Sybilion pipeline. You submit a ForecastRequestV1 and receive a job_id; you then poll until the job is settled; then you download the resulting JSON artifacts (point and quantile forecasts, driver attributions, optional backtest metrics and trajectories).
This page walks the full happy path with curl, the Python SDK, and the Go SDK. For the complete validation rules and per-field response shape see POST /api/v1/forecasts, GET /api/v1/forecasts/:id, and Artifact download.
When to use forecasts
- You have a monthly time series with at least 40 observations (more for longer horizons — see below).
- You want point forecasts and optionally quantile bands for the next 1–12 months.
- You want explanation: per-driver importance and direction.
- Optionally: rolling-window backtest metrics and trajectories.
For synchronous "what drivers correlate with my series?" answers without running the full pipeline, use Drivers instead.
1. Submit the job
A minimal valid body: pick a horizon and frequency, attach metadata and a timeseries map of YYYY-MM-DD → finite numbers. The example below uses 60 monthly points so it covers horizons up to 6.
cat > forecast_body.json <<'EOF'
{
"pipeline_version": "v1",
"horizon": 6,
"frequency": "monthly",
"backtest": true,
"recency_factor": 0.5,
"strictly_positive": true,
"timeseries_metadata": {
"title": "Aluminum price in Europe USD/KG",
"description": "Spot price of primary aluminum in the European market.",
"keywords": ["aluminum", "metals", "commodities"]
},
"filters": { "categories": [101], "regions": [42] },
"timeseries": {
"2021-01-01": 1.95, "2021-02-01": 2.05
/* … 58 more monthly points … */
}
}
EOF
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 | jqimport datetime as dt
import os
from sybilion import Client
client = Client(token=os.environ["SYBILION_API_TOKEN"])
# Build 60 synthetic monthly points (replace with your real series).
anchor = dt.date(2021, 1, 1)
timeseries = {
anchor.replace(
year=anchor.year + (anchor.month - 1 + i) // 12,
month=(anchor.month - 1 + i) % 12 + 1,
).isoformat(): 100.0 + i * 0.5
for i in range(60)
}
body = {
"pipeline_version": "v1",
"horizon": 6,
"frequency": "monthly",
"backtest": True,
"recency_factor": 0.5,
"strictly_positive": True,
"timeseries_metadata": {
"title": "Aluminum price in Europe USD/KG",
},
"timeseries": timeseries,
}
submit = client.raw.api_v1_forecasts_post(forecast_request_v1=body)
print("job_id:", submit.job_id)package main
import (
"context"
"fmt"
"log"
"os"
"time"
"go.sybilion.dev/sybilion"
api "go.sybilion.dev/sybilion/api"
)
func main() {
c := sybilion.New(sybilion.Options{
Token: os.Getenv("SYBILION_API_TOKEN"),
})
meta := api.NewTimeseriesMetadata("Aluminum price in Europe USD/KG")
ts := map[string]float32{}
anchor := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
for i := range 60 {
d := anchor.AddDate(0, i, 0).Format("2006-01-02")
ts[d] = 100 + float32(i)*0.5
}
body := api.NewForecastRequestV1("v1", 6, "monthly", 0.5, *meta, ts)
body.SetBacktest(true)
body.SetStrictlyPositive(true)
acc, _, err := c.DefaultAPI().
ApiV1ForecastsPost(context.Background()).
ForecastRequestV1(*body).
Execute()
if err != nil {
log.Fatal(err)
}
fmt.Println("job_id:", acc.GetJobId())
}A successful submit returns 202 Accepted with a job_id (UUID) and poll_url. Validation errors are 422 with one {field, message} per response (fail-fast); see Errors & limits for the envelope.
Minimum series length
Horizon 1–3 needs 40+ monthly points; 4–6 needs 60+; 7–12 needs 120+. The latest observation must fall within the past 12 months. Full rules: POST /api/v1/forecasts.
2. Poll until the job settles
Forecasts run for tens of seconds to a few minutes. Poll GET /api/v1/forecasts/:id until settled is true, then read the resulting status (completed, failed, or canceled) and the artifacts array.
JOB_ID="<paste job_id>"
API="https://api.sybilion.dev"
until curl -sS -H "Authorization: Bearer $SYBILION_API_TOKEN" \
"$API/api/v1/forecasts/$JOB_ID" | jq -e '.settled == true' >/dev/null; do
sleep 2
done
curl -sS -H "Authorization: Bearer $SYBILION_API_TOKEN" \
"$API/api/v1/forecasts/$JOB_ID" | jq '{status, eur_cents_final, artifacts: .artifacts | map(.name)}'job = client.wait_forecast(submit.job_id, poll_s=2.0, timeout_s=600.0)
print("status:", job.status, "cost (cents):", job.eur_cents_final)
for a in job.artifacts or []:
print(" -", a.name, a.size, "bytes")ctx := context.Background()
job, err := c.Forecasts().Wait(ctx, acc.GetJobId(), 2*time.Second)
if err != nil {
log.Fatal(err)
}
fmt.Println("status:", job.GetStatus(), "cost (cents):", job.GetEurCentsFinal())
for _, a := range job.GetArtifacts() {
fmt.Println(" -", a.GetName(), a.GetSize(), "bytes")
}status == "completed" means a successful run with downloadable artifacts. failed and canceled ship a terminal_reason and (when available) a bounded pipeline_error JSON object — read those to surface the problem to the caller. The job ID stays valid afterward for replays of the read endpoints.
3. Download artifacts
Artifacts are streamed through the API at GET /api/v1/forecasts/:id/artifacts/:name — there are no direct storage URLs. Use the name values from the artifacts array.
ART="forecast.json"
curl -sS -H "Authorization: Bearer $SYBILION_API_TOKEN" \
-o "$ART" \
"https://api.sybilion.dev/api/v1/forecasts/$JOB_ID/artifacts/$ART"
jq '.data.forecast_series | to_entries | .[0]' "$ART"import json
from sybilion import ApiException
raw_api = client.raw # underlying generated DefaultApi
try:
body = raw_api.api_v1_forecasts_id_artifacts_name_get(
id=submit.job_id, name="forecast.json"
)
forecast = json.loads(body)
first_date, point = next(iter(forecast["data"]["forecast_series"].items()))
print(first_date, "->", point.get("forecast"), point.get("quantile_forecast"))
except ApiException as exc:
print("download failed:", exc.status, exc.body)import (
"encoding/json"
"io"
"net/http"
)
req, _ := http.NewRequestWithContext(ctx, "GET",
"https://api.sybilion.dev/api/v1/forecasts/"+job.GetJobId()+"/artifacts/forecast.json",
nil,
)
req.Header.Set("Authorization", "Bearer "+os.Getenv("SYBILION_API_TOKEN"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
buf, _ := io.ReadAll(resp.Body)
var envelope struct {
Version string `json:"version"`
Data map[string]interface{} `json:"data"`
}
_ = json.Unmarshal(buf, &envelope)
fmt.Println(envelope.Version, "keys:", len(envelope.Data))Artifact set
A successful run produces up to four files, all sharing the envelope { "version": "...", "data": {...} }:
| File | When | Contents |
|---|---|---|
forecast.json | Always | Point forecast and (when applicable) quantile bands per horizon. |
external_signals.json | Always | Driver attributions: importance, direction, correlation per driver. |
backtest_trajectories.json | When backtest: true | Per-fold actual vs forecast (last 12 months retained). |
backtest_metrics.json | When backtest: true | Aggregated metrics over rolling 6m / 12m / 24m / 60m windows. |
Full schema for each file is in Artifact download.
Common errors
| Code | Cause | What to do |
|---|---|---|
402 | Available balance below the hold for this job. | Top up in the Developers Portal; recheck available_eur_cents on /me. |
422 | Validation failure (one detail). | Inspect details[0].field and details[0].message and fix the body. |
429 | Per-minute submit cap or concurrent-job cap exceeded. | Back off; check your tier on /tiers. |
413 | Body over 2 MiB. | Trim metadata or shorten the time series. |
For the full catalog of error codes and the JSON envelope, see Errors & limits.
See also
- API reference: POST /api/v1/forecasts · GET /api/v1/forecasts/:id · Artifact download
- Regions & categories — pick valid
filters.regions/filters.categoriesids. - Clients: Using curl · Python SDK · Go SDK.