Getting data in
One endpoint, one key. Push a reading whenever you like — every 5 minutes is a good default. Sending the same timestamp twice is safe.
The quickest possible start
Generate a key in Settings, then:
curl -X POST https://catproduc.ro/api/v1/readings \
-H "Authorization: Bearer cp_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"watts": 4210, "energy_today_kwh": 21.5}'Leave at out and we stamp it now. Send energy_today_kwh or energy_total_kwh, whichever your inverter reports — we work the daily total out either way.
Home Assistant
Easiest routeYes — and you don't need a custom component. Home Assistant already talks to practically every inverter, so it makes a perfect universal adapter: two blocks of YAML and it pushes once a minute. Swap sensor.solar_power and sensor.solar_energy_today for whatever your integration calls them.
# configuration.yaml
rest_command:
catproduc_push:
url: "https://catproduc.ro/api/v1/readings"
method: POST
headers:
Authorization: !secret catproduc_key
Content-Type: "application/json"
payload: >-
{
"watts": {{ states('sensor.solar_power') | float(0) | round(0) }},
"energy_today_kwh": {{ states('sensor.solar_energy_today') | float(0) }}
}
# automations.yaml
- alias: Push solar to catproduc
trigger:
- platform: time_pattern
# Once a minute. Measured against a real inverter, 5 minutes already
# captures 99.4% of the peak, so this is about resolution rather than
# accuracy: 1440 points a day instead of 288, which is what makes cloud
# edges visible. 60 pushes/hour against a 600/hour limit.
minutes: "/1"
condition:
# Don't push "unknown" while the inverter is asleep.
- condition: template
value_template: >-
{{ states('sensor.solar_power') not in ['unknown', 'unavailable', 'none'] }}
action:
- service: rest_command.catproduc_push
# secrets.yaml
catproduc_key: "Bearer cp_live_xxxxxxxxxxxxxxxxxxxxxxxx"Anything with a power sensor in HA works. Using something not on this list, or want a direct integration without HA in the middle? Tell us — that list is how we decide what to build next.
Coming from PVOutput
We speak PVOutput's Add Status format, so an uploader you already have keeps working — point it at us and change nothing else. Batch uploads (addbatchstatus.jsp) and the cumulative flag c1 behave the same.
# Already pushing to PVOutput? Change the host and keep everything else.
# was: https://pvoutput.org/service/r2/addstatus.jsp
# now: https://catproduc.ro/service/r2/addstatus.jsp
#
# Your catproduc key goes in the X-Pvoutput-Apikey header. The system id is
# ignored. d and t are read in YOUR timezone, exactly as PVOutput reads them.
curl "https://catproduc.ro/service/r2/addstatus.jsp?d=20260827&t=13:05&v1=18000&v2=4800" \
-H "X-Pvoutput-Apikey: cp_live_xxxxxxxxxxxxxxxxxxxxxxxx"A shell script and cron
#!/usr/bin/env bash
# push.sh — read your inverter, send one reading. Run from cron every 5 minutes:
# */5 * * * * /path/to/push.sh
set -euo pipefail
WATTS=$(your-inverter-command --power) # e.g. 4210
TODAY_KWH=$(your-inverter-command --today) # e.g. 21.5
curl -fsS -X POST "https://catproduc.ro/api/v1/readings" \
-H "Authorization: Bearer cp_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d "{\"watts\": $WATTS, \"energy_today_kwh\": $TODAY_KWH}"Importing old history
Daily totalsBringing years of history over from another platform, or from your inverter's own logs? That goes to a separate endpoint that takes daily totals only, with no limit on how far back they reach.
Imported days are marked declared — outlined on the chart rather than solid — because nothing was measured for them here. A day that already has readings is refused rather than overwritten, so an import can never quietly rewrite a real one.
# Import historical daily totals — no limit on how far back they go.
# These days are stored as "declared" and drawn differently from measured ones.
#
# A day that already has readings is REFUSED (409), never overwritten.
curl -X POST https://catproduc.ro/api/v1/outputs \
-H "Authorization: Bearer cp_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"outputs": [
{"date": "2024-06-01", "energy_kwh": 30.5},
{"date": "2024-06-02", "energy_kwh": 28.1, "peak_watts": 5100}
]}'Endpoint reference
| Endpoint | Does |
|---|---|
| POST /api/v1/readings | One reading, an array, or {readings:[…]}. Max 1000 per call. |
| POST /api/v1/outputs | Historical daily totals. Off by default; days are marked unverified. |
| GET /api/v1/me | Checks a key and returns the username and timezone it belongs to. |
| GET|POST /service/r2/addstatus.jsp | PVOutput Add Status, wire-compatible. |
| GET|POST /service/r2/addbatchstatus.jsp | PVOutput Add Batch Status. |
| GET /llms.txt | This page, written for a coding agent. |
- • Auth:
Authorization: Bearer <key>,X-Api-Key, orX-Pvoutput-Apikey. - • Rate limit: 600 requests/hour per key. A batch counts as one.
- • Fields:
at, watts, energy_today_wh, energy_total_wh, energy_today_kwh, energy_total_kwh, temp_c, voltage. At least one energy or power value is required. - •
/readingsis for live data; anything older than 14 days goes to/outputsas a daily total. - • Raw readings are kept 90 days; daily totals are kept forever.
Setting this up with an AI agent
If you use Claude Code, Cursor or similar to wire things up, point it at /llms.txt — the whole API in one plain-text file, written to be pasted into a prompt.
# In Claude Code, Cursor, or any agent that can fetch:
Read https://catproduc.ro/llms.txt and wire my inverter up to it.