catproduc

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:

POST one reading
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 route

Yes — 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 + automations.yaml
# 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"
Known to work through Home Assistant
SolarEdgeEnphase EnvoyFroniusHuawei SUN2000GrowattDeyeSungrowSMAVictronGoodWeSolaXShelly EMTasmota

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.

Same request, different host
# 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

push.sh
#!/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 totals

Bringing 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.

POST /api/v1/outputs
# 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

EndpointDoes
POST /api/v1/readingsOne reading, an array, or {readings:[…]}. Max 1000 per call.
POST /api/v1/outputsHistorical daily totals. Off by default; days are marked unverified.
GET /api/v1/meChecks a key and returns the username and timezone it belongs to.
GET|POST /service/r2/addstatus.jspPVOutput Add Status, wire-compatible.
GET|POST /service/r2/addbatchstatus.jspPVOutput Add Batch Status.
GET /llms.txtThis page, written for a coding agent.
  • • Auth: Authorization: Bearer <key>, X-Api-Key, or X-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.
  • /readings is for live data; anything older than 14 days goes to /outputs as 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.

Give your agent the docs
# In Claude Code, Cursor, or any agent that can fetch:
Read https://catproduc.ro/llms.txt and wire my inverter up to it.