Skip to main content

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:

  1. Open https://login.dominionenergy.com/CommonLogin?SelectedAppName=Electric
  2. Log in with your Dominion Energy credentials
  3. Open browser DevTools (F12) > Network tab
  4. Look for requests to prodsvc-dominioncip.smartcmobile.com
  5. Find the accessToken and refreshToken in 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 data
  • async_get_raw_excel(account_number, meter_number, start_date, end_date) - Get raw Excel file
  • async_get_accounts() - Get all accounts and meters for the authenticated user
  • async_get_customer_info() - Get customer profile with all accounts
  • async_get_bill_forecast(account_number) - Get bill forecast with last bill data
  • async_login(username, password, tfa_code_callback) - Login with username/password
  • async_set_tokens(access_token, refresh_token) - Set tokens manually
  • async_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 cookies
  • async_submit_credentials(username, password) - Submit login credentials
  • async_get_tfa_options(provider) - Get available TFA targets (phone/email)
  • async_send_tfa_code(target) - Send verification code to target
  • async_verify_tfa_code(code) - Verify TFA code and get tokens
  • async_login(username, password, tfa_callback) - Complete login with callback
  • save_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
    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    Unit
---------------------------------------------
2024-01-15 00:00          0.45           kWh
2024-01-15 00:30          0.38           kWh
2024-01-15 01:00          0.42           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

dompower-0.1.3.tar.gz (99.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dompower-0.1.3-py3-none-any.whl (35.8 kB view details)

Uploaded Python 3

File details

Details for the file dompower-0.1.3.tar.gz.

File metadata

  • Download URL: dompower-0.1.3.tar.gz
  • Upload date:
  • Size: 99.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dompower-0.1.3.tar.gz
Algorithm Hash digest
SHA256 0bf2c77317610f5b4314376ff8a7916bdbaae556ec25dff11255ca706bcfecae
MD5 90432c2ee1c94bfc886bb0178f9dd046
BLAKE2b-256 7ad5a8a102d06cf6919f48d1a510bd78bc9666f618e4b1e7cbd0d6465d5514f8

See more details on using hashes here.

Provenance

The following attestation bundles were made for dompower-0.1.3.tar.gz:

Publisher: publish.yml on YeomansIII/dompower

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dompower-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: dompower-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 35.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dompower-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 035d36d67e3044bc6b8637891d33c8f63b5e74ade9a66b14b06bfce5f71f25a0
MD5 2d57d8bd02129ffc9f469065e3ef096b
BLAKE2b-256 abf1db3135bee6296247615741ca1ed42e98142dae45d97ff7f1829db7b214ea

See more details on using hashes here.

Provenance

The following attestation bundles were made for dompower-0.1.3-py3-none-any.whl:

Publisher: publish.yml on YeomansIII/dompower

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page