Tasqyn API · Reference
Flood probability, one HTTP call ahead.
The Tasqyn API turns hydro-meteorological observations into a calibrated probability of spring flooding a month or more ahead for snowmelt-driven river basins. One trained XGBoost model, an honest walk-forward score of ROC-AUC 0.91, a 90% confidence interval where the ensemble is available, and live multi-basin forecasting from open weather data.
Every response is plain JSON. There are no SDKs to install — the examples on this page use
curl, but any HTTP client works. If you only need one thing, you almost certainly
want GET /forecast/live: give it a basin and it returns
the next few months of flood probability, calibrated, with confidence intervals, using live weather
it fetches for you.
Base URL
All endpoints are served from a single host and addressed by path. There is no resource
prefix — paths such as /predict sit at the root.
Running the service yourself? The reference server is a FastAPI app
(uvicorn api.main:app) that listens on http://127.0.0.1:8000 by default
and ships interactive Swagger UI at /docs and a raw schema at /openapi.json.
pip install -e ".[api]"
uvicorn api.main:app --reload
# interactive docs
open http://127.0.0.1:8000/docs
Authentication
The public preview is open: no key is required, and cross-origin requests are allowed from any origin so the web app and mobile client can call it directly.
Production deployments authenticate with a bearer token. Send it in the
Authorization header; requests without a valid key receive 401.
Treat keys as secret — never ship them in client-side code you don't control.
curl https://tasqyn-api.onrender.com/basins \
-H "Authorization: Bearer $TASQYN_KEY"
Errors
Tasqyn uses conventional HTTP status codes. 2xx means success; 4xx
means the request was rejected (a bad body, an unknown basin); 5xx means the model
or an upstream data source was unavailable. Error bodies follow FastAPI's shape with a single
detail field.
| Status | Meaning |
|---|---|
| 200 | Success. |
| 400 | The observation window could not be scored (e.g. empty or malformed). |
| 404 | Unknown basin_id. Call /basins for valid ids. |
| 422 | Request body failed schema validation. |
| 502 | Live weather (Open-Meteo) was unavailable for a live forecast. |
| 503 | Model artifacts are not loaded — the service is degraded. |
{
"detail": "Unknown basin 'volga'. Known: petropavl,
nur-sultan, atbasar, kokshetau, oral, kurgan"
}
{ "detail": "Model artifacts are not available." }
Versioning
The API surface is versioned independently of the model. The service version
(1.1.0) tracks the HTTP contract; the model version
(tasqyn-xgboost-1.1.1) tracks the weights and calibration. Every forecast echoes the
exact model_version that produced it, so a stored prediction is always reproducible.
Additive changes (new fields, new endpoints) ship without a version bump. Breaking changes to the HTTP contract increment the service version.
{
"status": "ok",
"model_loaded": true,
"calibrated": true,
"model_version": "tasqyn-xgboost-1.1.1",
"n_features": 44,
"n_basins": 6
}
Core object
The Forecast object
A forecast is the model's answer for one basin in one month: a calibrated flood probability,
a human risk band, a prediction interval and the provenance metadata that tells you how much to
trust it. /predict returns one; /forecast/live returns several.
| Attribute | Type | |
|---|---|---|
| flood_probability | float | Calibrated probability of flooding, 0–1. The number to act on. |
| raw_probability | float | Pre-calibration model score, 0–1. For inspection only. |
| risk | object | Human band: label (low/moderate/high) and a hex color. |
| confidence_interval | object | 90% interval from a 15-model bootstrap: lower, upper, std. May be null. |
| threshold | float | Decision threshold used for is_flood_predicted. |
| is_flood_predicted | bool | true when flood_probability ≥ threshold. |
| in_flood_season | bool | Whether the month is in the basin's snowmelt season. |
| reliability | object | Trust metadata — see Reliability. |
| provenance | string | How the inputs were obtained: user-supplied, observed (live weather) or climatology-filled. |
| basin_id / basin_label | string | Which basin the forecast is for. May be null for an unlabelled /predict call. |
| year / month | int | The month the forecast applies to. |
| model_version | string | Exact model that produced the forecast. |
| generated_at | string | ISO-8601 timestamp. |
{
"basin_id": "petropavl",
"basin_label": "Petropavl · Ishim (Esil)",
"year": 2024,
"month": 4,
"flood_probability": 0.82,
"raw_probability": 0.913,
"confidence_interval": {
"lower": 0.55,
"upper": 0.93,
"std": 0.11
},
"risk": { "label": "high", "color": "#ef4444" },
"threshold": 0.5,
"is_flood_predicted": true,
"in_flood_season": true,
"provenance": "observed",
"reliability": {
"in_distribution": true,
"out_of_range_features": [],
"river_level_imputed": false,
"transfer": false,
"note": "in-distribution anchor basin"
},
"model_version": "tasqyn-xgboost-1.1.1",
"generated_at": "2024-03-01T09:12:44Z"
}
Core object
The Basin object
A basin is a river gauge Tasqyn can forecast. One basin — Petropavl on the Ishim (Esil) — is the anchor: the model is trained on its 1995–2024 record. The others are hydrologically similar snowmelt basins scored by transfer from live weather, and are flagged accordingly in every forecast.
| Attribute | Type | |
|---|---|---|
| id | string | Stable identifier, e.g. petropavl. |
| name | string | Gauge / town name. |
| river | string | River, e.g. Ishim (Esil). |
| country | string | ISO country, KZ or RU. |
| latitude / longitude | float | Point used to query Open-Meteo. |
| flood_season | int[] | Months when snowmelt flooding is plausible, e.g. [3,4,5]. |
| is_anchor | bool | true only for Petropavl. |
| notes | string | Free-text context. |
{
"id": "atbasar",
"name": "Atbasar",
"river": "Zhabay",
"country": "KZ",
"latitude": 51.8,
"longitude": 68.33,
"flood_season": [3, 4],
"is_anchor": false,
"notes": "Severe 2024 spring floods."
}
petropavl, nur-sultan
(Astana), atbasar, kokshetau, oral, kurgan.Core object
The Observation object
One month of hydro-meteorological readings for a single basin. You supply a chronological list of
these to /predict; the model derives lags, rolling windows and seasonal terms from the
window, so at least 13 months is recommended. Any field may be null — missing
river levels are imputed and flagged.
| Field | Type | |
|---|---|---|
| year | int | Required. 1990–2100. |
| month | int | Required. 1–12. |
| river_level_mean | float? | Mean river level (cm). |
| river_level_max | float? | Max river level (cm). |
| snow_cover | float? | Snow-cover index — the dominant signal. |
| soil_moisture | float? | Soil-moisture index. |
| temperature | float? | Mean temperature (°C). |
{
"year": 2024,
"month": 3,
"river_level_mean": 412.0,
"river_level_max": 505.0,
"snow_cover": 78.0,
"soil_moisture": 0.34,
"temperature": -4.2
}
Liveness and readiness in one call. Reports whether the model artifacts are loaded, whether the
isotonic calibrator is present, and how many features and basins are available. Returns
status: "ok" when ready and "degraded" otherwise.
Returns
A HealthResponse with status,
model_loaded, calibrated, model_version,
n_features and n_basins.
curl https://tasqyn-api.onrender.com/health
{
"status": "ok",
"model_loaded": true,
"calibrated": true,
"model_version": "tasqyn-xgboost-1.1.1",
"n_features": 44,
"n_basins": 6
}
List every river basin Tasqyn can forecast. Use the returned id values with
/forecast/live or as the optional basin_id on /predict.
Returns
An array of Basin objects.
curl https://tasqyn-api.onrender.com/basins
[
{
"id": "petropavl",
"name": "Petropavl",
"river": "Ishim (Esil)",
"country": "KZ",
"is_anchor": true,
"flood_season": [3,4,5]
},
/* … 5 more basins … */
]
Score the most recent month in a supplied observation window. This is the low-level endpoint: you bring your own data, Tasqyn engineers the features, runs the model and returns one calibrated Forecast. The result is also appended to /history.
Body parameters
/basins. Used to label the forecast and to set the
reliability flags (anchor vs transfer). Returns 404 if unknown.Returns
A single Forecast, or
503 if the model is not loaded, or 400 if the window cannot be scored.
curl https://tasqyn-api.onrender.com/predict \
-H "Content-Type: application/json" \
-d '{
"basin_id": "petropavl",
"history": [
{ "year": 2023, "month": 4, "snow_cover": 12,
"temperature": 6.1, "river_level_max": 430 },
"… 12+ earlier months …",
{ "year": 2024, "month": 3, "snow_cover": 78,
"temperature": -4.2, "river_level_max": 505 }
]
}'
{
"basin_id": "petropavl",
"year": 2024, "month": 3,
"flood_probability": 0.82,
"risk": { "label": "high", "color": "#ef4444" },
"is_flood_predicted": true,
"provenance": "user-supplied",
"model_version": "tasqyn-xgboost-1.1.1"
}
The high-level endpoint, and the one most callers want. Give it a basin and a horizon; Tasqyn
fetches live weather from the open Open-Meteo ERA5 archive (no key), builds the observation window
for you, and returns a calibrated forecast for each of the next horizon months —
each a full Forecast with a confidence interval.
Query parameters
/basins. 404 if unknown.Returns
A LiveForecastResponse: the resolved
basin, the horizon, and a forecasts array. Returns
502 if live weather is unavailable. When the live window can't be filled from
observations, missing months fall back to climatology and the forecast's provenance
is climatology-filled.
curl "https://tasqyn-api.onrender.com/forecast/live?\
basin_id=oral&horizon=3"
{
"basin": { "id": "oral", "river": "Ural (Zhayyq)" },
"horizon": 3,
"forecasts": [
{
"month": 4,
"flood_probability": 0.71,
"confidence_interval": { "lower": 0.4, "upper": 0.9 },
"risk": { "label": "high" },
"reliability": { "transfer": true }
},
/* … months 5, 6 … */
]
}
Return the most recent forecasts the service has produced, newest first. The store is an in-memory ring buffer (the reference build keeps the last 200), so this is a convenience for the app rather than a system of record.
Query parameters
Returns
A HistoryResponse with items (each a Forecast plus
an id) and a count.
curl "https://tasqyn-api.onrender.com/history?limit=5"
{
"items": [
{ "id": "a3f1…", "basin_id": "oral",
"flood_probability": 0.71 }
],
"count": 1
}
Clear the in-memory forecast history. Returns a small acknowledgement.
Returns
{ "cleared": true }.
curl -X DELETE https://tasqyn-api.onrender.com/history
{ "cleared": true }
Register a device push token so Tasqyn can alert it when a basin's risk turns high. Tokens are stored by token value, so re-registering the same device is idempotent.
Body parameters
ios, android, web.Returns
{ "registered": true, "total_devices": n }.
curl https://tasqyn-api.onrender.com/push/register \
-H "Content-Type: application/json" \
-d '{
"token": "ExponentPushToken[xxx]",
"basin_id": "petropavl",
"platform": "android"
}'
{ "registered": true, "total_devices": 1 }
The model
Model card
Tasqyn is a gradient-boosted decision-tree classifier (XGBoost) trained on the Petropavl record of the Ishim (Esil), 1995–2024. It predicts whether a given month will flood from leakage-free engineered features: lags, rolling statistics, first differences and cyclical month encoding over five raw monthly inputs (river level mean/max, snow cover, soil moisture, temperature). Rolling windows are shifted by a month so a month can never peek at itself — a property a dedicated test locks in.
We report the honest score, not the flattering one. On a naive split the model reaches ROC-AUC 1.00, which is meaningless on so few events; we keep it only as a cautionary number. The headline figures below come from leave-one-year-out walk-forward validation, where the model is retrained for each held-out year and never sees the future.
| Metric | Value | |
|---|---|---|
| ROC-AUC | 0.91 | Walk-forward, leave-one-year-out (2018–2024). |
| PR-AUC | 0.72 | Fairer than ROC at a ~17% base rate. |
| Recall | 10 / 14 | Floods caught across held-out years. |
| False alarms | 1 / 70 | Non-flood months wrongly flagged. |
| F1 (optimal) | 0.80 | At the tuned decision threshold. |
| Top feature | 0.53 | snow_cover_lag4 — snowpack 4 months before the flood. |
That top feature is the result we trust most: with no hydrology coded in, the model independently rediscovered that a spring flood depends most on the snow that fell about four months earlier — the textbook snowmelt mechanism.
{
"protocol": "leave-one-year-out walk-forward",
"held_out_years": [2018, …, 2024],
"n_samples": 84,
"n_floods": 14,
"base_rate": 0.167,
"roc_auc": 0.914,
"pr_auc": 0.717,
"f1_at_optimal": 0.80,
"confusion_matrix": [[69, 1], [4, 10]]
}
The model
Risk bands
Every forecast carries a human-facing risk band derived from the calibrated
probability, so a UI can colour it without re-implementing thresholds. Bands are inclusive of their
lower bound.
| Label | Probability | Color |
|---|---|---|
| low | < 0.33 | #22c55e green |
| moderate | 0.33 – < 0.66 | #f59e0b amber |
| high | ≥ 0.66 | #ef4444 red |
is_flood_predicted uses the separate threshold field, which a caller can
tune to trade recall against false alarms.{
"label": "high",
"color": "#ef4444"
}
The model
Reliability & calibration
Probabilities are calibrated with isotonic regression fitted on out-of-sample scores, and the 90% interval comes from a 15-model bootstrap ensemble — so the numbers mean what they say and carry their own uncertainty.
Because the model is trained on one anchor basin, Tasqyn is explicit about when a forecast is a
stretch. Every forecast carries a reliability object you should surface to users.
| Field | Type | |
|---|---|---|
| in_distribution | bool | true when inputs are in range and it's the anchor basin. |
| transfer | bool | true for non-anchor basins scored by transfer — treat as indicative. |
| out_of_range_features | string[] | Inputs beyond the training range; non-empty means extrapolation. |
| river_level_imputed | bool | true when missing river levels were filled in. |
| note | string | Plain-language summary, ready to show. |
{
"in_distribution": false,
"transfer": true,
"out_of_range_features": ["snow_cover"],
"river_level_imputed": true,
"note": "transfer basin — indicative only"
}
calibrated flag.