Timeseries as a Service API

Send a daily series, get a probabilistic forecast back. Check your data first with the validate endpoint: it runs every rule below and stores nothing.

Access

Every TaaS endpoint needs an account with API access enabled and a tenant of its own; Kodaco provisions both (request access). Self-registered demo accounts and accounts without API access get 403, a missing or invalid token 401.

TOKEN=$(curl -s -X POST https://kodaco.dev/api/token \
  -d "username=you@company.com" -d "password=$PASSWORD" | jq -r .access_token)

Endpoints

Method and pathWhat it doesSuccess
POST /api/v1/forecast-jobs/validateChecks a series against every rule. Nothing is stored and no pipeline starts. Limited to 30 calls per minute.200
POST /api/v1/forecast-jobsRuns the same checks, then queues a forecast job. You get an e-mail when it finishes.202
GET /api/v1/forecast-jobs/{job_id}The job's status; once succeeded it links to the forecast.200

The full schema is in the OpenAPI reference.

Data format

The request body is JSON:

FieldRule
horizon_daysDays to forecast, 1 to 90 (default 28). The forecast starts the day after today (UTC), or after the last actual if that is later.
forecast_name1–64 of A-Z a-z 0-9 _ . -, starting with a letter or digit; RECONCILED_, demo_ and test_ are reserved. Required for forecast jobs, optional for validate.
prediction_intervalForecast jobs only: width of the interval, between 0 and 1 (default 0.8).
seriesA list of daily rows, at most 5,000 (about ten years of history plus the horizon).

Each row has exactly these columns:

ColumnRule
dateRequired. A real day as YYYY-MM-DD, on or after 2020-01-02, each date once.
actualRequired. The value to forecast: a JSON number (int or float), or null for an unknown day. Not after today (UTC). Strings, booleans, NaN, Infinity and values beyond ±1012 are rejected. Call it actual, not target.
ext_<name>Optional external regressors, at most 5; <name> is 1–40 lowercase letters, digits and underscores. Numbers only, on every day from the first date through the horizon end: today or the last actual, whichever is later, plus horizon_days. The forecast needs their future values; the validate response gives the date as summary.horizon_end_date. The model uses them alongside its calendar features.

History. The time between the first and last actual must be at least 3 × horizon_days, and at least what cross-validation needs (56 training days plus two folds of up to 28 days, which is 97 days at a 28-day horizon). The larger of the two applies: 66 days at horizon 7, 97 at 28, 270 at 90. The validate response tells you the number for your horizon in summary.required_history_days.

Rows after the last actual (with actual: null) carry ext_ values for the forecast days and may not go beyond the horizon end.

Validate a series

curl -X POST https://kodaco.dev/api/v1/forecast-jobs/validate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "horizon_days": 28,
    "series": [
      {"date": "2026-01-01", "actual": 112.0, "ext_promo": 0},
      {"date": "2026-01-02", "actual": 98.5,  "ext_promo": 1},
      …
      {"date": "2026-05-29", "actual": null,  "ext_promo": 0}
    ]
  }'

200: the series is valid. Warnings may still apply.

{
  "valid": true,
  "errors": [],
  "warnings": [],
  "summary": {
    "rows": 149, "horizon_days": 28, "required_history_days": 97,
    "first_date": "2026-01-01", "last_date": "2026-05-29",
    "first_actual_date": "2026-01-01", "last_actual_date": "2026-05-01",
    "history_days": 120, "horizon_end_date": "2026-05-29",
    "missing_days": 0, "ext_columns": ["ext_promo"]
  }
}

422: every problem is listed once per code and column, with how many rows have it and the first ten row indexes (counted from 0). Your values are never echoed back.

{
  "detail": "The series failed validation",
  "valid": false,
  "errors": [
    {"code": "duplicate_date", "message": "Each date may occur only once.",
     "column": "date", "count": 1, "rows": [120]},
    {"code": "insufficient_history", "message": "The series spans 30 days …"}
  ],
  "warnings": [],
  "summary": { … }
}

A malformed request (a wrong field type, horizon_days out of range, a reserved name) also returns 422 with the same errors list, using code invalid_request and a field instead of column.

Error codes

CodeMeaning
invalid_requestA request field is missing or invalid (see field).
empty_seriesseries is not a non-empty list.
too_many_rowsMore than 5,000 rows.
invalid_rowA row is not a JSON object.
missing_columnA row lacks date or actual.
inconsistent_columnsRows do not all have the same ext_ columns.
unknown_columnA column other than date, actual or a well-formed ext_<name>.
target_not_allowedA target column: use actual.
too_many_ext_columnsMore than 5 ext_ columns.
invalid_dateNot a real day written as YYYY-MM-DD.
date_too_earlyA date before 2020-01-02.
duplicate_dateA date occurs more than once.
invalid_actualactual is not a finite JSON number within ±1012 (or null).
invalid_ext_valueAn ext_ value is not a finite JSON number within ±1012.
ext_incompleteAn ext_ value is null, or a day between the first date and the horizon end has no row.
actual_in_futureAn actual dated after today (UTC).
no_actualsNo row has an actual.
insufficient_historyToo little time between the first and last actual (see History).
rows_beyond_horizonRows dated after the horizon end (today or the last actual, whichever is later, plus horizon_days).
pii_detectedA value or column name looks like personal data: an e-mail address, phone number, IBAN, payment card or citizen service number (BSN).
pii_suspect_columnA column name suggests personal data (email, name, phone, address, iban, bsn, birth, …).
forbidden_charactersA column name or value contains SQL or quoting characters (; ' -- /*, SQL keywords).

Other statuses: 401 (no or invalid token), 403 (no API access, or a shared demo tenant), 429 (more than 30 validate calls per minute).

Warnings

Warnings never fail a request; they point at things worth a second look.

CodeMeaning
missing_daysDays without a row before the last actual. They are treated as unknown, not zero.
constant_actualEvery actual has the same value.
negative_actualSome actual values are negative.

Privacy

Never send personal data. Only dates and numbers are accepted, so names, e-mail addresses and other text are rejected outright, and column names are checked as well.

The validate endpoint keeps your series in memory only for the duration of the request: it is never written to a database, file or log, and responses report row indexes, not values. Forecast jobs store the series, because the pipeline needs it, in your own tenant on EU-hosted servers.