mswx.net developer API reference

Hyperlocal weather for the Monte Sano plateau. The same JSON that powers this site — current conditions, multi-source forecasts, the pool, live radar & lightning. Public, read-only, no key.

No auth — no key, no token CORS open (*) JSON only UTC ISO-8601 timestamps °F · inches · mm/hr (radar)

Base URL

All paths are relative to https://mswx.net
Be a good citizen — cache. Conditions update at most every ~5 min; forecasts hourly. Every response carries a Cache-Control: max-age (60–600s). Honor it; don't poll /now faster than once a minute. There's no rate limit today, but tight polling loops are the fastest way to earn one.
Start with /api/v1/now. It's a single round-trip with everything a home screen needs. Reach for the others only when you need history, the multi-day strip, or the radar map.

Endpoints

GET /api/v1/now

Everything for a home screen in one call — plateau temp, valley, pool, sun, sky, rain chance + plateau rainfall today, thunder, live lightning, live rain-nowcast.

{
  "as_of": "2026-05-25T19:00:00.000Z",
  "mswx":   { "temp_f": 74.2, "rh_pct": 61, "today_high_f": 76.6, "today_low_f": 66.2, "n_stations": 5 },
  "valley": { "temp_f": 79.9, "station_id": "HSVWX", "label": "Downtown" },
  "pool":   { "temp_f": 75.9, "obs_time": "2026-05-25T18:58:00.000Z", "age_seconds": 120 },
  "sun":    { "sunrise_utc": "...", "sunset_utc": "...", "is_day": true },
  "sky":    { "cloud_pct": 81, "label": "mostly cloudy" },
  "rain":   { "today_pct": 88, "today_in": 0.42, "tomorrow_pct": 40 },
  "thunder":{ "peak_pct": 35, "peak_at": "..." },
  "lightning": { "strikes_nearby": false, "prob_30min": 5, "prob_60min": 7 },
  "rain_now":  { "currently_raining": true, "outcome": "keeps_raining",
                 "peak_rate_mm_hr": 10.0, "certainty_pct": 90,
                 "starts_iso": null, "ends_iso": null, "nowcast_age_seconds": 344 }
}
GET /api/v1/forecast/daily ?days=7 (1–10)

Per-day air high/low, pool high/low, cloud %, rain chance.

{ "days": [
  { "date": "2026-05-25", "air_high_f": 76.6, "air_low_f": 66.2,
    "pool_high_f": 77.3, "pool_low_f": 73.9,
    "cloud_pct": 81.4, "cloud_label": "mostly cloudy", "pop_pct": 88 }
] }
GET /api/v1/forecast/hourly ?hours=24 (1–168)

Hourly air temp, pool temp, cloud %, rain chance, UV index.

{ "hours": [
  { "t": "2026-05-25T19:00:00.000Z", "air_temp_f": 76.6,
    "pool_temp_f": 75.9, "cloud_pct": 83, "pop_pct": 79, "uv_index": 4 }
] }
GET /api/v1/forecast/valley ?hours=48 (1–168)

Valley (Downtown) air-temp forecast — the 1000-ft-lower microclimate.

{ "hours": [ { "t": "2026-05-25T19:00:00.000Z", "air_temp_f": 79.9 } ] }
GET /api/v1/observed/hourly ?hours=24 (1–168)

Observed history: plateau, valley, pool, rainfall. Hour buckets centered HH:30; a 10-min live tail covers the past ~2h. rain_in is plateau-composite inches in the bucket; pool_temp_f null when no reading that hour.

{ "hours": [
  { "t": "2026-05-24T19:30:00.000Z", "air_temp_f": 70.1,
    "valley_temp_f": 74.8, "pool_temp_f": null, "rain_in": 0.08 }
] }
GET /api/v1/alerts

Active NWS watches/warnings over us + nearby cells within ~50 mi. Each carries contains_us/distance_mi/bearing/local_area + priority; sorted by priority then closeness. banner=true marks the severe few the site shows as a top banner.

{ "as_of": "...", "count": 2, "banner_count": 1, "top_priority": 43, "alerts": [
  { "event": "Severe Thunderstorm Warning", "severity": "Severe", "headline": "...",
    "area": "Limestone, AL; Madison, AL; Lincoln, TN", "onset": "...", "ends": "...",
    "banner": true, "geometry": { "type": "Polygon", "coordinates": ["…"] },
    "contains_us": false, "distance_mi": 5.3, "bearing": "NW",
    "local_area": "~5 mi NW", "priority": 43 }
] }
GET /api/v1/radar/nowcast

Live rain nowcast: is it raining now, when does it start/stop, how sure are we, the 0–60 min advected rate, and hourly POP. rain_now matches /now.

{
  "as_of": "...", "location": { "lat": 34.7335, "lon": -86.5236 },
  "rain_now": { "currently_raining": true, "outcome": "keeps_raining",
                "peak_rate_mm_hr": 10.0, "certainty_pct": 90 },
  "motion":   { "u_km_min": 0.31, "v_km_min": 0.12, "method": "pysteps" },
  "freshness":{ "latest_obs_age_seconds": 120, "latest_nowcast_age_seconds": 344 },
  "observed": [ { "t": "...", "rate_mm_hr": 6.4 } ],
  "forecast": [ { "t": "...", "rate_mm_hr": 10.0 } ],
  "pop_hourly":[ { "t": "...", "blend_pct": 70, "radar_blend_pct": 88, "nws_pct": 60 } ]
}
GET /api/v1/radar/map

Manifest to draw the animated radar map: bbox, absolute frame PNG URLs (kind=obs|fcst), lightning strikes, PMTiles basemap, neighborhood overlay.

{
  "as_of": "...", "site": "KHTX", "bbox": [-87.55, 33.9, -85.5, 35.55],
  "frames": [ { "url": "https://mswx.net/radar-frames/o_12.png",
               "valid_time": "...", "kind": "obs" } ],
  "frame_cadence_seconds": 300,
  "lightning": null,
  "basemap": { "pmtiles_url": "https://mswx.net/tiles/mswx-20260601.pmtiles",
               "flavor": "dark", "glyphs": "...", "sprite": "...",
               "center": [-86.5236, 34.7335], "min_zoom": 5, "max_zoom": 14 },
  "overlays": { "neighborhood_geojson_url": "https://mswx.net/static/neighborhood.geojson" }
}
GET /api/v1/widget

Compact pre-shaped payload for a small home-screen widget (current + a few slots + daily strip + one insight line).

{ "as_of": "...", "sky": { "cloud_pct": 81, "label": "mostly cloudy" },
  "slots": [...], "daily": [ { "date": "2026-05-25", "hi_f": 76.6, "lo_f": 66.2,
  "icon": "☁", "pop_pct": 90 } ], "insight": { "emoji": "🌧", "text": "Rain 9p · 79%" } }

Machine-readable spec

OpenAPI 3.1 — import into Bruno / Insomnia / a Swift or Kotlin codegen /plateau-api/openapi.json

Quick start

curl

curl -s https://mswx.net/api/v1/now | jq .

Swift (URLSession + Codable)

struct Now: Decodable {
  struct MSWX: Decodable { let temp_f: Double?; let today_high_f: Double? }
  struct RainNow: Decodable { let currently_raining: Bool; let outcome: String
                              let peak_rate_mm_hr: Double; let certainty_pct: Int? }
  let as_of: String
  let mswx: MSWX
  let rain_now: RainNow?
}

let url = URL(string: "https://mswx.net/api/v1/now")!
let (data, _) = try await URLSession.shared.data(from: url)
let now = try JSONDecoder().decode(Now.self, from: data)
print(now.mswx.temp_f ?? 0, now.rain_now?.outcome ?? "—")