Skip to content

Telemetry

Four endpoints, all scoped telemetry:read, all addressed by serial number. Read Concepts first for units, bucket boundaries and how fresh each field is.

GET /api/v1/external/systems/{serialNumber}/telemetry/latest

One call for “what is this system doing”. Use it for dashboards and status pages.

Terminal window
curl -sS https://api.claytonpower.com/api/v1/external/systems/1234567890/telemetry/latest \
-H "X-API-Key: $CP_API_KEY"
{
"serialNumber": "1234567890",
"connectivity": { "status": "online", "lastSeenAt": "2026-09-17T09:41:12Z" },
"stateOfCharge": { "percent": 87.5, "measuredAt": "2026-09-17T09:41:00Z" },
"fault": null,
"latestInterval": {
"start": "2026-09-17T09:30:00Z",
"durationMinutes": 15,
"input": { "mainsKwh": 0.42, "solarKwh": 0.11, "vehicleKwh": 0.0, "totalKwh": 0.53 },
"output": { "consumptionKwh": 0.37 },
"peakOutputW": 1180
}
}

Every block is independently nullable, because the sources are independent:

  • connectivity - status is online, stale, offline or unknown; see Connectivity states. lastSeenAt is null when the status is unknown.

  • stateOfCharge - percent, minute-fresh. It falls back to the latest hourly bucket when the device has not reported a live value, in which case measuredAt can be up to an hour old. Always read measuredAt; do not assume the value is from now. Null when neither source has a reading.

  • fault - null when the system reports no failure code. Otherwise:

    {
    "fault": {
    "code": "27",
    "level": 3,
    "description": "Inverter over-temperature",
    "solution": "Check ventilation around the unit and restart it."
    }
    }

    level is the platform’s severity ranking for that code; level 3 and above are treated as critical and raise a critical alert. description and solution are the catalogue text for the code and can be absent for a code we have no entry for.

  • latestInterval - the most recent completed 15-minute bucket. Null when the system produced no bucket in the last 24 hours, and typically 15 to 30 minutes behind now even for a healthy system. The bucket currently filling is never returned.

A serial that does not exist, or belongs to another company, returns 404.

GET /api/v1/external/systems/{serialNumber}/telemetry/energy
?from=2026-09-16T00:00:00Z
&to=2026-09-17T00:00:00Z
&interval=1h
Parameter Required Default Notes
from no to - 24h ISO 8601 UTC. Clamped up to dataAvailableFrom
to no now ISO 8601 UTC
interval no see reference 15m, 1h or 1d

Maximum range: 31 days for 15m, 366 days for 1h and 1d. Beyond that the request is rejected with 400 and errorCode = ValidationFailed.

Terminal window
curl -sS -G https://api.claytonpower.com/api/v1/external/systems/1234567890/telemetry/energy \
-H "X-API-Key: $CP_API_KEY" \
--data-urlencode "from=2026-09-16T00:00:00Z" \
--data-urlencode "to=2026-09-17T00:00:00Z" \
--data-urlencode "interval=1h"
{
"serialNumber": "1234567890",
"interval": "1h",
"from": "2026-09-16T00:00:00Z",
"to": "2026-09-17T00:00:00Z",
"points": [
{
"start": "2026-09-16T00:00:00Z",
"input": { "mainsKwh": 1.2, "solarKwh": 0.4, "vehicleKwh": 0.0, "totalKwh": 1.6 },
"output": { "consumptionKwh": 1.1 },
"peakOutputW": 2210
}
]
}

Notes that save debugging time:

  • Values are per bucket. Sum them to get a period total; do not diff them.
  • Buckets with no data are omitted. Do not index the array by position in time.
  • peakOutputW is null when the bucket carries no peak-power measurement. A bucket can carry a peak while every energy channel reads zero: the unit was running, the meters had nothing to report.
  • Systems that do not carry cumulative energy meters report 0 on every energy channel. See Product families.
  • Echoed from and to tell you the range that was actually used after clamping.
GET /api/v1/external/systems/{serialNumber}/telemetry/soc
?from=…&to=…&interval=15m|1h|1d

Same parameters, same defaults and same range limits as the energy series.

{
"serialNumber": "1234567890",
"interval": "1h",
"points": [
{ "start": "2026-09-16T00:00:00Z", "avgPercent": 81.2, "minPercent": 74.0, "maxPercent": 90.1 }
]
}

State of charge is an instantaneous quantity, so buckets are averaged, never summed. minPercent and maxPercent are the extremes seen inside the bucket, which is what you want for “did this battery get dangerously low today” - a 1d average of 60 percent can hide a dip to 8 percent at 04:00.

SoC is read across both firmware schemes, so a unit that changed product family mid-range still produces one continuous series.

GET /api/v1/external/systems/{serialNumber}/health

The latest daily health snapshot.

{
"assessedAt": "2026-09-17T02:00:00Z",
"overallScore": 78,
"status": "warning",
"checks": [
{ "name": "Connectivity", "score": 95, "status": "healthy", "details": "Reported on 1435 of 1440 minutes." },
{ "name": "Error codes", "score": 60, "status": "warning", "details": "Error code 27 active for 4 hours." }
]
}

status is healthy, warning, critical or unknown. overallScore runs from 0 to 100.

Snapshots are produced once a day, so this endpoint is not a live signal. A new system has no snapshot yet; that is unknown, not a failure. When a snapshot is critical, a health.critical alert is raised as well, which is the better way to react to it.

You want Use
A live status tile /telemetry/latest, polled every minute or two
A chart of today, yesterday or a month /telemetry/energy or /telemetry/soc
Daily totals for reporting /telemetry/energy with interval=1d
To know when something goes wrong Webhooks, not polling

Polling the series endpoints more often than every 15 minutes returns the same points, because the underlying aggregation only refreshes that often.