Resources › Connect with the API
API reference
Every endpoint an API key can read, with all its parameters and response fields. This page is generated from the API's own description, so it always matches what the API serves. All endpoints are GET, at https://api.flowai-forecast.com, with the header Authorization: Bearer fai_…. For how to get a key and working scripts, see Connect with the API.
Gauges
GET /api/v1/gauges
List all gauges
Returns a lightweight summary of every registered gauge. Supports filtering by province, regulation_status, and ecozone. Results are served from Redis when no filters are applied and the cache is warm.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
q | query | string or null | Search by gauge name or ID (case-insensitive substring match) | |
province | query | string or null | Filter by HYDAT province/state code, e.g. ON, QC, MN. Gauges HYDAT does not list have no province and never match. | |
regulation_status | query | string or null | Filter by regulation status: NATURAL | REGULATED | UNKNOWN | |
ecozone | query | string or null | Filter by ecozone, e.g. SHIELD_CENTRAL, GREAT_LAKES_LOWLAND |
Response: GaugeListResponse (21 fields)
| Field | Type | Description |
|---|---|---|
count | integer | Number of gauges returned |
gauges | array of GaugeResponse | |
gauges[].aliases | array of string | ids of the ORRC points merged into this site. Each resolves to this site and is left out of the list; a search matches them |
gauges[].area_km2 | number or null | Published gross drainage area (km²) from HYDAT; null when HYDAT does not list the station or publishes no area |
gauges[].area_source | "hydat" or null | 'hydat' when the station is matched to a HYDAT record, else null |
gauges[].attribution | array of string | The credit lines the gauge's observations must carry wherever they are shown or exported, in print order; empty when the provider is unknown. |
gauges[].data_provider | string or null | Who the observations come from: wsc (Water Survey of Canada, read through the Ontario Surface Water Monitoring Centre), wsc_direct (Water Survey of Canada, read from ECCC directly: stations outside Ontario), swmc (Ontario Surface Water Monitoring Centre), grca (Grand River Conservation Authority), usgs (U.S. Geological Survey, through SWMC) or flowai_derived (computed by FlowAi from other providers' series; attribution credits them). Null when unknown. Not the same as `provider`, the KiWIS feed (WSC or CA). |
gauges[].ecozone | string or null | Ecozone classification |
gauges[].id | string | WSC station ID, e.g. 08MH006 |
gauges[].is_active | boolean | Whether gauge is currently active |
gauges[].lat | number | |
gauges[].location_province | string or null | two-letter code of the Canadian province or territory whose Statistics Canada 2021 boundary holds the gauge's coordinates (the nearest within 2 km when they fall just outside every boundary, unless HYDAT places the station in a US state). Null outside Canada. A lookup on the coordinates, unlike `province` |
gauges[].lon | number | |
gauges[].name | string | The gauge's name; a site that merges an ORRC forecast point is '<point> / <station>' when the station's name lacks the point's, so a search finds it by either |
gauges[].nearest_place | string or null | name of the nearest GeoNames populated place (a town of at least 200 people or a seat of government, not a district of a larger place; Canada and the US border states). Place names from GeoNames (geonames.org), CC BY 4.0 |
gauges[].nearest_place_km | number or null | great-circle distance from the gauge to the nearest place, km, 1 decimal |
gauges[].nearest_place_region | string or null | the nearest place's province, territory or US state code, e.g. ON, QC, MN |
gauges[].provider | string or null | Data provider, e.g. WSC |
gauges[].province | string or null | Province or state code from HYDAT; null for stations HYDAT does not list |
gauges[].regulation_status | string or null | NATURAL | REGULATED | UNKNOWN |
gauges[].updated_at | string (date-time) |
GET /api/v1/gauges/{gauge_id}
Get gauge detail
Returns metadata for a single gauge including its model_card. Below Commercial the card carries only kge and updated_at (model_card_gated is true), matching /gauges/{id}/model-card. Returns 404 if the gauge ID is not registered.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string |
Response: GaugeDetailResponse (64 fields)
| Field | Type | Description |
|---|---|---|
area_km2 | number or null | |
area_source | "hydat" or null | |
attribution | array of string | The credit lines the gauge's observations must carry wherever they are shown or exported, in print order; empty when the provider is unknown. |
basin_outline_withheld | boolean | True when FlowAi's basin outline for this river is under review (modelling refused it, outline-check/v1; or redrew it and has not re-judged it; until a gauge has a verdict, one implying more than 1,500 mm/yr of runoff): delineated_area_km2 is then null and basin weather is not served. Never true for a gauge with a live model |
data_provider | string or null | Who the observations come from: wsc (Water Survey of Canada, read through the Ontario Surface Water Monitoring Centre), wsc_direct (Water Survey of Canada, read from ECCC directly: stations outside Ontario), swmc (Ontario Surface Water Monitoring Centre), grca (Grand River Conservation Authority), usgs (U.S. Geological Survey, through SWMC) or flowai_derived (computed by FlowAi from other providers' series; attribution credits them). Null when unknown. Not the same as `provider`, the KiWIS feed (WSC or CA). |
delineated_area_km2 | number or null | Area of the basin FlowAi delineated for its model (km²). A model input, not a published drainage area; it can differ widely from HYDAT's. |
ecozone | string or null | |
flow_kind | "river_flow" | "reservoir_release" | "measured_flow" or null | What the gauge's `flow` signal measures: river_flow (a measured river flow, the only kind coloured against percentiles), reservoir_release (a dam's release) or measured_flow (the measured, regulated flow at a station whose natural flow FlowAi computes). Null when the gauge has no flow signal |
forecast_issued_at | string (date-time) or null | Issue time of this gauge's newest forecast (the issue date at 00:00 UTC); null when it has none, which is always the case for an observations-only gauge. Not tier-gated: it is not forecast content |
forecast_target_time_utc | string or null | The daily target issue time, HH:MM UTC, shown beside forecast_issued_at (a target, not a guarantee; GET /status has the history). Null for an observations-only gauge |
has_forecast_model | boolean | True when FlowAi runs a forecast model for this gauge (the live model registry). False means observations only; a True gauge with no current /forecast has a missing or late issue |
id | string | |
inflow_origins | map of "published" | "derived" | for each inflow among `signals`, whose series it is: published (the provider's own computed series, such as ORRC's natural or total inflow) or derived (FlowAi computes it from other providers' series: a reservoir water balance or a natural-flow sum). Flow, level and temperature are always the provider's readings and are not listed; empty when the gauge has no inflow |
is_active | boolean | |
is_stale | boolean | True when FlowAi runs a forecast model for this gauge and its newest forecast is older than the schedule allows, or it has none. The rule is the freshness sensor's (api/src/forecast_freshness.py). Always false for an observations-only gauge |
lat | number | |
level_kind | "water_level" | "reservoir_elevation" or null | What the level series measures |
listed | boolean | False when the station's feeds have never delivered a value: it is left out of the gauge list, search and map |
location_province | string or null | two-letter code of the Canadian province or territory whose Statistics Canada 2021 boundary holds the gauge's coordinates (the nearest within 2 km when they fall just outside every boundary, unless HYDAT places the station in a US state). Null outside Canada. A lookup on the coordinates, unlike `province` |
lon | number | |
model_card | ModelCardSchema or null | |
model_card.calibration_period | string or null | |
model_card.development_split | object or null | Which water years the served model used, lifted from held_out_skill so it is public at every tier (, published by): calibration_water_years (Raven fitted), validation_water_years (used to select the model and corrector, so not independent), spinup_water_years (not scored) and selection: peak_ranked_alternation | volume_peak_stratified | odd_even_water_years | template_year_list | gauge_year_list | experiment_year_list | unrecognised, as recorded when the split was made (ledger). Optional ranked_on (date of the observation record a peak or volume-and-peak ranking used) and spinup_scored (the early method also used the spin-up year). Optional masked_unforced_days and masked_unforced_events (integers/): the high-flow days, and the events they belong to, given weight 0 in calibration and validation because the model's forcing does not explain them; present only when the model masked days, absent otherwise. The held-out year is validation_period. Null when the card does not carry it. |
model_card.held_out_skill | object or null | Held-out WY2026 skill of the served model, per lead, beside raw Raven: median relative error, share of days off by 30% or more, NSE and sample count. Not scored against persistence. Carries its own schema, evaluation_window, weather_basis and bundle_identity. weather_basis is retained historical forcing, so these are DEVELOPMENT figures and not operational forecast skill. |
model_card.kge | number or null | Kling–Gupta Efficiency (primary metric) |
model_card.limitations | string or null | Free-text limitations note |
model_card.ml_kge_14d | number or null | ML post-processor KGE at 14-day lead |
model_card.ml_kge_1d | number or null | ML post-processor KGE at 1-day lead |
model_card.ml_kge_3d | number or null | ML post-processor KGE at 3-day lead |
model_card.ml_kge_7d | number or null | ML post-processor KGE at 7-day lead |
model_card.model_trained_at | string (date-time) or null | When the served model was trained; null when the card does not say |
model_card.nse | number or null | Nash–Sutcliffe Efficiency (display only) |
model_card.pbias | number or null | Percent bias (%) |
model_card.process_chain | string or null | Raven process combination string |
model_card.timestep | string or null | |
model_card.updated_at | string (date-time) | |
model_card.validation_period | string or null | |
model_card.validation_status | string or null | How the served model was validated, lifted from held_out_skill so it is public at every tier: 'not_performed_short_record' when the model was trained on a short record and not validated with a holdout year; null otherwise. |
model_card_gated | boolean | True when model_card fields other than kge and updated_at are withheld for this caller's plan |
models | array of GaugeModelSchema | the gauge's live forecast models, the primary first, then its second forecast where it has one (below an operated dam or regulated lake: the total inflow over the whole basin, beside the natural inflow primary). Empty when `has_forecast_model` is false. Choose which to show with `signal` on /forecast, /forecast-history, /hindcast, /model-card and /bundle |
models[].area_km2 | number or null | The area (km²) of the basin the MODEL is built over, which for a secondary is the whole basin and for a natural-inflow primary the local one. The gauge's own `area_km2` stays the whole basin. Null when it is not recorded (the local basin of a natural-inflow primary) |
models[].basin | "whole" | "local" or null | The basin it is calibrated on: `whole` (the drainage above the gauge) or `local` (the part between the upstream dams and the gauge). Null when not recorded (a model promoted before) |
models[].caveat | "total_upstream" or null | `total_upstream` for a second forecast (the total inflow follows the operation of the dams and regulated lakes upstream; the web app's INFLOW_FORECAST_CAVEAT). Null for the primary |
models[].forecast_issued_at | string (date-time) or null | Issue time of this model's newest forecast; null while it has issued none (a model that just went live): then GET /forecast?signal= answers with no forecasts |
models[].gate_kge | number or null | KGE of the live bundle's latest gate score. Commercial and Enterprise only (null below) |
models[].gate_nse | number or null | NSE of the live bundle's latest gate score (the 7-day corrected forecast on the validation record). Commercial and Enterprise only (null below) |
models[].is_stale | boolean | True when this model's newest forecast is older than the schedule allows, or it has none (the rule of the gauge's `is_stale`) |
models[].signal | "flow" | "natural_inflow" | "total_inflow" | "level" | What the model forecasts: pass it as `signal` to /forecast, /model-card and /bundle for this model (omit it for the primary). `level` (slot `derived`) is /forecast's only |
models[].slot | "primary" | "secondary" | "derived" | `primary` is the gauge's forecast (thresholds and alerts are on it); `secondary` is the second forecast issued beside it. The secondary is listed only while its own model and the primary are live. `derived` is the level forecast: the primary flow forecast through the gauge's stage-discharge rating, listed only while a rating that passed the publish gate is current. It has no model of its own: only /forecast takes `signal=level` (no model card, hindcast or history) |
models[].smoothing | "none" | "7day" or null | The target it is calibrated on: `none`, or `7day` (a centred 7-day mean) |
models[].template | string or null | The Raven template the model is built on. Commercial and Enterprise only, as the rest of the model card (null below) |
name | string | |
nearest_place | string or null | name of the nearest GeoNames populated place (a town of at least 200 people or a seat of government, not a district of a larger place; Canada and the US border states). Place names from GeoNames (geonames.org), CC BY 4.0 |
nearest_place_km | number or null | great-circle distance from the gauge to the nearest place, km, 1 decimal |
nearest_place_region | string or null | the nearest place's province, territory or US state code, e.g. ON, QC, MN |
provider | string or null | |
province | string or null | |
redirected_from | SiteRedirect or null | one gauge per site: present only when the id asked for is an ORRC point merged into this gauge (the measured gauge at its site). The point's inflows are this gauge's natural_inflow / total_inflow signals; show this gauge (its id is `id`) on `signal`. Absent for every other gauge |
redirected_from.gauge_id | string | The merged point's id, as asked for |
redirected_from.signal | "flow" | "level" | "natural_inflow" | "total_inflow" | "temperature" | The signal its own series is served as here: its model series (an inflow) |
regulation_status | string or null | |
signals | array of "flow" | "level" | "natural_inflow" | "total_inflow" | "temperature" | the signals this gauge can be looked at by, in display order: natural_inflow (a reservoir's computed natural inflow, or a natural flow), total_inflow (a reservoir's total inflow), flow (on a gauge with an inflow: its release or measured regulated flow, observation only), level, temperature. The forecast, thresholds and alerts attach to the signal the gauge's live model is calibrated on (; the forecast's `signal`): flow, natural_inflow or total_inflow; it is listed first. The percentile status is of the stored model series. at a site where an ORRC point was merged, natural_inflow / total_inflow are that point's series (local inflow between the upstream control and the site; total = natural + routed upstream releases) beside the site's own flow and level; the model series is the point's inflow at a dam or reservoir, else the site's own flow where it runs a live model |
time_zone | string or null | the IANA time zone the gauge keeps (America/Halifax for Nova Scotia, America/St_Johns for Newfoundland, America/Regina for Saskatchewan,...), from its HYDAT province and its coordinates where a province spans zones; the one source of truth for the zone a gauge's own times and 'today' are shown in. Left out when HYDAT lists no province for the station: the viewer's or organisation's zone then applies |
updated_at | string (date-time) |
GET /api/v1/gauges/sources
List distinct gauge source values
Returns the distinct data providers present in the gauge inventory (dim_gauges.provider), sorted: WSC (Water Survey of Canada) and CA (Ontario's provincial network). this was hard-coded to WSC.
No parameters.
A Delft-FEWS PI time series document (PI-XML, or PI-JSON with documentFormat=PI_JSON): see Connect Delft-FEWS.
GET /api/v1/gauges/map/conditions
Gauges for water conditions map
Returns all gauges with their latest readings and flow status relative to historical percentiles. Used by the Ontario Water Conditions map page. Latest readings are fetched from PostgreSQL (fact_hydrometric_obs_daily table). Percentile status is computed client-side based on day-of-year.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
source | query | string or null | Filter by data provider (WSC or CA), as listed by /gauges/sources. | |
series | query | "smoothed" | "raw" | "both" | smoothed | which series of an inflow signal (natural_inflow, total_inflow) is served: `smoothed` (default; bad values removed, short quiet gaps filled and marked, see each reading's `inflow_series`), `raw` (a derived gauge's unsmoothed water balance, called `balance`; a provider's published value) or `both` (smoothed, with the raw value and its QC verdict beside it). A gauge whose smoothed series failed its acceptance metrics is served its raw series whatever is asked (`inflow_smoothing` says so). Other signals are unaffected. |
Response: GaugeMapListResponse (68 fields)
| Field | Type | Description |
|---|---|---|
count | integer | Number of gauges returned |
forecast_freshness | ForecastFreshness | Fleet-wide forecast freshness, whatever `source` filters the gauges to |
forecast_freshness.is_stale | boolean | True when no forecast exists, or the newest one is older than the schedule allows (the freshness sensor's rule, api/src/forecast_freshness.py) |
forecast_freshness.latest_issued_at | string (date-time) or null | Newest forecast issue time across all gauges (the issue date at 00:00 UTC); null when there is none |
forecast_freshness.target_time_utc | string | The daily target issue time, HH:MM UTC: the moment is_stale turns true when today's forecast has not arrived. A target, not a guarantee; GET /status has the history |
gauges | array of GaugeMapResponse | |
gauges[].flow_kind | "river_flow" | "reservoir_release" | "measured_flow" or null | What the gauge's `flow` signal measures: river_flow (a measured river flow, the only kind coloured against percentiles), reservoir_release (a dam's release) or measured_flow (the measured, regulated flow at a station whose natural flow FlowAi computes). Null when the gauge has no flow signal |
gauges[].has_forecast | boolean | True if the gauge is in the live model registry and has an issued forecast (the same gate /forecast applies) |
gauges[].id | string | WSC station ID |
gauges[].inflow_origins | map of "published" | "derived" | for each inflow among `signals`, whose series it is: published (the provider's own computed series, such as ORRC's natural or total inflow) or derived (FlowAi computes it from other providers' series: a reservoir water balance or a natural-flow sum). Flow, level and temperature are always the provider's readings and are not listed; empty when the gauge has no inflow |
gauges[].last_observation_date | string (date) or null | Last daily observation, for a station that is not reporting |
gauges[].lat | number | |
gauges[].latest_reading | LatestReadingSchema or null | |
gauges[].latest_reading.age_minutes | integer or null | Minutes since the flow reading (the level reading when the gauge reports no flow) |
gauges[].latest_reading.flag | string or null | SUSPECT when FlowAi's automatic QC flagged the model series reading (flow_cms, natural_inflow_cms or total_inflow_cms). A release and an observation-only inflow carry their own verdicts (flow_flag, natural_inflow_flag, total_inflow_flag) |
gauges[].latest_reading.flag_reasons | array of string or null | QC reason codes behind the flag; null when clean |
gauges[].latest_reading.flow_cms | number or null | Latest flow in cubic metres per second: the real-time reading, or on a gauge with an inflow its newest daily release (flow_date) |
gauges[].latest_reading.flow_date | string (date) or null | on a gauge with an inflow, flow_cms is its newest daily release (or measured regulated flow), and this is the day it covers; null on a gauge whose flow is its real-time reading |
gauges[].latest_reading.flow_flag | string or null | SUSPECT when FlowAi's automatic QC flagged flow_cms (the gauge's own stored flow; the site's, at a merged site). where flow_cms is a release, the verdict on that day's release. Null when clean |
gauges[].latest_reading.flow_flag_reasons | array of string or null | QC reason codes behind flow_flag; null when clean |
gauges[].latest_reading.inflow_date | string (date) or null | the day the observation-only inflow (natural_inflow_cms or total_inflow_cms, whichever is not the gauge's model series) covers; null when the gauge has none |
gauges[].latest_reading.natural_inflow_cms | number or null | the latest natural inflow (m3/s) of a gauge modelled on it; timestamp, flag and age_minutes are then this reading's. on a gauge modelled on its total inflow, its observation-only natural inflow (inflow_date). Null elsewhere |
gauges[].latest_reading.natural_inflow_flag | string or null | SUSPECT when QC flagged natural_inflow_cms, where it is a stored model series, or the verdict on an observation-only inflow's day; null when clean |
gauges[].latest_reading.natural_inflow_flag_reasons | array of string or null | QC reason codes behind natural_inflow_flag; null when clean |
gauges[].latest_reading.natural_inflow_series | InflowSeriesDay or null | what natural_inflow_cms is, as /gauges/{id}/latest's natural_inflow_series (smoothed by default: the value the chart shows that day; the status colour is judged on it). Left out where the signal has no smoothed series |
gauges[].latest_reading.natural_inflow_series.adjusted | string or null | Why raw was not used on this day: `floored` (a remaining negative set to 0), `excluded:negative`, `excluded:qc:<reason>` (a flagged value) or `excluded:hampel` (an isolated spike); null when the smoothed value of a usable raw value was used. `edge_smoothed`: one of a derived series' newest 5 days, which have no later day to be fitted against yet: the smoothed value as of the newest day, revised by the daily runs as later days arrive (a reading, scored like any smoothed day). `capped`: a derived natural inflow cut to its total |
gauges[].latest_reading.natural_inflow_series.filled | boolean | True when the day has no usable raw value and the smoothed value is an estimate from the days around it (a gap of at most 3 days in quiet conditions). Filled days are not scored in training or validation |
gauges[].latest_reading.natural_inflow_series.gap_days | integer or null | Length of the gap a filled day sits in |
gauges[].latest_reading.natural_inflow_series.method | string or null | Method, version and parameters of the smoothed series, e.g. `rts-v4:n0.01:iv3.55` or `provider-qc-cap-v1`; null on a `raw` day |
gauges[].latest_reading.natural_inflow_series.raw_cms | number or null | Only with series=both: this day's raw value (what series=raw serves as the signal's value): the unsmoothed water balance for a derived gauge (`raw_series` balance), the published value for a provider series. Its QC verdict is raw_flag |
gauges[].latest_reading.natural_inflow_series.raw_flag | string or null | Only with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC |
gauges[].latest_reading.natural_inflow_series.raw_flag_reasons | array of string or null | Only with series=both: the reasons behind raw_flag |
gauges[].latest_reading.natural_inflow_series.series | "smoothed" | "raw" | Which series the signal's value (`natural_inflow_cms` / `total_inflow_cms`) is on this day. `smoothed` by default; `raw` when raw was asked for, and for a day the smoothed series does not cover yet (the newest day, which has no closing midnight level) - never silently |
gauges[].latest_reading.stage_age_minutes | integer or null | Minutes since the level reading; each value carries its own age |
gauges[].latest_reading.stage_flag | string or null | SUSPECT when FlowAi's automatic QC flagged the level reading |
gauges[].latest_reading.stage_flag_reasons | array of string or null | QC reason codes behind stage_flag; null when clean |
gauges[].latest_reading.stage_m | number or null | Latest water level in metres (if available) |
gauges[].latest_reading.timestamp | string (date-time) or null | Time of the most recent reading (UTC) of the gauge's model series: flow_cms, or on a gauge with an inflow natural_inflow_cms / total_inflow_cms; the level's when it has none |
gauges[].latest_reading.total_inflow_cms | number or null | the latest total inflow (m3/s) of a gauge modelled on it; timestamp, flag and age_minutes are then this reading's. on a gauge modelled on its natural inflow, its observation-only total inflow, a daily value (inflow_date says the day; its QC verdict is total_inflow_flag, never the status). Null elsewhere |
gauges[].latest_reading.total_inflow_flag | string or null | as natural_inflow_flag, for total_inflow_cms |
gauges[].latest_reading.total_inflow_flag_reasons | array of string or null | QC reason codes behind total_inflow_flag; null when clean |
gauges[].latest_reading.total_inflow_series | InflowSeriesDay or null | what total_inflow_cms is, as natural_inflow_series |
gauges[].latest_reading.total_inflow_series.adjusted | string or null | Why raw was not used on this day: `floored` (a remaining negative set to 0), `excluded:negative`, `excluded:qc:<reason>` (a flagged value) or `excluded:hampel` (an isolated spike); null when the smoothed value of a usable raw value was used. `edge_smoothed`: one of a derived series' newest 5 days, which have no later day to be fitted against yet: the smoothed value as of the newest day, revised by the daily runs as later days arrive (a reading, scored like any smoothed day). `capped`: a derived natural inflow cut to its total |
gauges[].latest_reading.total_inflow_series.filled | boolean | True when the day has no usable raw value and the smoothed value is an estimate from the days around it (a gap of at most 3 days in quiet conditions). Filled days are not scored in training or validation |
gauges[].latest_reading.total_inflow_series.gap_days | integer or null | Length of the gap a filled day sits in |
gauges[].latest_reading.total_inflow_series.method | string or null | Method, version and parameters of the smoothed series, e.g. `rts-v4:n0.01:iv3.55` or `provider-qc-cap-v1`; null on a `raw` day |
gauges[].latest_reading.total_inflow_series.raw_cms | number or null | Only with series=both: this day's raw value (what series=raw serves as the signal's value): the unsmoothed water balance for a derived gauge (`raw_series` balance), the published value for a provider series. Its QC verdict is raw_flag |
gauges[].latest_reading.total_inflow_series.raw_flag | string or null | Only with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC |
gauges[].latest_reading.total_inflow_series.raw_flag_reasons | array of string or null | Only with series=both: the reasons behind raw_flag |
gauges[].latest_reading.total_inflow_series.series | "smoothed" | "raw" | Which series the signal's value (`natural_inflow_cms` / `total_inflow_cms`) is on this day. `smoothed` by default; `raw` when raw was asked for, and for a day the smoothed series does not cover yet (the newest day, which has no closing midnight level) - never silently |
gauges[].latest_reading.water_temp_age_minutes | integer or null | Minutes since the water temperature reading |
gauges[].latest_reading.water_temp_c | number or null | Latest water temperature in °C (if available) |
gauges[].level_kind | "water_level" | "reservoir_elevation" or null | |
gauges[].lon | number | |
gauges[].name | string | |
gauges[].percentile_status | PercentileStatusSchema or null | |
gauges[].percentile_status.p10 | number or null | 10th percentile for today's DOY |
gauges[].percentile_status.p25 | number or null | 25th percentile |
gauges[].percentile_status.p50 | number or null | 50th percentile (median) |
gauges[].percentile_status.p75 | number or null | 75th percentile |
gauges[].percentile_status.p90 | number or null | 90th percentile |
gauges[].percentile_status.signal | "flow" | "level" | "natural_inflow" | "total_inflow" | Which signal the percentiles are derived from (the gauge's model series, or level; a release never sets the status) |
gauges[].percentile_status.status | "below_p50" | "p50_p75" | "p75_p90" | "above_p90" | "no_data" | "has_data" | "flagged" | "not_reporting" | "not_river" | Status relative to historical percentiles, from a reading no older than 72 h that QC has not flagged; flagged when the current flow reading is flagged, in which case the level does not colour the station either, or when the current level is flagged and no current flow colours the station (signal level); no_data when the latest reading is too old; not_reporting when the station has history but no realtime reading; not_river when its current reading is a reservoir release, computed inflow or reservoir elevation, which is not coloured as river conditions |
gauges[].province | string or null | |
gauges[].regulation_status | string or null | NATURAL | REGULATED | UNKNOWN |
gauges[].signals | array of "flow" | "level" | "natural_inflow" | "total_inflow" | "temperature" | the signals this gauge can be looked at by, in display order: natural_inflow (a reservoir's computed natural inflow, or a natural flow), total_inflow (a reservoir's total inflow), flow (on a gauge with an inflow: its release or measured regulated flow, observation only), level, temperature. The forecast, thresholds and alerts attach to the signal the gauge's live model is calibrated on (; the forecast's `signal`): flow, natural_inflow or total_inflow; it is listed first. The percentile status is of the stored model series. at a site where an ORRC point was merged, natural_inflow / total_inflow are that point's series (local inflow between the upstream control and the site; total = natural + routed upstream releases) beside the site's own flow and level; the model series is the point's inflow at a dam or reservoir, else the site's own flow where it runs a live model |
gauges[].source | string or null | Data provider: WSC (Water Survey of Canada) or CA (Ontario provincial network); null when unknown |
gauges[].updated_at | string (date-time) |
Observations
GET /api/v1/gauges/{gauge_id}/latest
Get latest reading for a gauge
Returns the most recent flow and/or level reading for a single gauge. Data is sourced from the latest_readings table, which is updated hourly by the pipeline. Not tier-gated — free users can see current conditions. Returns 404 if the gauge is not registered. Returns null flow/level fields if the gauge has no readings yet.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
series | query | "smoothed" | "raw" | "both" | smoothed | which series of an inflow signal (natural_inflow, total_inflow) is served: `smoothed` (default; bad values removed, short quiet gaps filled and marked, see each reading's `inflow_series`), `raw` (a derived gauge's unsmoothed water balance, called `balance`; a provider's published value) or `both` (smoothed, with the raw value and its QC verdict beside it). A gauge whose smoothed series failed its acceptance metrics is served its raw series whatever is asked (`inflow_smoothing` says so). Other signals are unaffected. |
Response: LatestReadingResponse (63 fields)
| Field | Type | Description |
|---|---|---|
flow_age_minutes | integer or null | Minutes since the flow reading; for a daily reading (flow_day) since its day ended, 0 while it is running |
flow_cms | number or null | The gauge's flow (m3/s): its real-time reading, or on a gauge whose model series is an inflow its newest daily release or measured regulated flow (; flow_day says the day, flow_flag is that release's own verdict) |
flow_daily_mean | boolean | true when flow_cms is a UTC-day mean FlowAi derives (reservoir natural inflow), stamped at the end of its day (flow_timestamp = D+1 00:00Z for day D). It arrives once a day, after the 07:00 UTC derivation, so it is current up to 36 h after its stamp; a real-time reading is not. |
flow_day | ReadingDaySchema or null | set when flow_cms is one value per day (a derived daily mean, or a provider daily series such as ORRC's computed natural inflow); null for a real-time reading |
flow_day.day | string (date) | The day the value covers, in `zone` |
flow_day.ends_at | string (date-time) | When that day ends; the reading's age counts from here |
flow_day.zone | "UTC" | "EST" | UTC: a UTC day FlowAi derives (reservoir natural inflow); EST: the provider's standard-time day (UTC-5), as KiWIS daily series (ORRC reservoir series, Q.DayMean) are published |
flow_flag | string or null | |
flow_flag_reasons | array of string or null | |
flow_timestamp | string (date-time) or null | |
gauge_id | string | |
level_age_minutes | integer or null | Minutes since the level reading; for a daily reading (level_day) since its day ended, 0 while it is running |
level_day | ReadingDaySchema or null | set when level_m is one value per day (ORRC reservoir elevation, LVL.DayMean); null for a real-time reading |
level_day.day | string (date) | The day the value covers, in `zone` |
level_day.ends_at | string (date-time) | When that day ends; the reading's age counts from here |
level_day.zone | "UTC" | "EST" | UTC: a UTC day FlowAi derives (reservoir natural inflow); EST: the provider's standard-time day (UTC-5), as KiWIS daily series (ORRC reservoir series, Q.DayMean) are published |
level_flag | string or null | |
level_flag_reasons | array of string or null | |
level_m | number or null | |
level_timestamp | string (date-time) or null | |
natural_inflow_age_minutes | integer or null | As flow_age_minutes, for natural_inflow_cms |
natural_inflow_cms | number or null | the latest natural inflow (m3/s) of a gauge modelled on it (a reservoir's computed natural inflow, or a natural flow); null elsewhere. Its forecast is /forecast's (signal natural_inflow). on a gauge modelled on its total inflow, its observation-only natural inflow: a daily value (natural_inflow_day) with its own verdict (natural_inflow_flag), no forecast |
natural_inflow_daily_mean | boolean | As flow_daily_mean, for natural_inflow_cms |
natural_inflow_day | ReadingDaySchema or null | As flow_day, for natural_inflow_cms |
natural_inflow_day.day | string (date) | The day the value covers, in `zone` |
natural_inflow_day.ends_at | string (date-time) | When that day ends; the reading's age counts from here |
natural_inflow_day.zone | "UTC" | "EST" | UTC: a UTC day FlowAi derives (reservoir natural inflow); EST: the provider's standard-time day (UTC-5), as KiWIS daily series (ORRC reservoir series, Q.DayMean) are published |
natural_inflow_flag | string or null | |
natural_inflow_flag_reasons | array of string or null | |
natural_inflow_series | InflowSeriesDay or null | where the gauge's natural_inflow has a smoothed series, what natural_inflow_cms is: `smoothed` (the default; the value the chart shows that day, with its method, `filled` for an estimate on a day with no usable raw value, `adjusted` for why raw was not used) or `raw` (the smoother has not reached the reading's day yet; or series=raw on a provider's series). Left out where the signal has no smoothed series, or series=raw is asked. A derived gauge's raw series is its unsmoothed water balance; a day it has no smoothed or balance value is null, never the stored value |
natural_inflow_series.adjusted | string or null | Why raw was not used on this day: `floored` (a remaining negative set to 0), `excluded:negative`, `excluded:qc:<reason>` (a flagged value) or `excluded:hampel` (an isolated spike); null when the smoothed value of a usable raw value was used. `edge_smoothed`: one of a derived series' newest 5 days, which have no later day to be fitted against yet: the smoothed value as of the newest day, revised by the daily runs as later days arrive (a reading, scored like any smoothed day). `capped`: a derived natural inflow cut to its total |
natural_inflow_series.filled | boolean | True when the day has no usable raw value and the smoothed value is an estimate from the days around it (a gap of at most 3 days in quiet conditions). Filled days are not scored in training or validation |
natural_inflow_series.gap_days | integer or null | Length of the gap a filled day sits in |
natural_inflow_series.method | string or null | Method, version and parameters of the smoothed series, e.g. `rts-v4:n0.01:iv3.55` or `provider-qc-cap-v1`; null on a `raw` day |
natural_inflow_series.raw_cms | number or null | Only with series=both: this day's raw value (what series=raw serves as the signal's value): the unsmoothed water balance for a derived gauge (`raw_series` balance), the published value for a provider series. Its QC verdict is raw_flag |
natural_inflow_series.raw_flag | string or null | Only with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC |
natural_inflow_series.raw_flag_reasons | array of string or null | Only with series=both: the reasons behind raw_flag |
natural_inflow_series.series | "smoothed" | "raw" | Which series the signal's value (`natural_inflow_cms` / `total_inflow_cms`) is on this day. `smoothed` by default; `raw` when raw was asked for, and for a day the smoothed series does not cover yet (the newest day, which has no closing midnight level) - never silently |
natural_inflow_timestamp | string (date-time) or null | |
source | string or null | |
total_inflow_age_minutes | integer or null | As flow_age_minutes, for total_inflow_cms |
total_inflow_cms | number or null | the latest total inflow (m3/s) of a reservoir modelled on it; null elsewhere. Its forecast is /forecast's (signal total_inflow). on a gauge modelled on its natural inflow (a reservoir below another, an ORRC point publishing both), its observation-only total inflow: a daily value (total_inflow_day) with its own verdict (total_inflow_flag), no forecast |
total_inflow_daily_mean | boolean | As flow_daily_mean, for total_inflow_cms |
total_inflow_day | ReadingDaySchema or null | As flow_day, for total_inflow_cms |
total_inflow_day.day | string (date) | The day the value covers, in `zone` |
total_inflow_day.ends_at | string (date-time) | When that day ends; the reading's age counts from here |
total_inflow_day.zone | "UTC" | "EST" | UTC: a UTC day FlowAi derives (reservoir natural inflow); EST: the provider's standard-time day (UTC-5), as KiWIS daily series (ORRC reservoir series, Q.DayMean) are published |
total_inflow_flag | string or null | |
total_inflow_flag_reasons | array of string or null | |
total_inflow_series | InflowSeriesDay or null | where the gauge's total_inflow has a smoothed series, what total_inflow_cms is: `smoothed` (the default; the value the chart shows that day, with its method, `filled` for an estimate on a day with no usable raw value, `adjusted` for why raw was not used) or `raw` (the smoother has not reached the reading's day yet; or series=raw on a provider's series). Left out where the signal has no smoothed series, or series=raw is asked. A derived gauge's raw series is its unsmoothed water balance; a day it has no smoothed or balance value is null, never the stored value |
total_inflow_series.adjusted | string or null | Why raw was not used on this day: `floored` (a remaining negative set to 0), `excluded:negative`, `excluded:qc:<reason>` (a flagged value) or `excluded:hampel` (an isolated spike); null when the smoothed value of a usable raw value was used. `edge_smoothed`: one of a derived series' newest 5 days, which have no later day to be fitted against yet: the smoothed value as of the newest day, revised by the daily runs as later days arrive (a reading, scored like any smoothed day). `capped`: a derived natural inflow cut to its total |
total_inflow_series.filled | boolean | True when the day has no usable raw value and the smoothed value is an estimate from the days around it (a gap of at most 3 days in quiet conditions). Filled days are not scored in training or validation |
total_inflow_series.gap_days | integer or null | Length of the gap a filled day sits in |
total_inflow_series.method | string or null | Method, version and parameters of the smoothed series, e.g. `rts-v4:n0.01:iv3.55` or `provider-qc-cap-v1`; null on a `raw` day |
total_inflow_series.raw_cms | number or null | Only with series=both: this day's raw value (what series=raw serves as the signal's value): the unsmoothed water balance for a derived gauge (`raw_series` balance), the published value for a provider series. Its QC verdict is raw_flag |
total_inflow_series.raw_flag | string or null | Only with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC |
total_inflow_series.raw_flag_reasons | array of string or null | Only with series=both: the reasons behind raw_flag |
total_inflow_series.series | "smoothed" | "raw" | Which series the signal's value (`natural_inflow_cms` / `total_inflow_cms`) is on this day. `smoothed` by default; `raw` when raw was asked for, and for a day the smoothed series does not cover yet (the newest day, which has no closing midnight level) - never silently |
total_inflow_timestamp | string (date-time) or null | |
updated_at | string (date-time) | |
water_temp_age_minutes | integer or null | |
water_temp_c | number or null | |
water_temp_timestamp | string (date-time) or null |
GET /api/v1/gauges/{gauge_id}/recent
Get a gauge's readings of the last hours
the flow and level readings of the last `hours` (1-48) for one gauge, oldest first, from the recent_readings table the hourly poll writes. Observed data, so not tier-gated, like /latest. A reading QC flagged is served with its flag, never hidden; a missing reading is absent, never filled. Returns 404 if the gauge is not registered.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
hours | query | integer | 48 | How far back, in hours (1-48) |
series | query | "smoothed" | "raw" | "both" | smoothed | which series of an inflow signal (natural_inflow, total_inflow) is served: `smoothed` (default; bad values removed, short quiet gaps filled and marked, see each reading's `inflow_series`), `raw` (a derived gauge's unsmoothed water balance, called `balance`; a provider's published value) or `both` (smoothed, with the raw value and its QC verdict beside it). A gauge whose smoothed series failed its acceptance metrics is served its raw series whatever is asked (`inflow_smoothing` says so). Other signals are unaffected. |
Response: GaugeRecentResponse (59 fields)
| Field | Type | Description |
|---|---|---|
fetched_at | string (date-time) | |
flow | array of RecentReadingPoint | Flow readings (m3/s) in the window, oldest first; empty when there are none, never filled (always empty on a gauge whose flow is a daily release) |
flow[].flag | string or null | SUSPECT when FlowAi's automatic QC flagged the reading (flow only); a flagged reading is shown, never hidden |
flow[].flag_reasons | array of string or null | QC reason codes behind the flag; null when clean |
flow[].smoothing | InflowSeriesDay or null | on an inflow point whose signal has a smoothed series, what `value` is (smoothed or raw), its method, whether the day is filled or adjusted. Left out elsewhere |
flow[].smoothing.adjusted | string or null | Why raw was not used on this day: `floored` (a remaining negative set to 0), `excluded:negative`, `excluded:qc:<reason>` (a flagged value) or `excluded:hampel` (an isolated spike); null when the smoothed value of a usable raw value was used. `edge_smoothed`: one of a derived series' newest 5 days, which have no later day to be fitted against yet: the smoothed value as of the newest day, revised by the daily runs as later days arrive (a reading, scored like any smoothed day). `capped`: a derived natural inflow cut to its total |
flow[].smoothing.filled | boolean | True when the day has no usable raw value and the smoothed value is an estimate from the days around it (a gap of at most 3 days in quiet conditions). Filled days are not scored in training or validation |
flow[].smoothing.gap_days | integer or null | Length of the gap a filled day sits in |
flow[].smoothing.method | string or null | Method, version and parameters of the smoothed series, e.g. `rts-v4:n0.01:iv3.55` or `provider-qc-cap-v1`; null on a `raw` day |
flow[].smoothing.raw_cms | number or null | Only with series=both: this day's raw value (what series=raw serves as the signal's value): the unsmoothed water balance for a derived gauge (`raw_series` balance), the published value for a provider series. Its QC verdict is raw_flag |
flow[].smoothing.raw_flag | string or null | Only with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC |
flow[].smoothing.raw_flag_reasons | array of string or null | Only with series=both: the reasons behind raw_flag |
flow[].smoothing.series | "smoothed" | "raw" | Which series the signal's value (`natural_inflow_cms` / `total_inflow_cms`) is on this day. `smoothed` by default; `raw` when raw was asked for, and for a day the smoothed series does not cover yet (the newest day, which has no closing midnight level) - never silently |
flow[].timestamp | string (date-time) | When the reading was taken, UTC (ISO 8601, ending in Z) |
flow[].value | number | |
gauge_id | string | |
hours | integer | |
level | array of RecentReadingPoint | Water level readings (m) in the window, oldest first; empty when there are none, never filled |
level[].flag | string or null | SUSPECT when FlowAi's automatic QC flagged the reading (flow only); a flagged reading is shown, never hidden |
level[].flag_reasons | array of string or null | QC reason codes behind the flag; null when clean |
level[].smoothing | InflowSeriesDay or null | on an inflow point whose signal has a smoothed series, what `value` is (smoothed or raw), its method, whether the day is filled or adjusted. Left out elsewhere |
level[].smoothing.adjusted | string or null | Why raw was not used on this day: `floored` (a remaining negative set to 0), `excluded:negative`, `excluded:qc:<reason>` (a flagged value) or `excluded:hampel` (an isolated spike); null when the smoothed value of a usable raw value was used. `edge_smoothed`: one of a derived series' newest 5 days, which have no later day to be fitted against yet: the smoothed value as of the newest day, revised by the daily runs as later days arrive (a reading, scored like any smoothed day). `capped`: a derived natural inflow cut to its total |
level[].smoothing.filled | boolean | True when the day has no usable raw value and the smoothed value is an estimate from the days around it (a gap of at most 3 days in quiet conditions). Filled days are not scored in training or validation |
level[].smoothing.gap_days | integer or null | Length of the gap a filled day sits in |
level[].smoothing.method | string or null | Method, version and parameters of the smoothed series, e.g. `rts-v4:n0.01:iv3.55` or `provider-qc-cap-v1`; null on a `raw` day |
level[].smoothing.raw_cms | number or null | Only with series=both: this day's raw value (what series=raw serves as the signal's value): the unsmoothed water balance for a derived gauge (`raw_series` balance), the published value for a provider series. Its QC verdict is raw_flag |
level[].smoothing.raw_flag | string or null | Only with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC |
level[].smoothing.raw_flag_reasons | array of string or null | Only with series=both: the reasons behind raw_flag |
level[].smoothing.series | "smoothed" | "raw" | Which series the signal's value (`natural_inflow_cms` / `total_inflow_cms`) is on this day. `smoothed` by default; `raw` when raw was asked for, and for a day the smoothed series does not cover yet (the newest day, which has no closing midnight level) - never silently |
level[].timestamp | string (date-time) | When the reading was taken, UTC (ISO 8601, ending in Z) |
level[].value | number | |
natural_inflow | array of RecentReadingPoint | natural inflow readings (m3/s) of a gauge modelled on it, oldest first; empty elsewhere |
natural_inflow[].flag | string or null | SUSPECT when FlowAi's automatic QC flagged the reading (flow only); a flagged reading is shown, never hidden |
natural_inflow[].flag_reasons | array of string or null | QC reason codes behind the flag; null when clean |
natural_inflow[].smoothing | InflowSeriesDay or null | on an inflow point whose signal has a smoothed series, what `value` is (smoothed or raw), its method, whether the day is filled or adjusted. Left out elsewhere |
natural_inflow[].smoothing.adjusted | string or null | Why raw was not used on this day: `floored` (a remaining negative set to 0), `excluded:negative`, `excluded:qc:<reason>` (a flagged value) or `excluded:hampel` (an isolated spike); null when the smoothed value of a usable raw value was used. `edge_smoothed`: one of a derived series' newest 5 days, which have no later day to be fitted against yet: the smoothed value as of the newest day, revised by the daily runs as later days arrive (a reading, scored like any smoothed day). `capped`: a derived natural inflow cut to its total |
natural_inflow[].smoothing.filled | boolean | True when the day has no usable raw value and the smoothed value is an estimate from the days around it (a gap of at most 3 days in quiet conditions). Filled days are not scored in training or validation |
natural_inflow[].smoothing.gap_days | integer or null | Length of the gap a filled day sits in |
natural_inflow[].smoothing.method | string or null | Method, version and parameters of the smoothed series, e.g. `rts-v4:n0.01:iv3.55` or `provider-qc-cap-v1`; null on a `raw` day |
natural_inflow[].smoothing.raw_cms | number or null | Only with series=both: this day's raw value (what series=raw serves as the signal's value): the unsmoothed water balance for a derived gauge (`raw_series` balance), the published value for a provider series. Its QC verdict is raw_flag |
natural_inflow[].smoothing.raw_flag | string or null | Only with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC |
natural_inflow[].smoothing.raw_flag_reasons | array of string or null | Only with series=both: the reasons behind raw_flag |
natural_inflow[].smoothing.series | "smoothed" | "raw" | Which series the signal's value (`natural_inflow_cms` / `total_inflow_cms`) is on this day. `smoothed` by default; `raw` when raw was asked for, and for a day the smoothed series does not cover yet (the newest day, which has no closing midnight level) - never silently |
natural_inflow[].timestamp | string (date-time) | When the reading was taken, UTC (ISO 8601, ending in Z) |
natural_inflow[].value | number | |
total_inflow | array of RecentReadingPoint | total inflow readings (m3/s), oldest first; empty elsewhere |
total_inflow[].flag | string or null | SUSPECT when FlowAi's automatic QC flagged the reading (flow only); a flagged reading is shown, never hidden |
total_inflow[].flag_reasons | array of string or null | QC reason codes behind the flag; null when clean |
total_inflow[].smoothing | InflowSeriesDay or null | on an inflow point whose signal has a smoothed series, what `value` is (smoothed or raw), its method, whether the day is filled or adjusted. Left out elsewhere |
total_inflow[].smoothing.adjusted | string or null | Why raw was not used on this day: `floored` (a remaining negative set to 0), `excluded:negative`, `excluded:qc:<reason>` (a flagged value) or `excluded:hampel` (an isolated spike); null when the smoothed value of a usable raw value was used. `edge_smoothed`: one of a derived series' newest 5 days, which have no later day to be fitted against yet: the smoothed value as of the newest day, revised by the daily runs as later days arrive (a reading, scored like any smoothed day). `capped`: a derived natural inflow cut to its total |
total_inflow[].smoothing.filled | boolean | True when the day has no usable raw value and the smoothed value is an estimate from the days around it (a gap of at most 3 days in quiet conditions). Filled days are not scored in training or validation |
total_inflow[].smoothing.gap_days | integer or null | Length of the gap a filled day sits in |
total_inflow[].smoothing.method | string or null | Method, version and parameters of the smoothed series, e.g. `rts-v4:n0.01:iv3.55` or `provider-qc-cap-v1`; null on a `raw` day |
total_inflow[].smoothing.raw_cms | number or null | Only with series=both: this day's raw value (what series=raw serves as the signal's value): the unsmoothed water balance for a derived gauge (`raw_series` balance), the published value for a provider series. Its QC verdict is raw_flag |
total_inflow[].smoothing.raw_flag | string or null | Only with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC |
total_inflow[].smoothing.raw_flag_reasons | array of string or null | Only with series=both: the reasons behind raw_flag |
total_inflow[].smoothing.series | "smoothed" | "raw" | Which series the signal's value (`natural_inflow_cms` / `total_inflow_cms`) is on this day. `smoothed` by default; `raw` when raw was asked for, and for a day the smoothed series does not cover yet (the newest day, which has no closing midnight level) - never silently |
total_inflow[].timestamp | string (date-time) | When the reading was taken, UTC (ISO 8601, ending in Z) |
total_inflow[].value | number |
GET /api/v1/gauges/{gauge_id}/observed
Recent observed flow for a gauge
Returns recent streamflow observations for the given gauge. Use the `days` query parameter to control the look-back window (default 90). Every caller, signed out included, gets at most the water-year window: Oct 1 of the current UTC year - 2 through today. `window_start`, `window_days` and `window_capped` say what was served; the full record is the Commercial CSV export's. The year endpoints stay open for the historical-year overlay. Readings are returned newest-first.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
days | query | integer | 90 | Number of days to look back (default 90). A request above the water-year window is cut to it (window_capped) |
series | query | "smoothed" | "raw" | "both" | smoothed | which series of an inflow signal (natural_inflow, total_inflow) is served: `smoothed` (default; bad values removed, short quiet gaps filled and marked, see each reading's `inflow_series`), `raw` (a derived gauge's unsmoothed water balance, called `balance`; a provider's published value) or `both` (smoothed, with the raw value and its QC verdict beside it). A gauge whose smoothed series failed its acceptance metrics is served its raw series whatever is asked (`inflow_smoothing` says so). Other signals are unaffected. |
Response: ObservedResponse (27 fields)
| Field | Type | Description |
|---|---|---|
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
gauge_id | string | |
inflow_smoothing | map of InflowSmoothingStatus or null | per inflow signal of the gauge that was assessed for smoothing, whether its smoothed series is served or the signal is raw only, and why. Left out for a gauge with no inflow signal assessed |
latest_data_fallback | boolean | True when the window held no readings and `readings` are the latest ones the gauge has, from before `window_start` (HYDAT-only gauges lag by about two years). Never silent: show it as older data. |
readings | array of ObservedReadingSchema | |
readings[].flag | string or null | SUSPECT = the stored flow series (flow_cms, or natural_inflow_cms / total_inflow_cms) is flagged by FlowAi's automatic QC (the value is kept); LVL = level-only row. A release and an observation-only inflow carry their own verdicts (flow_flag, natural_inflow_flag, total_inflow_flag) |
readings[].flag_reasons | array of string or null | QC reason codes behind a SUSPECT flag, e.g. ['runoff_ceiling']; null when clean |
readings[].flow_cms | number or null | Flow in cubic metres per second. Null for level-only rows (flag='LVL') where no flow was measured. On a gauge whose stored series is an inflow this is its daily release or measured regulated flow, null on a day it has none. |
readings[].flow_flag | string or null | SUSPECT = flow_cms is flagged by FlowAi's automatic QC in the table that stores it (the value is kept). At a merged site this is the site's own measured flow, which `flag` does not describe when the site's model series is its point's inflow. where flow_cms is a release, the verdict on that day's release (record maximum, jump, stuck value; or the WSC station behind it flagged). Null when clean |
readings[].flow_flag_reasons | array of string or null | QC reason codes behind that signal's SUSPECT flag; null when clean |
readings[].inflow_series | map of InflowSeriesDay or null | per inflow signal (`natural_inflow`, `total_inflow`) with a smoothed series, what this day's value is (smoothed or raw), its method, whether it is filled or adjusted. Left out where the gauge has no smoothed inflow. With the smoothed series the signal's `*_flag` is null (the value is an estimate, not the flagged reading); the raw verdict is `raw_flag` (series=both) or the signal's `*_flag` under series=raw |
readings[].level_flag | string or null | SUSPECT = level_m is flagged by FlowAi's automatic QC as one that cannot be the water surface (; the value is kept); null when clean. `flag` is the flow's. |
readings[].level_m | number or null | Water level in metres above datum |
readings[].natural_inflow_cms | number or null | the day's natural inflow (m3/s) of a gauge modelled on it (`flag` is its) or the observation-only natural inflow of a gauge modelled on its total inflow (`natural_inflow_flag` is its verdict). Null elsewhere |
readings[].natural_inflow_flag | string or null | SUSPECT = natural_inflow_cms is flagged by FlowAi's automatic QC in the table that stores it (the value is kept): the stored model series of the gauge (or, at a merged site, of its point) that holds that inflow. for an observation-only inflow, the verdict on that day: record maximum, jump, stuck value, a negative beyond the balance's noise, or an input (the reservoir's level or release) that QC flags SUSPECT, reason `input_level_suspect:<reason>` / `input_release_suspect:<reason>`. Null when clean |
readings[].natural_inflow_flag_reasons | array of string or null | QC reason codes behind that signal's SUSPECT flag; null when clean |
readings[].timestamp | string (date-time) | Observation time (UTC) |
readings[].total_inflow_cms | number or null | the day's total inflow (m3/s) of a gauge modelled on it (`flag` is its) or the observation-only total inflow of a gauge modelled on its natural inflow (a reservoir below another, an ORRC point publishing both; `total_inflow_flag` is its verdict, no forecast). Null elsewhere |
readings[].total_inflow_flag | string or null | SUSPECT = total_inflow_cms is flagged by FlowAi's automatic QC in the table that stores it (the value is kept): the stored model series of the gauge (or, at a merged site, of its point) that holds that inflow. for an observation-only inflow, the verdict on that day: record maximum, jump, stuck value, a negative beyond the balance's noise, or an input (the reservoir's level or release) that QC flags SUSPECT, reason `input_level_suspect:<reason>` / `input_release_suspect:<reason>`. Null when clean |
readings[].total_inflow_flag_reasons | array of string or null | QC reason codes behind that signal's SUSPECT flag; null when clean |
window_capped | boolean | True when the request asked for more than the water-year window (Oct 1 of the current UTC year - 2 through today) and was cut to it |
window_days | integer | Days served, today included: the requested `days`, or the water-year window if less |
window_start | string (date) | First UTC day of the window that was served: today - window_days + 1 |
GET /api/v1/gauges/{gauge_id}/observed/available-years
Available observed-data years for a gauge
Returns the list of calendar years that have observed flow data for the given gauge. This endpoint is public and can be used to populate the historical year selector.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string |
Response: AvailableYearsResponse (7 fields)
| Field | Type | Description |
|---|---|---|
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
gauge_id | string | |
years | array of integer |
GET /api/v1/gauges/{gauge_id}/observed/year/{year}
Observed flow for one historical year aligned by day-of-year
Returns one calendar year of observed flow for overlay on the current hydrograph. Readings are aligned into the current year by month/day. Public: every caller, signed out included, one year per request (, kept open by). Cache-Control: public, max-age one day for a past year and five minutes for the current one. `series` chooses an inflow signal's series as on /observed (default smoothed).
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
yearrequired | path | integer | ||
series | query | "smoothed" | "raw" | "both" | smoothed | which series of an inflow signal (natural_inflow, total_inflow) is served: `smoothed` (default; bad values removed, short quiet gaps filled and marked, see each reading's `inflow_series`), `raw` (a derived gauge's unsmoothed water balance, called `balance`; a provider's published value) or `both` (smoothed, with the raw value and its QC verdict beside it). A gauge whose smoothed series failed its acceptance metrics is served its raw series whatever is asked (`inflow_smoothing` says so). Other signals are unaffected. |
Response: HistoricalYearResponse (26 fields)
| Field | Type | Description |
|---|---|---|
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
gauge_id | string | |
inflow_smoothing | map of InflowSmoothingStatus or null | as on ObservedResponse (/observed): per assessed inflow signal, whether its smoothed series is served and what its raw series is. Left out for a gauge with no inflow signal assessed |
readings | array of HistoricalYearReading | |
readings[].date_aligned | string | |
readings[].date_original | string | |
readings[].doy | integer | |
readings[].flag | string or null | |
readings[].flag_reasons | array of string or null | |
readings[].flow_cms | number or null | |
readings[].flow_flag | string or null | SUSPECT = flow_cms is flagged by FlowAi's automatic QC in the table that stores it (the value is kept). At a merged site this is the site's own measured flow, which `flag` does not describe when the site's model series is its point's inflow. where flow_cms is a release, the verdict on that day's release (record maximum, jump, stuck value; or the WSC station behind it flagged). Null when clean |
readings[].flow_flag_reasons | array of string or null | QC reason codes behind that signal's SUSPECT flag; null when clean |
readings[].inflow_series | map of InflowSeriesDay or null | as on ObservedReadingSchema (/observed): per inflow signal served smoothed, what this day's value is. Left out where the gauge has no smoothed inflow |
readings[].level_flag | string or null | |
readings[].level_m | number or null | |
readings[].natural_inflow_cms | number or null | |
readings[].natural_inflow_flag | string or null | SUSPECT = natural_inflow_cms is flagged by FlowAi's automatic QC in the table that stores it (the value is kept): the stored model series of the gauge (or, at a merged site, of its point) that holds that inflow. for an observation-only inflow, the verdict on that day: record maximum, jump, stuck value, a negative beyond the balance's noise, or an input (the reservoir's level or release) that QC flags SUSPECT, reason `input_level_suspect:<reason>` / `input_release_suspect:<reason>`. Null when clean |
readings[].natural_inflow_flag_reasons | array of string or null | QC reason codes behind that signal's SUSPECT flag; null when clean |
readings[].total_inflow_cms | number or null | |
readings[].total_inflow_flag | string or null | SUSPECT = total_inflow_cms is flagged by FlowAi's automatic QC in the table that stores it (the value is kept): the stored model series of the gauge (or, at a merged site, of its point) that holds that inflow. for an observation-only inflow, the verdict on that day: record maximum, jump, stuck value, a negative beyond the balance's noise, or an input (the reservoir's level or release) that QC flags SUSPECT, reason `input_level_suspect:<reason>` / `input_release_suspect:<reason>`. Null when clean |
readings[].total_inflow_flag_reasons | array of string or null | QC reason codes behind that signal's SUSPECT flag; null when clean |
year | integer |
GET /api/v1/gauges/{gauge_id}/percentiles
Historical percentiles for a gauge, by signal
Returns climatological percentiles by day-of-year for the given gauge: today's row and the full annual series, for every caller (signed-out and Free included). `today` / `series` are the band of the signal the forecast is of (where the live model is calibrated on a signal other than the stored model series, that signal's band, or none); `level_*` the level's. `signals` holds the band of every signal that has one, observation-only series included, by the same rule; at a merged site its point's inflows.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string |
Response: PercentileSeriesResponse (36 fields)
| Field | Type | Description |
|---|---|---|
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
gauge_id | string | |
level_series | array of PercentileDOYSchema | |
level_series[].doy | integer | |
level_series[].p10 | number or null | |
level_series[].p25 | number or null | |
level_series[].p50 | number or null | |
level_series[].p75 | number or null | |
level_series[].p90 | number or null | |
level_today | PercentileDOYSchema or null | |
level_today.doy | integer | |
level_today.p10 | number or null | |
level_today.p25 | number or null | |
level_today.p50 | number or null | |
level_today.p75 | number or null | |
level_today.p90 | number or null | |
series | array of PercentileDOYSchema | The band of the signal the forecast is of, by day of year; empty when it has none |
series[].doy | integer | |
series[].p10 | number or null | |
series[].p25 | number or null | |
series[].p50 | number or null | |
series[].p75 | number or null | |
series[].p90 | number or null | |
signals | map of PercentileBandSchema | the band of every signal of the gauge that has one, observation-only series included (a release, an inflow that is not the model series; at a merged site its point's inflows), by the same rule (10 complete years minimum). A signal absent here has no band. Context only: status, thresholds and alerts stay on the forecast's signal. |
tier | string | |
today | PercentileDOYSchema or null | Today's row of the band of the signal the forecast is of; null when it has none |
today.doy | integer | |
today.p10 | number or null | |
today.p25 | number or null | |
today.p50 | number or null | |
today.p75 | number or null | |
today.p90 | number or null |
GET /api/v1/gauges/{gauge_id}/conditions
Rain, snow and wetness conditions for a gauge basin
Observed basin rain over the 24/48/72 h ending at the newest CaPA interval, forecast basin rain over the next 24/48/72 h (Pro), the hydrologic model's snowpack and snowfall (a model estimate), 7/14/30-day rain as a percent of normal with a plain-language wetness level, and the typical error of past forecasts by forecast day. Windows are returned as UTC instants with the zone to display them in (the gauge's own zone, as GET /gauges/{id} time_zone; America/Toronto for a station HYDAT lists no province for); they roll on the six-hour grid, so they never cut a UTC day. A gap is reported on its section; no value is ever filled.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string |
Response: GaugeConditionsResponse (135 fields)
| Field | Type | Description |
|---|---|---|
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
forecast_error | ForecastError | |
forecast_error.held_out | array of HeldOutLead | |
forecast_error.held_out[].lead_day | integer | |
forecast_error.held_out[].median_abs_error_pct | number or null | |
forecast_error.held_out[].samples | integer | |
forecast_error.held_out[].share_off_30pct | number or null | Share of days the forecast missed by 30% or more |
forecast_error.held_out_basis | string or null | |
forecast_error.min_n | integer | |
forecast_error.n_issues | integer | |
forecast_error.note | string or null | Plain-language statement of the basis or of the gap |
forecast_error.operational | array of LeadError | |
forecast_error.operational[].lead_day | integer | Forecast day, 1 = the issue day |
forecast_error.operational[].median_abs_error_pct | number or null | |
forecast_error.operational[].n | integer | Past forecasts with an accepted observation for that day |
forecast_error.operational[].p10_error_pct | number or null | 10th percentile of (forecast - observed) / observed, in % |
forecast_error.operational[].p50_error_pct | number or null | |
forecast_error.operational[].p90_error_pct | number or null | |
forecast_error.operational[].status | "ok" | "insufficient_history" | |
forecast_error.reason | string or null | Machine-readable cause when status is not 'ok' |
forecast_error.statement | string | |
forecast_error.status | "ok" | "partial" | "not_available" | "withheld" | |
forecast_error.window_days | integer | |
forecast_rain | ForecastRain | |
forecast_rain.age_hours | number or null | Hours since the run's reference time |
forecast_rain.basis | string | |
forecast_rain.issued_at | string (date-time) or null | Model reference time of the run (UTC) |
forecast_rain.note | string or null | Plain-language statement of the basis or of the gap |
forecast_rain.reason | string or null | Machine-readable cause when status is not 'ok' |
forecast_rain.run_id | string or null | |
forecast_rain.since_observed | RainWindow or null | NWP precipitation for the hours between the newest observed interval and the first forecast window (none when they meet) |
forecast_rain.since_observed.complete | boolean | True when every six-hour interval of the window is present |
forecast_rain.since_observed.end | string (date-time) | UTC end of the window |
forecast_rain.since_observed.hours | integer | Window length: 24, 48 or 72 |
forecast_rain.since_observed.missing_hours | integer | Hours of the window with no interval stored; total_mm excludes them |
forecast_rain.since_observed.rain_mm | number or null | total_mm - snow_mm |
forecast_rain.since_observed.snow_mm | number or null | Part of total_mm modelled as snow (water equivalent), by the model's own rain/snow partition; null when the partition is not available for the whole window |
forecast_rain.since_observed.start | string (date-time) | UTC start of the window (display in the timezone field) |
forecast_rain.since_observed.total_mm | number or null | Sum of the six-hour intervals present; null when none are |
forecast_rain.sources | array of string | NWP models that supply the windows, e.g. HRDPS, GDPS |
forecast_rain.status | "ok" | "partial" | "not_available" | "withheld" | |
forecast_rain.windows | array of RainWindow | |
forecast_rain.windows[].complete | boolean | True when every six-hour interval of the window is present |
forecast_rain.windows[].end | string (date-time) | UTC end of the window |
forecast_rain.windows[].hours | integer | Window length: 24, 48 or 72 |
forecast_rain.windows[].missing_hours | integer | Hours of the window with no interval stored; total_mm excludes them |
forecast_rain.windows[].rain_mm | number or null | total_mm - snow_mm |
forecast_rain.windows[].snow_mm | number or null | Part of total_mm modelled as snow (water equivalent), by the model's own rain/snow partition; null when the partition is not available for the whole window |
forecast_rain.windows[].start | string (date-time) | UTC start of the window (display in the timezone field) |
forecast_rain.windows[].total_mm | number or null | Sum of the six-hour intervals present; null when none are |
gauge_id | string | |
generated_at | string (date-time) | |
observed_rain | ObservedRain | |
observed_rain.basis | string | |
observed_rain.data_age_hours | number or null | Hours from data_end to the request |
observed_rain.data_end | string (date-time) or null | End of the newest observed interval (UTC) |
observed_rain.note | string or null | Plain-language statement of the basis or of the gap |
observed_rain.reason | string or null | Machine-readable cause when status is not 'ok' |
observed_rain.status | "ok" | "partial" | "not_available" | "withheld" | |
observed_rain.windows | array of RainWindow | |
observed_rain.windows[].complete | boolean | True when every six-hour interval of the window is present |
observed_rain.windows[].end | string (date-time) | UTC end of the window |
observed_rain.windows[].hours | integer | Window length: 24, 48 or 72 |
observed_rain.windows[].missing_hours | integer | Hours of the window with no interval stored; total_mm excludes them |
observed_rain.windows[].rain_mm | number or null | total_mm - snow_mm |
observed_rain.windows[].snow_mm | number or null | Part of total_mm modelled as snow (water equivalent), by the model's own rain/snow partition; null when the partition is not available for the whole window |
observed_rain.windows[].start | string (date-time) | UTC start of the window (display in the timezone field) |
observed_rain.windows[].total_mm | number or null | Sum of the six-hour intervals present; null when none are |
snow | Snow | |
snow.as_of | string (date-time) or null | |
snow.driven_by_forecast | boolean | True when as_of is later than the newest observed precipitation, so the state used forecast weather |
snow.forecast_snowfall | array of SnowWindow or null | Modelled snowfall over the next 24/48/72 h; null when withheld |
snow.forecast_snowfall[].complete | boolean | |
snow.forecast_snowfall[].end | string (date-time) | |
snow.forecast_snowfall[].hours | integer | |
snow.forecast_snowfall[].missing_hours | integer | |
snow.forecast_snowfall[].snowfall_mm | number or null | Modelled snowfall, water equivalent |
snow.forecast_snowfall[].start | string (date-time) | |
snow.label | string | |
snow.note | string or null | Plain-language statement of the basis or of the gap |
snow.observed_snowfall | array of SnowWindow | Modelled snowfall over the 24/48/72 h ending at the newest observed interval |
snow.observed_snowfall[].complete | boolean | |
snow.observed_snowfall[].end | string (date-time) | |
snow.observed_snowfall[].hours | integer | |
snow.observed_snowfall[].missing_hours | integer | |
snow.observed_snowfall[].snowfall_mm | number or null | Modelled snowfall, water equivalent |
snow.observed_snowfall[].start | string (date-time) | |
snow.reason | string or null | Machine-readable cause when status is not 'ok' |
snow.status | "ok" | "partial" | "not_available" | "withheld" | |
snow.swe_mm | number or null | Modelled basin-average snow water equivalent at as_of |
snow.swe_outlook | array of SweOutlook or null | Modelled SWE 24/48/72 h ahead; null when withheld |
snow.swe_outlook[].at | string (date-time) | |
snow.swe_outlook[].hours | integer | |
snow.swe_outlook[].swe_mm | number | |
tier | string | |
timezone | string | Zone the window labels are shown in (IANA): the gauge's own (, GET /gauges/{id} time_zone), America/Toronto where the gauge has none |
wetness | Wetness | |
wetness.data_end_date | string (date) or null | |
wetness.flow | FlowState or null | |
wetness.flow.band | "well_below" | "below" | "normal" | "above" | "well_above" or null | Against this date's historical flow percentiles: <p10, <p25, p25-p75, >=p75, >=p90 |
wetness.flow.flow_cms | number or null | |
wetness.flow.observed_at | string (date-time) or null | |
wetness.flow.p10 | number or null | |
wetness.flow.p25 | number or null | |
wetness.flow.p50 | number or null | |
wetness.flow.p75 | number or null | |
wetness.flow.p90 | number or null | |
wetness.flow.reason | string or null | |
wetness.flow.status | "ok" | "not_available" | |
wetness.indicator | WetnessIndicator or null | |
wetness.indicator.basis | array of string | The inputs the level was read from, in words |
wetness.indicator.caveat | string or null | |
wetness.indicator.label | string | |
wetness.indicator.level | "wet" | "somewhat_wet" | "near_normal" | "somewhat_dry" | "dry" | "mixed" | "not_available" | |
wetness.method | array of string | |
wetness.note | string or null | Plain-language statement of the basis or of the gap |
wetness.precip | array of PrecipVsNormal | 7, 14 and 30 complete UTC days ending at data_end_date |
wetness.precip[].band | "low" | "normal" | "high" or null | below 80%, 80-120%, above 120% of normal |
wetness.precip[].complete | boolean | True when every UTC day of the window is stored |
wetness.precip[].days | integer | |
wetness.precip[].end_date | string (date) or null | |
wetness.precip[].missing_days | integer | |
wetness.precip[].n_years | integer or null | Complete years behind the normal |
wetness.precip[].normal_mm | number or null | Mean N-day total for this time of year (see method) |
wetness.precip[].normal_period | string or null | e.g. 2012-2025 |
wetness.precip[].percent_of_normal | number or null | |
wetness.precip[].reason | string or null | Why a figure is missing: incomplete_days | no_normal | no_observed_days |
wetness.precip[].start_date | string (date) or null | |
wetness.precip[].total_mm | number or null | |
wetness.reason | string or null | Machine-readable cause when status is not 'ok' |
wetness.status | "ok" | "partial" | "not_available" | "withheld" |
Forecasts and skill
GET /api/v1/gauges/{gauge_id}/forecast
Latest forecast for a gauge (Pro and above on unlocked gauges)
Returns the latest forecast for the given gauge. Results are served from Redis when the cache is warm. The current product is deterministic daily flow with correction provenance. Pro and above callers on a gauge they have unlocked get `forecasts` (Pro leads 0-4, Commercial leads 0-9) and the 6 h `sub_daily` blocks. Everyone else (signed out, Free, or a paying account on a gauge it has not unlocked) gets `forecasts: []`: no forecast. Returns an empty `forecasts` array if no forecast has been issued yet.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
signal | query | "flow" | "level" | "natural_inflow" | "total_inflow" or null | Which of the gauge's forecasts. Omit it for the primary, the forecast of the gauge's forecast signal (its first signal). `total_inflow` is the gauge's second forecast where `models` lists one: the total inflow over the whole basin, with the dam-operations caveat. `level` is the primary flow forecast as a daily-mean water level, through the gauge's stage-discharge rating, where `models` lists a `derived` level entry: the gauge's rating passed the publish gate (`level.rating`), else 404. A level forecast is withheld (empty `forecasts`, `level.withheld`) while the latest 72 h carry ice. 404 when the gauge has no live forecast of that signal. |
Response: ForecastResponse (84 fields)
| Field | Type | Description |
|---|---|---|
archive_record_id | string or null | |
caveat | "total_upstream" or null | present only on a gauge's second forecast (the total inflow over the whole basin, `?signal=total_inflow`): `total_upstream`, the key of INFLOW_FORECAST_CAVEAT in the web app: it includes the releases of the dams and regulated lakes upstream, so it follows their operations. Absent from the gauge's primary forecast. |
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
forecasts | array of ForecastLeadSchema | One entry per lead day (empty if no forecast has been issued yet) |
forecasts[].archive_record_id | string or null | |
forecasts[].beyond_observed_range | boolean or null | , level forecast only: true when this lead's flow (any of its quantiles) is above the highest flow in the rating's fit (`level.rating.observed_q_max`). The level is then the power law extended past the data: shown, never hidden, and less certain |
forecasts[].beyond_range_tier | "flagged" | "low_confidence" | "understated" or null | , level forecast only, with `beyond_observed_range`: how far past the fit's highest flow this lead goes (`level.rating.observed_q_max`; tier edges are gate_config.serve.beyond_range_tiers, default 1.0x, 1.25x, 1.5x): `flagged` (above it), `low_confidence` (from 1.25x: error is several times larger), `understated` (from 1.5x: the level is probably too low, typically by 10-45 cm) |
forecasts[].correction_status | string | |
forecasts[].daily_mean | boolean or null | , level forecast only: always true. The level is a DAILY MEAN; the instantaneous peak of the day can be higher (median 12 cm, up to 24-29 cm on flashy southern gauges:) |
forecasts[].fallback_reason | string or null | |
forecasts[].flow_cms | number | Forecast flow (m³/s); current daily product is deterministic |
forecasts[].flow_lower | number or null | Always null: the model emits no confidence bands |
forecasts[].flow_upper | number or null | Always null: the model emits no confidence bands |
forecasts[].generated_at | string (date-time) or null | Actual producer generation time; unknown for legacy rows |
forecasts[].lead_days | number | Days ahead of the issue day (0 = issue day); integer (daily) steps only |
forecasts[].lead_hours | integer or null | Lead hours since issue_time (informational) |
forecasts[].level_lower_m | number or null | , level forecast only: `flow_lower` through the rating (the same quantile, as a level). Present exactly when the flow band is, which is never today (the model emits no bands) |
forecasts[].level_m | number or null | , level forecast only: the daily-mean water level (m, relative to the gauge datum; see `level.datum` for the geodetic offset) the gauge's current rating gives for `flow_cms`. The rating is monotone: a higher flow is a higher level |
forecasts[].level_rmse_cm | number or null | , level forecast only: the rating's in-sample RMSE (cm) on daily-mean level, to be shown beside every forecast level. A level is uncertain by about twice this (95%); 12% of gauges that pass the gate still exceed 4 cm the following year, and the error is larger on the highest flows (`level.rating.top_decile_rmse_cm`) |
forecasts[].level_upper_m | number or null | , level forecast only: `flow_upper` through the rating; see `level_lower_m` |
forecasts[].observed_flow_cms | number or null | Observed flow (m³/s), null for future dates |
forecasts[].observed_level_m | number or null | , level forecast only: the observed daily-mean level (m, gauge datum) of `valid_date`, null for future dates and for a day FlowAi's QC flags SUSPECT |
forecasts[].quality_flags | array of string | |
forecasts[].segment | string or null | 'warmstart' or 'forecast' |
forecasts[].valid_date | string (date) or null | |
gauge_id | string | |
generated_at | string (date-time) or null | |
issue_time | string (date-time) or null | Nominal forecast reference time in UTC, not actual generation or publication time |
level | LevelInfoSchema or null | present only on a level forecast (`?signal=level`): the gauge's primary flow forecast carried through its current stage-discharge rating, which passed the publish gate. A gauge without such a rating has no level forecast (404); nothing is estimated for it. |
level.daily_mean | boolean | The forecast levels are daily means (see each lead's `daily_mean`) |
level.datum | LevelDatumSchema | |
level.datum.conversions | array of LevelGeodeticSchema | Every conversion WSC publishes for the station |
level.datum.conversions[].datum | string | The geodetic datum as HYDAT names it, e.g. CANADIAN GEODETIC VERTICAL DATUM 2013:EPOCH2010 |
level.datum.conversions[].datum_id | integer | |
level.datum.conversions[].offset_m | number | Add to a level in the gauge datum for the elevation (m) above `datum` |
level.datum.conversions[].short | string | its short label for text (CGVD2013, CGVD28, GSC); show `datum` in full where there is room |
level.datum.geodetic | LevelGeodeticSchema or null | The preferred geodetic conversion WSC publishes for the station; null where it publishes none (then the level has no geodetic reading, and none is guessed) |
level.datum.geodetic.datum | string | The geodetic datum as HYDAT names it, e.g. CANADIAN GEODETIC VERTICAL DATUM 2013:EPOCH2010 |
level.datum.geodetic.datum_id | integer | |
level.datum.geodetic.offset_m | number | Add to a level in the gauge datum for the elevation (m) above `datum` |
level.datum.geodetic.short | string | its short label for text (CGVD2013, CGVD28, GSC); show `datum` in full where there is room |
level.datum.reference | "gauge_datum" | Levels are relative to the gauge's own datum (the station's zero) |
level.ice_season | boolean | True when a day of the latest 72 h is in the gauge's ice season but the level is not withheld: the observed level may be ice-affected, so say so beside the forecast (it is not hidden) |
level.observed | LevelObservedSchema or null | The newest clean observed daily level; absent when there is none |
level.observed.date | string (date) | |
level.observed.level_m | number | The newest observed daily-mean level that FlowAi's QC does not flag (m, gauge datum) |
level.rating | LevelRatingSchema | |
level.rating.fitted_at | string (date-time) | |
level.rating.gate_version | string or null | The publish-gate thresholds the rating passed (rating_config.toml) |
level.rating.holdout_365_rmse_cm | number or null | RMSE (cm) on the newest 365 days when the rating is refitted without them; null if not evaluable |
level.rating.holdout_top_flow_rmse_cm | number or null | RMSE (cm) of that hold-out on flows above the training 90th percentile; null if fewer than 5 such days |
level.rating.model_kind | "single" | "two_segment" | |
level.rating.n_pairs | integer | Clean daily level/flow pairs in the window (QC-suspect, ice and estimated days left out) |
level.rating.observed_h_max | number | Highest level (m, gauge datum) in the fit |
level.rating.observed_h_min | number | Lowest level (m, gauge datum) in the fit |
level.rating.observed_q_max | number | Highest flow (m3/s) in the fit; a forecast flow above it is `beyond_observed_range` |
level.rating.observed_q_min | number | |
level.rating.rmse_cm | number | In-sample RMSE of the rating's daily level, cm |
level.rating.top_decile_rmse_cm | number or null | In-sample RMSE (cm) on the highest 10% of the fit's flows |
level.rating.version | integer | |
level.rating.window_end | string (date) | Newest clean pair in the fit: the rating is current to this day |
level.rating.window_start | string (date) | First day of the fit window (10 years, recency-weighted) |
level.withheld | LevelWithheldSchema or null | Present when the level forecast is withheld for ice or a missing flow: then `forecasts` is empty |
level.withheld.lookback_hours | integer | |
level.withheld.reason | "ice_qualifier" | "level_without_flow" | "ice_season_off_rating" | `ice_qualifier`: the latest 72 h carry the real-time ice qualifier (WSC Qualifier 10). `level_without_flow`: the latest 72 h show a level and no flow (ice, or a gauge that lost its discharge). `ice_season_off_rating`: Ontario has no real-time ice qualifier, so instead a day of the latest 72 h is in the gauge's ice season (months WSC marked ice in 25% or more of its last 10 years) AND at least two of the latest three observed daily levels sit more than 5 cm off the rating for the observed flow. No level is forecast until the data is clean again |
signal | "flow" | "natural_inflow" | "total_inflow" | "level" | What the forecast values are: the signal the gauge's live model is calibrated on, flow, natural_inflow (a reservoir's computed natural inflow over its local basin, or a natural flow) or total_inflow (a reservoir's total inflow over its whole basin). It is listed first in the gauge's `signals`, and its thresholds and forecast alerts are on it. Every other series of the gauge (on a reservoir, its flow: the release; the inflow it is not modelled on) is observation only and never forecast. `level`: the primary flow forecast as a daily-mean water level, through the gauge's rating; see `level`. |
sub_daily | array of ForecastBlockSchema or null | 6 h blocks, Pro and above: four per forecast day, averaging exactly to that day's daily value, capped at the same lead as the daily rows. Absent from the response for Free and signed-out callers; an empty list when this issue was published without blocks, or when the gauge's flow is a computed inflow (a daily water balance with no sub-daily truth). |
sub_daily[].beyond_observed_range | boolean or null | , level forecast only: this block's flow is above the highest flow in the rating's fit |
sub_daily[].beyond_range_tier | "flagged" | "low_confidence" | "understated" or null | , level forecast only: the tier of this block's flow against the fit's highest flow (see a lead's) |
sub_daily[].block_hours | integer | Block length in hours (6) |
sub_daily[].daily_mean | boolean or null | , level forecast only: always false, the block is a 6 h value, not a daily mean |
sub_daily[].flow_cms | number | Mean forecast flow over the block (m³/s); not an instantaneous value |
sub_daily[].lead_hours | integer | valid_start - issue_time, in hours |
sub_daily[].level_m | number or null | , level forecast only: the water level (m, gauge datum) the gauge's current rating gives for this block's mean `flow_cms`. a daily-fit rating matches 5-minute level-flow pairs within 0.1-1.8 cm, so it is valid at 6 h. Closer to the instantaneous level than a daily mean is, still a 6 h mean flow's level |
sub_daily[].level_rmse_cm | number or null | , level forecast only: the rating's RMSE (cm), as on a lead |
sub_daily[].valid_date | string (date) | The UTC day the block falls in: the daily row whose flow_cms its day's four blocks average to |
sub_daily[].valid_end | string (date-time) | End of the block (UTC), exclusive: valid_start + block_hours |
sub_daily[].valid_start | string (date-time) | Start of the block (UTC) |
tier | string | Effective tier for this request |
upgrade_available | boolean | Whether upgrade would unlock more data |
GET /api/v1/gauges/{gauge_id}/forecast-history
Recent forecast timeseries for overlay
Returns the last N days' worth of issued forecasts: forecast-segment leads only, capped and band-gated per plan as /forecast. Intended for the frontend overlay selector — users can click a past issue date and overlay that forecast on the hydrograph. Each issue is a distinct 14-day forward run from that issue date. Use `days` to control how many past issue dates to return (default 14, max 30).
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
days | query | integer | 14 | How many days back to look for past issue dates (default 14, max 30) |
signal | query | "flow" | "natural_inflow" | "total_inflow" or null | Which of the gauge's forecasts. Omit it for the primary, the forecast of the gauge's forecast signal (its first signal). `total_inflow` is the gauge's second forecast where `models` lists one: the total inflow over the whole basin, with the dam-operations caveat. 404 when the gauge has no live forecast of that signal. |
Response: ForecastHistoryResponse (34 fields)
| Field | Type | Description |
|---|---|---|
caveat | "total_upstream" or null | present only on a gauge's second forecast (the total inflow over the whole basin, `?signal=total_inflow`): `total_upstream`, the key of INFLOW_FORECAST_CAVEAT in the web app: it includes the releases of the dams and regulated lakes upstream, so it follows their operations. Absent from the gauge's primary forecast. |
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
gauge_id | string | |
issues | array of ForecastHistoryIssueSchema | |
issues[].issue_time | string (date-time) | |
issues[].points | array of ForecastHistoryLeadSchema | |
issues[].points[].archive_record_id | string or null | |
issues[].points[].correction_status | string | |
issues[].points[].fallback_reason | string or null | |
issues[].points[].flow_cms | number | |
issues[].points[].flow_lower | number or null | |
issues[].points[].flow_upper | number or null | |
issues[].points[].generated_at | string (date-time) or null | |
issues[].points[].lead_days | number | |
issues[].points[].lead_hours | integer or null | |
issues[].points[].quality_flags | array of string | |
issues[].points[].valid_date | string (date) or null | |
issues[].sub_daily | array of ForecastBlockSchema or null | 6 h blocks, Pro and above: four per forecast day, averaging exactly to that day's daily value, capped at the same lead as the daily rows. Absent from the response for Free and signed-out callers; an empty list when this issue was published without blocks, or when the gauge's flow is a computed inflow (a daily water balance with no sub-daily truth). |
issues[].sub_daily[].beyond_observed_range | boolean or null | , level forecast only: this block's flow is above the highest flow in the rating's fit |
issues[].sub_daily[].beyond_range_tier | "flagged" | "low_confidence" | "understated" or null | , level forecast only: the tier of this block's flow against the fit's highest flow (see a lead's) |
issues[].sub_daily[].block_hours | integer | Block length in hours (6) |
issues[].sub_daily[].daily_mean | boolean or null | , level forecast only: always false, the block is a 6 h value, not a daily mean |
issues[].sub_daily[].flow_cms | number | Mean forecast flow over the block (m³/s); not an instantaneous value |
issues[].sub_daily[].lead_hours | integer | valid_start - issue_time, in hours |
issues[].sub_daily[].level_m | number or null | , level forecast only: the water level (m, gauge datum) the gauge's current rating gives for this block's mean `flow_cms`. a daily-fit rating matches 5-minute level-flow pairs within 0.1-1.8 cm, so it is valid at 6 h. Closer to the instantaneous level than a daily mean is, still a 6 h mean flow's level |
issues[].sub_daily[].level_rmse_cm | number or null | , level forecast only: the rating's RMSE (cm), as on a lead |
issues[].sub_daily[].valid_date | string (date) | The UTC day the block falls in: the daily row whose flow_cms its day's four blocks average to |
issues[].sub_daily[].valid_end | string (date-time) | End of the block (UTC), exclusive: valid_start + block_hours |
issues[].sub_daily[].valid_start | string (date-time) | Start of the block (UTC) |
signal | "flow" | "natural_inflow" | "total_inflow" | What the forecast values are: the signal the gauge's live model is calibrated on, flow, natural_inflow (a reservoir's computed natural inflow over its local basin, or a natural flow) or total_inflow (a reservoir's total inflow over its whole basin). It is listed first in the gauge's `signals`, and its thresholds and forecast alerts are on it. Every other series of the gauge (on a reservoir, its flow: the release; the inflow it is not modelled on) is observation only and never forecast. |
GET /api/v1/gauges/{gauge_id}/hindcast
Past forecasts vs observed (hindcast)
Returns historical forecasts issued in the last N days, joined with the observed values of its `signal` at each lead-day's valid time. Forecast-segment leads only, capped per plan as /forecast; the number of issues is also plan-limited. `error` is `predicted − actual`; null when the observation is missing.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
days | query | integer | 14 | How many days back to look at issued forecasts (default 14) |
signal | query | "flow" | "natural_inflow" | "total_inflow" or null | Which of the gauge's forecasts. Omit it for the primary, the forecast of the gauge's forecast signal (its first signal). `total_inflow` is the gauge's second forecast where `models` lists one: the total inflow over the whole basin, with the dam-operations caveat. 404 when the gauge has no live forecast of that signal. |
Response: HindcastResponse (28 fields)
| Field | Type | Description |
|---|---|---|
caveat | "total_upstream" or null | present only on a gauge's second forecast (the total inflow over the whole basin, `?signal=total_inflow`): `total_upstream`, the key of INFLOW_FORECAST_CAVEAT in the web app: it includes the releases of the dams and regulated lakes upstream, so it follows their operations. Absent from the gauge's primary forecast. |
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
gauge_id | string | |
hindcasts | array of HindcastIssueSchema | |
hindcasts[].forecasts | array of HindcastLeadSchema | |
hindcasts[].forecasts[].actual | number or null | Observed flow at the forecast valid time (m³/s); null if observation missing |
hindcasts[].forecasts[].error | number or null | predicted − actual (m³/s); null if observation missing |
hindcasts[].forecasts[].lead_days | number | |
hindcasts[].forecasts[].lead_hours | integer or null | |
hindcasts[].forecasts[].predicted | number | Forecast median flow (m³/s) |
hindcasts[].issue_time | string (date-time) | |
hindcasts[].sub_daily | array of ForecastBlockSchema or null | 6 h blocks, Pro and above: four per forecast day, averaging exactly to that day's daily value, capped at the same lead as the daily rows. Absent from the response for Free and signed-out callers; an empty list when this issue was published without blocks, or when the gauge's flow is a computed inflow (a daily water balance with no sub-daily truth). Predicted only: no sub-daily observation archive exists to pair with past blocks, so they carry no actual or error. |
hindcasts[].sub_daily[].beyond_observed_range | boolean or null | , level forecast only: this block's flow is above the highest flow in the rating's fit |
hindcasts[].sub_daily[].beyond_range_tier | "flagged" | "low_confidence" | "understated" or null | , level forecast only: the tier of this block's flow against the fit's highest flow (see a lead's) |
hindcasts[].sub_daily[].block_hours | integer | Block length in hours (6) |
hindcasts[].sub_daily[].daily_mean | boolean or null | , level forecast only: always false, the block is a 6 h value, not a daily mean |
hindcasts[].sub_daily[].flow_cms | number | Mean forecast flow over the block (m³/s); not an instantaneous value |
hindcasts[].sub_daily[].lead_hours | integer | valid_start - issue_time, in hours |
hindcasts[].sub_daily[].level_m | number or null | , level forecast only: the water level (m, gauge datum) the gauge's current rating gives for this block's mean `flow_cms`. a daily-fit rating matches 5-minute level-flow pairs within 0.1-1.8 cm, so it is valid at 6 h. Closer to the instantaneous level than a daily mean is, still a 6 h mean flow's level |
hindcasts[].sub_daily[].level_rmse_cm | number or null | , level forecast only: the rating's RMSE (cm), as on a lead |
hindcasts[].sub_daily[].valid_date | string (date) | The UTC day the block falls in: the daily row whose flow_cms its day's four blocks average to |
hindcasts[].sub_daily[].valid_end | string (date-time) | End of the block (UTC), exclusive: valid_start + block_hours |
hindcasts[].sub_daily[].valid_start | string (date-time) | Start of the block (UTC) |
signal | "flow" | "natural_inflow" | "total_inflow" | What the forecast values are: the signal the gauge's live model is calibrated on, flow, natural_inflow (a reservoir's computed natural inflow over its local basin, or a natural flow) or total_inflow (a reservoir's total inflow over its whole basin). It is listed first in the gauge's `signals`, and its thresholds and forecast alerts are on it. Every other series of the gauge (on a reservoir, its flow: the release; the inflow it is not modelled on) is observation only and never forecast. |
GET /api/v1/gauges/{gauge_id}/forecast-verification
Descriptive verification of archived operational forecasts (public: skill disclosure)
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string |
Response: VerificationResponse (80 fields)
| Field | Type | Description |
|---|---|---|
accuracy_claim | string or null | |
availability_basis | string or null | |
correction_score_scope | string or null | |
correction_scores | array of VerificationScore | |
correction_scores[].actual_lead_hours_max | number | |
correction_scores[].actual_lead_hours_min | number | |
correction_scores[].cohort | "all_served" | "published_correction_status" | |
correction_scores[].comparisons | BaselineComparisons | |
correction_scores[].comparisons.raw_raven | PairedComparison | |
correction_scores[].comparisons.raw_raven.equal_absolute_error_n | integer | |
correction_scores[].comparisons.raw_raven.higher_absolute_error_n | integer | |
correction_scores[].comparisons.raw_raven.lower_absolute_error_n | integer | |
correction_scores[].comparisons.raw_raven.mae_skill | number or null | |
correction_scores[].comparisons.raw_raven.mae_skill_unavailable_reason | "zero_baseline_mae" or null | |
correction_scores[].corrected_n | integer | |
correction_scores[].correction_status | "corrected" | "raw_fallback" or null | |
correction_scores[].gauge_id | string | |
correction_scores[].metrics | PredictionMetrics | |
correction_scores[].metrics.raw_raven | ErrorMetrics | |
correction_scores[].metrics.raw_raven.bias | number | Mean prediction-minus-observation error in m3/s |
correction_scores[].metrics.raw_raven.mae | number | Mean absolute error in m3/s |
correction_scores[].metrics.raw_raven.rmse | number | Root mean squared error in m3/s |
correction_scores[].metrics.served | ErrorMetrics | |
correction_scores[].metrics.served.bias | number | Mean prediction-minus-observation error in m3/s |
correction_scores[].metrics.served.mae | number | Mean absolute error in m3/s |
correction_scores[].metrics.served.rmse | number | Root mean squared error in m3/s |
correction_scores[].n | integer | |
correction_scores[].nominal_lead_day | integer | |
correction_scores[].target_quality | "accepted" | "provisional" | |
correction_scores[].unique_reference_days | integer | |
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
evaluated_at | string (date-time) or null | |
final_holdout_designated | false | |
flow_unit | "m3/s" | |
gauge_id | string | |
include_provisional_targets | boolean or null | |
interval_contract | "utc-interval-end-daily/v1" | |
nominal_lead_limit | integer | |
raw_baseline_policy | "receiver-replayed-raven-daily/v1" or null | |
report_sha256 | string or null | |
report_version | "forecast-operational-verification/v3" or null | |
runtime_evidence_policy | "receiver-validated-raven-execution/v1" or null | |
scores | array of VerificationScore | |
scores[].actual_lead_hours_max | number | |
scores[].actual_lead_hours_min | number | |
scores[].cohort | "all_served" | "published_correction_status" | |
scores[].comparisons | BaselineComparisons | |
scores[].comparisons.raw_raven | PairedComparison | |
scores[].comparisons.raw_raven.equal_absolute_error_n | integer | |
scores[].comparisons.raw_raven.higher_absolute_error_n | integer | |
scores[].comparisons.raw_raven.lower_absolute_error_n | integer | |
scores[].comparisons.raw_raven.mae_skill | number or null | |
scores[].comparisons.raw_raven.mae_skill_unavailable_reason | "zero_baseline_mae" or null | |
scores[].corrected_n | integer | |
scores[].correction_status | "corrected" | "raw_fallback" or null | |
scores[].gauge_id | string | |
scores[].metrics | PredictionMetrics | |
scores[].metrics.raw_raven | ErrorMetrics | |
scores[].metrics.raw_raven.bias | number | Mean prediction-minus-observation error in m3/s |
scores[].metrics.raw_raven.mae | number | Mean absolute error in m3/s |
scores[].metrics.raw_raven.rmse | number | Root mean squared error in m3/s |
scores[].metrics.served | ErrorMetrics | |
scores[].metrics.served.bias | number | Mean prediction-minus-observation error in m3/s |
scores[].metrics.served.mae | number | Mean absolute error in m3/s |
scores[].metrics.served.rmse | number | Root mean squared error in m3/s |
scores[].n | integer | |
scores[].nominal_lead_day | integer | |
scores[].target_quality | "accepted" | "provisional" | |
scores[].unique_reference_days | integer | |
selection_policy | string or null | |
source_arrival_times_verified | false | |
status | "not_configured" | "no_eligible_samples" | "descriptive_report" | |
target_evidence_policy | "replayed-native-utc-targets/v1" or null | |
target_snapshot_sha256 | string or null | |
tier | string | |
upgrade_available | boolean |
GET /api/v1/gauges/{gauge_id}/model-card
Model metadata and performance metrics
Returns the model card for the given gauge: calibration/validation metrics, ML post-processor KGE at each lead, process chain, and any known limitations. Returns 404 if the gauge doesn't exist or has no model card yet (status='pending').
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
signal | query | "flow" | "natural_inflow" | "total_inflow" or null | Which of the gauge's forecasts. Omit it for the primary, the forecast of the gauge's forecast signal (its first signal). `total_inflow` is the gauge's second forecast where `models` lists one: the total inflow over the whole basin, with the dam-operations caveat. 404 when the gauge has no live forecast of that signal. |
Response: ModelCardResponse (46 fields)
| Field | Type | Description |
|---|---|---|
calibration_period | string or null | |
caveat | "total_upstream" or null | present only on a gauge's second forecast (the total inflow over the whole basin, `?signal=total_inflow`): `total_upstream`, the key of INFLOW_FORECAST_CAVEAT in the web app: it includes the releases of the dams and regulated lakes upstream, so it follows their operations. Absent from the gauge's primary forecast. |
development_split | object or null | Which water years the served model used, lifted from held_out_skill so it is public at every tier (, published by): calibration_water_years (Raven fitted), validation_water_years (used to select the model and corrector, so not independent), spinup_water_years (not scored) and selection: peak_ranked_alternation | volume_peak_stratified | odd_even_water_years | template_year_list | gauge_year_list | experiment_year_list | unrecognised, as recorded when the split was made (ledger). Optional ranked_on (date of the observation record a peak or volume-and-peak ranking used) and spinup_scored (the early method also used the spin-up year). Optional masked_unforced_days and masked_unforced_events (integers/): the high-flow days, and the events they belong to, given weight 0 in calibration and validation because the model's forcing does not explain them; present only when the model masked days, absent otherwise. The held-out year is validation_period. Null when the card does not carry it. |
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
gauge_id | string | |
held_out_skill | object or null | Held-out WY2026 skill of the served model, per lead, beside raw Raven: median relative error, share of days off by 30% or more, NSE and sample count. Not scored against persistence. Carries its own schema, evaluation_window, weather_basis and bundle_identity. weather_basis is retained historical forcing, so these are DEVELOPMENT figures and not operational forecast skill. |
kge | number or null | Retrospective model KGE; not operational forecast skill |
level | LevelCardSchema or null | the level forecast section, present only on the primary card of a gauge that has a published level forecast. Public at every tier: it is the accuracy that travels with a forecast level |
level.daily_mean | boolean | |
level.datum_name | string or null | The geodetic datum WSC publishes a conversion to, null where it publishes none |
level.datum_offset_m | number or null | Add to a level (gauge datum) for the elevation above `datum_name` |
level.datum_short | string or null | `datum_name` as a short label (CGVD2013, CGVD28, GSC), null with it |
level.fit_window_end | string (date) | The rating is current to this day |
level.fit_window_start | string (date) | |
level.gate_version | string or null | |
level.half_life_days | number | |
level.holdout_365_rmse_cm | number or null | |
level.model_kind | "single" | "two_segment" | |
level.n_pairs | integer | |
level.observed_h_max | number | |
level.observed_h_min | number | |
level.observed_q_max | number | |
level.observed_q_min | number | |
level.rmse_cm | number | |
level.text | string | The user-facing description of the level forecast: method, accuracy, datum, daily mean, ice |
level.top_flow_rmse_cm | number | In-sample RMSE (cm) on the highest 10% of the fit's flows |
level.window_years | integer | |
limitations | string or null | |
ml_kge_14d | number or null | ML post-processor KGE at 14-day lead |
ml_kge_1d | number or null | ML post-processor KGE at 1-day lead |
ml_kge_3d | number or null | ML post-processor KGE at 3-day lead |
ml_kge_7d | number or null | ML post-processor KGE at 7-day lead |
model_trained_at | string (date-time) or null | When the served model was trained; null when the card does not say |
nse | number or null | Retrospective model NSE; not operational forecast skill |
pbias | number or null | Percent bias (%) |
process_chain | string or null | |
signal | "flow" | "natural_inflow" | "total_inflow" or null | present only on a second model's card: the signal that model forecasts (total_inflow). Absent from the primary's card, which is of the gauge's forecast signal. |
tier | string | |
timestep | string or null | |
updated_at | string (date-time) or null | |
validation_period | string or null | |
validation_status | string or null | How the served model was validated, lifted from held_out_skill so it is public at every tier: 'not_performed_short_record' when the model was trained on a short record and not validated with a holdout year; null otherwise. |
Basin weather
GET /api/v1/gauges/{gauge_id}/weather-forecast
Basin-average weather forecast (precip + temp, 10-day)
Returns the latest basin-average weather forecast for the given gauge. Includes daily precipitation (mm), temperature (°C), and cumulative precipitation. Updated 2-4x daily per NWP run. Public: every caller, signed out included, gets the 10 days; weather, not FlowAi model output.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string |
Response: WeatherForecastResponse (23 fields)
| Field | Type | Description |
|---|---|---|
days | array of WeatherForecastPoint | |
days[].precip_cumul_mm | number or null | |
days[].precip_mm | number or null | |
days[].temp_c | number or null | |
days[].temp_max_c | number or null | |
days[].temp_min_c | number or null | |
days[].valid_date | string (date) | |
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
gauge_id | string | |
issued_at | string (date-time) or null | |
recent_days | array of WeatherForecastPoint | Forecast weather for the days before today (UTC) that the gauge's observed basin weather (/forcing) does not reach yet, at most the 3 days before today, each from the newest issue that covered it. Empty when /forcing reaches yesterday. |
recent_days[].precip_cumul_mm | number or null | |
recent_days[].precip_mm | number or null | |
recent_days[].temp_c | number or null | |
recent_days[].temp_max_c | number or null | |
recent_days[].temp_min_c | number or null | |
recent_days[].valid_date | string (date) | |
run_id | string or null | |
withheld_reason | string or null | 'basin_outline_too_small' when modelling refused the basin outline (or, until the gauge has a verdict, it implies impossible runoff for the river), 'basin_outline_updating' when the outline was redrawn and its verdict is not renewed yet; no basin weather is served for it then. Never set for a gauge with a live model. null otherwise |
GET /api/v1/gauges/{gauge_id}/forcing
Precipitation and temperature forcing for a gauge
Returns observed basin precipitation (mm per UTC day) and temperature (°C: daily mean, min, max) averaged over the gauge's basin from the national CaPA and ERA5 grids. Use `days` to control the look-back window (default 90). Every caller gets at most the water-year window (Oct 1 of the current UTC year - 2 through today); `window_start` says where the served window begins. Days without a complete observed record are omitted.
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
gauge_idrequired | path | string | ||
days | query | integer | 90 | Number of days to look back (default 90). A request above the water-year window is cut to it (window_capped) |
Response: ForcingResponse (16 fields)
| Field | Type | Description |
|---|---|---|
disclaimer | Disclaimer | The forecast disclaimer for this caller: general, or the Public Agency version for a Public Agency organisation's users and API keys. Also sent as the X-FlowAi-Disclaimer header. |
disclaimer.text | string | The short disclaimer, to show with the forecast values |
disclaimer.url | string | The full disclaimer: the web app's /disclaimer or /disclaimer/agency |
disclaimer.variant | "general" | "public_agency" | general, or public_agency for a user (or API key) of an organisation FlowAi has flagged a Public Agency |
disclaimer.version | string | The disclaimer version, shared by both variants (the one users accept at sign-up) |
forcing | array of ForcingPoint | |
forcing[].date | string | |
forcing[].precip_mm | number or null | |
forcing[].temp_avg | number or null | |
forcing[].temp_max | number or null | |
forcing[].temp_min | number or null | |
gauge_id | string | |
window_capped | boolean | True when the request asked for more than the water-year window (Oct 1 of the current UTC year - 2 through today) and was cut to it |
window_days | integer | Days served, today included: the requested `days`, or the water-year window if less |
window_start | string (date) | First UTC day of the window that was served: today - window_days + 1 |
withheld_reason | string or null |
Delft-FEWS feed
GET /rest/fewspiservice/v1/timeseries
PI time series for Delft-FEWS (organisation API keys only)
| Parameter | In | Type | Default | Description |
|---|---|---|---|---|
locationIdsrequired | query | array of string | WSC station IDs | |
parameterIdsrequired | query | array of string | Q.obs, H.obs, Q.obs.day, H.obs.day, QIN.obs.day, QINT.obs.day, Q.fcst.day, QIN.fcst.day, QINT.fcst.day, Q.fcst.6h, H.fcst.day, H.fcst.6h | |
startTimerequired | query | string | UTC, e.g. 2026-10-01T00:00:00Z | |
endTimerequired | query | string | UTC | |
documentFormat | query | string or null | PI_XML (default) or PI_JSON | |
filterId | query | string or null | Accepted and ignored | |
convertDatum | query | boolean or null | Accepted and ignored | |
useDisplayUnits | query | boolean or null | Accepted and ignored | |
series | query | "smoothed" | "raw" | smoothed | which series of a gauge's daily inflow (QIN.obs.day, QINT.obs.day): `smoothed` (default; the gauge's chart; flag 1 an adjusted day, 5 a filled one) or `raw` (a provider's published value, a derived gauge's unsmoothed water balance). A signal with no smoothed series is its raw series either way |
A Delft-FEWS PI time series document (PI-XML, or PI-JSON with documentFormat=PI_JSON): see Connect Delft-FEWS.