Forecasts
The forecast delivers the expected PV power for a site in 15-minute intervals.
You query it via the site_id; site, strings and configuration come from the
stored site.
Code
Timestamps are in the site's local timezone by default (see timezone in the response).
Pass ?timezone=utc to receive UTC timestamps ending in Z instead.
The full parameter and field list is in the API Reference. This page explains the concepts.
Parameters
| Parameter | Meaning |
|---|---|
forecast_days | Forecast horizon in days. Without a value, the plan maximum. Hard limit 7, additionally capped to the plan limit. |
past_days | Past days before today (archived forecast). Default 0. Hard limit 30, capped to the plan limit. |
include | Which field groups come back (repeatable). Default default. |
timezone | Timezone of the timestamps: local (default) or utc. utc returns UTC timestamps ending in Z. |
past_days delivers archived forecast data, not actual measurements. For real past
yields use the Historical Data.
include groups
include is repeatable and selects field groups — not individual columns:
Code
| Group | Fields |
|---|---|
default | pv_power (PV power in W). |
weather | temp, weather_code, wind_speed, relative_humidity, precipitation_mm, snow_water_equivalent. |
irradiance | ghi, dhi, bni (horizontal irradiance). |
clearsky | pv_power_clearsky (power under a cloudless sky). |
strings | Adds the separate strings block (see below). values stays unchanged. |
variability | Adds the min/max band (see below). Paid feature, never part of all. |
all | All field groups and the strings block (but not variability). |
default is not added automatically. If you only request weather, you get
weather data without pv_power. The included field in the response reflects the resolved selection.
What your plan allows (available)
included echoes what you asked for. available lists every group your plan may
request on this endpoint:
Code
That lets a client discover its entitlements without hard-coding plan knowledge. A dashboard can grey out a feature the plan does not cover, and unlock it by itself after an upgrade — no new release, no configuration.
- Plan-gated groups appear only once the plan includes them. Today that is
variability. - The meta-token
allis not listed: it is shorthand for every group here exceptvariability, which always has to be requested explicitly. availablereflects the plan at the moment of the response. A plan change is therefore visible on the next request, not retroactively on the one you already hold.
Response structure
Code
daily— daily aggregates (energy in kWh, min/max temperature, dominant weather code).values— time series per 15 minutes.pv_poweris the sum over all strings.computed_at— when this forecast was computed (see caching).next_poll_at— when it is worth asking again. Only on/v2/forecast/{site_id}.included/available— what you got, and what your plan allows (see above).
The strings block
With include=strings (or all) you get a separate top-level array strings in
long/tidy format — one row per time step and string:
Code
string_indexrefers position-based tosite.strings[i]— same order as inGET /v2/sites/{site_id}. Always present.string_idis the string's stable id — the join key to its measurements. Present for saved sites; on the inline endpoint it appears only if you supplied anidon the string.- The tilted irradiance (
gti,gti_shaded) lives here, not invalues, because it depends on the string's orientation. - Each string delivers a row in every time step (0.0 at night), so there are no gaps.
values.pv_poweris the sum of the string powers.
In a DataFrame the block can be loaded directly and pivoted on string_index:
Code
Variability band (include=variability)
The band delivers a lower/upper bound of the PV power, if
available in your plan. A request with include=variability on a plan without this feature returns
403.
- Fields:
pv_power_min/pv_power_maxin everyvaluesandstringsrow, pluspv_energy_kwh_min/pv_energy_kwh_maxindaily. - Horizon: covers roughly the next 48 h. Outside that (and for
past_days)min == max == pv_power(flat band). Sites not covered also receive a flat band — not an error. variabilityis never included byall, only on explicit request.
Caching, computed_at and next_poll_at
Forecasts are not recomputed on every request, but only as often as your plan allows.
Repeated requests in the same slot return the same cached
result; computed_at shows when it was computed.
The cache is automatically invalidated when the site changes (coordinates, strings, config) or the plan changes.
When to ask again
next_poll_at tells you the first moment a request would return something newer than
what you are holding. Asking earlier costs a request and returns the identical cached
payload.
Code
Nothing is recomputed on a schedule — forecasts are calculated on demand. Today the update slots are spread evenly across the day starting at site-local midnight:
| Updates per day | Slots |
|---|---|
| 1 | next local midnight |
| 4 | 00:00 / 06:00 / 12:00 / 18:00 |
| 96 | every 15 minutes |
Plans refreshing more than 96×/day are advertised on the 15-minute grid, so next_poll_at
is never sooner than 15 minutes out.
Read the field — do not reproduce the rule. The even distribution above describes how
slots are placed today, and it is going to change: we intend to spend fewer of them at night,
when nothing is generating, and more of them around the hours that matter. A client that
computes its own next slot from its plan will then poll at the wrong times. One that reads
next_poll_at follows the change automatically.
Treat the value as a recommendation, not a guarantee. A recomputation triggered by
another request can make newer data available earlier. Polling on next_poll_at is the
cheapest correct strategy, not the freshest possible one.
next_poll_at follows the same timezone rules as computed_at (site-local with a UTC
offset by default, …Z with ?timezone=utc) and is absent on /forecast/inline, which
is computed fresh on every call.
Request quota headers
Every response — including the 429 — carries four headers describing your monthly quota:
| Header | Meaning |
|---|---|
RequestLimit-Limit | Requests allowed this calendar month, or the literal unmetered. |
RequestLimit-Used | Requests already counted this month, including the current one. Always numeric. |
RequestLimit-Remaining | Requests left this month, or the literal unmetered. |
RequestLimit-Reset | When the counter returns to 0: the 1st of the next month, 00:00 UTC. |
Code
Limit and Remaining are strings, and on plans without a cap they carry the word
unmetered rather than a number. Parse defensively — int(header) will raise on those
plans.
The counter is kept per (user, endpoint, month). Forecast and historical requests therefore have separate budgets, and the numbers are account-wide: querying five sites draws from the same forecast counter. The cap itself scales with your site count, so more sites means a proportionally larger budget.
Limits & errors
| Limit / error | Behavior |
|---|---|
| Forecast access | Without forecast access in the plan → 403. |
| Monthly requests | Monthly request limit (per user & endpoint), reset on the 1st of the month → 429 when exceeded. Read RequestLimit-Reset to know when to retry. |
include=variability without the plan | 403, raised before the request is counted — probing costs no quota. |
forecast_days / past_days | Values are capped to the plan limit. |
| Site not found | 404. |
You'll find your specific limits on the Usage & Limits page.