Plant Telemetry API

POST /v1/plant/telemetry — send SOC % and meter (kWh) regularly. Loxone: Telemetry only or bundled Hours & telemetry. Telemetry softcap Community 2000/day — does not eat Decision quota (120/day).

Plant Telemetry lets your system report live battery level and grid meter readings to SpotpriceAPI. The server learns your real consumption pattern over time. Hours Demand uses that for better charging plans. Each SOC ping is also stored as history (~15‑min rhythm) so Plant outlook can show the battery trajectory. Telemetry calls do not count against your daily Decision fair-use limit.

Exception: if live SOC is only 10 percentage points above min SoC (then again at 5 pp, and at the floor) — or recovers the other way (floor → 5 pp → 10 pp → headroom) — the server runs an internal Hours Decide asynchronously — snapshot + optional plant.plan_updated. That Decide does not count against Decision quota. Loxone Extract picks up the new discharge cap (O12) on the next Hours Decide (:00 / SOC band / I6), unless a webhook triggers a plan pull.

In the portal Plant outlook and the Sparvo app (home + plant) you see a live strip:

1. Battery SOC % 2. Day import so far (kWh) — waiting for meter data until the first positive delta; never the raw meter total 3. Estimated full-day import (kWh) 4. Current meter total since baseline date (kWh) — label “since {date}” (first transmission or last reset)

The strip shows values only — no method subtitle under estimated day use. Internally the estimate comes from meter history (recency-weighted, spike-damped; today plus a same-day overlay on the WD/WE hour shape, not clock × 24) or annual÷365 until ≥1 full day. Same number as Hours remaining-today and outlook D0. Hours explain “until this morning” additionally uses partial hour + recent rate (v1.9) for short-term cover — not identical to the calendar-day total above. After ≥1 full meter day we write typical × 365 (ceiled to 100 kWh) into the plant profile — including on a SOC-only ping once history exists. Details: Hours Demand.

The strip auto-refreshes about every 15 minutes while the tab is visible (no full page reload), with an “Updated …” timestamp. Endpoints: Portal GET /plant-outlook/live-telem, Sparvo GET /api/live-telem.

On /plant-outlook and Sparvo /plant below (order: live strip → Hours explain → PV day chart):

  • Starting battery level (manual) — slider fallback only until live telemetry is connected (locked when live SOC is active).
  • Reset meter baseline — next EMS ping becomes the new start value.

Battery level, day import, and meter total appear only in the live strip, not again in separate cards. Without live telemetry the portal still shows values in the telemetry card (fallback).

Portal slider: As soon as POST /v1/plant/telemetry sends SOC (soc_source=hours_api), it overrides the slider and locks it (~36 h freshness). Without fresh API SOC the slider is enabled again.

Everyday example

Every 15 minutes your inverter reports: “Battery 42%, grid meter 3,725.4 kWh total.” That first total is a baseline (not “today’s use”). Later pings send the rising total; SpotpriceAPI stores only the deltas.

When to use / when not

Use when:

  • You have SOC% and a cumulative import meter
  • You want smarter hours_needed without guessing load

Do not use when:

  • You need the actual charge schedule → call Hours Demand after telemetry (the server also auto-Decides when SOC is within 10 pp / 5 pp of min SoC, or at the floor)
  • You only want prices → Forecast

What you need

  • Community tier or higher
  • Plant profile in the portal (linked to your API key)
  • Header: X-API-Key: sf_live_YOUR_KEY
  • JSON body with SOC and meter total

Step by step

1. Create plant profile once in the portal (location for PV). 2. POST whenever SOC changes meaningfully (e.g. every 10 %) or on a fixed interval. 3. Send battery.soc_pct and meter.import_kwh_total (cumulative — e.g. 3725, not “12 kWh today”). 4. First ping sets a baseline — meter.is_baseline=true, delta_kwh empty. That is normal. Portal/Sparvo Day use so far shows waiting for meter data until the first positive delta (the cumulative total is not today’s kWh). 5. A first 0 then the real total is also a baseline (placeholder), not consumption. 6. From the second ping with an increase, meter.delta_kwh shows kWh used since last ping. 7. Optional: portal Reset meter baseline clears the stored total; the next ping is a new start value. 8. Call Hours Demand when you want an updated charging plan.

Minimal request and response


curl -X POST https://api.spotpriceapi.com/v1/plant/telemetry \
  -H "Content-Type: application/json" \
  -H "X-API-Key: sf_live_YOUR_KEY" \
  -d '{
    "battery": { "soc_pct": 40 },
    "meter": { "import_kwh_total": 3725.4 }
  }'

First ping (baseline):


{
  "telemetry_status": "ok",
  "soc_pct": 40.0,
  "meter": {
    "is_baseline": true,
    "import_kwh_total": 3725.4,
    "observed_at": "2026-08-05T08:00:00+00:00",
    "delta_kwh": null
  },
  "quota_class": "telemetry"
}

Later ping:


{
  "telemetry_status": "ok",
  "soc_pct": 41.0,
  "meter": {
    "is_baseline": false,
    "import_kwh_total": 3728.9,
    "observed_at": "2026-08-05T10:00:00+00:00",
    "delta_kwh": 3.5
  },
  "quota_class": "telemetry"
}

Endpoint


POST https://api.spotpriceapi.com/v1/plant/telemetry

Request body fields

FieldRequiredWhat it means
battery.soc_pctYes*Battery charge 0–100 %
meter.import_kwh_totalYes*Cumulative grid import kWh (any start value is fine)
meter.observed_atNoWhen measured (default: now)
grid_charge_allowedNoMaster switch 0/1 (or true/false). Omit = leave plant profile unchanged. Partial POST OK (same as SOC/meter-only).

\*Partial POSTs may send SOC only, meter only, grid_charge_allowed only, or combinations.

Important response fields

FieldWhat it means
telemetry_statusok, incomplete_input, invalid_input, or not_available
soc_pctStored value (clamped 0–100)
meter.is_baselinetrue on first ping / after portal reset / meter swap
meter.import_kwh_totalStored cumulative total
meter.observed_atWhen stored
meter.delta_kwhkWh since previous ping (null on baseline)
meter.reset_suspectedtrue if reading dropped vs previous (treated as new baseline)
quota_classAlways telemetry — separate from decision quota

Live strip (portal / Sparvo)

GET /plant-outlook/live-telem and GET /api/live-telem include e.g.:

FieldMeaning
day_import_kwhSum of meter deltas today (internally 0 until the first positive delta)
awaiting_first_deltatrue until the first positive delta after baseline/reset — UI shows waiting for meter data, not the meter total
awaiting_baselinetrue when no meter total is stored yet (waiting for ping after reset)
meter_total_kwhLast cumulative reading (also while awaiting_first_delta)
meter_since_dateLocal ISO date of the current baseline series (YYYY-MM-DD); after portal reset falls back to cleared_at until the next ping — UI “meter total since …”

Common mistakes

  • No plant profile in portal → incomplete_input with missing: ["plant_profile"] (or tier not_available)
  • Sending daily use instead of cumulative total
  • Expecting a charging plan from telemetry alone — you still need Hours Demand
  • Polling telemetry every second — use sensible intervals; see Rate limits
  • Loxone: official VO placeholders are <v> / <v.2> on the command Input — not named <v.Vi…> (those often stay literal → hint=loxone_vi_not_substituted). See Loxone / Miniserver below.
  • German decimal commas (85,0) are sanitized server-side; still prefer Content-Type: application/json and save to /user/common/spotprice_telemetry.json

Loxone / Miniserver

Per Loxone docs, a Virtual Output command only substitutes <v> / <v.1> / <v.2> / <v.3> / <v.t> — that is the value on the command Input, not <v.ViName>.

Recommended:

1. VO FetchPlantTelemetrySoc — Input = SOC, body {"battery":{"soc_pct":<v.2>}} 2. VO FetchPlantTelemetryMeter — Input = meter, body {"meter":{"import_kwh_total":<v.2>}} 3. Optional VO FetchPlantGridChargeAllow — Input = switch 0/1, body {"grid_charge_allowed":<v.0>} (send only on change) 4. SOC+meter trigger from FetchPlantHours O4; response to /user/common/spotprice_telemetry.json 5. Headers: X-API-Key + Content-Type: application/json

The API accepts partial POSTs (SOC only, meter only, grid_charge_allowed only, or combinations). FetchPlantHours v1.9 — O4 → Analog Memory → VO <v.2>: Telemetry only. Read grid charge: Extract O10 after Decide — Loxone Hours.

Next steps

Start with SpotpriceAPI

Register for Community or code samples with your personal key.

Live API status