FlowAi

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.

Data Integration package. API keys come with the Data Integration package (sold from summer 2027). Want to test the API with your systems before then? Email [email protected]and we will turn it on for your organisation. A key reads forecasts at your plan's horizon on your organisation's unlocked gauges (no forecast elsewhere) and observations on any gauge.

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.

Parameters of /api/v1/gauges
ParameterInTypeDefaultDescription
qquerystring or nullSearch by gauge name or ID (case-insensitive substring match)
provincequerystring or nullFilter by HYDAT province/state code, e.g. ON, QC, MN. Gauges HYDAT does not list have no province and never match.
regulation_statusquerystring or nullFilter by regulation status: NATURAL | REGULATED | UNKNOWN
ecozonequerystring or nullFilter by ecozone, e.g. SHIELD_CENTRAL, GREAT_LAKES_LOWLAND
Response: GaugeListResponse (21 fields)
Response fields of /api/v1/gauges
FieldTypeDescription
countintegerNumber of gauges returned
gaugesarray of GaugeResponse
gauges[].aliasesarray of stringids 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_km2number or nullPublished 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[].attributionarray of stringThe 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_providerstring or nullWho 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[].ecozonestring or nullEcozone classification
gauges[].idstringWSC station ID, e.g. 08MH006
gauges[].is_activebooleanWhether gauge is currently active
gauges[].latnumber
gauges[].location_provincestring or nulltwo-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[].lonnumber
gauges[].namestringThe 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_placestring or nullname 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_kmnumber or nullgreat-circle distance from the gauge to the nearest place, km, 1 decimal
gauges[].nearest_place_regionstring or nullthe nearest place's province, territory or US state code, e.g. ON, QC, MN
gauges[].providerstring or nullData provider, e.g. WSC
gauges[].provincestring or nullProvince or state code from HYDAT; null for stations HYDAT does not list
gauges[].regulation_statusstring or nullNATURAL | REGULATED | UNKNOWN
gauges[].updated_atstring (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.

Parameters of /api/v1/gauges/{gauge_id}
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
Response: GaugeDetailResponse (64 fields)
Response fields of /api/v1/gauges/{gauge_id}
FieldTypeDescription
area_km2number or null
area_source"hydat" or null
attributionarray of stringThe 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_withheldbooleanTrue 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_providerstring or nullWho 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_km2number or nullArea of the basin FlowAi delineated for its model (km²). A model input, not a published drainage area; it can differ widely from HYDAT's.
ecozonestring or null
flow_kind"river_flow" | "reservoir_release" | "measured_flow" or nullWhat 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_atstring (date-time) or nullIssue 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_utcstring or nullThe 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_modelbooleanTrue 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
idstring
inflow_originsmap 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_activeboolean
is_stalebooleanTrue 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
latnumber
level_kind"water_level" | "reservoir_elevation" or nullWhat the level series measures
listedbooleanFalse when the station's feeds have never delivered a value: it is left out of the gauge list, search and map
location_provincestring or nulltwo-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`
lonnumber
model_cardModelCardSchema or null
model_card.calibration_periodstring or null
model_card.development_splitobject or nullWhich 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_skillobject or nullHeld-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.kgenumber or nullKling–Gupta Efficiency (primary metric)
model_card.limitationsstring or nullFree-text limitations note
model_card.ml_kge_14dnumber or nullML post-processor KGE at 14-day lead
model_card.ml_kge_1dnumber or nullML post-processor KGE at 1-day lead
model_card.ml_kge_3dnumber or nullML post-processor KGE at 3-day lead
model_card.ml_kge_7dnumber or nullML post-processor KGE at 7-day lead
model_card.model_trained_atstring (date-time) or nullWhen the served model was trained; null when the card does not say
model_card.nsenumber or nullNash–Sutcliffe Efficiency (display only)
model_card.pbiasnumber or nullPercent bias (%)
model_card.process_chainstring or nullRaven process combination string
model_card.timestepstring or null
model_card.updated_atstring (date-time)
model_card.validation_periodstring or null
model_card.validation_statusstring or nullHow 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_gatedbooleanTrue when model_card fields other than kge and updated_at are withheld for this caller's plan
modelsarray of GaugeModelSchemathe 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_km2number or nullThe 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 nullThe 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_atstring (date-time) or nullIssue 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_kgenumber or nullKGE of the live bundle's latest gate score. Commercial and Enterprise only (null below)
models[].gate_nsenumber or nullNSE 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_stalebooleanTrue 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 nullThe target it is calibrated on: `none`, or `7day` (a centred 7-day mean)
models[].templatestring or nullThe Raven template the model is built on. Commercial and Enterprise only, as the rest of the model card (null below)
namestring
nearest_placestring or nullname 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_kmnumber or nullgreat-circle distance from the gauge to the nearest place, km, 1 decimal
nearest_place_regionstring or nullthe nearest place's province, territory or US state code, e.g. ON, QC, MN
providerstring or null
provincestring or null
redirected_fromSiteRedirect or nullone 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_idstringThe 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_statusstring or null
signalsarray 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_zonestring or nullthe 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_atstring (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.

Parameters of /api/v1/gauges/map/conditions
ParameterInTypeDefaultDescription
sourcequerystring or nullFilter by data provider (WSC or CA), as listed by /gauges/sources.
seriesquery"smoothed" | "raw" | "both"smoothedwhich 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)
Response fields of /api/v1/gauges/map/conditions
FieldTypeDescription
countintegerNumber of gauges returned
forecast_freshnessForecastFreshnessFleet-wide forecast freshness, whatever `source` filters the gauges to
forecast_freshness.is_stalebooleanTrue 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_atstring (date-time) or nullNewest forecast issue time across all gauges (the issue date at 00:00 UTC); null when there is none
forecast_freshness.target_time_utcstringThe 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
gaugesarray of GaugeMapResponse
gauges[].flow_kind"river_flow" | "reservoir_release" | "measured_flow" or nullWhat 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_forecastbooleanTrue if the gauge is in the live model registry and has an issued forecast (the same gate /forecast applies)
gauges[].idstringWSC station ID
gauges[].inflow_originsmap 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_datestring (date) or nullLast daily observation, for a station that is not reporting
gauges[].latnumber
gauges[].latest_readingLatestReadingSchema or null
gauges[].latest_reading.age_minutesinteger or nullMinutes since the flow reading (the level reading when the gauge reports no flow)
gauges[].latest_reading.flagstring or nullSUSPECT 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_reasonsarray of string or nullQC reason codes behind the flag; null when clean
gauges[].latest_reading.flow_cmsnumber or nullLatest 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_datestring (date) or nullon 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_flagstring or nullSUSPECT 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_reasonsarray of string or nullQC reason codes behind flow_flag; null when clean
gauges[].latest_reading.inflow_datestring (date) or nullthe 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_cmsnumber or nullthe 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_flagstring or nullSUSPECT 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_reasonsarray of string or nullQC reason codes behind natural_inflow_flag; null when clean
gauges[].latest_reading.natural_inflow_seriesInflowSeriesDay or nullwhat 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.adjustedstring or nullWhy 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.filledbooleanTrue 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_daysinteger or nullLength of the gap a filled day sits in
gauges[].latest_reading.natural_inflow_series.methodstring or nullMethod, 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_cmsnumber or nullOnly 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_flagstring or nullOnly with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC
gauges[].latest_reading.natural_inflow_series.raw_flag_reasonsarray of string or nullOnly 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_minutesinteger or nullMinutes since the level reading; each value carries its own age
gauges[].latest_reading.stage_flagstring or nullSUSPECT when FlowAi's automatic QC flagged the level reading
gauges[].latest_reading.stage_flag_reasonsarray of string or nullQC reason codes behind stage_flag; null when clean
gauges[].latest_reading.stage_mnumber or nullLatest water level in metres (if available)
gauges[].latest_reading.timestampstring (date-time) or nullTime 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_cmsnumber or nullthe 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_flagstring or nullas natural_inflow_flag, for total_inflow_cms
gauges[].latest_reading.total_inflow_flag_reasonsarray of string or nullQC reason codes behind total_inflow_flag; null when clean
gauges[].latest_reading.total_inflow_seriesInflowSeriesDay or nullwhat total_inflow_cms is, as natural_inflow_series
gauges[].latest_reading.total_inflow_series.adjustedstring or nullWhy 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.filledbooleanTrue 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_daysinteger or nullLength of the gap a filled day sits in
gauges[].latest_reading.total_inflow_series.methodstring or nullMethod, 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_cmsnumber or nullOnly 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_flagstring or nullOnly with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC
gauges[].latest_reading.total_inflow_series.raw_flag_reasonsarray of string or nullOnly 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_minutesinteger or nullMinutes since the water temperature reading
gauges[].latest_reading.water_temp_cnumber or nullLatest water temperature in °C (if available)
gauges[].level_kind"water_level" | "reservoir_elevation" or null
gauges[].lonnumber
gauges[].namestring
gauges[].percentile_statusPercentileStatusSchema or null
gauges[].percentile_status.p10number or null10th percentile for today's DOY
gauges[].percentile_status.p25number or null25th percentile
gauges[].percentile_status.p50number or null50th percentile (median)
gauges[].percentile_status.p75number or null75th percentile
gauges[].percentile_status.p90number or null90th 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[].provincestring or null
gauges[].regulation_statusstring or nullNATURAL | REGULATED | UNKNOWN
gauges[].signalsarray 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[].sourcestring or nullData provider: WSC (Water Survey of Canada) or CA (Ontario provincial network); null when unknown
gauges[].updated_atstring (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.

Parameters of /api/v1/gauges/{gauge_id}/latest
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
seriesquery"smoothed" | "raw" | "both"smoothedwhich 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)
Response fields of /api/v1/gauges/{gauge_id}/latest
FieldTypeDescription
flow_age_minutesinteger or nullMinutes since the flow reading; for a daily reading (flow_day) since its day ended, 0 while it is running
flow_cmsnumber or nullThe 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_meanbooleantrue 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_dayReadingDaySchema or nullset 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.daystring (date)The day the value covers, in `zone`
flow_day.ends_atstring (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_flagstring or null
flow_flag_reasonsarray of string or null
flow_timestampstring (date-time) or null
gauge_idstring
level_age_minutesinteger or nullMinutes since the level reading; for a daily reading (level_day) since its day ended, 0 while it is running
level_dayReadingDaySchema or nullset when level_m is one value per day (ORRC reservoir elevation, LVL.DayMean); null for a real-time reading
level_day.daystring (date)The day the value covers, in `zone`
level_day.ends_atstring (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_flagstring or null
level_flag_reasonsarray of string or null
level_mnumber or null
level_timestampstring (date-time) or null
natural_inflow_age_minutesinteger or nullAs flow_age_minutes, for natural_inflow_cms
natural_inflow_cmsnumber or nullthe 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_meanbooleanAs flow_daily_mean, for natural_inflow_cms
natural_inflow_dayReadingDaySchema or nullAs flow_day, for natural_inflow_cms
natural_inflow_day.daystring (date)The day the value covers, in `zone`
natural_inflow_day.ends_atstring (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_flagstring or null
natural_inflow_flag_reasonsarray of string or null
natural_inflow_seriesInflowSeriesDay or nullwhere 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.adjustedstring or nullWhy 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.filledbooleanTrue 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_daysinteger or nullLength of the gap a filled day sits in
natural_inflow_series.methodstring or nullMethod, 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_cmsnumber or nullOnly 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_flagstring or nullOnly with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC
natural_inflow_series.raw_flag_reasonsarray of string or nullOnly 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_timestampstring (date-time) or null
sourcestring or null
total_inflow_age_minutesinteger or nullAs flow_age_minutes, for total_inflow_cms
total_inflow_cmsnumber or nullthe 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_meanbooleanAs flow_daily_mean, for total_inflow_cms
total_inflow_dayReadingDaySchema or nullAs flow_day, for total_inflow_cms
total_inflow_day.daystring (date)The day the value covers, in `zone`
total_inflow_day.ends_atstring (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_flagstring or null
total_inflow_flag_reasonsarray of string or null
total_inflow_seriesInflowSeriesDay or nullwhere 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.adjustedstring or nullWhy 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.filledbooleanTrue 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_daysinteger or nullLength of the gap a filled day sits in
total_inflow_series.methodstring or nullMethod, 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_cmsnumber or nullOnly 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_flagstring or nullOnly with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC
total_inflow_series.raw_flag_reasonsarray of string or nullOnly 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_timestampstring (date-time) or null
updated_atstring (date-time)
water_temp_age_minutesinteger or null
water_temp_cnumber or null
water_temp_timestampstring (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.

Parameters of /api/v1/gauges/{gauge_id}/recent
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
hoursqueryinteger48How far back, in hours (1-48)
seriesquery"smoothed" | "raw" | "both"smoothedwhich 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)
Response fields of /api/v1/gauges/{gauge_id}/recent
FieldTypeDescription
fetched_atstring (date-time)
flowarray of RecentReadingPointFlow 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[].flagstring or nullSUSPECT when FlowAi's automatic QC flagged the reading (flow only); a flagged reading is shown, never hidden
flow[].flag_reasonsarray of string or nullQC reason codes behind the flag; null when clean
flow[].smoothingInflowSeriesDay or nullon 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.adjustedstring or nullWhy 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.filledbooleanTrue 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_daysinteger or nullLength of the gap a filled day sits in
flow[].smoothing.methodstring or nullMethod, 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_cmsnumber or nullOnly 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_flagstring or nullOnly with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC
flow[].smoothing.raw_flag_reasonsarray of string or nullOnly 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[].timestampstring (date-time)When the reading was taken, UTC (ISO 8601, ending in Z)
flow[].valuenumber
gauge_idstring
hoursinteger
levelarray of RecentReadingPointWater level readings (m) in the window, oldest first; empty when there are none, never filled
level[].flagstring or nullSUSPECT when FlowAi's automatic QC flagged the reading (flow only); a flagged reading is shown, never hidden
level[].flag_reasonsarray of string or nullQC reason codes behind the flag; null when clean
level[].smoothingInflowSeriesDay or nullon 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.adjustedstring or nullWhy 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.filledbooleanTrue 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_daysinteger or nullLength of the gap a filled day sits in
level[].smoothing.methodstring or nullMethod, 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_cmsnumber or nullOnly 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_flagstring or nullOnly with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC
level[].smoothing.raw_flag_reasonsarray of string or nullOnly 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[].timestampstring (date-time)When the reading was taken, UTC (ISO 8601, ending in Z)
level[].valuenumber
natural_inflowarray of RecentReadingPointnatural inflow readings (m3/s) of a gauge modelled on it, oldest first; empty elsewhere
natural_inflow[].flagstring or nullSUSPECT when FlowAi's automatic QC flagged the reading (flow only); a flagged reading is shown, never hidden
natural_inflow[].flag_reasonsarray of string or nullQC reason codes behind the flag; null when clean
natural_inflow[].smoothingInflowSeriesDay or nullon 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.adjustedstring or nullWhy 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.filledbooleanTrue 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_daysinteger or nullLength of the gap a filled day sits in
natural_inflow[].smoothing.methodstring or nullMethod, 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_cmsnumber or nullOnly 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_flagstring or nullOnly with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC
natural_inflow[].smoothing.raw_flag_reasonsarray of string or nullOnly 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[].timestampstring (date-time)When the reading was taken, UTC (ISO 8601, ending in Z)
natural_inflow[].valuenumber
total_inflowarray of RecentReadingPointtotal inflow readings (m3/s), oldest first; empty elsewhere
total_inflow[].flagstring or nullSUSPECT when FlowAi's automatic QC flagged the reading (flow only); a flagged reading is shown, never hidden
total_inflow[].flag_reasonsarray of string or nullQC reason codes behind the flag; null when clean
total_inflow[].smoothingInflowSeriesDay or nullon 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.adjustedstring or nullWhy 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.filledbooleanTrue 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_daysinteger or nullLength of the gap a filled day sits in
total_inflow[].smoothing.methodstring or nullMethod, 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_cmsnumber or nullOnly 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_flagstring or nullOnly with series=both: SUSPECT when raw_cms is flagged by FlowAi's QC
total_inflow[].smoothing.raw_flag_reasonsarray of string or nullOnly 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[].timestampstring (date-time)When the reading was taken, UTC (ISO 8601, ending in Z)
total_inflow[].valuenumber

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.

Parameters of /api/v1/gauges/{gauge_id}/observed
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
daysqueryinteger90Number of days to look back (default 90). A request above the water-year window is cut to it (window_capped)
seriesquery"smoothed" | "raw" | "both"smoothedwhich 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)
Response fields of /api/v1/gauges/{gauge_id}/observed
FieldTypeDescription
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
gauge_idstring
inflow_smoothingmap of InflowSmoothingStatus or nullper 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_fallbackbooleanTrue 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.
readingsarray of ObservedReadingSchema
readings[].flagstring or nullSUSPECT = 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_reasonsarray of string or nullQC reason codes behind a SUSPECT flag, e.g. ['runoff_ceiling']; null when clean
readings[].flow_cmsnumber or nullFlow 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_flagstring or nullSUSPECT = 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_reasonsarray of string or nullQC reason codes behind that signal's SUSPECT flag; null when clean
readings[].inflow_seriesmap of InflowSeriesDay or nullper 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_flagstring or nullSUSPECT = 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_mnumber or nullWater level in metres above datum
readings[].natural_inflow_cmsnumber or nullthe 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_flagstring or nullSUSPECT = 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_reasonsarray of string or nullQC reason codes behind that signal's SUSPECT flag; null when clean
readings[].timestampstring (date-time)Observation time (UTC)
readings[].total_inflow_cmsnumber or nullthe 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_flagstring or nullSUSPECT = 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_reasonsarray of string or nullQC reason codes behind that signal's SUSPECT flag; null when clean
window_cappedbooleanTrue 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_daysintegerDays served, today included: the requested `days`, or the water-year window if less
window_startstring (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.

Parameters of /api/v1/gauges/{gauge_id}/observed/available-years
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
Response: AvailableYearsResponse (7 fields)
Response fields of /api/v1/gauges/{gauge_id}/observed/available-years
FieldTypeDescription
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
gauge_idstring
yearsarray 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).

Parameters of /api/v1/gauges/{gauge_id}/observed/year/{year}
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
yearrequiredpathinteger
seriesquery"smoothed" | "raw" | "both"smoothedwhich 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)
Response fields of /api/v1/gauges/{gauge_id}/observed/year/{year}
FieldTypeDescription
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
gauge_idstring
inflow_smoothingmap of InflowSmoothingStatus or nullas 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
readingsarray of HistoricalYearReading
readings[].date_alignedstring
readings[].date_originalstring
readings[].doyinteger
readings[].flagstring or null
readings[].flag_reasonsarray of string or null
readings[].flow_cmsnumber or null
readings[].flow_flagstring or nullSUSPECT = 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_reasonsarray of string or nullQC reason codes behind that signal's SUSPECT flag; null when clean
readings[].inflow_seriesmap of InflowSeriesDay or nullas 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_flagstring or null
readings[].level_mnumber or null
readings[].natural_inflow_cmsnumber or null
readings[].natural_inflow_flagstring or nullSUSPECT = 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_reasonsarray of string or nullQC reason codes behind that signal's SUSPECT flag; null when clean
readings[].total_inflow_cmsnumber or null
readings[].total_inflow_flagstring or nullSUSPECT = 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_reasonsarray of string or nullQC reason codes behind that signal's SUSPECT flag; null when clean
yearinteger

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.

Parameters of /api/v1/gauges/{gauge_id}/percentiles
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
Response: PercentileSeriesResponse (36 fields)
Response fields of /api/v1/gauges/{gauge_id}/percentiles
FieldTypeDescription
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
gauge_idstring
level_seriesarray of PercentileDOYSchema
level_series[].doyinteger
level_series[].p10number or null
level_series[].p25number or null
level_series[].p50number or null
level_series[].p75number or null
level_series[].p90number or null
level_todayPercentileDOYSchema or null
level_today.doyinteger
level_today.p10number or null
level_today.p25number or null
level_today.p50number or null
level_today.p75number or null
level_today.p90number or null
seriesarray of PercentileDOYSchemaThe band of the signal the forecast is of, by day of year; empty when it has none
series[].doyinteger
series[].p10number or null
series[].p25number or null
series[].p50number or null
series[].p75number or null
series[].p90number or null
signalsmap of PercentileBandSchemathe 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.
tierstring
todayPercentileDOYSchema or nullToday's row of the band of the signal the forecast is of; null when it has none
today.doyinteger
today.p10number or null
today.p25number or null
today.p50number or null
today.p75number or null
today.p90number 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.

Parameters of /api/v1/gauges/{gauge_id}/conditions
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
Response: GaugeConditionsResponse (135 fields)
Response fields of /api/v1/gauges/{gauge_id}/conditions
FieldTypeDescription
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
forecast_errorForecastError
forecast_error.held_outarray of HeldOutLead
forecast_error.held_out[].lead_dayinteger
forecast_error.held_out[].median_abs_error_pctnumber or null
forecast_error.held_out[].samplesinteger
forecast_error.held_out[].share_off_30pctnumber or nullShare of days the forecast missed by 30% or more
forecast_error.held_out_basisstring or null
forecast_error.min_ninteger
forecast_error.n_issuesinteger
forecast_error.notestring or nullPlain-language statement of the basis or of the gap
forecast_error.operationalarray of LeadError
forecast_error.operational[].lead_dayintegerForecast day, 1 = the issue day
forecast_error.operational[].median_abs_error_pctnumber or null
forecast_error.operational[].nintegerPast forecasts with an accepted observation for that day
forecast_error.operational[].p10_error_pctnumber or null10th percentile of (forecast - observed) / observed, in %
forecast_error.operational[].p50_error_pctnumber or null
forecast_error.operational[].p90_error_pctnumber or null
forecast_error.operational[].status"ok" | "insufficient_history"
forecast_error.reasonstring or nullMachine-readable cause when status is not 'ok'
forecast_error.statementstring
forecast_error.status"ok" | "partial" | "not_available" | "withheld"
forecast_error.window_daysinteger
forecast_rainForecastRain
forecast_rain.age_hoursnumber or nullHours since the run's reference time
forecast_rain.basisstring
forecast_rain.issued_atstring (date-time) or nullModel reference time of the run (UTC)
forecast_rain.notestring or nullPlain-language statement of the basis or of the gap
forecast_rain.reasonstring or nullMachine-readable cause when status is not 'ok'
forecast_rain.run_idstring or null
forecast_rain.since_observedRainWindow or nullNWP precipitation for the hours between the newest observed interval and the first forecast window (none when they meet)
forecast_rain.since_observed.completebooleanTrue when every six-hour interval of the window is present
forecast_rain.since_observed.endstring (date-time)UTC end of the window
forecast_rain.since_observed.hoursintegerWindow length: 24, 48 or 72
forecast_rain.since_observed.missing_hoursintegerHours of the window with no interval stored; total_mm excludes them
forecast_rain.since_observed.rain_mmnumber or nulltotal_mm - snow_mm
forecast_rain.since_observed.snow_mmnumber or nullPart 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.startstring (date-time)UTC start of the window (display in the timezone field)
forecast_rain.since_observed.total_mmnumber or nullSum of the six-hour intervals present; null when none are
forecast_rain.sourcesarray of stringNWP models that supply the windows, e.g. HRDPS, GDPS
forecast_rain.status"ok" | "partial" | "not_available" | "withheld"
forecast_rain.windowsarray of RainWindow
forecast_rain.windows[].completebooleanTrue when every six-hour interval of the window is present
forecast_rain.windows[].endstring (date-time)UTC end of the window
forecast_rain.windows[].hoursintegerWindow length: 24, 48 or 72
forecast_rain.windows[].missing_hoursintegerHours of the window with no interval stored; total_mm excludes them
forecast_rain.windows[].rain_mmnumber or nulltotal_mm - snow_mm
forecast_rain.windows[].snow_mmnumber or nullPart 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[].startstring (date-time)UTC start of the window (display in the timezone field)
forecast_rain.windows[].total_mmnumber or nullSum of the six-hour intervals present; null when none are
gauge_idstring
generated_atstring (date-time)
observed_rainObservedRain
observed_rain.basisstring
observed_rain.data_age_hoursnumber or nullHours from data_end to the request
observed_rain.data_endstring (date-time) or nullEnd of the newest observed interval (UTC)
observed_rain.notestring or nullPlain-language statement of the basis or of the gap
observed_rain.reasonstring or nullMachine-readable cause when status is not 'ok'
observed_rain.status"ok" | "partial" | "not_available" | "withheld"
observed_rain.windowsarray of RainWindow
observed_rain.windows[].completebooleanTrue when every six-hour interval of the window is present
observed_rain.windows[].endstring (date-time)UTC end of the window
observed_rain.windows[].hoursintegerWindow length: 24, 48 or 72
observed_rain.windows[].missing_hoursintegerHours of the window with no interval stored; total_mm excludes them
observed_rain.windows[].rain_mmnumber or nulltotal_mm - snow_mm
observed_rain.windows[].snow_mmnumber or nullPart 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[].startstring (date-time)UTC start of the window (display in the timezone field)
observed_rain.windows[].total_mmnumber or nullSum of the six-hour intervals present; null when none are
snowSnow
snow.as_ofstring (date-time) or null
snow.driven_by_forecastbooleanTrue when as_of is later than the newest observed precipitation, so the state used forecast weather
snow.forecast_snowfallarray of SnowWindow or nullModelled snowfall over the next 24/48/72 h; null when withheld
snow.forecast_snowfall[].completeboolean
snow.forecast_snowfall[].endstring (date-time)
snow.forecast_snowfall[].hoursinteger
snow.forecast_snowfall[].missing_hoursinteger
snow.forecast_snowfall[].snowfall_mmnumber or nullModelled snowfall, water equivalent
snow.forecast_snowfall[].startstring (date-time)
snow.labelstring
snow.notestring or nullPlain-language statement of the basis or of the gap
snow.observed_snowfallarray of SnowWindowModelled snowfall over the 24/48/72 h ending at the newest observed interval
snow.observed_snowfall[].completeboolean
snow.observed_snowfall[].endstring (date-time)
snow.observed_snowfall[].hoursinteger
snow.observed_snowfall[].missing_hoursinteger
snow.observed_snowfall[].snowfall_mmnumber or nullModelled snowfall, water equivalent
snow.observed_snowfall[].startstring (date-time)
snow.reasonstring or nullMachine-readable cause when status is not 'ok'
snow.status"ok" | "partial" | "not_available" | "withheld"
snow.swe_mmnumber or nullModelled basin-average snow water equivalent at as_of
snow.swe_outlookarray of SweOutlook or nullModelled SWE 24/48/72 h ahead; null when withheld
snow.swe_outlook[].atstring (date-time)
snow.swe_outlook[].hoursinteger
snow.swe_outlook[].swe_mmnumber
tierstring
timezonestringZone the window labels are shown in (IANA): the gauge's own (, GET /gauges/{id} time_zone), America/Toronto where the gauge has none
wetnessWetness
wetness.data_end_datestring (date) or null
wetness.flowFlowState or null
wetness.flow.band"well_below" | "below" | "normal" | "above" | "well_above" or nullAgainst this date's historical flow percentiles: <p10, <p25, p25-p75, >=p75, >=p90
wetness.flow.flow_cmsnumber or null
wetness.flow.observed_atstring (date-time) or null
wetness.flow.p10number or null
wetness.flow.p25number or null
wetness.flow.p50number or null
wetness.flow.p75number or null
wetness.flow.p90number or null
wetness.flow.reasonstring or null
wetness.flow.status"ok" | "not_available"
wetness.indicatorWetnessIndicator or null
wetness.indicator.basisarray of stringThe inputs the level was read from, in words
wetness.indicator.caveatstring or null
wetness.indicator.labelstring
wetness.indicator.level"wet" | "somewhat_wet" | "near_normal" | "somewhat_dry" | "dry" | "mixed" | "not_available"
wetness.methodarray of string
wetness.notestring or nullPlain-language statement of the basis or of the gap
wetness.preciparray of PrecipVsNormal7, 14 and 30 complete UTC days ending at data_end_date
wetness.precip[].band"low" | "normal" | "high" or nullbelow 80%, 80-120%, above 120% of normal
wetness.precip[].completebooleanTrue when every UTC day of the window is stored
wetness.precip[].daysinteger
wetness.precip[].end_datestring (date) or null
wetness.precip[].missing_daysinteger
wetness.precip[].n_yearsinteger or nullComplete years behind the normal
wetness.precip[].normal_mmnumber or nullMean N-day total for this time of year (see method)
wetness.precip[].normal_periodstring or nulle.g. 2012-2025
wetness.precip[].percent_of_normalnumber or null
wetness.precip[].reasonstring or nullWhy a figure is missing: incomplete_days | no_normal | no_observed_days
wetness.precip[].start_datestring (date) or null
wetness.precip[].total_mmnumber or null
wetness.reasonstring or nullMachine-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.

Parameters of /api/v1/gauges/{gauge_id}/forecast
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
signalquery"flow" | "level" | "natural_inflow" | "total_inflow" or nullWhich 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)
Response fields of /api/v1/gauges/{gauge_id}/forecast
FieldTypeDescription
archive_record_idstring or null
caveat"total_upstream" or nullpresent 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.
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
forecastsarray of ForecastLeadSchemaOne entry per lead day (empty if no forecast has been issued yet)
forecasts[].archive_record_idstring or null
forecasts[].beyond_observed_rangeboolean 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_statusstring
forecasts[].daily_meanboolean 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_reasonstring or null
forecasts[].flow_cmsnumberForecast flow (m³/s); current daily product is deterministic
forecasts[].flow_lowernumber or nullAlways null: the model emits no confidence bands
forecasts[].flow_uppernumber or nullAlways null: the model emits no confidence bands
forecasts[].generated_atstring (date-time) or nullActual producer generation time; unknown for legacy rows
forecasts[].lead_daysnumberDays ahead of the issue day (0 = issue day); integer (daily) steps only
forecasts[].lead_hoursinteger or nullLead hours since issue_time (informational)
forecasts[].level_lower_mnumber 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_mnumber 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_cmnumber 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_mnumber or null, level forecast only: `flow_upper` through the rating; see `level_lower_m`
forecasts[].observed_flow_cmsnumber or nullObserved flow (m³/s), null for future dates
forecasts[].observed_level_mnumber 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_flagsarray of string
forecasts[].segmentstring or null'warmstart' or 'forecast'
forecasts[].valid_datestring (date) or null
gauge_idstring
generated_atstring (date-time) or null
issue_timestring (date-time) or nullNominal forecast reference time in UTC, not actual generation or publication time
levelLevelInfoSchema or nullpresent 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_meanbooleanThe forecast levels are daily means (see each lead's `daily_mean`)
level.datumLevelDatumSchema
level.datum.conversionsarray of LevelGeodeticSchemaEvery conversion WSC publishes for the station
level.datum.conversions[].datumstringThe geodetic datum as HYDAT names it, e.g. CANADIAN GEODETIC VERTICAL DATUM 2013:EPOCH2010
level.datum.conversions[].datum_idinteger
level.datum.conversions[].offset_mnumberAdd to a level in the gauge datum for the elevation (m) above `datum`
level.datum.conversions[].shortstringits short label for text (CGVD2013, CGVD28, GSC); show `datum` in full where there is room
level.datum.geodeticLevelGeodeticSchema or nullThe 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.datumstringThe geodetic datum as HYDAT names it, e.g. CANADIAN GEODETIC VERTICAL DATUM 2013:EPOCH2010
level.datum.geodetic.datum_idinteger
level.datum.geodetic.offset_mnumberAdd to a level in the gauge datum for the elevation (m) above `datum`
level.datum.geodetic.shortstringits 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_seasonbooleanTrue 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.observedLevelObservedSchema or nullThe newest clean observed daily level; absent when there is none
level.observed.datestring (date)
level.observed.level_mnumberThe newest observed daily-mean level that FlowAi's QC does not flag (m, gauge datum)
level.ratingLevelRatingSchema
level.rating.fitted_atstring (date-time)
level.rating.gate_versionstring or nullThe publish-gate thresholds the rating passed (rating_config.toml)
level.rating.holdout_365_rmse_cmnumber or nullRMSE (cm) on the newest 365 days when the rating is refitted without them; null if not evaluable
level.rating.holdout_top_flow_rmse_cmnumber or nullRMSE (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_pairsintegerClean daily level/flow pairs in the window (QC-suspect, ice and estimated days left out)
level.rating.observed_h_maxnumberHighest level (m, gauge datum) in the fit
level.rating.observed_h_minnumberLowest level (m, gauge datum) in the fit
level.rating.observed_q_maxnumberHighest flow (m3/s) in the fit; a forecast flow above it is `beyond_observed_range`
level.rating.observed_q_minnumber
level.rating.rmse_cmnumberIn-sample RMSE of the rating's daily level, cm
level.rating.top_decile_rmse_cmnumber or nullIn-sample RMSE (cm) on the highest 10% of the fit's flows
level.rating.versioninteger
level.rating.window_endstring (date)Newest clean pair in the fit: the rating is current to this day
level.rating.window_startstring (date)First day of the fit window (10 years, recency-weighted)
level.withheldLevelWithheldSchema or nullPresent when the level forecast is withheld for ice or a missing flow: then `forecasts` is empty
level.withheld.lookback_hoursinteger
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_dailyarray of ForecastBlockSchema or null6 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_rangeboolean 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_hoursintegerBlock length in hours (6)
sub_daily[].daily_meanboolean or null, level forecast only: always false, the block is a 6 h value, not a daily mean
sub_daily[].flow_cmsnumberMean forecast flow over the block (m³/s); not an instantaneous value
sub_daily[].lead_hoursintegervalid_start - issue_time, in hours
sub_daily[].level_mnumber 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_cmnumber or null, level forecast only: the rating's RMSE (cm), as on a lead
sub_daily[].valid_datestring (date)The UTC day the block falls in: the daily row whose flow_cms its day's four blocks average to
sub_daily[].valid_endstring (date-time)End of the block (UTC), exclusive: valid_start + block_hours
sub_daily[].valid_startstring (date-time)Start of the block (UTC)
tierstringEffective tier for this request
upgrade_availablebooleanWhether 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).

Parameters of /api/v1/gauges/{gauge_id}/forecast-history
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
daysqueryinteger14How many days back to look for past issue dates (default 14, max 30)
signalquery"flow" | "natural_inflow" | "total_inflow" or nullWhich 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)
Response fields of /api/v1/gauges/{gauge_id}/forecast-history
FieldTypeDescription
caveat"total_upstream" or nullpresent 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.
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
gauge_idstring
issuesarray of ForecastHistoryIssueSchema
issues[].issue_timestring (date-time)
issues[].pointsarray of ForecastHistoryLeadSchema
issues[].points[].archive_record_idstring or null
issues[].points[].correction_statusstring
issues[].points[].fallback_reasonstring or null
issues[].points[].flow_cmsnumber
issues[].points[].flow_lowernumber or null
issues[].points[].flow_uppernumber or null
issues[].points[].generated_atstring (date-time) or null
issues[].points[].lead_daysnumber
issues[].points[].lead_hoursinteger or null
issues[].points[].quality_flagsarray of string
issues[].points[].valid_datestring (date) or null
issues[].sub_dailyarray of ForecastBlockSchema or null6 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_rangeboolean 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_hoursintegerBlock length in hours (6)
issues[].sub_daily[].daily_meanboolean or null, level forecast only: always false, the block is a 6 h value, not a daily mean
issues[].sub_daily[].flow_cmsnumberMean forecast flow over the block (m³/s); not an instantaneous value
issues[].sub_daily[].lead_hoursintegervalid_start - issue_time, in hours
issues[].sub_daily[].level_mnumber 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_cmnumber or null, level forecast only: the rating's RMSE (cm), as on a lead
issues[].sub_daily[].valid_datestring (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_endstring (date-time)End of the block (UTC), exclusive: valid_start + block_hours
issues[].sub_daily[].valid_startstring (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.

Parameters of /api/v1/gauges/{gauge_id}/hindcast
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
daysqueryinteger14How many days back to look at issued forecasts (default 14)
signalquery"flow" | "natural_inflow" | "total_inflow" or nullWhich 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)
Response fields of /api/v1/gauges/{gauge_id}/hindcast
FieldTypeDescription
caveat"total_upstream" or nullpresent 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.
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
gauge_idstring
hindcastsarray of HindcastIssueSchema
hindcasts[].forecastsarray of HindcastLeadSchema
hindcasts[].forecasts[].actualnumber or nullObserved flow at the forecast valid time (m³/s); null if observation missing
hindcasts[].forecasts[].errornumber or nullpredicted − actual (m³/s); null if observation missing
hindcasts[].forecasts[].lead_daysnumber
hindcasts[].forecasts[].lead_hoursinteger or null
hindcasts[].forecasts[].predictednumberForecast median flow (m³/s)
hindcasts[].issue_timestring (date-time)
hindcasts[].sub_dailyarray of ForecastBlockSchema or null6 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_rangeboolean 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_hoursintegerBlock length in hours (6)
hindcasts[].sub_daily[].daily_meanboolean or null, level forecast only: always false, the block is a 6 h value, not a daily mean
hindcasts[].sub_daily[].flow_cmsnumberMean forecast flow over the block (m³/s); not an instantaneous value
hindcasts[].sub_daily[].lead_hoursintegervalid_start - issue_time, in hours
hindcasts[].sub_daily[].level_mnumber 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_cmnumber or null, level forecast only: the rating's RMSE (cm), as on a lead
hindcasts[].sub_daily[].valid_datestring (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_endstring (date-time)End of the block (UTC), exclusive: valid_start + block_hours
hindcasts[].sub_daily[].valid_startstring (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)

Parameters of /api/v1/gauges/{gauge_id}/forecast-verification
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
Response: VerificationResponse (80 fields)
Response fields of /api/v1/gauges/{gauge_id}/forecast-verification
FieldTypeDescription
accuracy_claimstring or null
availability_basisstring or null
correction_score_scopestring or null
correction_scoresarray of VerificationScore
correction_scores[].actual_lead_hours_maxnumber
correction_scores[].actual_lead_hours_minnumber
correction_scores[].cohort"all_served" | "published_correction_status"
correction_scores[].comparisonsBaselineComparisons
correction_scores[].comparisons.raw_ravenPairedComparison
correction_scores[].comparisons.raw_raven.equal_absolute_error_ninteger
correction_scores[].comparisons.raw_raven.higher_absolute_error_ninteger
correction_scores[].comparisons.raw_raven.lower_absolute_error_ninteger
correction_scores[].comparisons.raw_raven.mae_skillnumber or null
correction_scores[].comparisons.raw_raven.mae_skill_unavailable_reason"zero_baseline_mae" or null
correction_scores[].corrected_ninteger
correction_scores[].correction_status"corrected" | "raw_fallback" or null
correction_scores[].gauge_idstring
correction_scores[].metricsPredictionMetrics
correction_scores[].metrics.raw_ravenErrorMetrics
correction_scores[].metrics.raw_raven.biasnumberMean prediction-minus-observation error in m3/s
correction_scores[].metrics.raw_raven.maenumberMean absolute error in m3/s
correction_scores[].metrics.raw_raven.rmsenumberRoot mean squared error in m3/s
correction_scores[].metrics.servedErrorMetrics
correction_scores[].metrics.served.biasnumberMean prediction-minus-observation error in m3/s
correction_scores[].metrics.served.maenumberMean absolute error in m3/s
correction_scores[].metrics.served.rmsenumberRoot mean squared error in m3/s
correction_scores[].ninteger
correction_scores[].nominal_lead_dayinteger
correction_scores[].target_quality"accepted" | "provisional"
correction_scores[].unique_reference_daysinteger
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
evaluated_atstring (date-time) or null
final_holdout_designatedfalse
flow_unit"m3/s"
gauge_idstring
include_provisional_targetsboolean or null
interval_contract"utc-interval-end-daily/v1"
nominal_lead_limitinteger
raw_baseline_policy"receiver-replayed-raven-daily/v1" or null
report_sha256string or null
report_version"forecast-operational-verification/v3" or null
runtime_evidence_policy"receiver-validated-raven-execution/v1" or null
scoresarray of VerificationScore
scores[].actual_lead_hours_maxnumber
scores[].actual_lead_hours_minnumber
scores[].cohort"all_served" | "published_correction_status"
scores[].comparisonsBaselineComparisons
scores[].comparisons.raw_ravenPairedComparison
scores[].comparisons.raw_raven.equal_absolute_error_ninteger
scores[].comparisons.raw_raven.higher_absolute_error_ninteger
scores[].comparisons.raw_raven.lower_absolute_error_ninteger
scores[].comparisons.raw_raven.mae_skillnumber or null
scores[].comparisons.raw_raven.mae_skill_unavailable_reason"zero_baseline_mae" or null
scores[].corrected_ninteger
scores[].correction_status"corrected" | "raw_fallback" or null
scores[].gauge_idstring
scores[].metricsPredictionMetrics
scores[].metrics.raw_ravenErrorMetrics
scores[].metrics.raw_raven.biasnumberMean prediction-minus-observation error in m3/s
scores[].metrics.raw_raven.maenumberMean absolute error in m3/s
scores[].metrics.raw_raven.rmsenumberRoot mean squared error in m3/s
scores[].metrics.servedErrorMetrics
scores[].metrics.served.biasnumberMean prediction-minus-observation error in m3/s
scores[].metrics.served.maenumberMean absolute error in m3/s
scores[].metrics.served.rmsenumberRoot mean squared error in m3/s
scores[].ninteger
scores[].nominal_lead_dayinteger
scores[].target_quality"accepted" | "provisional"
scores[].unique_reference_daysinteger
selection_policystring or null
source_arrival_times_verifiedfalse
status"not_configured" | "no_eligible_samples" | "descriptive_report"
target_evidence_policy"replayed-native-utc-targets/v1" or null
target_snapshot_sha256string or null
tierstring
upgrade_availableboolean

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').

Parameters of /api/v1/gauges/{gauge_id}/model-card
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
signalquery"flow" | "natural_inflow" | "total_inflow" or nullWhich 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)
Response fields of /api/v1/gauges/{gauge_id}/model-card
FieldTypeDescription
calibration_periodstring or null
caveat"total_upstream" or nullpresent 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_splitobject or nullWhich 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.
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
gauge_idstring
held_out_skillobject or nullHeld-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.
kgenumber or nullRetrospective model KGE; not operational forecast skill
levelLevelCardSchema or nullthe 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_meanboolean
level.datum_namestring or nullThe geodetic datum WSC publishes a conversion to, null where it publishes none
level.datum_offset_mnumber or nullAdd to a level (gauge datum) for the elevation above `datum_name`
level.datum_shortstring or null`datum_name` as a short label (CGVD2013, CGVD28, GSC), null with it
level.fit_window_endstring (date)The rating is current to this day
level.fit_window_startstring (date)
level.gate_versionstring or null
level.half_life_daysnumber
level.holdout_365_rmse_cmnumber or null
level.model_kind"single" | "two_segment"
level.n_pairsinteger
level.observed_h_maxnumber
level.observed_h_minnumber
level.observed_q_maxnumber
level.observed_q_minnumber
level.rmse_cmnumber
level.textstringThe user-facing description of the level forecast: method, accuracy, datum, daily mean, ice
level.top_flow_rmse_cmnumberIn-sample RMSE (cm) on the highest 10% of the fit's flows
level.window_yearsinteger
limitationsstring or null
ml_kge_14dnumber or nullML post-processor KGE at 14-day lead
ml_kge_1dnumber or nullML post-processor KGE at 1-day lead
ml_kge_3dnumber or nullML post-processor KGE at 3-day lead
ml_kge_7dnumber or nullML post-processor KGE at 7-day lead
model_trained_atstring (date-time) or nullWhen the served model was trained; null when the card does not say
nsenumber or nullRetrospective model NSE; not operational forecast skill
pbiasnumber or nullPercent bias (%)
process_chainstring or null
signal"flow" | "natural_inflow" | "total_inflow" or nullpresent 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.
tierstring
timestepstring or null
updated_atstring (date-time) or null
validation_periodstring or null
validation_statusstring or nullHow 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.

Parameters of /api/v1/gauges/{gauge_id}/weather-forecast
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
Response: WeatherForecastResponse (23 fields)
Response fields of /api/v1/gauges/{gauge_id}/weather-forecast
FieldTypeDescription
daysarray of WeatherForecastPoint
days[].precip_cumul_mmnumber or null
days[].precip_mmnumber or null
days[].temp_cnumber or null
days[].temp_max_cnumber or null
days[].temp_min_cnumber or null
days[].valid_datestring (date)
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
gauge_idstring
issued_atstring (date-time) or null
recent_daysarray of WeatherForecastPointForecast 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_mmnumber or null
recent_days[].precip_mmnumber or null
recent_days[].temp_cnumber or null
recent_days[].temp_max_cnumber or null
recent_days[].temp_min_cnumber or null
recent_days[].valid_datestring (date)
run_idstring or null
withheld_reasonstring 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.

Parameters of /api/v1/gauges/{gauge_id}/forcing
ParameterInTypeDefaultDescription
gauge_idrequiredpathstring
daysqueryinteger90Number of days to look back (default 90). A request above the water-year window is cut to it (window_capped)
Response: ForcingResponse (16 fields)
Response fields of /api/v1/gauges/{gauge_id}/forcing
FieldTypeDescription
disclaimerDisclaimerThe 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.textstringThe short disclaimer, to show with the forecast values
disclaimer.urlstringThe 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.versionstringThe disclaimer version, shared by both variants (the one users accept at sign-up)
forcingarray of ForcingPoint
forcing[].datestring
forcing[].precip_mmnumber or null
forcing[].temp_avgnumber or null
forcing[].temp_maxnumber or null
forcing[].temp_minnumber or null
gauge_idstring
window_cappedbooleanTrue 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_daysintegerDays served, today included: the requested `days`, or the water-year window if less
window_startstring (date)First UTC day of the window that was served: today - window_days + 1
withheld_reasonstring or null

Delft-FEWS feed

GET /rest/fewspiservice/v1/timeseries

PI time series for Delft-FEWS (organisation API keys only)

Parameters of /rest/fewspiservice/v1/timeseries
ParameterInTypeDefaultDescription
locationIdsrequiredqueryarray of stringWSC station IDs
parameterIdsrequiredqueryarray of stringQ.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
startTimerequiredquerystringUTC, e.g. 2026-10-01T00:00:00Z
endTimerequiredquerystringUTC
documentFormatquerystring or nullPI_XML (default) or PI_JSON
filterIdquerystring or nullAccepted and ignored
convertDatumqueryboolean or nullAccepted and ignored
useDisplayUnitsqueryboolean or nullAccepted and ignored
seriesquery"smoothed" | "raw"smoothedwhich 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.