Skip to content

API

The CoSWAT API returns model results as JSON. It is read-only and needs no key.

Base address

https://coswat.com/api/v1/

Planned

The API will also be served at https://api.coswat.com/v1/.

Rules

  • Only GET, HEAD and OPTIONS requests are accepted.
  • The API sets no cookies and needs no sign-in. Requests that act for an account use a personal token.
  • Browsers may call it from pages on coswat.com (CORS). Requests from other websites' pages are refused by the browser; scripts and command-line tools are not affected.
  • Answers can be cached for 10 minutes (Cache-Control: public, max-age=600). Series answers also have an ETag, so a repeated request with If-None-Match gets 304 Not Modified.
  • Errors are JSON with one field, for example {"error": "No layer nope"}, and an HTTP status: 400 for a missing or invalid parameter, 404 for an unknown layer, station or channel or a point outside the model, 405 for a method other than GET, HEAD or OPTIONS, and 413 and 429 as described under Limits.
  • Observed river flow is left out of answers for visitors who are not signed in, while the licence for the observations is being settled. The metrics and simulated flow are always included.

Personal tokens

Some requests act for a signed-in account: observed flow in station hydrographs, preparing and downloading slices, contributions, and data download requests. A script can make these requests with a personal token in place of signing in.

Getting a token

Tokens are issued by the CoSWAT team. Ask the team for a token for your account, saying what it is for. A token can be given an expiry date. It looks like cwt_1a2b3c4d_ followed by a long secret; the eight characters after cwt_ are its prefix, which identifies it to the team.

The token is shown once and cannot be recovered. If you lose it, ask for a new one.

Using a token

Send it in the Authorization header. Tokens are accepted on requests under https://coswat.com/api/, and on the download addresses of slices (/calibration/slices/<code>/download/) and of data downloads (/data/downloads/). Other pages of the website ignore them.

For example:

curl -H "Authorization: Bearer $COSWAT_TOKEN" \
  "https://coswat.com/api/v1/stations/1196405/hydrograph?layer=coswat_v2_0_0_observed_precip"

A request with a token acts as your account, with its role: any account's token can read observed flow and prepare slices, and a Contributor's token can also send contributions. Requests that change something (POST) need no other security token, and no session or cookie is created.

The planned address api.coswat.com will not accept tokens: it stays anonymous, and observed flow is hidden there. Use https://coswat.com/api/v1/ for requests with a token.

A request with an unknown, expired or revoked token gets 401 Unauthorized, with the header WWW-Authenticate: Bearer and {"error": "The API token is not valid: unknown, revoked or expired."}. It is not answered anonymously instead.

Keeping a token safe

Treat a token like a password. Read it from an environment variable, such as COSWAT_TOKEN above, and leave it out of code you share or commit. The team can revoke a token at any time, for example to replace tokens after a calibration round; ask for a new one when that happens.

Layers

Every request names a layer by its identifier, such as coswat_v2_0_0_observed_precip (precipitation, CoSWATv2.0.0, observed climate). For example:

Layer Variable Units
coswat_v2_0_0_observed_precip Precipitation mm
coswat_v2_0_0_observed_et Evapotranspiration mm
coswat_v2_0_0_observed_wateryld Water yield mm

Series at a point

GET /api/v1/series?layer=<layer>&lon=<longitude>&lat=<latitude>

Returns the series of the HRU at a point, from the published model run for that region.

Parameter Required Meaning
layer yes The layer identifier.
lon, lat yes The point, in decimal degrees (WGS 84).
variable no Another variable of the same model run, for example et.
variables no all returns every variable of the run in one answer.

Other parameters are ignored.

Example (values from a local build for the live period):

curl "https://coswat.com/api/v1/series?layer=coswat_v2_0_0_observed_precip&lon=31.5&lat=-21.0"
{
  "layer": "coswat_v2_0_0_observed_precip",
  "region": "africa_save",
  "subregion": "01-upper-save",
  "hru": "647",
  "area_km2": 356.0,
  "coordinates": {"lon": 31.5, "lat": -21.0},
  "generation": "coswat_v2_0_0_observed_4675e31f3eb10384938f",
  "frequency": "yearly",
  "times": ["1982", "1983", "1984", "1985", "1986"],
  "variable": "precip",
  "units": "mm",
  "values": [389.958, 353.236, 503.365, 661.872, 509.455],
  "stats": {
    "mean_annual": 483.577,
    "wettest_year": {"time": "1985", "value": 661.872},
    "trend_per_decade": 547.63,
    "years": 5
  }
}
Field Meaning
region, subregion The model the point belongs to. subregion is null for a region modelled as one model.
hru, area_km2 The HRU at the point and its area.
generation The published model run the values come from.
frequency The time step of the series. It follows the published model run (yearly for the current results), and a request cannot ask for another.
times, values The series. A time step with no value is null.
stats.mean_annual The mean of the annual totals over whole years.
stats.wettest_year The year with the highest value.
stats.trend_per_decade The least-squares slope of the annual totals, per ten years; null with fewer than two years.
stats.years The number of whole years in the series.

With variables=all, the top-level variable, units, values and stats are replaced by a variables object with one entry for each variable:

"variables": {
  "precip": {"units": "mm", "values": [389.958, ...], "stats": {...}},
  "et": {"units": "mm", "values": [388.104, ...], "stats": {...}},
  "wateryld": {"units": "mm", "values": [2.54, ...], "stats": {...}}
}

A point outside every published region returns 404 with {"error": "No modelled place at this point"}. Only regions with results for the live period are answered, and only for its years.

Gauging stations

GET /api/v1/stations?layer=<layer>

Returns the gauging stations in the regions of a layer, with the model's fit at each (see Evaluation).

curl "https://coswat.com/api/v1/stations?layer=coswat_v2_0_0_observed_precip"
{
  "layer": "coswat_v2_0_0_observed_precip",
  "stations": [
    {
      "id": "1196405",
      "name": "BOTSWANA",
      "river": "LIMPOPO RIVER",
      "region": "africa_limpopo",
      "subregion": null,
      "channel": 756,
      "source": "GRDC",
      "lon": 28.00392156,
      "lat": -22.93476395,
      "period": {"first": "1982-01", "last": "1986-12"},
      "months": 60,
      "match": "auto",
      "match_by": "framework outlet",
      "metrics": {"nse": -330.0204, "kge": -18.6803, "pbias": -1077.0907, "mae": 18.9145}
    }
  ]
}
Field Meaning
id, name, river The station, as named by its source.
region, subregion, channel The model channel the station is matched to.
source Where the observations come from, for example GRDC.
lon, lat The station's location.
period, months The months where both simulated and observed flow have values.
match How sure the pairing is: auto (paired and scored), review (a channel was found but the upstream areas disagree, so it needs a person and is not scored) or confirmed (confirmed by the framework's lookup or by a reviewer).
match_by Who paired the station with its channel: framework (the CoSWAT framework's lookup), framework outlet (the outlet the framework placed at the station), ours (the platform's matching) or reviewer (set by hand). See Matching stations to channels.
metrics NSE, KGE, PBIAS (%) and MAE (m³/s). null when the station has fewer than 24 months to score, in which case period is also null.

Station hydrograph

GET /api/v1/stations/<id>/hydrograph?layer=<layer>

Returns a station's simulated and observed monthly flow, in m³/s, with its metrics.

curl "https://coswat.com/api/v1/stations/1196405/hydrograph?layer=coswat_v2_0_0_observed_precip"
{
  "id": "1196405",
  "name": "BOTSWANA",
  "river": "LIMPOPO RIVER",
  "region": "africa_limpopo",
  "units": "m3/s",
  "frequency": "monthly",
  "run_id": "africa-limpopo-20261020T065507-dbcc",
  "times": ["1982-01", "1982-02", "1982-03", ..., "1986-12"],
  "simulated": [11.96, 1.042, 2.151, ..., 42.33],
  "observed": null,
  "observed_hidden": true,
  "metrics": {"nse": -330.0204, "kge": -18.6803, "pbias": -1077.0907, "mae": 18.9145},
  "period": {"first": "1982-01", "last": "1986-12"}
}

For visitors who are not signed in, observed is null and observed_hidden is true. A signed-in visitor on coswat.com, or a request with a personal token, gets the observed values, in an answer that is not cached.

Nearest channel

GET /api/v1/channels/nearest?layer=<layer>&lon=<longitude>&lat=<latitude>&zoom=<zoom>

Returns the model channel nearest a point, as a map click would choose it. zoom is the map zoom level (10 if left out); the search reaches about ten screen pixels at that zoom.

curl "https://coswat.com/api/v1/channels/nearest?layer=coswat_v2_0_0_observed_precip&lon=29.0&lat=-22.5&zoom=8"
{
  "layer": "coswat_v2_0_0_observed_precip",
  "region": "africa_limpopo",
  "subregion": "africa-limpopo",
  "channel": 555,
  "geometry": {"type": "LineString", "coordinates": [[28.9446, -22.5121], [28.9266, -22.495], [28.9266, -22.4864], [28.9177, -22.4779]]},
  "upstream_area_km2": 169.0,
  "basin_area_km2": 169.0,
  "distance_m": 5848.2,
  "station": null,
  "series": "/api/v1/channels/africa_limpopo/africa-limpopo/555/series?layer=coswat_v2_0_0_observed_precip"
}

distance_m is the distance from the point to the channel. station names the matched gauging station, if there is one, and series is the address of the channel's series. A point with no channel within reach returns 404 with {"error": "No channel near this point"}.

Channel network

GET /api/v1/channels?layer=<layer>&region=<region>
GET /api/v1/channels?layer=<layer>&bbox=<west>,<south>,<east>,<north>&zoom=<zoom>

Returns river channels as GeoJSON lines, one for each channel that has a series. Name either a region or a map view:

Parameter Meaning
layer The layer identifier (required).
region One region, for example africa_save: all of its channels.
bbox, zoom A map view: its edges in degrees (WGS 84) and the web map zoom (0 to 24). The answer has the channels of the view large enough to draw at that zoom, with coordinates rounded to suit it. bbox needs zoom.

At zoom 5 and below, only channels with an upstream area of at least 20,000 km² are included; at zoom 6, 5,000 km²; at zoom 7, 1,500 km²; at zoom 8, 400 km²; from zoom 9, every channel.

curl "https://coswat.com/api/v1/channels?layer=coswat_v2_0_0_observed_precip&region=africa_save"
{
  "type": "FeatureCollection",
  "scope": {"region": "africa_save"},
  "features": [
    {
      "type": "Feature",
      "geometry": {"type": "LineString", "coordinates": [[30.7433, -19.4818], [30.7343, -19.4905], [30.7343, -19.4991], ...]},
      "properties": {"region": "africa_save", "subregion": "01-upper-save", "channel": 1, "upstream_area_km2": 811.0, "order": 1}
    },
    ...
  ]
}

scope repeats what was asked for, with bbox rounded to 6 decimals; for a map view it also gives min_area_km2, the smallest upstream area included. Map views are answered gzip-compressed to clients that accept it. order is the channel's stream order, from 1 for a headwater stream upwards. The region, subregion and channel of a feature give the address of its series.

A request with neither region nor bbox still returns every channel of the layer, with a Deprecation header and a warning. It will be refused in a later release.

Channel series

GET /api/v1/channels/<region>/<subregion>/<channel>/series?layer=<layer>

Returns a channel's monthly flow. variables chooses one of them, or both separated by a comma, for example variables=flo_out.

curl "https://coswat.com/api/v1/channels/africa_limpopo/africa-limpopo/756/series?layer=coswat_v2_0_0_observed_precip"
{
  "layer": "coswat_v2_0_0_observed_precip",
  "region": "africa_limpopo",
  "subregion": "africa-limpopo",
  "channel": 756,
  "generation": "coswat_v2_0_0_observed_cd768db3668619d9da95",
  "frequency": "monthly",
  "upstream_area_km2": 118199.0,
  "basin_area_km2": 118199.0,
  "times": ["1982-01", "1982-02", "1982-03", ..., "1986-12"],
  "variables": {
    "flo_out": {"label": "Flow out", "units": "m3/s", "kind": "mean", "values": [11.96, 1.042, 2.151, ...]},
    "flo_in": {"label": "Flow in", "units": "m3/s", "kind": "mean", "values": [11.83, 0.9636, 2.143, ...]}
  },
  "station": {
    "id": "1196405",
    "name": "BOTSWANA",
    "months": 60,
    "metrics": {"nse": -330.0204, "kge": -18.6803, "pbias": -1077.0907, "mae": 18.9145},
    "hydrograph": "/api/v1/stations/1196405/hydrograph?layer=coswat_v2_0_0_observed_precip"
  },
  "not_provided": "sediment and nutrients are not provided until the water quality of the model is calibrated"
}
Variable Meaning Units
flo_out Flow out of the channel, monthly mean m³/s
flo_in Flow into the channel, monthly mean m³/s

station is the matched gauging station, or null. Sediment and nutrient loads are not provided until the model's water quality is calibrated; asking for them returns 400 with that reason.

Lakes and reservoirs

GET /api/v1/lakes?layer=<layer>&region=<region>
GET /api/v1/lakes?layer=<layer>&bbox=<west>,<south>,<east>,<north>&zoom=<zoom>

Returns lakes and reservoirs as GeoJSON polygons (EPSG:4326). As for the channel network, name either one region (all its water bodies) or a map view, which gives the water bodies of the view large enough to draw at that zoom: at least 1,000 km² at zoom 4 and below, 500 km² at zoom 5, 100 km² at zoom 6, and every one from zoom 7. A region without water bodies in its model adds nothing. Answers are sent compressed when the request allows it, for example with curl's --compressed option.

curl --compressed "https://coswat.com/api/v1/lakes?layer=coswat_v2_0_0_observed_precip&region=africa_save"
{
  "type": "FeatureCollection",
  "scope": {"region": "africa_save"},
  "features": [
    {
      "type": "Feature",
      "geometry": {"type": "Polygon", "coordinates": [[[30.9068, -20.1803], [30.9103, -20.1856], [30.9141, -20.1846], ...]]},
      "properties": {"region": "africa_save", "subregion": "01-upper-save", "id": 1, "res_id": 1, "name": "Kyle", "kind": "reservoir", "area_km2": 81.86}
    },
    ...
  ]
}

scope repeats what was asked for, as for the channel network. A request with neither region nor bbox still returns every water body of the layer, with a Deprecation header and a warning. It will be refused in a later release.

Property Meaning
region, subregion The model the water body belongs to.
id The water body's number.
res_id The reservoir's gis_id in reservoir.con, also used for the res objects of a slice's mapping.json and in the reservoir outputs. It is different from the reservoir's row number in reservoir.con; in the Limpopo it starts at 0.
name Its name, or null where none is known.
kind reservoir or lake.
area_km2 Its area.

Raster for a year

GET /api/v1/raster?layer=<layer>&region=<region>&year=<year>

Returns one year of a layer for a region as a GeoTIFF file.

Parameter Required Meaning
layer yes The layer identifier.
region yes The region, for example africa_save.
year yes A year of the live period, for example 1984.
subregions no Some of the region's subregions, separated by commas. The rest of the region is left empty.
curl -OJ "https://coswat.com/api/v1/raster?layer=coswat_v2_0_0_observed_precip&region=africa_save&year=1984"

The answer is the file precip_africa_save_1984.tif (54 kB for this example). It is in geographic coordinates (EPSG:4326) at 1/120 of a degree (about 1 km), with one Float32 band and a no-data value of −9999 outside the region. A year outside the period of the region's results returns 404 with the years it covers.

Region series

GET /api/v1/region-series?layer=<layer>&region=<region>&years=<first>-<last>

Returns every HRU's yearly values for a region, as CSV or JSON.

Parameter Required Meaning
layer yes The layer identifier.
region yes The region, for example africa_save.
years yes A range of years such as 1982-1986, or one year such as 1984.
subregions no Some of the region's subregions, separated by commas.
format no csv (the default) or json.
curl -OJ "https://coswat.com/api/v1/region-series?layer=coswat_v2_0_0_observed_precip&region=africa_save&years=1982-1986&format=csv"

The CSV answer is the file precip_africa_save_1982-1986.csv (612 kB for this example), with one row for each HRU and year:

subregion,hru,area_km2,year,precip
01-upper-save,1,29.0,1982,464.832
01-upper-save,1,29.0,1983,487.061
01-upper-save,1,29.0,1984,644.858
...

With format=json, the same rows come with the units and the years:

{
  "layer": "coswat_v2_0_0_observed_precip",
  "region": "africa_save",
  "variable": "precip",
  "units": "mm/year",
  "years": [1984, 1984],
  "columns": ["subregion", "hru", "area_km2", "year", "precip"],
  "rows": [
    ["01-upper-save", "1", 29.0, "1984", 644.858],
    ["01-upper-save", "2", 95.0, "1984", 697.206],
    ...
  ]
}

years gives the first and last year of the answer.

Limits

The raster and region series requests are limited to 60 of each per hour from one network address. Over the limit, the answer is 429 with a Retry-After header giving the seconds to wait.

An answer larger than 50 MB is refused with 413. Ask for fewer years or subregions. Queued exports of larger requests from the Data page are being built.

Map tiles

The map layers are also served as map tiles by GeoServer, for use in other maps and applications. The tiles are PNG images, 256 pixels square, in Web Mercator (EPSG:3857), for zoom levels 0 to 8. A tile at zoom 7 or 8 that nobody has requested yet is drawn on the first request, so that request can take longer. These are not part of the JSON API above.

https://coswat.com/geoserver/gwc/service/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0
  &LAYER=coswat:coswat_v2_0_0_observed_precip&STYLE=
  &TILEMATRIXSET=EPSG:3857&TILEMATRIX=5&TILEROW=15&TILECOL=17
  &FORMAT=image/png&TIME=1984-01-01T00:00:00Z

(The request is one line; it is broken here to fit.)

Parameter Value
LAYER coswat: followed by the layer identifier for a year's map, for example coswat:coswat_v2_0_0_observed_precip, or with _annual_mean added for the annual average, for example coswat:coswat_v2_0_0_observed_precip_annual_mean.
TILEMATRIXSET EPSG:3857. Other names for the same projection, such as EPSG:900913, are refused.
TILEMATRIX The zoom level as a plain number from 0 to 8, for example 5. The prefixed form EPSG:3857:5 fails.
TILEROW, TILECOL The tile's row and column at that zoom.
TIME The year, written as the capabilities document lists it, for example 1984-01-01T00:00:00Z. Other forms (a bare year, or milliseconds) fail. The annual average layers take no TIME.

The capabilities document lists the layers, their years and the tile addresses:

https://coswat.com/geoserver/gwc/service/wmts?SERVICE=WMTS&REQUEST=GetCapabilities

GeoServer also answers WMS requests at https://coswat.com/geoserver/wms. With WMS 1.3.0 and EPSG:4326, BBOX is written latitude first: minimum latitude, minimum longitude, maximum latitude, maximum longitude.