Async Python client for Dominion Energy API
Project description
dompower
Async Python client for Dominion Energy API. Retrieves 30-minute interval electricity usage data.
Requirements
- Python 3.12+
- Dominion Energy account (Virginia/North Carolina)
Installation
pip install dompower
Or with uv:
uv add dompower
Authentication
Option 1: Login with Username/Password (Recommended)
The CLI supports direct login with two-factor authentication (TFA):
dompower login -u your.email@example.com
You'll be prompted for your password and then guided through TFA verification (SMS or email). Tokens are saved automatically.
Option 2: Manual Token Extraction
If the login command doesn't work, you can manually extract tokens from a browser session:
- Open https://login.dominionenergy.com/CommonLogin?SelectedAppName=Electric
- Log in with your Dominion Energy credentials
- Open browser DevTools (F12) > Network tab
- Look for requests to
prodsvc-dominioncip.smartcmobile.com - Find the
accessTokenandrefreshTokenin request/response headers
Create a tokens.json file:
{
"access_token": "eyJhbGciOiJodHRwOi...",
"refresh_token": "pd9YAsV9HKNkrECM..."
}
Token Refresh
The library automatically refreshes tokens when they expire (every 30 minutes). Both tokens rotate on each refresh - the library handles this automatically and notifies via callback.
CLI Usage
Get Usage Data
# Last 7 days of 30-minute interval data
dompower --token-file tokens.json usage -a ACCOUNT_NUMBER -m METER_NUMBER
# Custom date range
dompower --token-file tokens.json usage -a 123456 -m 789 \
--start-date 2024-01-01 --end-date 2024-01-31
# Output as JSON
dompower --token-file tokens.json usage -a 123456 -m 789 --json
# Save raw Excel file
dompower --token-file tokens.json usage -a 123456 -m 789 --raw -o usage.xlsx
Login
# Interactive login with TFA support
dompower login -u your.email@example.com
# Specify output file and cookie storage
dompower login -u your.email@example.com -o tokens.json --cookies ~/.dompower/cookies.json
Cookies are saved to enable TFA bypass on subsequent logins.
Account Discovery
# List all accounts and meters
dompower --token-file tokens.json accounts
# Include inactive/closed accounts
dompower --token-file tokens.json accounts --all
# Output as JSON
dompower --token-file tokens.json accounts --json
# Select default account/meter (interactive)
dompower --token-file tokens.json select-account
# Select specific account
dompower --token-file tokens.json select-account -a 123456789
After selecting an account, you can omit -a and -m from usage commands.
Bill Forecast
# Get bill forecast with last bill details and current usage
dompower --token-file tokens.json bill-forecast
# For specific account
dompower --token-file tokens.json bill-forecast -a 123456789
# Output as JSON
dompower --token-file tokens.json bill-forecast --json
Other Commands
# Manually refresh tokens
dompower --token-file tokens.json refresh
# Show authentication instructions
dompower auth-info
Library Usage
Basic Example
import asyncio
from datetime import date, timedelta
import aiohttp
from dompower import DompowerClient
async def main():
async with aiohttp.ClientSession() as session:
client = DompowerClient(
session,
access_token="your_access_token",
refresh_token="your_refresh_token",
)
usage = await client.async_get_interval_usage(
account_number="123456789",
meter_number="000000000123456789",
start_date=date.today() - timedelta(days=7),
end_date=date.today(),
)
for record in usage:
print(f"{record.timestamp}: {record.consumption} {record.unit}")
asyncio.run(main())
With Token Persistence
import json
from pathlib import Path
import aiohttp
from dompower import DompowerClient
TOKEN_FILE = Path("tokens.json")
def load_tokens():
with TOKEN_FILE.open() as f:
return json.load(f)
def save_tokens(access_token: str, refresh_token: str):
with TOKEN_FILE.open("w") as f:
json.dump({
"access_token": access_token,
"refresh_token": refresh_token
}, f)
async def main():
tokens = load_tokens()
async with aiohttp.ClientSession() as session:
client = DompowerClient(
session,
access_token=tokens["access_token"],
refresh_token=tokens["refresh_token"],
token_update_callback=save_tokens, # Called when tokens refresh
)
usage = await client.async_get_interval_usage(...)
Login with Username/Password
import aiohttp
from dompower import GigyaAuthenticator, TFAProvider
async def login_with_tfa():
async with aiohttp.ClientSession() as session:
auth = GigyaAuthenticator(session)
# Initialize session (gets WAF and Gigya cookies)
await auth.async_init_session()
# Submit credentials
result = await auth.async_submit_credentials(
"your.email@example.com",
"your_password"
)
if result.tfa_required:
# Get available TFA targets
targets = await auth.async_get_tfa_options(TFAProvider.PHONE)
# Send verification code
await auth.async_send_tfa_code(targets[0])
# Get code from user and verify
code = input(f"Enter code sent to {targets[0].obfuscated}: ")
tokens = await auth.async_verify_tfa_code(code)
else:
# No TFA required
tokens = await auth._async_complete_login()
print(f"Access token: {tokens.access_token[:20]}...")
print(f"Refresh token: {tokens.refresh_token[:20]}...")
Home Assistant Integration Pattern
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from dompower import DompowerClient
async def async_setup_entry(hass, entry):
session = async_get_clientsession(hass)
def token_callback(access_token: str, refresh_token: str):
hass.config_entries.async_update_entry(
entry,
data={
**entry.data,
"access_token": access_token,
"refresh_token": refresh_token,
},
)
client = DompowerClient(
session,
access_token=entry.data["access_token"],
refresh_token=entry.data["refresh_token"],
token_update_callback=token_callback,
)
# Store client for use in sensors/coordinators
hass.data[DOMAIN][entry.entry_id] = client
API Reference
DompowerClient
Main client class for API interaction.
DompowerClient(
session: ClientSession,
*,
access_token: str | None = None,
refresh_token: str | None = None,
token_update_callback: Callable[[str, str], None] | None = None,
)
Methods:
async_get_interval_usage(account_number, meter_number, start_date, end_date)- Get 30-minute usage dataasync_get_raw_excel(account_number, meter_number, start_date, end_date)- Get raw Excel fileasync_get_accounts()- Get all accounts and meters for the authenticated userasync_get_customer_info()- Get customer profile with all accountsasync_get_bill_forecast(account_number)- Get bill forecast with last bill dataasync_login(username, password, tfa_code_callback)- Login with username/passwordasync_set_tokens(access_token, refresh_token)- Set tokens manuallyasync_refresh_tokens()- Force token refresh
GigyaAuthenticator
Handles Gigya/SAP authentication with TFA support. Use for step-by-step login flows.
GigyaAuthenticator(
session: ClientSession,
cookie_file: Path | None = None, # Persist cookies for TFA bypass
)
Methods:
async_init_session()- Initialize WAF and Gigya cookiesasync_submit_credentials(username, password)- Submit login credentialsasync_get_tfa_options(provider)- Get available TFA targets (phone/email)async_send_tfa_code(target)- Send verification code to targetasync_verify_tfa_code(code)- Verify TFA code and get tokensasync_login(username, password, tfa_callback)- Complete login with callbacksave_cookies()/load_cookies()- Persist cookies for TFA bypass
Data Models
@dataclass(frozen=True)
class IntervalUsageData:
timestamp: datetime # Start of 30-minute interval
consumption: float # kWh consumed
generation: float # kWh excess generation returned to grid
unit: str # "kWh"
Exceptions
DompowerError # Base exception
AuthenticationError # Authentication issues
InvalidAuthError # Invalid tokens
TokenExpiredError # Tokens expired, need re-auth
BrowserAuthRequiredError # Initial auth needed
GigyaError # Gigya authentication errors
InvalidCredentialsError # Wrong username/password
TFARequiredError # TFA required (not an error, normal flow)
TFAVerificationError # TFA code verification failed
TFAExpiredError # TFA session expired (~5 min timeout)
CannotConnectError # Network issues
ApiError # API returned error
RateLimitError # Rate limited (429)
Data Format
The API returns 30-minute interval data. Example output:
Timestamp Consumption Generation Unit
---------------------------------------------------------
2024-01-15 00:00 0.45 0.01 kWh
2024-01-15 00:30 0.38 0.00 kWh
2024-01-15 01:00 0.42 0.01 kWh
...
Limitations
- TFA verification codes expire after ~5 minutes
- Refresh tokens may expire after extended periods of inactivity
- API rate limits are not documented; library does not implement rate limiting
Development
With uv (recommended)
git clone https://github.com/YeomansIII/dompower
cd dompower
uv sync --dev
uv run pytest
uv run mypy dompower
uv run ruff check dompower
With pip/venv
git clone https://github.com/YeomansIII/dompower
cd dompower
python3 -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -e ".[dev]"
pytest
mypy dompower
ruff check dompower
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dompower-0.2.0.tar.gz.
File metadata
- Download URL: dompower-0.2.0.tar.gz
- Upload date:
- Size: 100.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f154b35c79cbdbe9f58f758c7c130ce5e559b17e1200b02b08147a9a8e05546
|
|
| MD5 |
d6b2a5926e0acea3c12d9625878e814c
|
|
| BLAKE2b-256 |
4e97bc1b9b52ede712fb9fd09426ee9d6fa35e403dcb003274919e2f592bc79a
|
Provenance
The following attestation bundles were made for dompower-0.2.0.tar.gz:
Publisher:
publish.yml on YeomansIII/dompower
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dompower-0.2.0.tar.gz -
Subject digest:
4f154b35c79cbdbe9f58f758c7c130ce5e559b17e1200b02b08147a9a8e05546 - Sigstore transparency entry: 986995647
- Sigstore integration time:
-
Permalink:
YeomansIII/dompower@0fee4a073f5dace8591821028d2419408fd1b9d7 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/YeomansIII
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0fee4a073f5dace8591821028d2419408fd1b9d7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file dompower-0.2.0-py3-none-any.whl.
File metadata
- Download URL: dompower-0.2.0-py3-none-any.whl
- Upload date:
- Size: 36.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a818581fd5ee9d780e039409c1f45a392fbd2482ccb4b58d51c091397345332f
|
|
| MD5 |
2ed83e601944b3bdc1e91b927fb8ef1f
|
|
| BLAKE2b-256 |
229bf46328365bd23267341f9ee6057249dccd8e2432e8f7a8ad976ca375bab8
|
Provenance
The following attestation bundles were made for dompower-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on YeomansIII/dompower
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dompower-0.2.0-py3-none-any.whl -
Subject digest:
a818581fd5ee9d780e039409c1f45a392fbd2482ccb4b58d51c091397345332f - Sigstore transparency entry: 986995704
- Sigstore integration time:
-
Permalink:
YeomansIII/dompower@0fee4a073f5dace8591821028d2419408fd1b9d7 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/YeomansIII
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0fee4a073f5dace8591821028d2419408fd1b9d7 -
Trigger Event:
release
-
Statement type: