Most weather API documentation stops at “here are the fields” and leaves you to figure out what to actually do with them. This post is the missing step: how to go from WeatherAPI’s hourly forecast response to a usable power output estimate for solar PV and wind turbines, with honest notes on where the numbers hold up and where they don’t.
What WeatherAPI Actually Gives You
The hourly forecast endpoint returns several fields relevant to generation estimation. The useful ones for energy applications are:
- uv — UV index (dimensionless, 0–11+)
- cloud — cloud cover percentage (0–100)
- vis_km — visibility in kilometres (proxy for aerosol/haze attenuation)
- wind_kph — sustained wind speed at surface level
- wind_degree — wind direction in degrees
- gust_kph — peak gust speed
- temp_c — air temperature (affects PV panel efficiency)
- humidity — relative humidity percentage
What you don’t get is irradiance in W/m² directly. Global Horizontal Irradiance (GHI) is what solar engineers actually want, and WeatherAPI doesn’t expose it as a labelled field. You can derive a reasonable proxy from the UV index and cloud cover together, which is what the solar section below does.
Solar PV Output: Deriving GHI from UV and Cloud Cover
UV index is proportional to the erythemally-weighted component of solar irradiance — it’s not GHI, but the two track together during daylight hours closely enough for an estimation layer. A rough empirical relationship used in atmospheric science is that 1 UV index unit corresponds to approximately 25 W/m² of erythemally effective radiation at the surface. GHI runs significantly higher: under clear-sky conditions at solar noon in mid-latitudes, expect 800–1000 W/m² GHI with a UV index of around 6–8.
A practical conversion that holds reasonably well for estimation purposes:
// Approximate clear-sky GHI from UV index
double clearSkyGHI = uvIndex * 130.0; // W/m², rough empirical scalar
// Apply cloud attenuation
// Cloud cover reduces GHI non-linearly; a common simple model:
double cloudFraction = cloudCoverPercent / 100.0;
double cloudTransmissivity = 1.0 - (0.75 * Math.Pow(cloudFraction, 3.4));
double estimatedGHI = clearSkyGHI * cloudTransmissivity;
The 0.75 coefficient and 3.4 exponent come from the Kasten-Czeplak empirical cloud transmissivity model. It’s not as precise as a full radiative transfer model, but it’s orders of magnitude better than ignoring cloud cover or treating it as a linear multiplier — the non-linearity matters because thin cloud and overcast behave very differently, and a linear model gets the middle of that range badly wrong.
Once you have an estimated GHI, converting to DC power output for a PV array is straightforward:
// Panel capacity in kWp, performance ratio typically 0.75–0.85 for real systems
double panelCapacityKWp = 10.0;
double performanceRatio = 0.80;
double peakSunHoursEquivalent = estimatedGHI / 1000.0; // fraction of STC irradiance
// Temperature derating: panels lose ~0.4% per °C above 25°C (STC)
double tempCoefficient = -0.004; // per °C, typical crystalline silicon
double cellTemp = tempC + 25.0; // simplified NOCT-based cell temp estimate
double tempDerate = 1.0 + tempCoefficient * (cellTemp - 25.0);
double dcOutputKW = panelCapacityKWp * peakSunHoursEquivalent * performanceRatio * tempDerate;
The temperature derating is the piece most simple estimators drop, and it noticeably matters in summer. A 40°C air temperature translates to something like 60–65°C cell temperature under full sun, which for typical crystalline silicon panels means roughly a 14–16% output reduction relative to STC nameplate. That’s real money at scale, and it shows up as a consistent over-forecast on hot afternoons if you leave it out.
Wind Turbine Output: The Cube Law and Why Surface Wind Is Tricky
Wind power is proportional to the cube of wind speed: P = 0.5 × ρ × A × v³ × Cp, where ρ is air density (~1.225 kg/m³ at sea level), A is rotor swept area, v is wind speed in m/s, and Cp is the power coefficient (typically 0.35–0.45 for modern utility turbines, bounded theoretically at 0.593 by the Betz limit).
The immediate problem: WeatherAPI reports surface wind at approximately 10 metres. Most utility-scale turbines have hub heights of 80–120 metres. Wind speed increases with altitude following a power law profile, and the difference is not small — at hub height, wind speed can be 40–60% higher than at 10m depending on surface roughness. Skipping this correction before plugging the speed into the power equation compounds badly, because any under-estimate of v gets cubed.
// Wind power law extrapolation to hub height
// alpha is the Hellmann exponent: ~0.143 for open flat terrain, higher for rough/urban terrain
double windAt10m = windKph / 3.6; // convert to m/s
double alpha = 0.143;
double hubHeightMetres = 100.0;
double windAtHubHeight = windAt10m * Math.Pow(hubHeightMetres / 10.0, alpha);
// Turbine parameters
double rotorDiameterMetres = 90.0;
double sweptArea = Math.PI * Math.Pow(rotorDiameterMetres / 2.0, 2);
double airDensity = 1.225; // kg/m³, adjust for elevation/temperature if needed
double powerCoefficient = 0.40;
// Raw power
double rawPowerWatts = 0.5 * airDensity * sweptArea * Math.Pow(windAtHubHeight, 3) * powerCoefficient;
// Apply cut-in and cut-out limits (typical: 3 m/s cut-in, 25 m/s cut-out)
double cutIn = 3.0;
double cutOut = 25.0;
double turbineOutputKW = (windAtHubHeight >= cutIn && windAtHubHeight <= cutOut)
? Math.Min(rawPowerWatts / 1000.0, ratedCapacityKW)
: 0.0;
The cut-in and cut-out logic is non-negotiable if you're showing this to anyone making operational decisions. A 28 m/s wind event doesn't generate maximum power — the turbine shuts down for protection. Leaving that out produces the worst estimates at exactly the moments when conditions are most extreme, which is the worst possible time to be wrong.
Stitching It Into an Hourly Forecast Loop
The WeatherAPI forecast endpoint with q=lat,lon&days=3&aqi=no&alerts=no gives you 72 hourly buckets. Loop over them, skip any hour where is_day == 0 for the solar calculation (no point running the UV math at 2am), and accumulate both output streams:
foreach (var hour in forecastHours)
{
double solar = 0.0;
if (hour.IsDay == 1)
{
solar = EstimateSolarOutputKW(
hour.Uv, hour.Cloud, hour.TempC,
panelCapacityKWp, performanceRatio);
}
double wind = EstimateWindOutputKW(
hour.WindKph, hubHeightMetres, ratedCapacityKW);
results.Add(new { hour.TimeEpoch, SolarKW = solar, WindKW = wind, TotalKW = solar + wind });
}
For a three-day window, this gives you something actually useful for scheduling battery dispatch, demand response signalling, or flagging low-generation windows that need grid backup.
Where This Breaks Down
A few genuine limits worth knowing before you ship this to production.
The UV-to-GHI derivation works acceptably at mid-latitudes during summer but gets shakier near the poles or at very low sun angles — winter, early morning, late afternoon — because the erythemally-weighted spectrum diverges more from broadband GHI at high zenith angles. If your use case involves high-latitude sites or year-round operation, consider supplementing with a clear-sky model like ESRA or REST2, which work from latitude and day-of-year directly rather than leaning on UV as a proxy.
Surface-level wind readings from METAR stations — which is ultimately what's feeding real-time conditions — are highly location-specific. A station at a flat airfield is not representative of a ridgeline wind farm 12km away. For forecast hours, the GFS and HRRR model output underlying the API handles this somewhat better than raw METAR at distance, but it's still not a substitute for on-site anemometry if you're doing actual plant performance management.
Air density varies with elevation and temperature in ways that matter for wind power calculations. At 1,500m elevation in summer, air density can be around 15% lower than the 1.225 kg/m³ sea-level standard — and since power scales linearly with density, that error goes straight through to your output estimate. The correction is straightforward: ρ = P / (R × T), where P is pressure (pull pressure_mb from the API response directly) and T is temperature in Kelvin.
A Note on Forecast Horizon
Day-1 hourly forecasts from HRRR (3km grid, updates every hour) are meaningfully more accurate than Day-3 forecasts from GFS (13km grid, 6-hour update cycle). If you're building a scheduling tool on top of this, don't present Day-3 wind estimates with the same UI confidence as Day-1. The uncertainty on a cubic relationship compounds fast: a 10% wind speed forecast error becomes roughly a 33% power output error before you've added any other source of noise.
If you want to check which model cycle the data is sourced from, the last_updated field in the location block tells you when the underlying data was last refreshed — your best proxy for staleness on any given request.
Before you build elaborate confidence intervals, run the estimator against historical API data for a site where you have actual generation records and measure the error distribution directly. That empirical calibration will tell you more about where the weak points actually are than any amount of theoretical analysis — and it'll show you which failure mode to fix first.
