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 path | What it does | Success |
|---|---|---|
POST /api/v1/forecast-jobs/validate | Checks a series against every rule. Nothing is stored and no pipeline starts. Limited to 30 calls per minute. | 200 |
POST /api/v1/forecast-jobs | Runs 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:
| Field | Rule |
|---|---|
horizon_days | Days 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_name | 1–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_interval | Forecast jobs only: width of the interval, between 0 and 1 (default 0.8). |
series | A list of daily rows, at most 5,000 (about ten years of history plus the horizon). |
Each row has exactly these columns:
| Column | Rule |
|---|---|
date | Required. A real day as YYYY-MM-DD, on or after 2020-01-02, each date once. |
actual | Required. 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
| Code | Meaning |
|---|---|
invalid_request | A request field is missing or invalid (see field). |
empty_series | series is not a non-empty list. |
too_many_rows | More than 5,000 rows. |
invalid_row | A row is not a JSON object. |
missing_column | A row lacks date or actual. |
inconsistent_columns | Rows do not all have the same ext_ columns. |
unknown_column | A column other than date, actual or a well-formed ext_<name>. |
target_not_allowed | A target column: use actual. |
too_many_ext_columns | More than 5 ext_ columns. |
invalid_date | Not a real day written as YYYY-MM-DD. |
date_too_early | A date before 2020-01-02. |
duplicate_date | A date occurs more than once. |
invalid_actual | actual is not a finite JSON number within ±1012 (or null). |
invalid_ext_value | An ext_ value is not a finite JSON number within ±1012. |
ext_incomplete | An ext_ value is null, or a day between the first date and the horizon end has no row. |
actual_in_future | An actual dated after today (UTC). |
no_actuals | No row has an actual. |
insufficient_history | Too little time between the first and last actual (see History). |
rows_beyond_horizon | Rows dated after the horizon end (today or the last actual, whichever is later, plus horizon_days). |
pii_detected | A value or column name looks like personal data: an e-mail address, phone number, IBAN, payment card or citizen service number (BSN). |
pii_suspect_column | A column name suggests personal data (email, name, phone, address, iban, bsn, birth, …). |
forbidden_characters | A 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.
| Code | Meaning |
|---|---|
missing_days | Days without a row before the last actual. They are treated as unknown, not zero. |
constant_actual | Every actual has the same value. |
negative_actual | Some 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.