Snow depth is one of the more deceptive fields in any weather API. The number looks simple — a depth in centimetres — but what it actually represents, how quickly it goes stale, and where it silently breaks down are all things that’ll bite you if you don’t think through them before building.
Here’s how we think about snow accumulation tracking at WeatherAPI.com, what the fields in our response actually mean, and a working pattern for building something reasonably reliable on top of them.
What the Fields Are Actually Giving You
The WeatherAPI forecast response includes totalsnow_cm at the day level and snow_cm at the hourly level. These are forecasted snowfall amounts — the expected new accumulation during that period — not a current snow depth on the ground.
That distinction matters more than it sounds. If you sum snow_cm across 24 hours and treat the result as current snow depth, you’re ignoring compaction, melting, and any snow already on the ground before your forecast window started. Fresh snow compacts by roughly 30–50% over the first 24 hours depending on temperature and moisture content — so a 10cm forecast accumulation doesn’t leave 10cm of depth. How much less depends on conditions you don’t have in the API response.
Current conditions responses include a snow_cm field as well. This comes from METAR observations where available, and METAR snow depth reporting is inconsistent — many stations don’t report it at all, report it intermittently, or only report it when it exceeds a threshold (typically 2 inches / 5cm under ICAO guidance). Below that threshold, or at locations without a nearby reporting station, you may be getting a null or zero that doesn’t reflect actual ground conditions.
A Practical Pattern: Forward-Accumulating from Forecast
The most useful thing you can build, given what’s actually available, is a forward accumulation estimate — starting from a known or estimated baseline and projecting forward using the hourly forecast.
Here’s a minimal Python example:
import requests
from datetime import datetime, timedelta
API_KEY = "your_key"
LOCATION = "Denver,CO"
def fetch_forecast(days=3):
url = "https://api.weatherapi.com/v1/forecast.json"
params = {
"key": API_KEY,
"q": LOCATION,
"days": days,
"aqi": "no",
"alerts": "no"
}
r = requests.get(url, params=params)
r.raise_for_status()
return r.json()
def build_accumulation_curve(forecast_json, baseline_cm=0.0, compaction_factor=0.7):
"""
compaction_factor: what fraction of new snow stays as depth
0.7 is a rough midpoint for dry/cold conditions
"""
hours = []
running_depth = baseline_cm
for day in forecast_json["forecast"]["forecastday"]:
for hour in day["hour"]:
new_snow = hour.get("snow_cm", 0.0) or 0.0
temp_c = hour.get("temp_c", 0.0)
# crude melt: above 2°C, assume some surface melt
melt = 0.0
if temp_c > 2.0:
melt = min(running_depth, 0.3 * (temp_c - 2.0))
running_depth = max(0.0, running_depth + (new_snow * compaction_factor) - melt)
hours.append({
"time": hour["time"],
"new_snow_cm": new_snow,
"estimated_depth_cm": round(running_depth, 2),
"temp_c": temp_c
})
return hours
forecast = fetch_forecast(days=3)
curve = build_accumulation_curve(forecast, baseline_cm=5.0)
for h in curve[:12]: # first 12 hours
print(f"{h['time']}: depth ~{h['estimated_depth_cm']}cm (new: {h['new_snow_cm']}cm, {h['temp_c']}°C)")
A few things worth flagging about this code:
- The
baseline_cmparameter is the hardest part. Unless you have a ground truth measurement — a nearby SNOTEL station, a user-reported reading, a physical sensor — you’re estimating it. For most use cases, seeding it from whatever the current conditions endpoint reports, even knowing it’s imprecise, beats assuming zero. - The melt model above is deliberately crude. A proper degree-day melt model, the kind NOAA uses in Snow Water Equivalent calculations, requires energy balance inputs the API alone doesn’t provide. The 0.3 multiplier is a rough heuristic, not a calibrated figure.
- Compaction varies a lot by snow type. Wet snow near 0°C packs down faster than dry powder at -15°C. If you’re building for a ski area where snow type drives real decisions, adjust the compaction factor based on temperature at the time of snowfall rather than using a fixed midpoint.
Which Model Is Behind the Forecast?
For US locations, our GRIB2 ingestion pipeline pulls from HRRR at 3km resolution, NAM, and GFS depending on forecast horizon. HRRR is what matters most for snow accumulation in the 0–18 hour window — its 3km grid actually resolves terrain well enough to capture orographic lift effects that drive snowfall to vary significantly over short distances in mountain terrain. GFS at roughly 13km resolution smooths over those gradients and can meaningfully underestimate totals on the windward side of a ridge.
For non-US locations, ECMWF is in the mix. ECMWF’s global ensemble is the stronger performer for precipitation type and accumulation at medium range (days 3–7), which is why it’s worth treating short-range and medium-range snow forecasts differently in terms of how much confidence you assign them.
None of this is exposed directly in the API response — you can’t ask “which model is this from?” — but knowing it helps you calibrate how much to trust the numbers at different horizons.
Elevation Is the Hidden Variable
The same issue we’ve run into with station selection applies here: snow accumulation at a coordinate sitting at 1,200m is not well-represented by the nearest low-elevation METAR. The rain/snow line can shift by hundreds of metres depending on the atmospheric moisture profile, meaning a location our closest station reports as rain may actually be receiving snow — and vice versa.
For mountain or ski-area use cases, cross-check the hourly temp_c and dewpoint_c against the forecast snow_cm value to sanity-check whether snowfall is even physically plausible at the elevation you’re requesting. If the dew point at that hour is above 2°C, treat any snowfall forecast with scepticism regardless of what the model says.
What to Do About Missing or Zero Values
snow_cm returns null or 0 for hours where no snowfall is expected. In JSON parsing this creates ambiguity: does 0 mean “no snow forecast” or “data unavailable”? In practice you can treat both the same — default to 0 — but explicitly coalesce nulls rather than letting your accumulation loop break on one. The or 0.0 in the code above handles this.
Also: totalsnow_cm at the day level is not always the sum of the 24 hourly snow_cm values. Daily rollup fields go through a separate aggregation path, and floating-point accumulation across 24 buckets doesn’t always match the daily figure. If precision matters, build your accumulation from the hourly fields rather than trusting the daily total — the same logic that applies to hourly_precip_mm vs. daily_precip_mm.
What to Actually Tell Your Users
If you’re surfacing these numbers in a UI, frame them as “estimated accumulation” rather than “snow depth”. The distinction is real, and users in snow-heavy regions — skiers, road maintenance crews, agricultural users monitoring snowmelt — notice quickly when your number diverges from what they can see out the window.
A confidence indicator tied to forecast horizon is worth building in your application layer: higher confidence within 6 hours (HRRR territory for US locations), moderate within 24 hours, lower beyond that. It’s a judgement call, not a value the API exposes — but it’s a more honest representation of what the underlying data can actually support.
If your users are in terrain where snow depth genuinely matters — ski patrol, avalanche risk assessment, winter road ops — supplement the API forecast with SNOTEL or SNODAS data where it’s available. We’re useful for general-purpose snow tracking, but we’re not a replacement for a calibrated snowpack model. Better to establish that boundary now than have someone learn it from an incident in the field.
