Getting Started

Quickstart

Find a station, read its latest observations, then pull a model forecast for the same place.

None of these requests need an account. The shell examples use jq to filter JSON.

Find a station

Raindrop identifies stations by UUID. GET /stations/map/details returns every station keyed by ID. The window=1d parameter limits it to stations that reported in the last day. This finds the NAV CANADA station at London, Ontario:

curl -s --compressed "https://raindrop.cssl.ca/api/v1/stations/map/details?window=1d" \
  | jq 'to_entries[]
        | select(.value.name == "London")
        | {id: .key, network: .value.network}'
{
  "id": "12efaad4-2ddd-42c1-bba2-0c00031e4a98",
  "network": "NAV CANADA"
}

Get the latest readings

POST /stations/readings returns the most recent reading of each requested variable for the stations you name:

curl -s --compressed -X POST \
  "https://raindrop.cssl.ca/api/v1/stations/readings?names=airTemperature,dewPointTemperature,windSpeed10m" \
  -H "Content-Type: application/json" \
  -d '{"stationIds": ["12efaad4-2ddd-42c1-bba2-0c00031e4a98"]}'
{
  "stations": {
    "12efaad4-2ddd-42c1-bba2-0c00031e4a98": {
      "readings": [
        {
          "timestamp": "2026-09-24T19:00:00Z",
          "type": "airTemperature",
          "typeId": 560,
          "value": 19.7,
          "quality": { "baseQuality": "Good", "reasonQuality": "None" }
        },
        {
          "timestamp": "2026-09-24T19:00:00Z",
          "type": "windSpeed10m",
          "typeId": 38094,
          "value": 19.8,
          "quality": { "baseQuality": "Good", "reasonQuality": "None" }
        }
      ]
    }
  }
}

Temperatures are in °C and wind speeds in km/h. GET /observations/units lists the unit for every variable.

Pull a time series

For one variable over a time range, use POST /observations/series:

curl -s --compressed -X POST \
  "https://raindrop.cssl.ca/api/v1/observations/series?start=2026-09-24T00:00:00Z&end=2026-09-24T23:59:59Z" \
  -H "Content-Type: application/json" \
  -d '{"stationId": "12efaad4-2ddd-42c1-bba2-0c00031e4a98", "name": "airTemperature"}' \
  | jq -c '.readings[] | [.timestamp, .value]'
import requests

res = requests.post(
    "https://raindrop.cssl.ca/api/v1/observations/series",
    params={"start": "2026-09-24T00:00:00Z", "end": "2026-09-24T23:59:59Z"},
    json={"stationId": "12efaad4-2ddd-42c1-bba2-0c00031e4a98", "name": "airTemperature"},
)
res.raise_for_status()

for reading in res.json()["readings"]:
    print(reading["timestamp"], reading["value"])
const params = new URLSearchParams({
  start: '2026-09-24T00:00:00Z',
  end: '2026-09-24T23:59:59Z',
});

const res = await fetch(`https://raindrop.cssl.ca/api/v1/observations/series?${params}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    stationId: '12efaad4-2ddd-42c1-bba2-0c00031e4a98',
    name: 'airTemperature',
  }),
});

const series = await res.json();
for (const r of series.readings) console.log(r.timestamp, r.value);

Without an account the window can be at most 7 days and must end within the last 7 days. See time windows.

Get a forecast from Cyclone

Cyclone serves model output as 256×256 data tiles of packed 16-bit integers. This script reads the RDPS 2 m temperature forecast for London six hours into the latest run:

import math
import requests
import numpy as np

BASE = "https://cyclone.cssl.ca/models/rdps"

run = requests.get(f"{BASE}/runs").json()["runs"][0]
products = {p["name"]: p for p in requests.get(f"{BASE}/products").json()["products"]}
enc = products["T2m"]["encoding"]

# Find the Web Mercator tile and pixel that contain London, Ontario.
lat, lon, z = 43.03, -81.15, 6
n = 2**z
x = (lon + 180) / 360 * n
y = (1 - math.asinh(math.tan(math.radians(lat))) / math.pi) / 2 * n

res = requests.get(f"{BASE}/products/T2m/data/{z}/{int(x)}/{int(y)}", params={"run": run, "hour": 6})
res.raise_for_status()
tile = np.frombuffer(res.content, dtype="<i2").reshape(enc["height"], enc["width"])

raw = tile[int(y % 1 * enc["height"]), int(x % 1 * enc["width"])]
if raw != enc["nodata"]:
    kelvin = raw * enc["scale"] + enc["offset"]
    print(f"RDPS {run}, +6 h: {kelvin - 273.15:.1f} °C")
RDPS 20260924T120000Z, +6 h: 18.8 °C

Most Cyclone products use SI units, so this temperature is in kelvin. /products lists the unit of every product.

Next steps