polyweather.today · API

polyweather OpenAPI

Programmatic access to live, minute-by-minute observed airport temperatures — the same sub-hourly runway-sensor data that leads the official METAR — plus today's-high tracking, METAR history and our next-METAR nowcast, across 36 airports. JSON over HTTPS, rolling window only.

The minute-level runway feed covers 26 of the 36; the 10 mainland-China airports are METAR + daily-high only — see Coverage.

Copies the whole spec (endpoints + fields) as text — paste it into ChatGPT/Claude to generate a client.

Base URL

https://www.polyweather.today/api/open/v1

Authentication

Send your key in the Authorization header. Requests must come from an IP bound to the key, or you get 403 ip_not_allowed (the response's detail shows the detected IP — bind that one). Manage keys in the developer console.

Authorization: Bearer pmw_live_xxxxxxxx

Plans & limits

PlanPriceRateMonthlyIPs / keyAccess
Basic$49/mo60/min100,0001cities, latest, window, daily-high, metar
Pro$99/mo300/min1,000,0003+ nowcast, multi-city latest

Every response carries X-RateLimit-Remaining / X-RateLimit-Remaining-Month; a 429 includes Retry-After. {city} = ICAO (RKSI) or slug (rksi); °C, UTC ISO-8601.

Coverage

Not every airport carries every feed, so GET /cities returns two booleans per city — runway and nowcast. Branch on those rather than hard-coding a list.

Airportsrunway_temp_c · /window · /nowcastmetar · daily-high
26 — Asia (ex-China), Europe, US, Middle East✓✓
10 — mainland China
ZBAA, ZGGG, ZSPD, ZHHH, ZSQD, ZUUU, ZUCK, ZGSZ, ZSJN, ZHCC
— null / count 0 / null✓

Mainland China: our upstream provider withdrew minute-level runway-sensor access on 2026-07-07. For those 10 airports runway_temp_c and runway_at are null, /window returns count: 0, and /nowcast returns nowcast: null. METAR and daily-high tracking are unaffected and keep updating on each station's own cadence — the daily high those markets settle on is still there. If access is restored the two booleans flip back to true with no other change to the API.

Everywhere else a field can still be momentarily null during a feed gap, and nowcast is null whenever the feed is stale or the next METAR is outside the model's horizon. Treat null as “not available right now” — never as 0.

Quick start

curl -s "https://www.polyweather.today/api/open/v1/weather/RKSI/latest" \
  -H "Authorization: Bearer pmw_live_your_key"

Code examples

Python (requests)
import requests

BASE = "https://www.polyweather.today/api/open/v1"
KEY = "pmw_live_your_key"   # create at https://www.polyweather.today/developers
headers = {"Authorization": f"Bearer {KEY}"}

# Latest observation for Incheon (runway temp leads the METAR)
r = requests.get(f"{BASE}/weather/RKSI/latest", headers=headers, timeout=10)
r.raise_for_status()
d = r.json()
print(d["runway_temp_c"], "->", d["metar_temp_c"], "high", d["daily_high_c"])

# Coverage is not uniform: skip cities with no runway feed instead of
# treating a null as a reading. See the Coverage section.
cities = requests.get(f"{BASE}/cities", headers=headers).json()["cities"]
fast = [c["code"] for c in cities if c["runway"]]

# Next-METAR nowcast (Pro) — null if the feed is stale or out of horizon
nc = requests.get(f"{BASE}/weather/RKSI/nowcast", headers=headers).json()["nowcast"]
if nc:
    print(nc["pred_int"], "p", nc["p_hit"], nc["light"])
Node.js (fetch, Node 18+)
const BASE = "https://www.polyweather.today/api/open/v1";
const KEY = "pmw_live_your_key"; // create at https://www.polyweather.today/developers
const headers = { Authorization: `Bearer ${KEY}` };

// Latest observation for Incheon (runway temp leads the METAR)
const res = await fetch(`${BASE}/weather/RKSI/latest`, { headers });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const d = await res.json();
console.log(d.runway_temp_c, "->", d.metar_temp_c, "high", d.daily_high_c);

// Coverage is not uniform: skip cities with no runway feed instead of
// treating a null as a reading. See the Coverage section.
const { cities } = await (await fetch(`${BASE}/cities`, { headers })).json();
const fast = cities.filter((c) => c.runway).map((c) => c.code);

// Next-METAR nowcast (Pro) — null if the feed is stale or out of horizon
const { nowcast } = await (await fetch(`${BASE}/weather/RKSI/nowcast`, { headers })).json();
if (nowcast) console.log(nowcast.pred_int, "p", nowcast.p_hit, nowcast.light);

Endpoints

GET/cities
API Basic

List supported cities and their codes.

{
  "count": 34,
  "cities": [
    {
      "code": "RKSI",
      "slug": "rksi",
      "name": "Incheon",
      "airport": "Incheon International Airport (RKSI)",
      "beta": false,
      "runway": true,
      "nowcast": true
    }
  ]
}
code ICAO code — use as {city} in other calls
slug also accepted as {city}
name city name
airport airport full name
beta true = newer station, less-calibrated nowcast
runway false = no minute-level runway feed here: runway_temp_c is null and /window is empty (see Coverage)
nowcast false = /nowcast returns null here (the nowcast is computed from the runway minutes)
GET/weather/{city}/latest
API Basic

Newest observation for one city.

{
  "city": "RKSI",
  "name": "Incheon",
  "runway_temp_c": 23.5,
  "runway_at": "2026-09-14T02:26:00Z",
  "metar_temp_c": 23,
  "metar_at": "2026-09-14T02:00:00Z",
  "daily_high_c": 24,
  "daily_high_at": "2026-09-14T01:30:00Z",
  "yesterday_high_c": 24,
  "conditions": {
    "wind_dir": 320,
    "wind_kt": 10,
    "sky": "BKN",
    "sky_cover": 3,
    "visibility_km": 10,
    "pressure_hpa": 1019,
    "dewpoint_c": 19
  },
  "source": "noaa",
  "generated_at": "2026-09-14T02:27:12Z"
}
runway_temp_c latest minute-level runway-sensor mean (°C) — leads the METAR; null where cities.runway is false
runway_at time of that runway reading (UTC)
metar_temp_c latest official METAR temperature (whole °C)
metar_at METAR observation time (UTC)
daily_high_c today's running daily high — the number markets settle on
daily_high_at when today's high was reached (UTC)
yesterday_high_c yesterday's daily high (context)
conditions observed wind/sky/visibility/pressure/dewpoint — each field present only when reported
source METAR source: noaa | amsc
generated_at snapshot build time (UTC)
GET/weather/{city}/window
API Basic

Rolling minute-by-minute series. Window only — no history beyond it.

Query:
limit — points to return, default 90, max 120
{
  "city": "RKSI",
  "name": "Incheon",
  "window_minutes": 90,
  "runway_ends": ["15R", "16L"],
  "count": 90,
  "points": [
    {
      "at": "2026-09-14T02:26:00Z",
      "ends": { "15R": 23.5, "16L": 23.5 },
      "mean": 23.5
    }
  ]
}
window_minutes rolling window length — only the last ~90 min is exposed
runway_ends this airport's runway-end labels
count number of points returned — 0 where cities.runway is false (see Coverage)
points[].at minute timestamp (UTC), newest first
points[].ends per-runway-end temperature (°C)
points[].mean average across ends
GET/weather/{city}/daily-high
API Basic

Today's running high so far.

{
  "city": "RKSI",
  "name": "Incheon",
  "daily_high_c": 24,
  "peaked_at": "2026-09-14T01:30:00Z",
  "hourly_settlement": false,
  "generated_at": "2026-09-14T02:27:12Z"
}
daily_high_c today's running max (°C) — the settlement number
peaked_at time of that high (UTC)
hourly_settlement true = only on-the-hour readings count toward the high; readings between the hours are ignored. True for every China city except Beijing/Shanghai/Guangzhou.
GET/weather/{city}/metar
API Basic

Recent METARs (parsed temp + raw text).

Query:
limit — count, default 8, max 24
{
  "city": "RKSI",
  "name": "Incheon",
  "metars": [
    {
      "at": "2026-09-14T02:00:00Z",
      "temp_c": 23,
      "raw": "METAR RKSI 140200Z 32010KT 9999 FEW025 BKN200 23/19 Q1019 NOSIG",
      "source": "noaa"
    }
  ]
}
metars[].at observation time (UTC)
metars[].temp_c METAR temperature (°C)
metars[].raw raw METAR text
metars[].source data source
(cadence) each station reports on its own schedule — typically half-hourly or hourly, and some issue off-schedule SPECIs. Poll on the interval you see in metars[].at rather than assuming 30 min; /daily-high's hourly_settlement tells you which stations settle on the hour.
GET/weather/{city}/nowcast
API Pro

Next-METAR forecast: prediction, probability distribution, decision light.

{
  "city": "RKSI",
  "name": "Incheon",
  "nowcast": {
    "model": "anchor-v3",
    "pred_int": 24,
    "pred_float": 23.51,
    "h_min": 4,
    "next_metar_at": "2026-09-14T02:30:00Z",
    "p_hit": 0.514,
    "p_hit_raw": 0.482,
    "gamma": 0.73,
    "ladder": [
      { "t": 23, "p": 0.518 },
      { "t": 24, "p": 0.482 }
    ],
    "light": "red"
  },
  "slope_30": 0,
  "regime": "flat",
  "forecast": {
    "high_c": 25,
    "peak_hour_local": "15:00",
    "cloud_pct_peak": 57,
    "models": [ { "name": "ECMWF", "high": 25.5 }, { "name": "GFS", "high": 23.6 } ],
    "tomorrow_high_c": 24.6
  }
}
nowcast null when the city has no runway feed (cities.nowcast false), the feed is stale, or the next METAR is outside the model's horizon
nowcast.pred_int predicted NEXT METAR, rounded (°C)
nowcast.pred_float predicted, unrounded
nowcast.h_min horizon: minutes until the next METAR
nowcast.next_metar_at expected next METAR time (UTC)
nowcast.p_hit calibrated probability pred_int is exactly right
nowcast.p_hit_raw same, before the daily monotone calibration
nowcast.gamma slope-damping factor applied at this hour
nowcast.ladder full probability distribution: { t: temp, p: probability }
nowcast.light decision light green / yellow / red (signal reliability)
slope_30 runway-temp slope, °C per 30 min (signed)
regime flat | mid | fast
forecast Open-Meteo model guidance: high_c, peak_hour_local, cloud_pct_peak, models[], tomorrow_high_c
GET/weather/latest
API Pro

Every city's latest in one call.

{
  "count": 34,
  "generated_at": "2026-09-14T02:27:12Z",
  "cities": [
    {
      "city": "RKSI",
      "name": "Incheon",
      "runway_temp_c": 23.5,
      "metar_temp_c": 23,
      "daily_high_c": 24,
      "daily_high_at": "2026-09-14T01:30:00Z"
    }
  ]
}
cities[] per city: runway_temp_c, metar_temp_c, daily_high_c, daily_high_at

Errors

HTTPerrorMeaning
401missing_api_key / invalid_api_key / key_revokedBad or missing Authorization header.
402no_active_api_planPlan lapsed — renew at /developers.
403ip_not_allowedRequest IP isn't allowlisted. The detected IP is in `detail` — bind that IP.
403plan_upgrade_requiredEndpoint needs API Pro (nowcast, multi-city).
404unknown_city / unknown_resourceCheck the path.
429rate_limitedPer-minute or monthly cap hit — see Retry-After.

Getting started

API access is a paid add-on. Open the developer console to buy a plan (crypto, instant) and create an IP-locked key — your plan also unlocks the full web dashboard.

View-only data — not betting or financial advice. Rolling-window access; historical bulk export is not offered.