Why Cloud Cover Percentages Are Lying to You (And What to Use Instead)

If you’re building anything that depends on sky conditions — solar yield estimates, photography lighting windows, outdoor activity scoring, drone flight eligibility — you’ve almost certainly reached for the cloud field in a weather API response. It comes back as a clean integer: 0, 25, 75, 100. It looks authoritative. It usually isn’t.

Where the number actually comes from

Cloud cover percentage in a forecast API response is not a measurement. It’s a model output — specifically, a grid-cell average from whichever NWP model sits behind the API (GFS, NAM, ECMWF, HRRR, or a blend). The model computes fractional cloud cover across a grid cell that might be 3km × 3km in HRRR’s case, or 13km × 13km for GFS. What you get back is the average cloudiness across that whole cell, expressed as a percentage.

That matters for a few reasons. A cell with a dense bank of cloud covering half of it and a perfectly clear other half returns 50% — which tells you almost nothing useful about whether the specific rooftop or field you care about will see direct sun. The spatial averaging is lossy by design. Models need to parameterize sub-grid-scale cloud processes they can’t explicitly resolve, and that parameterization introduces its own uncertainty on top of the averaging.

For current conditions specifically, the number gets weirder. Most APIs, ours included, blend model output with recent observations in the current response. But cloud observations from surface stations are reported in oktas — eighths of the sky — by a human observer or an automated ceilometer looking straight up, which is a far more local measurement than a grid cell. Reconciling a point observation in oktas with a model cell average is genuinely awkward, and different APIs handle it differently. Some serve the raw model value. Some do a weighted nudge toward the nearest METAR observation. The field name is identical either way.

The okta problem hiding in METAR data

When a cloud cover value is derived from a METAR observation, it’s translated from oktas into a percentage (1 okta = 12.5%). That translation is lossy at the edges. “Few” clouds in aviation terminology means 1–2 oktas, which maps to roughly 12–25%. “Broken” means 5–7 oktas, or 62–87%. The midpoint of each bucket is what gets reported, not a precise reading. A broken layer at 5 oktas and one at 7 oktas both read as “broken”, but for solar applications that spread is meaningful.

We built our own METAR condition mapping rather than relying on third-party libraries for this, partly because the options we evaluated handled edge cases inconsistently — partial obscuration, sky-obscured vertical visibility codes, and layers above 12,000ft that matter for astronomy but not aviation. The raw METAR string carries more information than any single percentage field can represent.

What to use instead (or alongside)

A few things that are more reliable for specific use cases:

The condition code and description

WeatherAPI’s condition.code field maps to a specific sky state that incorporates cloud cover, precipitation type, and visibility together. For a lot of applications — “is it overcast right now?” or “will it be sunny this afternoon?” — this is more actionable than a raw percentage. It’s a coarser signal, but it’s been explicitly mapped to a meaningful state rather than passed through from a model grid cell.

UV index as a cross-check

For solar-related applications, uv in the hourly forecast is often more useful than cloud cover because it’s computed from a radiative transfer model that accounts for cloud optical depth, not just fractional cover. A thin cirrus layer at 50% cover attenuates UV and solar irradiance much less than 50% cover of low stratus does. The UV index collapses that distinction into one number that’s actually closer to what you want if you’re estimating available solar energy.

Visibility for fog and low-cloud detection

If you’re trying to detect fog, low ceilings, or overnight radiation fog, vis_km and cloud together are far more useful than either alone. Cloud cover at 100% with visibility at 10km is overcast. Cloud cover at 100% with visibility at 0.2km is fog. The forecast gets fog wrong more often than it gets cloud cover wrong, but combining the fields at least lets you flag ambiguous cases rather than silently serving bad data to users.

Hourly vs. daily granularity

The daily daily_chance_of_rain and daily_will_it_rain fields are probabilistic aggregates the model has been tuned to produce reasonably. The daily cloud cover average is less calibrated — it’s just a mean of the hourly model values. If you’re showing a daily summary, consider whether the condition icon plus precipitation probability tells the story better than a cloud percentage that’s already been averaged twice.

Where the percentage is still useful

Trend direction. If hourly cloud cover goes 10, 20, 40, 75, 90 across an afternoon, that deteriorating signal is reliable even if each individual value is approximate. Rate-of-change is more robust than point estimates. Build your logic around transitions — “cloud cover increasing past 60% in the next three hours” — rather than threshold crossings on a single value.

Relative comparisons across nearby locations also work reasonably well. If you’re comparing two potential event venues 50 miles apart, a model that gives one 20% and the other 80% is probably directionally right even if neither number is precise in absolute terms.

The honest limit

No API, including ours, can give you sub-kilometer cloud cover with high confidence from NWP model output alone. HRRR’s 3km grid is the best operational model resolution available for the continental US, and even that is too coarse to resolve individual cumulus streets or the cloud shadow patterns that matter for utility-scale solar. Satellite-derived products from GOES-16/17 in North America and MSG in Europe are more spatially precise for current conditions, but they’re observational snapshots with no forecast horizon. The gap between “what’s happening now at this exact point” and “what will happen here in six hours” is real, and it’s not closing anytime soon.

If your application is sensitive enough that a 20-percentage-point cloud error materially changes your output — energy yield calculations being the clearest example — treat the API cloud value as one input into your own ensemble logic, not a ground truth to display directly.

A calibration exercise worth doing before you build production logic on this field: pull cloud values for a handful of historical timestamps from our API and cross-reference against the GOES satellite imagery archive for the same time and location. Agreement tends to be better than you’d expect for large-scale patterns, and worse than you’d expect for anything at the scale of a single building or field. Do that check first. It usually changes what you build.

Scroll to Top