pvnodepvnode
  • pvnode.com
  • Studio
  • Pricing
  • Deutsch
  • English
  • API Reference
Product
  • Studio
  • API Documentation
  • API Reference
  • Pricing
Resources
  • Quickstart
  • Integrations
Legal
  • Imprint
  • Privacy
  • Terms
  • Licenses
pvnodepvnode

© 2026 pvnode. All rights reserved.

linkedin
IntroductionQuickstartMigration from V1
Sites & Data
    Sites & StringsForecastsHistorical DataData UploadCalibration & Monitoring
Guides
Enterprise
Integrations
    Build Your OwnHome AssistantevccSolectrusioBroker
Data Sources
(Archive) V1 API
powered by Zudoku
Sites & Data

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.

TerminalCode
GET /v2/forecast/{site_id}

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

ParameterMeaning
forecast_daysForecast horizon in days. Without a value, the plan maximum. Hard limit 7, additionally capped to the plan limit.
past_daysPast days before today (archived forecast). Default 0. Hard limit 30, capped to the plan limit.
includeWhich field groups come back (repeatable). Default default.
timezoneTimezone 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:

TerminalCode
GET /v2/forecast/{site_id}?include=weather&include=irradiance
GroupFields
defaultpv_power (PV power in W).
weathertemp, weather_code, wind_speed, relative_humidity, precipitation_mm, snow_water_equivalent.
irradianceghi, dhi, bni (horizontal irradiance).
clearskypv_power_clearsky (power under a cloudless sky).
stringsAdds the separate strings block (see below). values stays unchanged.
variabilityAdds the min/max band (see below). Paid feature, never part of all.
allAll 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
"included": ["default", "strings"], "available": ["default", "weather", "irradiance", "clearsky", "strings", "variability"]

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 all is not listed: it is shorthand for every group here except variability, which always has to be requested explicitly.
  • available reflects 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
{ "site_id": "site_rv8wm5…", "timezone": "Europe/Berlin", "computed_at": "2026-06-09T08:00:00", "next_poll_at": "2026-06-09T12:00:00", "included": [ "default" ], "available": [ "default", "weather", "irradiance", "clearsky", "strings" ], "daily": [ { "date": "2026-06-09", "pv_energy_kwh": 42.7, "temp_min": 11.0, "temp_max": 24.3 } ], "values": [ { "timestamp": "2026-06-09T12:00:00", "pv_power": 8200.0 } ] }
  • daily — daily aggregates (energy in kWh, min/max temperature, dominant weather code).
  • values — time series per 15 minutes. pv_power is 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
"strings": [ {"timestamp": "2026-06-09T12:00:00", "string_index": 0, "string_id": "str_9f3a1c08", "pv_power": 5100.0, "gti": 820.0, "gti_shaded": 815.0}, {"timestamp": "2026-06-09T12:00:00", "string_index": 1, "string_id": "str_4b7e2d10", "pv_power": 3100.0, "gti": 760.0, "gti_shaded": 752.0} ]
  • string_index refers position-based to site.strings[i] — same order as in GET /v2/sites/{site_id}. Always present.
  • string_id is 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 an id on the string.
  • The tilted irradiance (gti, gti_shaded) lives here, not in values, 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_power is the sum of the string powers.

In a DataFrame the block can be loaded directly and pivoted on string_index:

Code
import pandas as pd df = pd.DataFrame(response["strings"]) wide = df.pivot(index="timestamp", columns="string_index", values="pv_power")

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_max in every values and strings row, plus pv_energy_kwh_min / pv_energy_kwh_max in daily.
  • 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.
  • variability is never included by all, 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
"computed_at": "2026-06-09T08:00:00", "next_poll_at": "2026-06-09T12:00:00"

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 daySlots
1next local midnight
400:00 / 06:00 / 12:00 / 18:00
96every 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:

HeaderMeaning
RequestLimit-LimitRequests allowed this calendar month, or the literal unmetered.
RequestLimit-UsedRequests already counted this month, including the current one. Always numeric.
RequestLimit-RemainingRequests left this month, or the literal unmetered.
RequestLimit-ResetWhen the counter returns to 0: the 1st of the next month, 00:00 UTC.
Code
RequestLimit-Limit: 10000 RequestLimit-Used: 4231 RequestLimit-Remaining: 5769 RequestLimit-Reset: 2026-09-01T00:00:00Z

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 / errorBehavior
Forecast accessWithout forecast access in the plan → 403.
Monthly requestsMonthly 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 plan403, raised before the request is counted — probing costs no quota.
forecast_days / past_daysValues are capped to the plan limit.
Site not found404.

You'll find your specific limits on the Usage & Limits page.

Last modified on August 18, 2026
Sites & StringsHistorical Data
On this page
  • Parameters
  • include groups
  • What your plan allows (available)
  • Response structure
  • The strings block
  • Variability band (include=variability)
  • Caching, computed_at and next_poll_at
    • When to ask again
  • Request quota headers
  • Limits & errors
JSON
JSON
JSON
JSON