POST /v1/forecast
POST https://api.sequenxai.com/v1/forecast forecasts the history of one or more entities online. The server first cleans the data under the effective cleaning policy (the raw preset only checks its structure), then runs the selected model, and finally maps the forecast back onto timestamps. Authentication is described in Authentication & Headers.
1. Request examples
export SEQUENX_API_KEY="sqx_live_..." # created at https://platform.sequenxai.com
# 96 points at 15-minute steps, h=24, level=[80, 90], model hora-1.0
curl -sSO https://docs.sequenxai.com/data/power-grid-15m.json
curl -sS -X POST "https://api.sequenxai.com/v1/forecast" \
-H "Authorization: Bearer $SEQUENX_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Request-Id: quickstart-0001" \
-d @power-grid-15m.jsonimport math
import os
import requests
API_URL = "https://api.sequenxai.com/v1/forecast"
API_KEY = os.environ["SEQUENX_API_KEY"]
# 48 hourly observations of one meter.
series = [
{
"unique_id": "meter-001",
"ds": f"2026-09-{1 + i // 24:02d}T{i % 24:02d}:00:00Z",
"y": round(140 + 8 * math.sin(2 * math.pi * i / 24), 1),
}
for i in range(48)
]
resp = requests.post(
API_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
json={"model": "hora-1.0", "series": series, "h": 24, "freq": "1h", "level": [80, 90]},
timeout=180,
)
body = resp.json()
if resp.status_code != 200:
error = body["error"]
raise SystemExit(f"{resp.status_code} {error['code']}: {error['message']} ({error['request_id']})")
print(body["request_id"], body["effective_policy"]["id"], body["usage"])
for row in body["forecast"][:3]:
print(row["ds"], row["y_hat"], row["intervals"]["80"])// Node.js 18+ (global fetch). Call from your server; never ship the key to a browser.
const API_URL = "https://api.sequenxai.com/v1/forecast";
// 48 hourly observations of one meter.
const series = Array.from({ length: 48 }, (_, i) => ({
unique_id: "meter-001",
ds: new Date(Date.UTC(2026, 8, 1, i)).toISOString().replace(".000Z", "Z"),
y: Math.round((140 + 8 * Math.sin((2 * Math.PI * i) / 24)) * 10) / 10,
}));
const resp = await fetch(API_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SEQUENX_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "hora-1.0", series, h: 24, freq: "1h", level: [80, 90] }),
signal: AbortSignal.timeout(180_000),
});
const body = await resp.json();
if (!resp.ok) {
const { code, message, request_id } = body.error;
throw new Error(`${resp.status} ${code}: ${message} (${request_id})`);
}
console.log(body.request_id, body.effective_policy.id, body.usage);
for (const row of body.forecast.slice(0, 3)) {
console.log(row.ds, row.y_hat, row.intervals["80"]);
}2. Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
model | string | No | hora-1.0 | hora-1.0 or its alias hora; any other id returns 422 MODEL_NOT_AVAILABLE. Limits are in Models & Limits. |
model_options | object | No | — | Model switches nonnegative ("auto" / true / false, default "auto") and flip_average (default true). Closed object: an unknown key returns 422 VALIDATION_ERROR, a key the model does not take returns 422 MODEL_OPTION_UNSUPPORTED. Billing stays per forecast point. See Model options. |
series | array | Yes | — | Historical long-table rows, at least 1; together with future_covariates at most 200,000 rows per request (more returns POINTS_EXCEEDED). Each entity may not be longer than the model's maximum context (SERIES_TOO_LONG); send only the most recent points. |
series[].unique_id | string | Yes | — | Entity identifier; one request may carry several entities. |
series[].ds | string | Yes | — | Timestamp. An explicit offset (Z, +08:00) is honored; values without one are read in options.timezone, see Long Table, Frequency & Time Zones. |
series[].y | number or null | Yes | — | Observation; null marks a missing value. The key itself must be present; non-finite numbers are rejected. |
Other series[] columns | number / string / boolean / null | No | — | Past covariates, at most 8 columns; aligned to the cleaned series and reported per column in model_input[].covariates. The model does not use covariates today; each column that passes the data checks is reported model_unsupported. See Covariate Support. |
h | integer | Yes | — | Forecast horizon, 1–2048, and no larger than the model's maximum horizon, otherwise 422 HORIZON_EXCEEDS_MODEL. |
freq | string | No | Inferred | Frequency string, see Frequency strings. Inferred by the cleaning service when omitted; required with the raw preset. |
level | integer[] | No | None | Interval levels, each 1–99; duplicates are merged. Without it the response has no intervals. For quantiles a model cannot produce natively, see Prediction Intervals. |
quantile_policy | nearest / strict | No | nearest | Beta. nearest uses the nearest native quantile and labels the interval with the level actually delivered; strict returns 422 QUANTILE_UNSUPPORTED for any non-native quantile. Applies to point_quantile too. |
point_quantile | number | No | None (median) | Beta. Quantile returned as y_hat (for example 0.7 for P70). 0.01–0.99 with at most 3 decimal places, otherwise 422 VALIDATION_ERROR. Mapped to the model's native quantiles under quantile_policy; see section 5 of Prediction Intervals. |
clean_series | boolean | No | true | false detects without repairing: as soon as a repair is needed the call returns 422 TIMESTAMP_IRREGULAR or SERIES_DISCONTINUOUS. |
clean_exog | boolean | No | true | Fill short covariate gaps linearly (up to the policy's short-gap limit); with false any gap makes the column unusable. |
future_covariates | array | No | — | Future-known values, one row per unique_id and forecast stamp. Columns must also appear on series rows (422 COVARIATE_COLUMN_MISMATCH); y is not allowed (422 FUTURE_COVARIATES_INVALID). |
options.timezone | string | No | UTC | IANA zone name (for example Asia/Shanghai) for reading ds values without an offset; unknown names return 422 VALIDATION_ERROR. |
options.ds_format | dmy or mdy | No | — | Day/month order for D/M/YYYY-style stamps the data cannot prove on its own; otherwise AMBIGUOUS_DATE_FORMAT. |
options.gap_split_min_steps | integer ≥ 0 | No | Automatic | Gap length in grid steps after which only the latest contiguous segment is kept; 0 disables the split. |
options.cleaning.preset | default / strict / raw | No | See below | Cleaning preset. When omitted: key default, then organization default, then platform default (default). |
options.cleaning.version | 2026-13 (2026-10 / 2026-11 / 2026-12 deprecated) | No | 2026-13 | Rule-set version, resolved like the preset. We recommend omitting it; 2026-10, 2026-11 and 2026-12 will be removed after notice, and pinning one adds a cleaning_version_deprecated warning. See Policy versions. |
options.cleaning.impute_method, seasonal_period, nonnegative, bounds, structural_zero, business_days, long_gap | see link | No | — | Knobs of version 2026-11 and later (so also of the default 2026-13); under the deprecated 2026-10 they return 422 CLEANING_OPTION_UNSUPPORTED with details.requires_version. See the Cleaning Policy Reference. |
options.cleaning.accept | string[] | No | — | Applies proposed decisions by id; at most 200 ids of up to 200 characters each. |
options.cleaning.decline | string[] | No | — | Reverts applied, reversible decisions by id; same limits. |
options.router_strategy | auto / ensemble / single / fast | No | — | Contract-reserved; the value is validated but has no effect today. |
Fields outside the contract at the top level or inside options.cleaning return 422 VALIDATION_ERROR (details.errors[].field names the field, e.g. options.cleaning.imputer) and are never silently ignored; unknown keys directly under options are still ignored. Presets, accept and decline are explained in Cleaning Presets & Report.
3. Validation order
The server handles a request in this order; a failed check stops the request before later steps. No non-200 response is billed.
- Authentication and scope: 401
INVALID_API_KEY, 403INSUFFICIENT_SCOPE. Then the account checks: 503SERVICE_UNAVAILABLEwithRetry-Afterwhile SequenxAI has paused the endpoint, 403ACCOUNT_SUSPENDED, 403ORG_PENDING_DELETION. ThenIdempotency-Key, when sent: 400VALIDATION_ERRORfor a malformed key, 422IDEMPOTENCY_KEY_REUSED, 409IDEMPOTENCY_IN_PROGRESS; an identical retry of a finished request returns the stored result here, without counting toward the per-minute limit. - Requests per minute per account (shared by all its keys): 429 with
X-RateLimit-Policy: forecast-rpm. Rate-limited calls are not recorded. - Body parsing and field validation: 400
MALFORMED_JSON, 422VALIDATION_ERROR(including more than 8 covariate columns),FUTURE_COVARIATES_INVALID,COVARIATE_COLUMN_MISMATCH. - Row cap (
seriesplusfuture_covariates): 422POINTS_EXCEEDED. - Cleaning preset: 422
CLEANING_OPTION_UNSUPPORTED;rawwithoutfreqreturns 422VALIDATION_ERROR. - Model: any id other than
hora-1.0/horareturns 422MODEL_NOT_AVAILABLE, amodel_optionskey the model does not take returns 422MODEL_OPTION_UNSUPPORTED, temporarily unavailable returns 503SERVICE_UNAVAILABLE;habove the model's limit returns 422HORIZON_EXCEEDS_MODEL; withquantile_policy: strict, a non-native quantile (includingpoint_quantile) returns 422QUANTILE_UNSUPPORTED; an entity with more distinct timestamps (rows underraw) than the model's maximum context returns 422SERIES_TOO_LONG. - Daily point quota: reserves "rows +
h× entities"; if that does not fit, 429 withX-RateLimit-Policy: forecast-daily-points. - Cleaning (or the
rawstructural check): the 422 cleaning codes,RAW_INPUT_INVALID,SERIES_TOO_SHORT, andSERIES_TOO_LONGwhen the cleaned grid (filled slots included) is longer than the model's maximum context. - Inference: 503
MODEL_BUSY, 504GATEWAY_TIMEOUT, 503SERVICE_UNAVAILABLE. - Success: settles on the observed and forecast points the model actually handled and returns the rest of the reservation.
4. Response fields
| Field | Notes |
|---|---|
request_id | Server request ID, same as the X-Request-Id header. |
model_used | The model ID that ran: hora-1.0, also when the request sent the alias hora. |
freq | Your freq as sent; when omitted, the inferred canonical token (for example h, 15min, MS). |
point_quantile | Beta. Present only when the request sent point_quantile: the native quantile actually served as y_hat (differs from the request when it was substituted). |
forecast[] | h rows per entity: unique_id, ds (UTC, YYYY-MM-DDTHH:MM:SSZ), y_hat, and intervals when level was requested. y_hat is the model's median (quantile 0.5) by default, not the expected value; with point_quantile it is that quantile. |
forecast[].intervals | Keyed by the level string of the quantiles actually used ("80", "90"), each value {"lo", "hi"} with lo ≤ hi. See Prediction Intervals. |
quantile_report | Beta. {policy, requested, used, substitutions[]}: the requested quantiles, the native quantiles actually used, and each substitution {requested, used}. With point_quantile it also has point_forecast: {requested, used, method}. |
data_quality | Legacy quality summary: status (ok or cleaned), inferred_freq (set only when freq was omitted), missing_ratio (timestamp-gap share before repair), imputed_points, outlier_points_suppressed (always 0 today; outliers are flagged, never modified), timezone_normalized, notes. |
usage.series_count | Entities processed. |
usage.input_data_points | Observed points actually fed to the model, counted after the long-gap split, excluding imputed values and inserted stamps. |
usage.forecast_data_points | Forecast points produced (h × entities). |
usage.latency_ms | Server time from receiving the request to assembling the forecast; not an SLA. |
effective_policy | {id, preset, version, source}: the cleaning preset and version that applied, and its source (request / key / org / platform). |
model_input[] | Per entity: context_points (points handed to the model), fed_points (observed ones among them), truncated (always 0: a series longer than the model's context is rejected, never cut), missing_mode, covariates_used, and covariates[] (per column: name, role, used, reason) when covariates were sent. Present only when they apply: masked_tail and tail_handling (trailing empty values kept under policy version 2026-12), and long_gap_points, long_gap_handling and long_gap_reason (with options.cleaning.long_gap: "mask"; SequenxAI Omni reads long gaps as missing values, so long_gap_handling is "mask"; see Long gaps: fill or mask). |
warnings[] | Beta. Notices that did not stop the forecast; omitted when there are none. code is trailing_values_missing, long_gap_mask_unavailable or cleaning_version_deprecated, with unique_id when it concerns one entity. Codes may be added; read them before using the forecast. |
cleaning_report | The cleaning_report/1 structured report (Beta); absent with the raw preset. See Cleaning Presets & Report. |
5. Response example
Numbers are illustrative and cleaning_report is abridged:
{
"request_id": "req_4f1c8e2a9b7d4e0c8a3f5b6d7e8f9a0b",
"model_used": "hora-1.0",
"freq": "1h",
"forecast": [
{
"unique_id": "meter-001",
"ds": "2026-09-03T00:00:00Z",
"y_hat": 140.6,
"intervals": {
"80": {"lo": 137.9, "hi": 143.2},
"90": {"lo": 136.4, "hi": 144.8}
}
}
],
"data_quality": {
"status": "ok",
"inferred_freq": null,
"missing_ratio": 0.0,
"imputed_points": 0,
"outlier_points_suppressed": 0,
"timezone_normalized": true,
"notes": []
},
"usage": {
"series_count": 1,
"input_data_points": 48,
"forecast_data_points": 24,
"latency_ms": 812
},
"effective_policy": {
"id": "default@2026-13",
"preset": "default",
"version": "2026-13",
"source": "platform"
},
"model_input": [
{
"unique_id": "meter-001",
"context_points": 48,
"fed_points": 48,
"truncated": 0,
"missing_mode": "filled",
"covariates_used": false
}
],
"quantile_report": {
"policy": "nearest",
"requested": [0.05, 0.1, 0.5, 0.9, 0.95],
"used": [0.05, 0.1, 0.5, 0.9, 0.95],
"substitutions": []
},
"cleaning_report": {
"schema_version": "cleaning_report/1",
"policy": {"id": "default@2026-13", "...": "..."},
"overall": {"series_count": 1, "input_points": 48, "output_points": 48, "...": "..."},
"series": ["..."],
"warnings": [],
"blocking": [],
"decisions": []
}
}6. The raw preset
options.cleaning.preset: "raw" skips cleaning and only runs a structural check before the model. Use it for data you already clean in your own systems. Requirements:
freqis required and must be a fixed step (s,min,h,D,Wand multiples); calendar frequencies such asMSorM@15are not supported.- Every
dsis exactlyYYYY-MM-DDTHH:MM[:SS[.ffffff]]followed byZor±HH:MM; no other format is accepted. - Each entity's rows are strictly increasing in time, one
freqstep apart; nothing is sorted, de-duplicated or filled. - Every
yis a finite number, nevernull. - It cannot be combined with
accept/declineor cleaning parameters such asimpute_method(422CLEANING_OPTION_UNSUPPORTED).
Otherwise the call returns 422 RAW_INPUT_INVALID. details.violations lists up to 20 problems, each with unique_id, index and reason, where reason is one of freq_invalid, calendar_freq_unsupported, ds_format, ds_duplicate, ds_not_increasing, step_mismatch, not_observed:
{
"error": {
"code": "RAW_INPUT_INVALID",
"message": "Raw input is not a regular, finite series",
"request_id": "req_…",
"details": {
"violations": [
{"unique_id": "meter-001", "index": 5, "reason": "step_mismatch"}
],
"hint": "Use the default cleaning preset, or call /v1/clean to see the cleaning report."
}
}
}Raw responses carry no cleaning_report, and data_quality is always {"status": "ok", "missing_ratio": 0.0}. Under raw the row count of each entity is checked against the model's maximum context before anything runs (SERIES_TOO_LONG); nothing is cut.