AddOutput API silently ignores valid exports from battery

When using a battery in conjunction with a solar PV system, it is possible for exports to greatly exceed generation, when, for example, the battery is discharged to the grid to take advantage of a high Feed In Tariffs.

If you edit an output and try and enter a value for Exports that exceeds Generation, you get an error message: Energy exported cannot exceed energy generated by 1000W

However, when you use the API, it just fails silently. e.g.

# curl -H "X-Pvoutput-Apikey: $KEY" -H "X-Pvoutput-SystemId: 93768" "https://pvoutput.org/service/r2/getoutput.jsp?df=20251103&dt=20251103"

20251103,6956,0.798,0,12139,2539,17:30,Cloudy,13,22,NaN,NaN,NaN,NaN#
# curl -d "d=20251103" -d "e=9510" -d "pp=3068" -d "pt=17:23" -d "ip=1511" -H "X-Pvoutput-Apikey: $KEY" -H "X-Pvoutput-SystemId: 93768" "https://pvoutput.org/service/r2/addoutput.jsp"

OK 200: Added Output#
# curl -H "X-Pvoutput-Apikey: $KEY" -H "X-Pvoutput-SystemId: 93768" "https://pvoutput.org/service/r2/getoutput.jsp?df=20251103&dt=20251103"

20251103,6956,0.798,0,12139,2539,17:30,Cloudy,13,22,NaN,NaN,NaN,NaN#

The update responded with OK, but the output record has not been updated. Generation on this day was 6956Wh.

There are two issues:

  1. The API should not fail silently on an error.
  2. Validation needs to take battery storage into account. Maybe the validation should be something like consumption + exports cannot exceed generation + imports + battery capacity.

Here is another example where addoutput returns a status of OK, but subsequent getoutput results shows the update has not been applied:

Nov 08 10:52:21  INFO  service:118 Uploading payload={'d': '20251108', 'e': 30, 'pp': 2089, 'pt': '10:08', 'ip': 30}
Nov 08 10:52:21  DEBUG service:127 Attempt #1 to url='https://pvoutput.org/service/r2/addoutput.jsp'...
Nov 08 10:52:21  DEBUG service:132 Attempt #1 OKAY status_code=200 limit=300 remaining=276 reset=2025-11-08 11:00:00 (458s)
Nov 08 10:57:22  DEBUG output:84 Verifying uploaded payload={'d': '20251108', 'e': 30, 'pp': 2089, 'pt': '10:08', 'ip': 30}
Nov 08 10:57:22  DEBUG output:106 Verification attempt #1 to url='https://pvoutput.org/service/r2/getoutput.jsp?df=20251108&dt=20251108'...
Nov 08 10:57:23  DEBUG output:110 Verification attempt #1 OKAY status_code=200 limit=300 remaining=273 reset=2025-11-08 11:00:00 (156s) response=20251108,1941,0.223,0,8910,1320,10:20,Showers,12,16,NaN,NaN,NaN,NaN
Nov 08 10:57:23  DEBUG output:130 Verification failure: payload['e']=30 != result['e']=0
Nov 08 10:57:23  DEBUG output:139 Verification attempt #1 of uploaded payload={'d': '20251108', 'e': 30, 'pp': 2089, 'pt': '10:08', 'ip': 30} FAILED, retrying...
Nov 08 10:58:23  DEBUG output:106 Verification attempt #2 to url='https://pvoutput.org/service/r2/getoutput.jsp?df=20251108&dt=20251108'...
Nov 08 10:58:24  DEBUG output:110 Verification attempt #2 OKAY status_code=200 limit=300 remaining=272 reset=2025-11-08 11:00:00 (96s) response=20251108,1941,0.223,0,8910,1320,10:20,Showers,12,16,NaN,NaN,NaN,NaN
Nov 08 10:58:24  DEBUG output:130 Verification failure: payload['e']=30 != result['e']=0
Nov 08 10:58:24  DEBUG output:139 Verification attempt #2 of uploaded payload={'d': '20251108', 'e': 30, 'pp': 2089, 'pt': '10:08', 'ip': 30} FAILED, retrying...
Nov 08 10:59:24  DEBUG output:106 Verification attempt #3 to url='https://pvoutput.org/service/r2/getoutput.jsp?df=20251108&dt=20251108'...
Nov 08 10:59:25  DEBUG output:110 Verification attempt #3 OKAY status_code=200 limit=300 remaining=271 reset=2025-11-08 11:00:00 (35s) response=20251108,1941,0.223,0,8910,1320,10:20,Showers,12,16,NaN,NaN,NaN,NaN
Nov 08 10:59:25  DEBUG output:130 Verification failure: payload['e']=30 != result['e']=0
Nov 08 10:59:25  DEBUG output:139 Verification attempt #3 of uploaded payload={'d': '20251108', 'e': 30, 'pp': 2089, 'pt': '10:08', 'ip': 30} FAILED, retrying...
Nov 08 10:59:25  INFO  service:118 Uploading payload={'d': '20251108', 'e': 30, 'pp': 2089, 'pt': '10:08', 'ip': 30}
Nov 08 10:59:25  DEBUG service:127 Attempt #1 to url='https://pvoutput.org/service/r2/addoutput.jsp'...
Nov 08 10:59:25  DEBUG service:132 Attempt #1 OKAY status_code=200 limit=300 remaining=270 reset=2025-11-08 11:00:00 (34s)

This is the expected behaviour since data is processed asynchronously. The Add Output API update request has no knowledge of existing data to perform validation upfront.

Only when this is processed on the backend does the validation occur.

Okay. I can accept the need to process asynchronously.

But shouldn’t the return status then reflect that it might not be OK, and that there be some mechanism to actually check what happened, apart from using getoutput and finding that nothing has changed with no idea why? Even an extra header on getoutput that included the real status of the last addoutput would be something.

Are the uploads of exports/imports failing because of some verification against generation and consumption? I have had multiple attempts to upload the same exports/imports values over several hours, only to have them be accepted unchanged at 00:01:31 the next day. The only thing that would have changed over that interval, apart from the actual date being different to that in the upload, would have been consumption uploaded via the addstatus API. Is there any way to disable such validation, if it is in fact the problem? Or is there some other thing that suddenly accepts valid data that has been previously rejected?

Another example:

Nov 18 19:27:29 INFO  PVOutputOutputService Uploading payload={'d': '20251118', 'e': 17310, 'pp': 7864, 'pt': '11:21', 'ip': 0, 'io': 11510, 'is': 20, 'ih': 0, 'ep': 11590, 'eo': 3070, 'es': 2650, 'eh': 0}
Nov 18 19:32:28 INFO  PVOutputOutputService Skipped uploading unchanged payload={'d': '20251118', 'e': 17310, 'pp': 7864, 'pt': '11:21', 'ip': 0, 'io': 11510, 'is': 20, 'ih': 0, 'ep': 11590, 'eo': 3070, 'es': 2650, 'eh': 0}
Nov 18 19:32:29 ERROR PVOutputOutputService Verification FAILED after 1 attempts for uploaded payload={'d': '20251118', 'e': 17310, 'pp': 7864, 'pt': '11:21', 'ip': 0, 'io': 11510, 'is': 20, 'ih': 0, 'ep': 11590, 'eo': 3070, 'es': 2650, 'eh': 0} downloaded={'d': '20251118', 'e': 0, 'pp': 6120, 'ip': None, 'io': None, 'is': None, 'ih': None, 'ep': None, 'eo': None, 'es': None, 'eh': None} (20251118,21806,2.501,0,13788,6120,11:25,Partly Cloudy,14,23,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN)

In this case, the TOU view on the web site is showing the correct values for Import Peak and Import Shoulder, but getoutput is returning NaN for those fields.

Add Status will recalculate import/export and overwrite any Add Output data, so ideally it should be sent at the end of the day for the previous day, once when Add Status is complete.