Python client library for the Pinergy smart meter API
Project description
pypinergy
A Python client library for the Pinergy smart-meter API.
Note: This library is built on a reverse-engineered, unofficial API. It is not affiliated with or endorsed by Pinergy.
Installation
pip install pypinergy
Requires Python 3.9+ and requests.
Quick Start
from pypinergy import PinergyClient
client = PinergyClient("you@example.com", "your-password")
# Check your current balance
balance = client.get_balance()
print(f"Balance: €{balance.credit_balance:.2f}")
print(f"Estimated days remaining: {balance.top_up_in_days}")
# Today's usage
usage = client.get_usage()
today = usage.day[0]
print(f"{today.date:%Y-%m-%d} {today.kwh:.2f} kWh €{today.amount:.2f}")
Authentication is lazy — the client logs in automatically on the first API
call. You can also call .login() explicitly if you want the LoginResponse
data (account details, credit cards, etc.).
Authentication
Automatic (recommended)
client = PinergyClient("you@example.com", "your-password")
# First API call triggers login transparently
balance = client.get_balance()
Explicit login
from pypinergy import PinergyClient
client = PinergyClient("you@example.com", "your-password")
login = client.login()
print(f"Welcome, {login.user.first_name}!")
print(f"Account type: {login.account_type}")
print(f"Premises: {login.premises_number}")
print(f"Level pay: {login.is_level_pay}")
Check if an email is registered
if client.check_email("someone@example.com"):
print("Account exists")
else:
print("No account found for that email")
Logout
client.logout()
print(client.is_authenticated) # False
Usage Data
Smart / PAYG customers
usage = client.get_usage()
print("--- Daily (last 7 days) ---")
for entry in usage.day:
print(f" {entry.date:%Y-%m-%d} {entry.kwh:6.2f} kWh €{entry.amount:.2f}")
print("--- Weekly (last 8 weeks) ---")
for entry in usage.week:
print(f" w/c {entry.date:%Y-%m-%d} {entry.kwh:7.2f} kWh €{entry.amount:.2f}")
print("--- Monthly (last 11 months) ---")
for entry in usage.month:
print(f" {entry.date:%Y-%m} {entry.kwh:8.2f} kWh €{entry.amount:.2f}")
Each UsageEntry has:
| Field | Type | Description |
|---|---|---|
available |
bool |
Whether data is available for this period |
amount |
float |
Cost in euros (€) |
kwh |
float |
Energy consumed in kilowatt-hours |
co2 |
float |
CO₂ in kg (0.0 for renewable supply) |
date |
datetime |
UTC datetime for the start of the period |
date_ts |
int |
Raw Unix timestamp |
Level Pay customers
lp = client.get_level_pay_usage()
print("Half-hourly labels:", lp.labels[:4]) # ["00:00", "00:30", ...]
print("Tariff flags:", lp.flags[:2]) # ["Standard", ...]
for day_val in lp.values:
print(f" {day_val.label}: {day_val.day_kwh}")
Balance
bal = client.get_balance()
print(f"Current balance: €{bal.credit_balance:.2f}")
print(f"Days remaining: {bal.top_up_in_days}")
print(f"Last top-up: €{bal.last_top_up_amount:.2f} on {bal.last_top_up_time:%Y-%m-%d}")
print(f"Last meter reading: {bal.last_reading:%Y-%m-%d %H:%M UTC}")
print(f"Credit low? {bal.credit_low}")
print(f"Emergency credit? {bal.emergency_credit}")
print(f"Power off? {bal.power_off}")
BalanceResponse fields:
| Field | Type | Description |
|---|---|---|
credit_balance |
float |
Credit balance in euros (€) |
top_up_in_days |
int |
Estimated days until credit runs out |
pending_top_up |
bool |
Whether a top-up is pending |
pending_top_up_by |
str |
Who initiated the pending top-up |
last_top_up_amount |
float |
Amount of last top-up (€) |
last_top_up_time |
datetime | None |
UTC datetime of last top-up |
last_top_up_ts |
int | None |
Raw Unix timestamp of last top-up |
last_reading |
datetime | None |
UTC datetime of last meter reading |
last_reading_ts |
int | None |
Raw Unix timestamp of last reading |
credit_low |
bool |
Balance below configured threshold |
emergency_credit |
bool |
On emergency credit |
power_off |
bool |
Supply disconnected |
Top-Ups
topups = client.get_active_topups()
for sched in topups.scheduled:
owner = "you" if sched.current_user else sched.customer
print(f" Day {sched.top_up_day:2d} of each month: €{sched.top_up_amount:.0f} ({owner})")
if topups.auto_top_ups:
print("Auto top-ups configured:", topups.auto_top_ups)
current_user: Falsemeans the scheduled top-up belongs to another resident on the same premises (e.g. in an apartment building).
Usage Comparison
Compare your home's usage against a cohort of similar homes:
cmp = client.compare_usage()
for label, period in [("Today", cmp.day), ("This week", cmp.week), ("This month", cmp.month)]:
if not period.available:
continue
diff_kwh = period.kwh.users_home - period.kwh.average_home
direction = "more" if diff_kwh > 0 else "less"
print(
f"{label}: {period.kwh.users_home:.1f} kWh "
f"({abs(diff_kwh):.1f} kWh {direction} than average)"
)
ComparePeriod has three metric groups — euro, kwh, co2 — each with
users_home and average_home floats.
Configuration
Valid top-up amounts and alert thresholds
cfg = client.get_config_info()
print("Top-up options:", cfg.top_up_amounts)
print("Low-balance thresholds:", cfg.thresholds)
House and heating type reference data
defaults = client.get_defaults_info()
for ht in defaults.house_types:
print(f" {ht.id}: {ht.name}")
for ht in defaults.heating_types:
print(f" {ht.id}: {ht.name}")
Notifications
notif = client.get_notification_preferences()
print(f"Email notifications: {notif.email}")
print(f"SMS notifications: {notif.sms}")
print(f"Phone notifications: {notif.phone}")
Device Token (FCM)
For headless / server-side usage you can pass an empty string or skip this entirely — it only affects push notifications on mobile devices.
client.update_device_token(
device_token="", # empty for headless use
device_type="android",
os_version="",
)
App Version
Unauthenticated endpoint — does not require a login:
version_info = client.get_version()
print(version_info)
Error Handling
from pypinergy import PinergyClient
from pypinergy.exceptions import PinergyAuthError, PinergyAPIError, PinergyHTTPError
client = PinergyClient("you@example.com", "wrong-password")
try:
client.login()
except PinergyAuthError as e:
print(f"Bad credentials: {e}")
try:
balance = client.get_balance()
except PinergyAPIError as e:
print(f"API error {e.error_code}: {e.message}")
except PinergyHTTPError as e:
print(f"Network error: {e}")
| Exception | When raised |
|---|---|
PinergyError |
Base class for all library errors |
PinergyAuthError |
Invalid credentials or expired session |
PinergyAPIError |
API returned success: false (has .error_code and .message) |
PinergyHTTPError |
Network-level failure (timeout, 5xx, DNS, etc.) |
Advanced Usage
Custom timeout
client = PinergyClient("you@example.com", "password", timeout=10)
Custom base URL (testing / proxy)
client = PinergyClient("you@example.com", "password", base_url="http://localhost:8080")
Checking account type before fetching usage
login = client.login()
if login.is_level_pay:
data = client.get_level_pay_usage()
else:
data = client.get_usage()
Running Tests
Unit tests (no credentials required)
pip install -e ".[dev]"
pytest tests/unit/ -v
Integration tests (real API)
export PINERGY_EMAIL=you@example.com
export PINERGY_PASSWORD=your-password
pytest tests/integration/ -v
All tests with coverage
pytest --cov=pypinergy --cov-report=html
Publishing to PyPI
pip install build twine
# Build distribution
python -m build
# Upload to TestPyPI first
twine upload --repository testpypi dist/*
# Upload to PyPI
twine upload dist/*
Or use Trusted Publishing via
GitHub Actions — add a .github/workflows/publish.yml that triggers on a
version tag.
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 pypinergy-0.1.5.tar.gz.
File metadata
- Download URL: pypinergy-0.1.5.tar.gz
- Upload date:
- Size: 22.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
454bb318241d4c2755a765d34826a5beccd792f7e6a5f57bd19333ba754f2d80
|
|
| MD5 |
b476d8e52c8e122d7f2e4dd62de54ffd
|
|
| BLAKE2b-256 |
89dd15621d0a30d6055781eb99cd52b52865651794343f2647da8265e8981d17
|
File details
Details for the file pypinergy-0.1.5-py3-none-any.whl.
File metadata
- Download URL: pypinergy-0.1.5-py3-none-any.whl
- Upload date:
- Size: 12.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e08bea9fb1268cfb433f7698e761bf65143fff99c4f4cfc34f9a3b322e4ca7e2
|
|
| MD5 |
ec80bd94f00610f7ce935638d505c69e
|
|
| BLAKE2b-256 |
6a588d89bb6b756b675b96351bcee2d3dd358bea6ec3a9eef382f5e9ecc4fb89
|