Smart Customer Mobile API
As I was writing this I decided to scan GitHub for the URLs I found so far, and, well, people smarter than me have already written a home_assistant integration against #SEW, but it is a bit different from what I saw in the field:
I'd still like to describe how to locate the endpoints and the login process, so here we go...
This is the second post about #SEW SCM API – Smart Customer Mobile API by Smart Energy Water, this time we will learn about different APIs using real world utility websites.
It appears that there are at least two different API “flavors”. The one that uses ModuleName.svc/MethodNameMob
naming convention and usually resides under PortalService
endpoint, and the newer one, which lives under /API/
.
So e.g. Nebraska Public Power District has endpoints at https://onlineaccount.nppd.com/PortalService/
, e.g. https://onlineaccount.nppd.com/PortalService/UserLogin.svc/help
. Rochester Public Utilities runs a different set of endpoints, with the root at https://connectwith.rpu.com/api
.
The endpoints for the latter API can also be browsed at https://scmcx.smartcmobile.com/API/Help/.
Different utilities pay for different set of modules, and here's some of the modules I have discovered so far:
- AdminBilling
- CompareSpending
- ConnectMe
- EnergyEfficiency
- Generation
- Notifications
- Outage
- PaymentGateway
- Usage
- UserAccount
- UserLogin
For /PortalService/
endpoints you can visit BASE_URL
+ /PortalService/
+ ModuleName
+ .svc
+ /help
to get the list of RPC calls you can issue. In order to find out what to send in the requests, you need to look into the calls within the apps for your utility. Note that some utilities opted out of the AES/CBC/PKCS5Padding PasswordPassword
encryption, so let's hope this will be a trend forward. Currently SEW web portals talk to a completely different set of APIs to populate the interface, even though they are querying the same thing.
So to start, here's how to login to your favorite utility:
from typing import Mapping, Any
import base64
import json
import hashlib
import requests
import urllib.parse
from Crypto.Cipher import AES
BASE_URL = "https://example.com/PortalService"
def _encrypt_query(
params: Mapping[str, str], encryption_key: str = "PasswordPassword"
) -> str:
"""Encrypt with AES/CBC/PKCS5Padding."""
cipher = AES.new(encryption_key, AES.MODE_CBC, IV=encryption_key)
cleartext = urllib.parse.urlencode(params).encode()
# PKCS5 Padding - https://www.rfc-editor.org/rfc/rfc8018#appendix-B.2.5
padding_length = 16 - len(cleartext) % 16
cleartext += padding_length * chr(padding_length).encode()
return base64.b64encode(cipher.encrypt(cleartext)).decode("ascii")
def request(module: str, method: str, data: Mapping[str, Any]) -> Mapping[str, str]:
enc_query = _encrypt_query(data)
# Or module + '.svc/'
url = BASE_URL + "/" + module + "/" + method
resp = requests.post(url, json={"EncType": "A", "EncQuery": enc_query})
if not resp.ok:
raise Exception(resp.status_code)
return resp.json()
password_digest = hashlib.sha256("PASSWORD".encode())
# Or ValidateUserLoginMob
response = request(
"UserLogin",
"ValidateUserLogin",
{"UserId": "USERNAME", "Password": password_digest},
)
print(response)
response
will contain some object, you will need LoginToken
and AccountNumber
to proceed with most of the other calls.
It's a bit awkward that different utilities have different endpoints, which makes creating a universal client challenging, so for now I am researching the ways to get info from the Usage
module. The parameters are weird (“type”: “MI”, or “HourlyType”: “H”), but we will get there.