solarwhen.com
Free hourly solar forecast for any UK postcode. No API key. No signup.
made by Tim Jones
Quick start
curl 'https://uk.solarwhen.com/forecast/SW1A?dir=s&tilt=35&kwh=4.5'
Replace SW1A with your postcode outcode (the part before the space).
Try it
Embeddable stats card
Append &format=svg to get an
800×460 SVG of the chart + headline numbers. Clickable example:
uk.solarwhen.com/forecast/SW1A?dir=s&tilt=35&kwh=4.5&format=svg
Drop the URL into an <img> tag, a markdown image,
a Notion / Discord / Slack message, or a README badge — anywhere
an image URL works:
<img src="https://uk.solarwhen.com/forecast/SW1A?kwh=4.5&format=svg"
alt="UK solar forecast">

Each unique URL is cached at the edge for 1 hour, so embedding it on a popular page costs nothing extra.
GET /forecast/{outcode}
{outcode} is the first part of a UK postcode —
e.g. SW1A, EC1Y,
N1.
| Param | Type | Default | Description |
|---|---|---|---|
dir |
enum | s | Panel facing: n / ne / e / se / s / sw / w / nw |
tilt |
0 – 90 | 35 | Panel tilt in degrees from horizontal |
kwh |
number | — | System size in kWp. Presence flips default mode to kwh. |
mode |
percent | kwh |
percent (kwh if kwh given) | Output units: percent of nameplate or absolute kWh. |
when |
today | now | tomorrow |
today | Window: today (00–23 local), now (rolling 24 h from this hour), tomorrow. |
electricity_region |
A–P | auto-derived from outcode | DNO region letter for the Agile price lookup. Override only if you're in a postcode-area-split edge case. |
format |
json | svg |
json | svg returns an 800×460 embeddable stats card (chart + headline numbers). Drop it into a <img> tag or a README. |
Response
{
"date": "2026-05-08", // local date the window starts on
"electricity_region": "C", // DNO region used for the Agile price lookup
"solar_production": "medium", // low | medium | high (peak as % of nameplate)
"sunrise": "05:24", // local time HH:MM (null at polar latitudes)
"sunset": "20:52",
"peak_pv": { "hour": "14", // best solar-generation hour
"kwh": 2.0 }, // or "percent" in percent mode; null if no daylight
"total_produced_kwh": 15.8, // only present in kwh mode
"average_price": 22.4, // avg Agile p/kWh across the 24 h window
"hourly_pv": { // PV production: kWh (1 dp) or % (integer)
"00": 0.0, "01": 0.0, ...
"14": 2.0, "15": 2.0, ...
"23": 0.0
},
"hourly_price": { // Agile p/kWh per hour (1 dp)
"00": 12.3, "01": 8.5, ... // missing days fall back to the previous day
"23": 18.7
},
"hourly_charge": { // recommended battery-charge schedule
"00": 0, "01": 1, ... // 1 = charge from grid this hour, 0 = stop
"23": 0
}
}
Charge plan
hourly_charge is a per-hour
battery-from-grid schedule (1 =
charge, 0 = stop), computed from
the same price + solar curve. Drop the array straight into a
Home
Assistant automation to drive a Fox ESS / GivEnergy / similar
battery's force-charge slots without writing any rule logic yourself.
Rules, in priority order:
- Never charge during peak times — the whole point of the charge plan.
- Charge in the run-up to peak times — ensures there's enough charge in the battery so you're not forced to pay peak prices.
- Charge during cheap hours when solar can't help.
- Otherwise, don't charge — it's more efficient to let solar charge the battery than to pre-emptively grab from the grid.
Convenience endpoints
If you only need the charge bit and not the rest of the forecast object,
two thin endpoints are exposed alongside /forecast:
GET /charge/{outcode}/now
|
Returns 0 or 1 — should the battery charge from the grid this hour. |
GET /charge/{outcode}/schedule
|
Returns a 24-element array of 0/1, one per local hour 00..23 today. |
Both accept the same dir, tilt, and
electricity_region query params as /forecast.
Drop the schedule array straight into a Home Assistant REST sensor and
an automation can write your battery's force-charge slots without any
rule logic in HA itself.
GET /weather/forecast/{outcode}
The raw hourly weather we already fetch from MET Norway to drive the solar forecast — exposed directly for callers who just want a simple per-postcode weather feed (no panel direction / tilt / battery rules needed).
Example: uk.solarwhen.com/weather/forecast/SW1A
Embeddable card: uk.solarwhen.com/weather/forecast/SW1A?format=svg
| Param | Type | Default | Description |
|---|---|---|---|
when |
today | now | tomorrow |
today | Window: today (00–23 local), now (rolling 24 h from this hour), tomorrow. |
format |
json | svg |
json | svg returns an 800×460 embeddable weather card (temp line + precip bars + condition strip + highs/lows). Drop into an <img> tag or README. |
{
"date": "2026-05-11", // local date the window starts on
"outcode": "SW1A",
"sunrise": "05:18", // local HH:MM (null at polar latitudes)
"sunset": "20:38",
"temp_high_c": 11.9, // max / min across the 24 h window
"temp_low_c": 5.2,
"total_precip_mm": 1.0, // sum of hourly precip in the window
"hourly_temp_c": { // air temperature, °C (1 dp)
"00": 8.1, "01": 7.6, ...
"23": 5.4
},
"hourly_cloud_pct": { // total cloud cover, % (integer)
"00": 12, "01": 25, ...
"23": 100
},
"hourly_precip_mm": { // liquid-equivalent precip per hour (2 dp)
"00": 0.0, "13": 0.1, "14": 0.8, ...
},
"hourly_condition": { // derived label, see below
"00": "clear", "13": "rain", ...
"23": "overcast"
}
}
hourly_condition is a coarse
label derived from the same row:
rain— precipitation ≥ 0.1 mm this hour.clear— cloud cover < 12 %.partly cloudy— 12–50 %.cloudy— 50–85 %.overcast— ≥ 85 %.
Precipitation is liquid-equivalent (lumps drizzle, rain, sleet, snow
into a single number). Same edge-cache TTL as
/forecast: 1 hour.
Rate limits
100 requests / minute and 5 000 requests / day per IP. Every response carries:
RateLimit: limit=100, remaining=99, reset=59
RateLimit-Policy: 100;w=60, 5000;w=86400
On a hit, the API responds 429 with a
Retry-After seconds header.
Status codes
200 | Success |
400 | Bad input (invalid outcode format, tilt out of range, unknown dir / mode / when). |
404 | Outcode not in the UK postcode list. |
429 | Rate limit hit. Wait the Retry-After seconds. |
503 | Forecast batch not yet refreshed for this hour. Try again shortly. |
How it works
- UK split into a 25 km grid (~570 cells). Each cell pulls a fresh forecast from MET Norway every 6 hours.
- Solar irradiance is reconstructed locally from cloud cover & clear-sky physics (Spencer solar position, Haurwitz GHI, Kasten-Czeplak attenuation, isotropic transposition).
- Predicted output is typically within ~15–25 % of actual on a UK-aggregate day (validated against Sheffield Solar PVLive). At a single address, hyperlocal cloud + shading can push individual hours further off.
- Forecasts are model estimates, not guarantees. Use at your own risk.