Docs navigation
Reference
Agents
Build a weather app
From your first 200 to a working app: where the key lives, the query
to send, how long to keep each answer, how to read times and icons, and
what to show for attribution. Every field the query returns is listed
in the Weather field reference.
1. Call Smarter Weather from your own backend
Call Smarter Weather from your own backend, and have that backend serve
only your own app. Your app calls your backend; your backend holds the
key and calls https://api.smarterweather.com.
- Keep the key server-side. A key shipped in browser or mobile code is a key anyone can copy. Acceptable Use §4 covers keys in public clients: use a server-side or edge proxy for them.
- Serve your app, nobody else. Sharing your key with third parties, or operating a proxy, gateway or bring-your-own-key service that lets third parties use your credentials, is prohibited by Acceptable Use §1. The Acceptable Use Policy is the authority on what is allowed; this guide does not add to it.
- Put the route behind your app's own sign-in. Your backend's weather route must accept only requests from your signed-in users or your own app, checked with a session or an app token. Without that check it is an open relay for your key.
- CORS is not that check. In production the API accepts browser calls only from Smarter Weather's own sites, so a page on your domain cannot call it directly. CORS only governs what a browser lets a page read. Any other client can call your backend whatever its CORS headers say.
The two backends below are deliberately narrow:
- Each serves one route,
GET /weather?lat=..&lon=... - It answers
401unless the request carries theX-App-Tokenheader set toAPP_TOKEN, compared in constant time. That shared token is a minimal stand-in for your app's sign-in. Replace it with your session check before you ship: a token in page code can be copied too. - It refuses to start when
SMARTERWEATHER_API_KEYorAPP_TOKENis unset or empty. - It rejects a
latorlonthat is not a number in range, and calls only/v1/weatherwith a fixed query. - It caches each answer as section 3 describes. Requests for the same location that arrive while a call is in flight share that call's answer, whatever it is, so a burst makes one upstream call.
- It gives up on the API after 10 seconds for the whole call, body
included, and answers
502for a timeout, a dropped connection or a truncated body. - It passes
Retry-Afterand theRateLimit-*headers of the API's answer through to your app. After a429it answers every uncached request with a429of its own untilRetry-Afterpasses, without calling the API. Those local answers carry a recomputedRetry-Afterand noRateLimit-*headers, which described the API's call.
Set SMARTERWEATHER_API_BASE only to point the backend at a local stub
in tests.
Node 20
// server.mjs. Run: node --env-file=.env server.mjs (Node 20.6+)
import { createServer } from "node:http";
import { createHash, timingSafeEqual } from "node:crypto";
const KEY = process.env.SMARTERWEATHER_API_KEY ?? "";
const APP_TOKEN = process.env.APP_TOKEN ?? ""; // stand-in for your app's own sign-in
const BASE = process.env.SMARTERWEATHER_API_BASE ?? "https://api.smarterweather.com";
const QUERY = "view=concise&days=7&hourly_hours=24&units=imperial";
const PASS = /^(content-type|retry-after|ratelimit-.*)$/i; // headers your app sees
const OUTSIDE_COVERAGE_TTL = 3600; // a point outside coverage stays outside
if (!KEY || !APP_TOKEN) {
console.error("set SMARTERWEATHER_API_KEY and APP_TOKEN");
process.exit(1);
}
const cache = new Map(); // "lat,lon" -> { expires, status, headers, body }
const inflight = new Map(); // "lat,lon" -> the pending upstream call, shared by every request for it
let throttled = null; // the last 429, answered locally until its Retry-After passes
const coord = (v, limit) => {
const n = v === null || v.trim() === "" ? NaN : Number(v);
return Math.abs(n) <= limit ? n.toFixed(4) : null;
};
const maxAge = (cc) =>
/no-store/i.test(cc) ? 0 : Number(/(?:^|,)\s*max-age=(\d+)/i.exec(cc)?.[1] ?? 0);
const sha256 = (value) => createHash("sha256").update(value).digest();
const WANT = sha256(APP_TOKEN);
// Constant time: equal-length digests, so neither the length nor the bytes leak.
const signedIn = (req) => timingSafeEqual(sha256(String(req.headers["x-app-token"] ?? "")), WANT);
const problemType = (body) => {
try { return JSON.parse(body).type ?? ""; } catch { return ""; }
};
// RateLimit-* describe one upstream call, so a stored copy keeps just the type.
const stored = (entry) => ({ ...entry, headers: { "content-type": entry.headers["content-type"] } });
async function upstream(lat, lon) {
// The signal bounds the whole call, body included.
const res = await fetch(`${BASE}/v1/weather?lat=${lat}&lon=${lon}&${QUERY}`, {
headers: { Authorization: `Bearer ${KEY}` },
signal: AbortSignal.timeout(10_000),
});
const headers = Object.fromEntries([...res.headers].filter(([name]) => PASS.test(name)));
const body = await res.text();
let ttl = res.ok ? maxAge(res.headers.get("cache-control") ?? "") : 0;
if (res.status === 404 && problemType(body).endsWith("outside-coverage")) ttl = OUTSIDE_COVERAGE_TTL;
if (res.status === 429) ttl = Number(res.headers.get("retry-after")) || 1;
return { expires: Date.now() + ttl * 1000, status: res.status, headers, body };
}
async function fetchAndStore(key, lat, lon) {
const entry = await upstream(lat, lon);
for (const [k, v] of cache) if (v.expires <= Date.now()) cache.delete(k);
if (entry.status === 429) throttled = stored(entry);
else if (entry.expires > Date.now()) cache.set(key, stored(entry));
return entry;
}
function weather(key, lat, lon) {
const hit = cache.get(key);
if (hit?.expires > Date.now()) return hit;
if (throttled?.expires > Date.now()) return throttled;
if (!inflight.has(key)) {
inflight.set(key, fetchAndStore(key, lat, lon).finally(() => inflight.delete(key)));
}
return inflight.get(key);
}
createServer(async (req, res) => {
const url = new URL(req.url, "http://localhost");
if (req.method !== "GET" || url.pathname !== "/weather") return res.writeHead(404).end();
if (!signedIn(req)) return res.writeHead(401).end("sign in first");
const lat = coord(url.searchParams.get("lat"), 90);
const lon = coord(url.searchParams.get("lon"), 180);
if (lat === null || lon === null) return res.writeHead(400).end("lat and lon must be numbers");
try {
const entry = await weather(`${lat},${lon}`, lat, lon);
const headers = { ...entry.headers };
if (entry.status === 429) {
headers["retry-after"] = String(Math.max(1, Math.ceil((entry.expires - Date.now()) / 1000)));
}
res.writeHead(entry.status, headers).end(entry.body);
} catch {
res.writeHead(502).end("weather unavailable");
}
}).listen(8080);
Python
# server.py. Run: SMARTERWEATHER_API_KEY=... APP_TOKEN=... python3 server.py (stdlib only)
import concurrent.futures, hashlib, hmac, http.client, json, math, os, re, threading, time
import urllib.error, urllib.parse, urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
KEY = os.environ.get("SMARTERWEATHER_API_KEY", "")
APP_TOKEN = os.environ.get("APP_TOKEN", "") # stand-in for your app's own sign-in
if not KEY or not APP_TOKEN:
raise SystemExit("set SMARTERWEATHER_API_KEY and APP_TOKEN")
BASE = os.environ.get("SMARTERWEATHER_API_BASE", "https://api.smarterweather.com")
QUERY = "view=concise&days=7&hourly_hours=24&units=imperial"
PASS = re.compile(r"^(content-type|retry-after|ratelimit-.*)$", re.I) # headers your app sees
OUTSIDE_COVERAGE_TTL = 3600 # a point outside coverage stays outside
DEADLINE = 10 # seconds for the whole upstream call, body included
WANT = hashlib.sha256(APP_TOKEN.encode()).digest()
cache = {} # "lat,lon" -> (expires, status, headers, body)
inflight = {} # "lat,lon" -> Pending, shared by every request for it while one thread calls upstream
guard = threading.Lock() # guards cache, inflight and throttled
pool = concurrent.futures.ThreadPoolExecutor(max_workers=8) # runs upstream calls under DEADLINE
throttled = None # the last 429, answered locally until its Retry-After passes
FAILED = (OSError, http.client.HTTPException, concurrent.futures.TimeoutError)
class Pending:
def __init__(self):
self.done, self.entry, self.error = threading.Event(), None, None
def coord(values, limit):
try:
n = float(values[0])
except (TypeError, ValueError, IndexError):
return None
return f"{n:.4f}" if abs(n) <= limit else None
def max_age(cc):
m = re.search(r"(?:^|,)\s*max-age=(\d+)", cc or "", re.I)
return 0 if not m or "no-store" in cc.lower() else int(m.group(1))
def problem_type(body):
try:
return json.loads(body).get("type", "")
except ValueError:
return ""
def read_body(res, deadline):
chunks = []
while chunk := res.read1(65536): # whatever has arrived, so a slow drip hits the deadline
if time.monotonic() > deadline:
raise TimeoutError("upstream took longer than DEADLINE")
chunks.append(chunk)
body = b"".join(chunks)
length = res.headers.get("Content-Length")
if length and length.isdigit() and len(body) != int(length):
raise http.client.IncompleteRead(body, int(length) - len(body))
return body
def upstream(lat, lon):
deadline = time.monotonic() + DEADLINE
req = urllib.request.Request(f"{BASE}/v1/weather?lat={lat}&lon={lon}&{QUERY}",
headers={"Authorization": f"Bearer {KEY}"})
try:
with urllib.request.urlopen(req, timeout=DEADLINE) as res:
status, hdrs, body = res.status, res.headers, read_body(res, deadline)
ttl = max_age(hdrs["Cache-Control"])
except urllib.error.HTTPError as err:
status, hdrs, body, ttl = err.code, err.headers, err.read(), 0
if status == 404 and problem_type(body).endswith("outside-coverage"):
ttl = OUTSIDE_COVERAGE_TTL
if status == 429:
ttl = int(hdrs["Retry-After"]) if (hdrs["Retry-After"] or "").isdigit() else 1
headers = {k: v for k, v in hdrs.items() if PASS.match(k)}
return (time.time() + ttl, status, headers, body)
def store(key, entry):
global throttled
# RateLimit-* describe one upstream call, so a stored copy keeps just the type.
types = {k: v for k, v in entry[2].items() if k.lower() == "content-type"}
kept = (entry[0], entry[1], types, entry[3])
with guard:
now = time.time()
for k in [k for k, v in cache.items() if v[0] <= now]:
del cache[k]
if entry[1] == 429:
throttled = kept
elif entry[0] > now:
cache[key] = kept
def weather(key, lat, lon):
hit = cache.get(key)
if hit and hit[0] > time.time():
return hit
with guard:
hit = cache.get(key)
if hit and hit[0] > time.time():
return hit
if throttled and throttled[0] > time.time():
return throttled
pending = inflight.get(key)
owner = pending is None
if owner:
pending = inflight[key] = Pending()
if not owner: # another request is already calling upstream for this point
pending.done.wait()
if pending.error:
raise pending.error
return pending.entry
try:
pending.entry = pool.submit(upstream, lat, lon).result(timeout=DEADLINE)
store(key, pending.entry)
return pending.entry
except Exception as err:
pending.error = err
raise
finally:
with guard:
inflight.pop(key, None)
pending.done.set()
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
url = urllib.parse.urlsplit(self.path)
if url.path != "/weather":
return self.send_error(404)
got = hashlib.sha256(self.headers.get("X-App-Token", "").encode()).digest()
if not hmac.compare_digest(got, WANT): # constant time on equal-length digests
return self.send_error(401, "sign in first")
q = urllib.parse.parse_qs(url.query)
lat, lon = coord(q.get("lat"), 90), coord(q.get("lon"), 180)
if lat is None or lon is None:
return self.send_error(400, "lat and lon must be numbers")
try:
expires, status, headers, body = weather(f"{lat},{lon}", lat, lon)
except FAILED:
return self.send_error(502, "weather unavailable")
self.send_response(status)
for name, value in headers.items():
if not (status == 429 and name.lower() == "retry-after"):
self.send_header(name, value)
if status == 429:
self.send_header("Retry-After", str(max(1, math.ceil(expires - time.time()))))
self.end_headers()
self.wfile.write(body)
ThreadingHTTPServer(("", 8080), Handler).serve_forever()
Both round lat and lon to 4 decimal places (about 11 m), so nearby
requests share one cached answer. Other errors pass through to your app
with their status and are not cached.
2. Send the recommended query
GET /v1/weather?lat=42.3601&lon=-71.0589&view=concise&days=7&hourly_hours=24&units=imperial
view=concisereturns a documented subset of thehourly[]anddaily[]columns, enough for a forecast screen. The subset only grows.view=detailed(the default) returns every column.days=7returns a week of daily rows, andhourly_hours=24the next 24 hourly rows.units=imperial(the default) orunits=metric. Read the labels from the response'sunitsobject (for exampleunits.temperatureisForC) instead of hard-coding them.
The full list of query parameters is on the Weather endpoint page.
3. Cache each answer for its max-age
Every successful /v1/weather answer carries a Cache-Control header.
Keep the body on your backend for the max-age that header gives,
keyed on the coordinates and the query, and call the API again once it
expires. Parse the header on each response rather than hard-coding a
lifetime: the value can differ between answers, and an answer built on
a degraded upstream source says no-store, which means do not cache it
at all. A cached answer never reaches the API, so it does not count
against your rate limit or your credits.
Error responses (application/problem+json) carry no Cache-Control,
so decide per type:
outside-coverage(404). It does not change for a point, so keep it. The backends above keep it for an hour.429. Wait forRetry-Afterbefore any further call; the limit is per key, not per location.- Others. Do not cache them.
4. Read times in the location's timezone
hourly[].timeis an ISO 8601 timestamp in UTC. Convert it tolocation.timezone(an IANA name such asAmerica/New_York) for display, not to the device's timezone.daily[].dateis a calendar date (2026-04-20) that is already local tolocation.timezone. Display it as is. Parsing it as a timestamp reads it as UTC midnight, which shows the previous day west of Greenwich.- Label daily rows with
relative_day(-1yesterday,0today,1tomorrow) instead of comparing dates with the device clock. Select today byrelative_day == 0, not by position: before 6 AM local, an extrarelative_day: -1row for the overnight still in progress comes first, and itshighcan be null. active_periodisdayornighton exactly the row that holds the current local time, and null on every other row. Use it to choose between "Today" and "Tonight".
5. Pick an icon
icon is a string slug from an open vocabulary: new slugs can appear
without a new API version, so never treat the list below as closed.
Each slug is a family name, sometimes followed by -day or -night.
The families today are:
clear, partly-cloudy, mostly-cloudy, cloudy, showers, rain,
thunder-showers, thunder-rain, thunderstorms, snow-showers,
snow, rain-snow-showers, rain-snow, sleet, fog and haze.
- Suffixes.
clear,partly-cloudy,mostly-cloudy,showers,thunder-showers,snow-showersandrain-snow-showersalways carry-dayor-night, for examplepartly-cloudy-night.hazecan appear with or without one. The other families have none. - Which suffix.
current.iconandhourly[].iconfollow the sun at the location:-daybetween sunrise and sunset.daily[].iconanddaily[].day.iconare worked out for local noon, so they are normally day variants;daily[].night.iconis worked out for late evening, so it is normally a night variant. Withview=concise,daily[].dayanddaily[].nightcarry onlyprecip_probability:day.icon,night.icon, theirconditionsandnarrative, and every other day/night field needview=detailed. - Fallback. For a slug you have no artwork for, strip a trailing
-dayor-night, then match the longest family name the slug starts with. If nothing matches, show the row'sconditionstext (for examplePartly Cloudy).
6. Show the attribution notice
Display the attribution.notice string from each response wherever
you show its data, as returned, and link
smarterweather.com/acknowledgements
next to it. Read the notice from the response instead of copying it
into your app, so a change to the wording reaches your users without a
release. attribution.sources lists the sources that contributed, for
your own records.
7. Stay inside coverage
Smarter Weather covers the 50 US states and DC only. Every route that takes a point (lat and lon, zip or location) returns 404 outside-coverage for a point outside the 50 states and DC. /v1/alerts and /v1/observations return it for a bounding box that touches no US domain (CONUS, Alaska, Hawaii); a box that touches one may still include area outside the US. /v1/weather/batch reports it per item as outside_coverage, and /v1/point with points and /v1/series/batch mark such points out_of_domain, as they do a point the check could not reach in time (retry it). If the location lookup is unavailable, the check can briefly fail open. Routes keyed by a station, radar site, forecast office or alert id, and nationwide lists, are not filtered by point, so do not treat an empty result as an all-clear. See Errors.
The US territories (Puerto Rico, the US Virgin Islands, Guam, the Northern Mariana Islands and American Samoa) and other countries are outside it.
A point outside coverage gets a 404 with the
outside-coverage
problem type. Show your own "not covered" message for it, and do not
retry it.
8. Look up a field
The Weather field reference
lists every field of every object the /v1/weather body can carry,
with its type, whether it can be null and whether it is always present,
and the unit each number is in. It is generated from
openapi.yaml,
the full contract.
Weather field reference
Every object the GET /v1/weather body can carry, generated from openapi.yaml at build.
Tables show the detailed view: view=concise returns a subset of the hourly[] and daily[] columns,
and in it daily[].day and daily[].night carry only precip_probability. A section appears only when
it is included; opt-in sections say which include token adds them. "Always present" means the schema
requires the field.
Units by field
The list covers current, hourly[], daily[] and daily[].day / night; the Unit column in each
table shows the same.
Converted to the unit system you asked for; read the label from the response's units object:
units.temperature:temperature,feels_like,dew_point,high,low,feels_like_high,feels_like_low,dew_point_max,dew_point_min.units.wind_speed:wind_speed,wind_gust,wind_speed_max,wind_gust_max.units.pressure:pressure,pressure_avg.units.visibility:visibility,visibility_min.units.precip_amount:precip_amount,precip_total,snow_amount,snow_total,ice_total.units.precip_amountper hour:precip_rate.units.altitude:snow_level,cloud_ceiling,cloud_ceiling_min.units.solar_radiation:solar_radiation_max.
Fixed, whatever units says, as are fields whose name ends in a unit suffix (_ft, _km, _m, _mm, _prob):
- percent (0 to 100):
humidity,humidity_avg,cloud_cover,cloud_cover_avg,cloud_cover_low,cloud_cover_mid,cloud_cover_high,precip_probability,thunderstorm_probability,thunderstorm_probability_max,precip_type_rain_prob,precip_type_snow_prob,precip_type_freezing_rain_prob,precip_type_ice_pellets_prob,lightning_prob. - degrees (0 = N, 90 = E):
wind_direction. - mm:
precip_24h. - J/kg:
cape,cape_max. - strikes per unit area per time window:
lightning_density. - none (AQI scale):
aqi,aqi_max,pm25,pm10,ozone. - none (UV index):
uv_index,uv_index_max. - whole days from today, local:
relative_day.
Top level (Weather)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
location |
Location | no | yes | Resolved location. | |
current |
Current | no | no | Current weather conditions derived from the RTMA + MRMS blend. | |
hourly |
Hourly[] | no | no | Hourly forecast, ordered ascending by time. |
|
daily |
Daily[] | no | no | Daily forecast, ordered ascending by date. |
|
alerts |
Alert[] | no | no | Active NWS alerts for the location, present whenever the section is included and its fetch succeeded. | |
outlooks |
Outlook[] | no | no | SPC convective outlooks active for the location, present whenever the section is included and its fetch succeeded. | |
astro |
Astro | no | no | Astronomical data for the requested location and date. | |
gis |
GIS | no | yes | Geographic metadata for the resolved location. | |
units |
Units | no | no | Unit labels for the numeric fields in this response. | |
attribution |
Attribution | no | yes | Attribution for the data sources that contributed to this response. | |
narrations |
Narrations | no | no | Rule-based narrative text for current and short-term conditions, generated deterministically from the blended forecast (not an LLM). | |
guidance |
Guidance | no | no | NWS forecaster text products (AFD, HWO, NOW, HLS, MCD, MPD, PMD) for the point: structured fields plus raw bulletin body when available. |
|
discussions |
Discussions | no | no | Deprecated. | |
nowcast |
Nowcast | no | no | Minute-scale MRMS radar precipitation nowcast for the queried location. | |
climate |
ClimateNormals | no | no | Day-of-year climate normals for the location, populated when include=climate is requested. |
|
metar |
Metar | no | no | Compact nearest METAR observation for the queried point. | |
meta |
Meta | no | yes | Per-response metadata: response schema version, generation time, upstream data freshness, and any non-fatal warnings. |
location, climate.location (Location)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
lat |
number | no | yes | ||
lon |
number | no | yes | ||
name |
string | no | yes | Human-readable place name, usually without the state; see region. |
|
region |
string | no | no | State or region abbreviation. | |
country |
string | no | no | ISO 3166-1 alpha-2 country code. | |
timezone |
string | no | yes | IANA timezone identifier. | |
county |
string | no | no | County name (US only). | |
zip |
string | no | no | ZIP code (US only). |
current (Current)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
time |
string (date-time) | no | yes | Observation time (ISO 8601, UTC). | |
temperature |
number | units.temperature |
yes | no | Temperature (unit per units.temperature). |
feels_like |
number | units.temperature |
yes | no | Apparent temperature (wind chill or heat index). |
humidity |
number | percent (0 to 100) | yes | no | Relative humidity, percent. |
dew_point |
number | units.temperature |
yes | no | |
pressure |
number | units.pressure |
yes | no | Atmospheric pressure (unit per units.pressure). |
wind_speed |
number | units.wind_speed |
yes | no | |
wind_gust |
number | units.wind_speed |
yes | no | |
wind_direction |
integer | degrees (0 = N, 90 = E) | yes | no | Wind direction in degrees (0 = N, 90 = E). |
visibility |
number | units.visibility |
yes | no | Visibility distance (null when no station or model value is available). |
cloud_cover |
number | percent (0 to 100) | yes | no | Cloud cover, percent. |
cloud_ceiling |
number | units.altitude |
yes | no | Analysis cloud-base height above ground (RTMA/URMA), in the units.altitude unit. |
cloud_cover_low |
number | percent (0 to 100) | yes | no | HRRR low-cloud-layer cover, percent (CONUS). |
cloud_cover_mid |
number | percent (0 to 100) | yes | no | HRRR mid-cloud-layer cover, percent (CONUS). |
cloud_cover_high |
number | percent (0 to 100) | yes | no | HRRR high-cloud-layer cover, percent (CONUS). |
uv_index |
number | none (UV index) | yes | no | |
aqi |
integer | none (AQI scale) | yes | no | Air-quality index (US EPA scale): usually 0-500, can exceed 500 in extreme events ("Beyond the AQI"). |
aqi_category |
string | no | no | Human-readable AQI category (e.g., "Good", "Moderate", "Unhealthy for Sensitive Groups"). |
|
pm25 |
number | none (AQI scale) | yes | no | AirNow AQI sub-index for PM2.5, on the same scale as aqi: usually 0-500, can exceed 500 in extreme events. |
ozone |
number | none (AQI scale) | yes | no | AirNow AQI sub-index for ozone, on the same scale as aqi: usually 0-500, can exceed 500 in extreme events. |
pm10 |
number | none (AQI scale) | yes | no | AirNow AQI sub-index for PM10, on the same scale as aqi: usually 0-500, can exceed 500 in extreme events. |
precip_rate |
number | units.precip_amount per hour |
yes | no | Current precipitation rate. |
precip_type |
string | no | no | Current precipitation type. | |
precip_24h |
number | mm | yes | no | Observed precipitation accumulated over the trailing 24 hours, from MRMS MultiSensor QPE Pass 2. |
lightning_density |
number | strikes per unit area per time window | yes | no | Lightning strike density (strikes per unit area per time window). |
lightning_prob |
integer | percent (0 to 100) | yes | no | Lightning probability, percent. |
conditions |
string | no | yes | Human-readable weather description. | |
icon |
string | no | yes | Icon slug for UI rendering. | |
severe |
boolean | no | no | True when severe conditions are present or imminent. |
hourly[] (Hourly)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
time |
string (date-time) | no | yes | Forecast valid time (ISO 8601, UTC). | |
temperature |
number | units.temperature |
yes | no | |
feels_like |
number | units.temperature |
yes | no | |
humidity |
number | percent (0 to 100) | yes | no | |
wind_speed |
number | units.wind_speed |
yes | no | |
wind_direction |
integer | degrees (0 = N, 90 = E) | yes | no | |
precip_probability |
number | percent (0 to 100) | yes | no | Probability of measurable precipitation (0–100) for the hour. |
precip_amount |
number | units.precip_amount |
yes | no | Liquid-equivalent precipitation for the hour ending at time. |
conditions |
string | no | no | ||
icon |
string | no | no | ||
severe |
boolean | no | no | ||
wind_gust |
number | units.wind_speed |
yes | no | |
dew_point |
number | units.temperature |
yes | no | |
cloud_cover |
number | percent (0 to 100) | yes | no | |
visibility |
number | units.visibility |
yes | no | |
pressure |
number | units.pressure |
yes | no | |
thunderstorm_probability |
number | percent (0 to 100) | yes | no | |
uv_index |
number | none (UV index) | yes | no | |
aqi |
number | none (AQI scale) | yes | no | |
precip_type |
string | no | no | ||
precip_type_rain_prob |
number | percent (0 to 100) | yes | no | |
precip_type_snow_prob |
number | percent (0 to 100) | yes | no | |
precip_type_freezing_rain_prob |
number | percent (0 to 100) | yes | no | |
precip_type_ice_pellets_prob |
number | percent (0 to 100) | yes | no | |
cape |
number | J/kg | yes | no | Convective available potential energy (J/kg). |
snow_amount |
number | units.precip_amount |
yes | no | Null when no snow is forecast or no source covers the hour. |
cloud_ceiling |
number | units.altitude |
yes | no | NBM forecast cloud-base height above ground, in the units.altitude unit. |
cloud_cover_low |
number | percent (0 to 100) | yes | no | HRRR low-cloud-layer cover, percent (CONUS). |
cloud_cover_mid |
number | percent (0 to 100) | yes | no | HRRR mid-cloud-layer cover, percent (CONUS). |
cloud_cover_high |
number | percent (0 to 100) | yes | no | HRRR high-cloud-layer cover, percent (CONUS). |
snow_level |
number | units.altitude |
yes | no | |
thunder_possible |
boolean | no | no | True when any hourly thunder indicator (thunderstorm_probability or cape) crosses the blender's lightning-risk threshold for this hour. |
|
uncertainty |
HourlyUncertainty | yes | no | Optional probabilistic sub-object sourced from the NBM model. |
daily[] (Daily)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
date |
string (date) | no | yes | Forecast date (ISO 8601, local to the location's timezone). | |
high |
number | units.temperature |
yes | no | Daytime maximum. |
low |
number | units.temperature |
yes | no | Overnight minimum, covering 00:00–05:59 of the following calendar date. |
conditions |
string | no | no | ||
icon |
string | no | no | ||
severe |
boolean | no | no | ||
precip_probability |
number | percent (0 to 100) | yes | no | |
precip_total |
number | units.precip_amount |
yes | no | Null when no forecast source covers any hour of the day. |
snow_total |
number | units.precip_amount |
yes | no | Null when no forecast source covers any hour of the day. |
ice_total |
number | units.precip_amount |
yes | no | Null when no forecast source covers any hour of the day. |
wind_speed_max |
number | units.wind_speed |
yes | no | |
wind_gust_max |
number | units.wind_speed |
yes | no | Maximum sustained-gust value across the calendar day. |
wind_direction |
integer | degrees (0 = N, 90 = E) | yes | no | |
humidity_avg |
number | percent (0 to 100) | yes | no | |
cloud_cover_avg |
number | percent (0 to 100) | yes | no | |
uv_index_max |
number | none (UV index) | yes | no | The day's maximum UV index. |
cape_max |
number | J/kg | yes | no | |
dew_point_max |
number | units.temperature |
yes | no | |
dew_point_min |
number | units.temperature |
yes | no | |
pressure_avg |
number | units.pressure |
yes | no | |
visibility_min |
number | units.visibility |
yes | no | |
cloud_ceiling_min |
number | units.altitude |
yes | no | |
thunderstorm_probability_max |
number | percent (0 to 100) | yes | no | |
aqi_max |
number | none (AQI scale) | yes | no | |
feels_like_high |
number | units.temperature |
yes | no | |
feels_like_low |
number | units.temperature |
yes | no | |
solar_radiation_max |
number | units.solar_radiation |
yes | no | Peak downward shortwave radiation across the day (W/m^2). |
snow_level |
number | units.altitude |
yes | no | |
thunder_possible |
boolean | no | no | Distilled flag; true if any hour in the day crosses the blender's lightning-risk threshold. | |
astro |
Astro | no | no | Astronomical data for the requested location and date. | |
day |
DailyPeriod | no | no | Forecast data for a single period (day or night) within a Daily entry. |
|
night |
DailyPeriod | no | no | Forecast data for a single period (day or night) within a Daily entry. |
|
threats |
DailyThreat[] | no | no | Per-day hazard callouts (winter, severe, heat, etc.). | |
relative_day |
integer | whole days from today, local | no | no | Signed calendar offset from the local current date at request time (-1 = yesterday, 0 = today, 1 = tomorrow). |
active_period |
string | yes | no | Non-null on exactly the row containing the current local time (day during 06:00–17:59, night during 18:00–05:59). |
|
uncertainty |
DailyUncertainty | yes | no | Optional NBM probabilistic sub-object for this day. | |
climate |
DailyClimate | yes | no | Optional climate sub-object for this day. |
alerts[] (Alert)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
id |
string | no | yes | Unique alert identifier (the NWS message id). | |
event |
string | no | yes | ||
severity |
string | no | yes | ||
urgency |
string | no | yes | ||
certainty |
string | no | yes | ||
headline |
string | no | yes | ||
sender |
string | no | yes | Issuing NWS office identifier. | |
status |
string | no | yes | Alert lifecycle status (e.g., Actual, Exercise, Test). |
|
onset |
string (date-time) | no | yes | ||
ends |
string (date-time) | no | yes | ||
effective |
string (date-time) | no | yes | ||
expires |
string (date-time) | no | yes | ||
expiration |
string (date-time) | no | yes | Final expiration time used for cache invalidation. | |
area_desc |
string | no | no | Human-readable affected area. | |
highlights |
map of string | no | no | Map of salient callouts extracted from the alert text (e.g., {"hazard": "Strong winds", "impact": "Loose debris possible"}). |
outlooks[] (Outlook)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
product_id |
string | no | yes | Upstream product identifier (e.g., spc_otlk_day1). |
|
type |
string | no | yes | Hazard category (severe, tornado, hail, wind, etc.). |
|
day |
integer | no | yes | Day number relative to issuance (1 = today). | |
risk |
string | no | no | Raw risk identifier from the upstream source. | |
probability |
number | no | no | Fractional probability (0-1) when the source provides one. | |
risk_label |
string | no | yes | Human-readable risk label. | |
valid |
string (date-time) | no | yes | ||
expires |
string (date-time) | no | yes | ||
significant |
boolean | no | no | The location falls inside SPC's hatched significant-severe area for this hazard — tornadoes of EF2+, hail 2 in or larger, or wind gusts of 65 kt or more. |
astro, daily[].astro (Astro)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
civil_dawn |
string (date-time) | yes | no | Null when civil twilight does not begin or end on this date. | |
sunrise |
string (date-time) | yes | no | Null on polar day and night (see polar). |
|
solar_noon |
string (date-time) | yes | no | ||
sunset |
string (date-time) | yes | no | Null on polar day and night (see polar). |
|
civil_dusk |
string (date-time) | yes | no | Null when civil twilight does not begin or end on this date. | |
day_length |
integer | no | yes | Seconds from sunrise to sunset; 86400 under midnight sun, 0 in polar night. | |
daylight_length |
integer | no | yes | Seconds from civil dawn to civil dusk; 86400 when civil twilight never ends (white nights, midnight sun). | |
moonrise |
string (date-time) | yes | no | Null when no moonrise occurs on this date. | |
moonset |
string (date-time) | yes | no | Null when no moonset occurs on this date. | |
moon_phase |
number | no | yes | Moon phase (0 = new, 0.5 = full, 1 = new). | |
moon_phase_name |
string | no | yes | ||
moon_illumination |
number | no | yes | ||
polar |
string | yes | no | Set when the sun neither rises nor sets on this date: midnight_sun (above the horizon all day; day_length is 86400) or polar_night (below it all day; day_length is 0). |
gis (GIS)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
lat |
number | no | yes | ||
lon |
number | no | yes | ||
city |
string | no | no | City name covering the point (if the point lies inside city limits). | |
nearest_city |
string | no | no | Nearest named city (useful when city is not populated). |
|
state |
string | no | no | State or equivalent subdivision (abbreviation). | |
county |
string | no | no | ||
country |
string | no | no | ISO 3166-1 alpha-2 country code. | |
population |
integer | yes | no | Approximate population of city or nearest_city. |
|
timezone |
string | no | yes | IANA timezone identifier. | |
nearest_radar |
NearestRadar | no | no | Metadata for the nearest NEXRAD radar site. | |
nearest_station |
NearestStation | no | no | Metadata for the nearest METAR observation station. |
units (Units)
| Dimension | Labels | Description |
|---|---|---|
temperature |
F, C |
Temperature unit. |
wind_speed |
mph, kph |
|
pressure |
inHg, hPa |
|
visibility |
mi, km |
|
precip_amount |
in, mm |
|
altitude |
m, ft |
Unit for altitude-dimensioned fields (snow_level, cloud_ceiling). |
solar_radiation |
W/m^2 |
Unit for irradiance fields (currently always W/m^2). |
attribution (Attribution)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
sources |
string[] | no | yes | Canonical source identifiers that contributed data. | |
notice |
string | no | yes | Human-readable attribution notice. |
meta (Meta)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
schema_version |
string | no | yes | Response schema version, a semver string (currently 1.0.0). |
|
generated_at |
string (date-time) | no | yes | Server time at which this response body was generated. | |
freshness |
SourceFreshness[] | no | no | Freshness metadata for each upstream data source used. | |
warnings |
MetaWarning[] | no | no | Non-fatal warnings generated during request fulfillment. | |
hourly_sampling |
object | no | no | Present only when hourly_step dropped at least one hourly row. |
daily[].day, daily[].night (DailyPeriod)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
temperature |
number | units.temperature |
yes | no | High for day, low for night; null when the half has no temperature. |
wind_speed |
number | units.wind_speed |
yes | no | |
wind_direction |
integer | degrees (0 = N, 90 = E) | yes | no | |
precip_probability |
number | percent (0 to 100) | yes | no | Probability of measurable precipitation (0–100) for the day or night period. |
precip_total |
number | units.precip_amount |
yes | no | Null when no forecast source covers any hour of the day. |
snow_total |
number | units.precip_amount |
yes | no | Null when no forecast source covers any hour of the day. |
ice_total |
number | units.precip_amount |
yes | no | Null when no forecast source covers any hour of the day. |
humidity |
number | percent (0 to 100) | yes | no | |
cloud_cover |
number | percent (0 to 100) | yes | no | |
wind_gust |
number | units.wind_speed |
yes | no | |
dew_point |
number | units.temperature |
yes | no | |
feels_like |
number | units.temperature |
yes | no | High-for-day / low-for-night of hourly apparent temperature. |
visibility |
number | units.visibility |
yes | no | |
pressure |
number | units.pressure |
yes | no | |
cloud_ceiling |
number | units.altitude |
yes | no | |
uv_index |
number | none (UV index) | yes | no | Max UV index within the period (day only; ~0 overnight). |
thunderstorm_probability |
number | percent (0 to 100) | yes | no | |
cape |
number | J/kg | yes | no | |
aqi |
number | none (AQI scale) | yes | no | |
precip_type |
string | no | no | ||
thunder_possible |
boolean | no | no | ||
conditions |
string | no | no | ||
icon |
string | no | no | ||
severe |
boolean | no | no | ||
narrative |
string | no | no | Human-readable narrative for this period. |
daily[].threats[] (DailyThreat)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
type |
string | no | yes | Hazard category (e.g., severe, winter, heat, fire). |
|
risk |
string | no | no | Raw risk identifier from the upstream source. | |
probability |
number | no | no | Fractional probability (0-1) when known. | |
risk_label |
string | no | yes | Human-readable risk label (e.g., "Slight", "Enhanced"). |
gis.nearest_radar (NearestRadar)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
id |
string | no | yes | Radar site code without ICAO prefix (e.g., LOT, OKX, AKC). |
|
name |
string | no | yes | ||
lat |
number | no | yes | ||
lon |
number | no | yes | ||
distance_km |
number | km | no | yes | Great-circle distance from the requested point to the radar site, rounded to 0.1 km. |
gis.nearest_station (NearestStation)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
id |
string | no | yes | ICAO station identifier (e.g., KJFK). |
|
name |
string | no | no | ||
state |
string | no | no | ||
lat |
number | no | yes | ||
lon |
number | no | yes | ||
distance_km |
number | km | no | yes | Great-circle distance from the requested point to the station, rounded to 0.1 km. |
elevation_m |
number | m | yes | no |
meta.freshness[] (SourceFreshness)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
source |
string | no | yes | Source family (e.g., rtma, hrrr, nbm, mrms). |
|
product_id |
string | no | yes | Specific product identifier. | |
reference_time |
string (date-time) | yes | no | Model run / observation reference time. | |
latest_valid |
string (date-time) | yes | no | Latest valid time represented in the source. | |
age_seconds |
integer | no | yes | Seconds elapsed since reference_time. |
meta.warnings[] (MetaWarning)
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
code |
string | no | yes | Stable machine-parseable warning code. | |
section |
string | no | yes | Response section the warning applies to (e.g., "alerts", "hourly", "outlooks"). |
|
detail |
string | no | no | Human-readable explanation. | |
product_id |
string | no | no | Catalog product id (GET /v1/catalog/{product_id}) of the upstream product the warning is about; the same id as the product's meta.freshness entry. |
|
fields |
string[] | no | no | Response fields in section that this warning leaves null or omits. |
meta.hourly_sampling
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
step_hours |
integer | no | yes | Effective hourly_step. |
|
from_hour |
integer | no | yes | Effective hourly_step_from. |
narrations (Narrations), with include=narrations
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
current |
string | no | no | Time-of-day-aware summary of current conditions. | |
short_term |
string | no | no | Narrative covering the next few hours. |
guidance (Guidance), with include=guidance
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
afd |
AFDGuidance | no | no | Area Forecast Discussion: structured fields plus optional raw bulletin body. |
|
hwo |
HWOGuidance | no | no | Hazardous Weather Outlook: structured fields plus optional raw bulletin body. |
|
now |
NOWGuidance | no | no | WFO short-term NOW product (NWS NOW, not radar nowcast): pipeline LLM summary plus optional raw bulletin body. |
|
hls |
HLSGuidance | no | no | WFO Hurricane Local Statement for the office covering the point: pipeline LLM summary plus optional raw bulletin body. |
|
active_mcds |
integer | no | yes | Count of non-expired SPC Mesoscale Discussions whose polygons overlap the location (matches mcds length when present). |
|
mcds |
MCDGuidance[] | no | no | SPC Mesoscale Discussions overlapping the location (structured fields plus raw body). |
|
active_mpds |
integer | no | no | Count of non-expired WPC Mesoscale Precipitation Discussions whose polygons overlap the location (matches mpds length when present). |
|
mpds |
MPDGuidance[] | no | no | WPC Mesoscale Precipitation Discussions overlapping the location. | |
pmds |
PMDGuidance[] | no | no | Latest national Prognostic Discussion per enabled desk: WPC (PMDSPD, PMDEPD, PMDHI, PMDCA) and CPC (PMDMRD 6-10/8-14 day, PMDTHR week-2 hazards, PMDENS ENSO). |
|
swo_day1_risk |
string | no | no | SPC Day-1 convective outlook categorical risk label (when active). |
discussions (Discussions), with include=discussions
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
mesoscale |
MCDGuidance[] | no | yes | Active SPC Mesoscale Discussions whose polygons overlap the location. | |
area_forecast |
AFDGuidance | no | no | Latest Area Forecast Discussion for the WFO covering the point. |
nowcast (Nowcast), with include=nowcast
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
interval_seconds |
integer | no | no | Length of each step, in seconds: the most common spacing between the frame's lead times (300 today). | |
generated_at |
string (date-time) | no | no | Nowcast reference (anchor) time from the latest radar frame. | |
summary |
NowcastSummary | no | no | Server-computed headline for the first precipitation episode in the nowcast window, derived from the frame's steps. | |
points |
NowcastPoint[] | no | no |
climate (ClimateNormals), with include=climate
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
station |
ClimateNormalsStation | no | yes | The station a normals series was drawn from. | |
meta |
ClimateNormalsMeta | no | yes | Provenance for a normals response. | |
days |
ClimateNormalsDay[] | no | yes | Per-day normals series, in requested date order. | |
location |
Location | no | no | Resolved location, as /v1/weather returns it. |
metar (Metar), with include=metar
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
station_id |
string | no | yes | ||
distance_km |
number | km | no | yes | Great-circle distance from the requested point to the station, rounded to 0.1 km. |
observed_at |
string (date-time) | no | yes | ||
age_min |
number | no | yes | Minutes between observed_at and response meta.generated_at. |
|
ceiling_ft |
integer | ft | no | no | Lowest BKN/OVC/VV base, feet AGL. |
layers |
MetarSkyLayer[] | no | no | Parsed sky groups. | |
weather |
string[] | no | no | Raw present-weather tokens (-RA, VCTS, TSRA). |
hourly[].uncertainty (HourlyUncertainty), with include=uncertainty
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
temperature_stddev |
number | yes | no | Ensemble standard deviation of temperature. | |
dew_point_stddev |
number | yes | no | ||
wind_speed_stddev |
number | yes | no | ||
wind_gust_stddev |
number | yes | no | ||
visibility_prob_below_1mi |
number | yes | no | ||
visibility_prob_below_2mi |
number | yes | no | ||
visibility_prob_below_3mi |
number | yes | no | ||
visibility_prob_below_5mi |
number | yes | no | ||
ceiling_prob_below_500ft |
number | yes | no | ||
ceiling_prob_below_1000ft |
number | yes | no | ||
ceiling_prob_below_2000ft |
number | yes | no | ||
ceiling_prob_below_3000ft |
number | yes | no | ||
ceiling_prob_below_6600ft |
number | yes | no | ||
wind_speed_p50 |
number | yes | no | NBM median 10m wind speed. | |
wind_speed_prob_gt_75mph |
number | yes | no |
daily[].uncertainty (DailyUncertainty), with include=uncertainty
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
temperature_stddev_avg |
number | yes | no | ||
dew_point_stddev_avg |
number | yes | no | ||
wind_speed_stddev_avg |
number | yes | no | ||
wind_gust_stddev_avg |
number | yes | no | ||
visibility_prob_below_1mi_max |
number | yes | no | ||
ceiling_prob_below_1000ft_max |
number | yes | no | ||
snow_total_p10 |
number | yes | no | ||
snow_total_p50 |
number | yes | no | ||
snow_total_p90 |
number | yes | no | ||
snow_prob_gt_1in |
number | yes | no | ||
snow_prob_gt_2in |
number | yes | no | ||
snow_prob_gt_4in |
number | yes | no |
daily[].climate (DailyClimate), with include=climate
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
normal_high |
number | yes | no | Normal daily maximum temperature for this day of year. | |
normal_low |
number | yes | no | Normal daily minimum temperature for this day of year. | |
high_departure |
number | yes | no | Forecast high minus normal high; positive is warmer than normal. | |
low_departure |
number | yes | no | Forecast low minus normal low; positive is warmer than normal. |
guidance.afd, discussions.area_forecast (AFDGuidance), with include=guidance or include=discussions
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
synopsis |
string | no | no | ||
key_takeaway |
string | no | no | ||
confidence |
string | no | no | Forecaster-stated confidence level (free text). | |
issued_at |
string (date-time) | yes | no | ||
wfo |
string | no | no | Issuing Weather Forecast Office identifier. | |
body |
string | no | no | Full AFD bulletin text when available. |
guidance.hwo (HWOGuidance), with include=guidance
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
day_one |
string | no | no | Day-one hazard summary text. | |
summary |
string | no | no | Pipeline LLM overall hazard summary when enrichment has run. | |
has_active_hazards |
boolean | no | yes | ||
issued_at |
string (date-time) | yes | no | ||
body |
string | no | no | Full HWO bulletin text when available. |
guidance.now (NOWGuidance), with include=guidance
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
summary |
string | no | no | Pipeline LLM summary when enrichment has run. | |
issued_at |
string (date-time) | yes | no | ||
wfo |
string | no | no | Issuing Weather Forecast Office identifier. | |
body |
string | no | no | Full NOW bulletin text when available. |
guidance.hls (HLSGuidance), with include=guidance
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
summary |
string | no | no | Pipeline LLM summary when enrichment has run. | |
issued_at |
string (date-time) | yes | no | ||
wfo |
string | no | no | Issuing Weather Forecast Office identifier. | |
body |
string | no | no | Full HLS bulletin text when available. |
guidance.mcds[], discussions.mesoscale[] (MCDGuidance), with include=guidance or include=discussions
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
mcd_number |
integer | no | no | SPC mesoscale discussion number. | |
issued_at |
string (date-time) | yes | no | ||
expires_at |
string (date-time) | yes | no | End of the product valid window when parsed. | |
concerning |
string | no | no | SPC "Concerning..." line (e.g. severe potential / watch possible). | |
areas_affected |
string | no | no | ||
watch_probability |
integer | no | no | Stated probability of watch issuance, percent. | |
summary |
string | no | no | Pipeline LLM summary when enrichment has run. | |
body |
string | no | no | Full MCD bulletin text when available. |
guidance.mpds[] (MPDGuidance), with include=guidance
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
mpd_number |
integer | no | no | WPC mesoscale precipitation discussion number. | |
issued_at |
string (date-time) | yes | no | ||
expires_at |
string (date-time) | yes | no | ||
concerning |
string | no | no | ||
areas_affected |
string | no | no | ||
flash_flood_tier |
string | no | no | Headline tier (possible / likely / occurring). | |
summary |
string | no | no | ||
body |
string | no | no |
guidance.pmds[] (PMDGuidance), with include=guidance
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
awips_id |
string | no | no | Full AWIPS identifier (e.g. PMDSPD). | |
pmd_desk |
string | no | no | Desk suffix after the PMD prefix (SPD, EPD, HI, CA, MRD, THR, ENS). | |
issued_at |
string (date-time) | yes | no | ||
summary |
string | no | no | Pipeline LLM summary when enrichment has run. | |
body |
string | no | no | Full PMD bulletin text when available. |
nowcast.summary (NowcastSummary), with include=nowcast
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
starts_at |
string (date-time) | no | no | time of the onset step: the first of two consecutive wet steps. |
|
ends_at |
string (date-time) | yes | no | End of the last wet step of the episode: that step's time plus interval_seconds. |
|
duration_minutes |
integer | yes | no | Minutes from starts_at to ends_at, so a multiple of the step length (at least 10 for a two-step episode). |
|
type |
string | no | no | Precipitation type of the onset step, as on points[].type (rain, snow, freezing_rain, wintry_mix, or unknown for an unrecognized radar type code), or none when there is no onset. |
|
peak_intensity |
string | no | no | Highest intensity across the episode, from the onset to its end (or to the end of the window while it continues). |
|
already_occurring |
boolean | no | no | True when starts_at is at or before the request time: the onset step has begun. |
nowcast.points[] (NowcastPoint), with include=nowcast
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
time |
string (date-time) | no | yes | ||
type |
string | no | yes | Radar precipitation type at this step: none, rain, snow, freezing_rain, wintry_mix, no_coverage (outside radar coverage), or unknown for an unrecognized radar type code. |
|
intensity_dbz |
number | no | yes | ||
intensity |
string | no | yes |
climate.station (ClimateNormalsStation), with include=climate
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
station_id |
string | no | yes | GHCN station identifier. | |
name |
string | no | no | Station name as published by NCEI. | |
state |
string | no | no | Two-letter state or territory code. | |
latitude |
number | no | yes | Station latitude. | |
longitude |
number | no | yes | Station longitude. | |
elevation_m |
number | m | no | no | Station elevation in metres. |
distance_km |
number | km | no | yes | Great-circle distance from the requested point to the station, rounded to 0.1 km. |
climate.meta (ClimateNormalsMeta), with include=climate
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
normals_period |
string | no | no | Base period the normals were computed over. | |
source |
string | no | yes | Where the values came from: station for the NCEI station product, grid for gridded normals, mixed when a series draws on both. |
|
normals_method |
string | no | no | How the normals were produced, which source does not answer. |
|
unit |
string | no | yes | Unit system applied to the values: °F and inches for imperial, °C and millimetres for metric. |
|
approximate |
boolean | no | no | True when a station more than 50 km away supplied values that are actually being served — station.distance_km says how far. |
|
missing_days |
integer | no | no | How many days in the requested range no normal is available for, from either the grid or the station. |
climate.days[] (ClimateNormalsDay), with include=climate
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
date |
string (date) | no | yes | Calendar date this slice was requested for. | |
month |
integer | no | yes | Month of the day-of-year slice (1-12). | |
day |
integer | no | yes | Day of month (1-31). | |
normal_high |
number | no | no | Average daily maximum temperature. | |
normal_low |
number | no | no | Average daily minimum temperature. | |
normal_mean |
number | no | no | Average daily mean temperature. | |
normal_high_stddev |
number | no | no | Standard deviation of the daily maximum. | |
normal_low_stddev |
number | no | no | Standard deviation of the daily minimum. | |
normal_mean_stddev |
number | no | no | Standard deviation of the daily mean. | |
normal_precip |
number | no | no | Average daily precipitation amount — inches when unit=imperial, millimetres when unit=metric. |
|
years |
integer | no | no | Years of the base period contributing to this day, at the station. | |
completeness_flag |
string | no | no | NCEI per-value completeness code, for the station. |
metar.layers[] (MetarSkyLayer), with include=metar
| Field | Type | Unit | Nullable | Always present | Description |
|---|---|---|---|---|---|
cover |
string | no | yes | ||
base_ft |
integer | ft | no | no | Layer base in feet AGL. |
convective |
string | no | no |