Ovo Energy API - Get your energy data from Ovo

Not sure if there are any Ovo energy users in the forum, but if there are, you might like to know about my quick mini-project. Still a work in progress but the basics already in place.

This uses the unofficial Ovo API's (v2) to grab your energy use and cost data. It was inspired by this post in the Ovo forum and the C# app written by Mike Williams.

Thankfully, Node-RED and JavaScript is a heck of a lot easier to work with than a Windows C# app! Took about 20min of trying to work through the C# and about 10minutes of Node-RED coding. :grinning:

Here is where I'm up to:

The only reason the flow is split in 2 right now is that I cache the output from the top line in some flow variables so that I don't have to keep logging in. The 2nd line gets a calendar year of data and saves to a persisted flow variable.

The last node is a function because I now want to reformat from the API raw data to something more useful.

Last steps will be to create a web page to display what Ovo don't provide - the ability to compare any 2 years, or perhaps compare kWh to cost. Before I get to a web page, I'll probably dump the data to CSV files and do some playing in Excel PoweQuery.

Anyway, here is the flow so far:

[{"id":"684c3335c4b8c0f4","type":"http request","z":"30fdd9a9702231b0","name":"Login Request","method":"POST","ret":"obj","paytoqs":"ignore","url":"https://my.ovoenergy.com/api/v2/auth/login","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":480,"y":5560,"wires":[["90fca0eb6cee56a9"]]},{"id":"353f19c455104822","type":"http request","z":"30fdd9a9702231b0","name":"get account ID's","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://smartpaymapi.ovoenergy.com/first-login/api/bootstrap/v2/","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":940,"y":5560,"wires":[["aa2511901c4507f5"]]},{"id":"90fca0eb6cee56a9","type":"change","z":"30fdd9a9702231b0","name":"Retain auth cookies","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload1","tot":"msg"},{"t":"set","p":"cookies","pt":"msg","to":"responseCookies","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":690,"y":5560,"wires":[["353f19c455104822"]]},{"id":"941566573a2bd7dd","type":"debug","z":"30fdd9a9702231b0","name":"debug 429","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1255,"y":5560,"wires":[],"l":false},{"id":"aa2511901c4507f5","type":"change","z":"30fdd9a9702231b0","name":"Temp save output","rules":[{"t":"set","p":"OvoMyAccounts","pt":"flow","to":"payload","tot":"msg","dc":true},{"t":"set","p":"OvoAuthCookies.refresh_token","pt":"flow","to":"responseCookies.refresh_token","tot":"msg","dc":true}],"action":"","property":"","from":"","to":"","reg":false,"x":1130,"y":5560,"wires":[["941566573a2bd7dd"]]},{"id":"1aa00925f3e123c0","type":"inject","z":"30fdd9a9702231b0","name":"AcctID, Yr, Cookies","props":[{"p":"accountId","v":"OvoMyAccounts.selectedAccountId","vt":"flow"},{"p":"year","v":"2024","vt":"num"},{"p":"cookies","v":"OvoAuthCookies","vt":"flow"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":5600,"wires":[["2eed78f88be65967"]]},{"id":"2eed78f88be65967","type":"http request","z":"30fdd9a9702231b0","name":"Request Yr Data","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://smartpaymapi.ovoenergy.com/usage/api/monthly/{{accountId}}?date={{year}}","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":480,"y":5600,"wires":[["6d607f97da5cdc03"]]},{"id":"f4cbf4e8d6de435e","type":"debug","z":"30fdd9a9702231b0","name":"debug 430","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1255,"y":5600,"wires":[],"l":false},{"id":"6d607f97da5cdc03","type":"function","z":"30fdd9a9702231b0","name":"Save Yr Data & Upd Cookies","func":"const ovoData = flow.get('ovoData', 'file') ?? {}\n\novoData[msg.year] = msg.payload\nflow.set('ovoData', ovoData, 'file')\n\nflow.set('ovoAuthCookies.refresh_token', msg.responseCookies.refresh_token)\n\nreturn msg","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":5600,"wires":[["f4cbf4e8d6de435e"]]},{"id":"6f17099f032ed31b","type":"inject","z":"30fdd9a9702231b0","name":"Set uid/pw","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"username\":\"MYOVOEMAILADDRESS HERE\",\"password\":\"MYOVOPASSWORDHERE\",\"rememberMe\":false}","payloadType":"json","x":300,"y":5560,"wires":[["684c3335c4b8c0f4"]]}]

You will need to supply your own My Ovo login details of course. The 2nd line is currently only set to bring back this year's monthly data.

These are the Ovo v2 API endpoints. If you have a smart meter, you can get a lot more data of course. I don't have one and I'm only interested in the monthly/yearly data.

{
  "LoginUri": "https://my.ovoenergy.com/api/v2/auth/login",
  "AccountsUri": "https://smartpaymapi.ovoenergy.com/first-login/api/bootstrap/v2/",
  "MonthlyUri": "https://smartpaymapi.ovoenergy.com/usage/api/monthly/{0}?date={1}",
  "DailyUri": "https://smartpaymapi.ovoenergy.com/usage/api/daily/{0}?date={1}",
  "HalfHourlyUri": "https://smartpaymapi.ovoenergy.com/usage/api/half-hourly/{0}?date={1}"
}

(Thanks to Mike W. and others for finding these out for everyone)

These seem to be earlier attempts at using the API's using Python: ovoenergy · PyPI,
GitHub - timmo001/ovoenergy: Get energy data from OVO's API.

3 Likes

I do have a so called 'smart meter' and I have managed to get the 1/2 hour data.

Add msg.day in the AcctID, Yr, Cookies inject node as a string (example 2024-01-01)
The 1/2 hourly endpoint (HalfHourlyUri) should then look like this.

https://smartpaymapi.ovoenergy.com/usage/api/half-hourly/{{accountId}}?date={{day}}
1 Like

Cool. Do you have any actual use for the 1/2hrly data? :grinning:

Ovo keep pestering me about replacing my meters but I've heard so many negative stories and I really don't want to be out of power for however long it takes to switch the electric over.

Gas & electric took a couple of hours. I had the same issues with changing over but so far no problems ( I am pretty sure that all current OVO 'smart meters' are of the latest design). The engineer should check transmission of data using the normal transmitter, but if there are issues they should supply a wifi transmitter. OVO have a page which provides some info on this. Gas to 'smart meter' is via Bluetooth.

I hope to use the data to determine average usage and also to deduct the energy used by items I currently monitor with zigbee power monitoring sockets.

1 Like

I know too many people who's smart meters have never been smart, sometimes after YEARS, but also more recent installs where the reporting back to the centre just never works. I know others who have been charged outrageous amounts incorrectly after a smart meter fitted. Not currently liking the odds that much.

Thanks for this.
I’ve got the ovo smart meters and have tried all the api calls successfully. I’m now just wondering about how to graph the data? Influxdb/graphana possibly seem over kill. It would be interesting to know what others are thinking of using.

Actually not really. They are fairly easy to install and configure. There is some guidance that I and some others put into the forum a few years back on configuring InfluxDB.

Otherwise you have the Dashboard that can show charts. And UIBUILDER.

I think I’ll look into those options.
The format of data returned from ovo would need quite a bit of manipulation to enable it to be loaded into a node red graph. Also, I’ve not checked yet, but wonder on how much data ovo hold. I started with then in 2018, so wonder if they have the half hour data going back to then? It would be interesting to load all that into a database and then produce various graphs.

Not sure where I got the info from now, but I have been doing this for 18 Months or so.

I get the monthly data on the 1st of the month, and then the daily and 1/2 hourly data for the previous day, each morning at 8am. I found any earlier than this and the data is often not available.

I dump all this into influxdb for graphing, and have a few charts embedded into my dashboard.

Just a note there is a different spelling of ovo / Ovo in your switch and function nodes :wink:

Can you give some more details on getting the data into influx etc?

I will tidy up the code and share here when I get a chance.

1 Like

That looks good, just the sort of thing I’d like to do. Would appreciate any code or high level pointers on how to do this.

I’ve had a play with the csv node and by specifying consumption and interval in the columns field, I sort of get what I need to graph. However, interval is an object consisting of start & end. Is there a way to just pick the start element in the columns field? I tried interval.start, but that produces no output.

@paul-thomas1964 Hastily cobbled together as I didn't want to leave you hanging :wink:

It should give you a start on how to collect and format the data for the influx batch node.

You will need to install and setup influx and Grafana first. There are plenty of topics on the subject.
Good luck :grinning:

EDIT: updated this flow 05/05/24

[{"id":"684c3335c4b8c0f4","type":"http request","z":"712086ff.3e3668","name":"Login","method":"POST","ret":"obj","paytoqs":"ignore","url":"https://my.ovoenergy.com/api/v2/auth/login","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":715,"y":945,"wires":[["90fca0eb6cee56a9"]]},{"id":"353f19c455104822","type":"http request","z":"712086ff.3e3668","name":"get account Id","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://smartpaymapi.ovoenergy.com/first-login/api/bootstrap/v2/","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1045,"y":945,"wires":[["aa2511901c4507f5"]]},{"id":"90fca0eb6cee56a9","type":"change","z":"712086ff.3e3668","name":"pass on cookies","rules":[{"t":"set","p":"cookies","pt":"msg","to":"responseCookies","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":865,"y":945,"wires":[["353f19c455104822"]]},{"id":"941566573a2bd7dd","type":"debug","z":"712086ff.3e3668","name":"debug 429","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1335,"y":945,"wires":[],"l":false},{"id":"aa2511901c4507f5","type":"change","z":"712086ff.3e3668","name":"Save account","rules":[{"t":"set","p":"account","pt":"flow","to":"payload.accounts[0].accountId","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1225,"y":945,"wires":[["941566573a2bd7dd"]]},{"id":"6f17099f032ed31b","type":"inject","z":"712086ff.3e3668","name":"Set uid/pw","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"{\"username\":\"name\",\"password\":\"1234\",\"rememberMe\":false}","payloadType":"json","x":415,"y":945,"wires":[["0faf80101d11f3cf"]]},{"id":"daba20e623b83d33","type":"inject","z":"712086ff.3e3668","name":"08:00","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"00 08 * * *","once":false,"onceDelay":0.1,"topic":"half-hourly","payload":"","payloadType":"date","x":505,"y":870,"wires":[["0a026588ca3e7dfe"]]},{"id":"08196494f1094e6f","type":"function","z":"712086ff.3e3668","name":"prev day 1/2hr data","func":"if (msg.statusCode !== 200) {\n    const text = `error - statusCode: ${msg.statusCode} - ${msg.payload.message}`\n    node.error(text);\n    node.status({ fill: \"red\", shape: \"dot\", text: text });\n    return\n}\nlet gas = msg.payload.gas.data\nlet elec = msg.payload.electricity.data\n\nif (gas.length === 0) { // check data exists\n    node.warn(\"No Data\");\n    node.status({ fill: \"yellow\", shape: \"dot\", text: \"No Data \" + msg.date });\n    return [null, msg]\n} else {\n    node.send([null, { reset: true }]);\n    node.status({ fill: \"green\", shape: \"dot\", text: \"success \" + msg.date });\n}\n\nlet msgData = [];\n\nfor (let index = 0; index < gas.length; index++) {\n\n    msgData.push({\n        measurement: \"meters\",\n        retentionPolicy: \"month\",\n        fields: {\n            kWh: gas[index].consumption\n        },\n        tags: { type: \"gas\" },\n        timestamp: new Date(gas[index].interval.end).getTime()\n    })\n\n    msgData.push({\n        measurement: \"meters\",\n        retentionPolicy: \"month\",\n        fields: {\n            kWh: elec[index].consumption\n        },\n        tags: { type: \"electric\" },\n        timestamp: new Date(elec[index].interval.end).getTime()\n    })\n}\n\nmsg.retentionPolicy = \"month\"\nmsg.payload = msgData\nreturn msg\n","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":870,"wires":[["f30fe26d9b66da20"],["9a2d6b76f7a01497"]]},{"id":"08415f8845dc554c","type":"function","z":"712086ff.3e3668","name":"prev day daily data","func":"if (msg.statusCode !== 200) {\n    const text = `error - statusCode: ${msg.statusCode} - ${msg.payload.message}`\n    node.error(text);\n    node.status({ fill: \"red\", shape: \"dot\", text: text });\n    return\n}\n\nlet gas = msg.payload.gas.data\nlet elec = msg.payload.electricity.data\n\nlet date = new Date()\nlet index = date.getDate() - 1 //start at 0\n\nif (gas.length === 0 || !gas[index]) { // check data exists\n    node.warn(\"No Data\");\n    node.status({ fill: \"yellow\", shape: \"dot\", text: \"No Data \" + msg.date });\n    return [null, msg]\n} else {\n    node.send([null, { reset: true }]);\n    node.status({ fill: \"green\", shape: \"dot\", text: \"success \" + msg.date });\n}\n\nlet msgData = [];\n\nmsgData.push({\n    measurement: \"meters_daily\",\n    fields: {\n        kWh: gas[index].consumption,\n        cost: parseFloat(gas[index].cost.amount),\n        rate: gas[index].rates.anytime,\n        standing: gas[index].rates.standing\n    },\n    tags: { type: \"gas\" },\n    timestamp: new Date(gas[index].interval.end).getTime()\n})\n\nmsgData.push({\n    measurement: \"meters_daily\",\n    fields: {\n        kWh: elec[index].consumption,\n        cost: parseFloat(elec[index].cost.amount),\n        rate: elec[index].rates.anytime,\n        standing: elec[index].rates.standing\n    },\n    tags: { type: \"electric\" },\n    timestamp: new Date(elec[index].interval.end).getTime()\n})\n\nmsg.payload = msgData\nreturn msg","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":780,"wires":[["f9805cc3853816cd"],["28535746a2b0102f"]],"info":"let gas = msg.payload.gas.data\r\nlet elec = msg.payload.electricity.data\r\n\r\nif (gas.length <= 0) {\r\n    node.warn(\"No Data\");\r\n    return\r\n}\r\n\r\nlet msgData = [];\r\n\r\nfor (let index = 0; index < gas.length; index++) {\r\n\r\n    msgData.push({\r\n        measurement: \"meters_daily\",\r\n        fields: {\r\n            kWh: gas[index].consumption,\r\n            cost: parseFloat(gas[index].cost.amount),\r\n            rate: gas[index].rates.anytime,\r\n            standing: gas[index].rates.standing\r\n        },\r\n        tags: { type: \"gas\" },\r\n        timestamp: new Date(gas[index].interval.end).getTime()\r\n    })\r\n\r\n    msgData.push({\r\n        measurement: \"meters_daily\",\r\n        fields: {\r\n            kWh: elec[index].consumption,\r\n            cost: parseFloat(elec[index].cost.amount),\r\n            rate: elec[index].rates.anytime,\r\n            standing: elec[index].rates.standing\r\n        },\r\n        tags: { type: \"electric\" },\r\n        timestamp: new Date(elec[index].interval.end).getTime()\r\n    })\r\n\r\n}\r\n\r\nmsg.payload = msgData\r\nreturn msg"},{"id":"ba83939051e4d299","type":"inject","z":"712086ff.3e3668","name":"08:01","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"01 08 * * *","once":false,"onceDelay":0.1,"topic":"daily","payload":"","payloadType":"date","x":505,"y":780,"wires":[["40b033d6f34637ed"]]},{"id":"84f8082f57dd83fc","type":"function","z":"712086ff.3e3668","name":"prev month data","func":"if (msg.statusCode !== 200) {\n    const text = `error - statusCode: ${msg.statusCode} - ${msg.payload.message}`\n    node.error(text);\n    node.status({ fill: \"red\", shape: \"dot\", text: text });\n    return\n}\n\nlet date = new Date()\n// array index starts at 0, but contains Jan data, luckily JS uses 0 for Jan so returns prev months data for us, but in Jan we need to get Dec\nlet index = (date.getMonth() > 0 ? date.getMonth() : 12)\n\nlet gas = msg.payload.gas.data.filter(el => el.month === index)\nlet elec = msg.payload.electricity.data.filter(el => el.month === index)\n\nif (gas.length === 0) { // check data exists\n    node.warn(\"No Data\");\n    node.status({ fill: \"yellow\", shape: \"dot\", text: \"No Data \" + msg.date });\n    return [null, msg]\n}\nnode.send([null, { reset: true }]);\nnode.status({ fill: \"green\", shape: \"dot\", text: \"success \" + msg.date });\n\nlet msgData = [];\n\nmsgData.push({\n    measurement: \"meters_monthly\",\n    fields: {\n        kWh: gas[0].consumption,\n        cost: parseFloat(gas[0].cost.amount)\n    },\n    tags: { type: \"gas\" },\n    timestamp: new Date(`${gas[0].year}-${gas[0].month}-28`).getTime()\n})\n\nmsgData.push({\n    measurement: \"meters_monthly\",\n    fields: {\n        kWh: elec[0].consumption,\n        cost: parseFloat(elec[0].cost.amount)\n    },\n    tags: { type: \"electric\" },\n    timestamp: new Date(`${elec[0].year}-${elec[0].month}-28`).getTime()\n})\n\nmsg.payload = msgData\nreturn msg","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":980,"y":690,"wires":[["0e51e4fbe51c8e82"],["bb2892828da855ae"]]},{"id":"28535746a2b0102f","type":"trigger","z":"712086ff.3e3668","name":"request again","op1":"","op2":"0","op1type":"nul","op2type":"str","duration":"1","extend":false,"overrideDelay":false,"units":"hr","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":1195,"y":780,"wires":[["40b033d6f34637ed"]]},{"id":"9a2d6b76f7a01497","type":"trigger","z":"712086ff.3e3668","name":"request again","op1":"","op2":"0","op1type":"nul","op2type":"str","duration":"1","extend":false,"overrideDelay":false,"units":"hr","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":1195,"y":870,"wires":[["0a026588ca3e7dfe"]]},{"id":"0e51e4fbe51c8e82","type":"debug","z":"712086ff.3e3668","name":"month","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1165,"y":660,"wires":[]},{"id":"f9805cc3853816cd","type":"debug","z":"712086ff.3e3668","name":"daily","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1165,"y":750,"wires":[]},{"id":"f30fe26d9b66da20","type":"debug","z":"712086ff.3e3668","name":"1/2 Hourly","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1185,"y":840,"wires":[]},{"id":"a233e567ed14f81b","type":"function","z":"712086ff.3e3668","name":"1st of Month","func":"if (msg.payload && typeof msg.payload === 'number') {\n    let date = new Date(msg.payload);\n    if (date.getDate() === 1) { return msg };\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":690,"wires":[["f5af5a459c186a81"]]},{"id":"9a1f40d1980e7bce","type":"inject","z":"712086ff.3e3668","name":"08:03","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"03 08 * * *","once":false,"onceDelay":0.1,"topic":"monthly","payload":"","payloadType":"date","x":505,"y":690,"wires":[["a233e567ed14f81b"]]},{"id":"ba72fc1e5f5409fa","type":"link in","z":"712086ff.3e3668","name":"token","links":[],"x":335,"y":990,"wires":[["3d0a81a8b9338aa1"]]},{"id":"4997c59341cf820c","type":"link out","z":"712086ff.3e3668","name":"link out 124","mode":"return","links":[],"x":1095,"y":990,"wires":[]},{"id":"9fc2ad832cb1b28c","type":"function","z":"712086ff.3e3668","name":"set date","func":"let date = new Date();\ndate.setDate(date.getDate() - 1); // Set date to yesterday\n\nswitch (msg.topic) {\n\n    case \"daily\":\n        msg.date = date.toISOString().slice(0, 7); // eg 2024-02\n        break;\n\n    case \"monthly\":\n        msg.date = date.getFullYear(); // eg 2024\n        break;\n\n    case \"half-hourly\":\n        msg.date = date.toISOString().split('T')[0]; // eg 2024-02-29\n        break;\n\n}\n\n\nmsg.cookies = { \"refresh_token\": msg.responseCookies?.refresh_token };\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":810,"y":990,"wires":[["dc559f20c7b236a2"]],"info":"let date = new Date()\r\n\r\ndate.setDate(date.getDate() - 1); //yesterday\r\n\r\nvar year = date.toLocaleString(\"default\", { year: \"numeric\" });\r\nvar month = date.toLocaleString(\"default\", { month: \"2-digit\" });\r\nvar day = date.toLocaleString(\"default\", { day: \"2-digit\" });\r\n\r\nmsg.date = `${year}-${month}-${day}`;\r\n\r\nmsg.cookies = { \"refresh_token\": msg.responseCookies.refresh_token }\r\n\r\nreturn msg;"},{"id":"0e5cb6b7979bba5e","type":"http request","z":"712086ff.3e3668","name":"login and get token","method":"POST","ret":"obj","paytoqs":"ignore","url":"https://my.ovoenergy.com/api/v2/auth/login","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":615,"y":990,"wires":[["9fc2ad832cb1b28c"]]},{"id":"3d0a81a8b9338aa1","type":"change","z":"712086ff.3e3668","name":"get login","rules":[{"t":"set","p":"payload","pt":"msg","to":"login","tot":"flow"},{"t":"set","p":"account","pt":"msg","to":"account","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":435,"y":990,"wires":[["0e5cb6b7979bba5e"]]},{"id":"0faf80101d11f3cf","type":"change","z":"712086ff.3e3668","name":"","rules":[{"t":"set","p":"login","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":575,"y":945,"wires":[["684c3335c4b8c0f4"]]},{"id":"bb2892828da855ae","type":"trigger","z":"712086ff.3e3668","name":"request again","op1":"","op2":"0","op1type":"nul","op2type":"str","duration":"1","extend":false,"overrideDelay":false,"units":"hr","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":1195,"y":690,"wires":[["f5af5a459c186a81"]]},{"id":"0a026588ca3e7dfe","type":"link call","z":"712086ff.3e3668","name":"","links":["ba72fc1e5f5409fa"],"linkType":"static","timeout":"30","x":820,"y":870,"wires":[["08196494f1094e6f"]]},{"id":"40b033d6f34637ed","type":"link call","z":"712086ff.3e3668","name":"","links":["ba72fc1e5f5409fa"],"linkType":"static","timeout":"30","x":820,"y":780,"wires":[["08415f8845dc554c"]]},{"id":"f5af5a459c186a81","type":"link call","z":"712086ff.3e3668","name":"","links":["ba72fc1e5f5409fa"],"linkType":"static","timeout":"30","x":820,"y":690,"wires":[["84f8082f57dd83fc"]]},{"id":"a887108a06c1c13f","type":"comment","z":"712086ff.3e3668","name":"Enter login details here and run once","info":"","x":170,"y":945,"wires":[]},{"id":"dc559f20c7b236a2","type":"http request","z":"712086ff.3e3668","name":"get data ","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://smartpaymapi.ovoenergy.com/usage/api/{{topic}}/{{account}}?date={{date}}","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":975,"y":990,"wires":[["4997c59341cf820c"]]},{"id":"4e7f1f609def5432","type":"inject","z":"712086ff.3e3668","name":"Manual","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"monthly","payload":"","payloadType":"date","x":505,"y":645,"wires":[["985e25a568aca41b"]]},{"id":"985e25a568aca41b","type":"junction","z":"712086ff.3e3668","x":735,"y":645,"wires":[["f5af5a459c186a81"]]}]

Just had a quick play with the data in Python/Pandas to better think about how to analyse it. Will try one of the newer Dataframes-like libraries for node.js in Node-RED next. I want to be able to summarise by year and month and compare cost with kWh.

Of course, you should be able to do that in InfluxDB as well if you have set the record timestamp correctly. That makes it trivial to chart in Grafana.

But as I'm just playing with the data, InfluxDB isn't so useful for now.

Thanks, I’ll have a look over the weekend.
I’m thinking I should put influxdb on my windows PC, so as to not wear out the SD card on the rasp pi.

Will the PC always be on ?

Yes, it’s on 24*7. It has resilient storage too. I have wondered about the nvme expansion boards for the PI.
I’m wondering how far back the OVO data goes? I’ve been with them since 2018. It should be possible to write something to pull back all data.
I’m not familiar with Influx, if you send a duplicate time point, will it be stored or ignored?

I "think" it will store duplicates, but will only show 1 record.

The point of the flows above is to extract only the records needed, in my case anything added since the day before, so there are no redundant operations / storage etc.

Me too, but I'm happy with a USB3 SSD at the moment.

There is an easy way to find out :wink:

I've been with them since the end of 2015 and I have all the monthly data.

If it is exactly the same timestamp, I think it should be overwritten. But it needs to be to at least the ms. Not a problem if you are setting the timestamp yourself. I do suggest testing on a measure that you don't mind throwing away though, correcting their tables are quite hard.