Scraper for the Minol Kundenportal (utility metering data)
Project description
Minol Kundenportal Scraper
A Python scraper that authenticates to the Minol Kundenportal and fetches consumption data (heating, warm water, cold water) on a per-room basis. Pure Python — stdlib only, no third-party dependencies.
For authentication internals, data endpoint reference, and debugging, see DEVELOPMENT.md.
Credentials
Credentials are resolved in order: CLI arguments > environment variables > config file.
| Source | Password | User Number | |
|---|---|---|---|
| CLI | --email |
--password |
--user-num |
| Env var | MINOL_EMAIL |
MINOL_PASSWORD |
MINOL_USER_NUM |
| Config file | email |
password |
user_num |
The default config file location is ~/.minol.json (override with --config):
{
"email": "user@example.com",
"password": "password",
"user_num": "000000000000"
}
Password security
Avoid --password on shared systems. Any value passed via --password is visible to other local users in the process listing (ps aux) and in /proc/PID/cmdline for the lifetime of the process.
Safer alternatives, in order of preference:
-
Config file — store credentials in
~/.minol.jsonand restrict access:chmod 600 ~/.minol.json
The scraper warns at startup if the file is readable by group or other users.
-
Environment variables — set
MINOL_EMAIL,MINOL_PASSWORD, andMINOL_USER_NUMin your shell profile or via a secrets manager. -
--password-stdin— pipe the password from a secrets store or a variable, avoiding it ever appearing in the argument list:echo "$MINOL_PASSWORD" | minol --email 'user@example.com' --user-num '000000000000' --password-stdin # Or from a file: minol --email 'user@example.com' --user-num '000000000000' --password-stdin < ~/.minol_password
The session cache (~/.minol_session.json) is created with permissions 0600 (owner-read-write only) and contains the session token rather than the plaintext password. See Session Caching.
Installation
Install from PyPI:
pip install minol
Or install from source:
git clone https://codeberg.org/BastiOfBerlin/minol
cd minol
pip install .
python -m minol also works without installation — just clone the repo and run from the project root.
Note for bind-mounted filesystems (e.g. container setup: some mounts do not support atomic file rename, which causes
pip installto fail withEPERM. Install from a/tmpcopy instead:cp -r /workspace/minol /workspace/pyproject.toml /workspace/README.md /workspace/LICENSE /tmp/minol-build/ pip install /tmp/minol-build
Usage
All examples use the minol console script installed by pip install minol. If you are running from source without installing, substitute python -m minol for minol.
# Fetch all consumption types, last 12 months
minol \
--email 'user@example.com' \
--password 'password' \
--user-num '000000000000'
# Heating only, specific date range, verbose, save to file
minol \
--email 'user@example.com' \
--password 'password' \
--user-num '000000000000' \
--type heating \
--start 202501 \
--end 202603 \
--output consumption.json \
-v
# Warm water in KWH instead of the default M3
minol \
--email 'user@example.com' \
--password 'password' \
--user-num '000000000000' \
--type warm_water \
--unit kwh
# Raw API response (unprocessed JSON from the portal)
minol \
--email 'user@example.com' \
--password 'password' \
--user-num '000000000000' \
--raw
# Credentials from env vars or ~/.minol.json — no flags needed
minol
Shell escaping — Passwords containing
$,!, backticks, or backslashes will be mangled by bash in double quotes. Always use single quotes for--passwordand--password-stdinto avoid the issue entirely.
Output Format
By default the scraper returns structured data with only the relevant fields:
{
"unit": "KWH",
"rooms": {
"Küche": {
"total": 111.0,
"device": "04B648FD82639440",
"monthly": {
"202503": 0,
"202504": 5.107,
"202505": null
}
}
}
}
unit—"KWH"(heating) or"M3"(warm water, cold water) by default. Override with--unit kwhor--unit m3.rooms— keyed by room name; each entry hastotal,device, andmonthly(nullfor months with no data yet).
Pass --raw to get the unprocessed API response instead.
Programmatic Usage
from minol import MinolScraper
scraper = MinolScraper("user@example.com", "password", "000000000000")
scraper.login()
# Parsed structured data (default)
heating = scraper.fetch_heating(timeline_start="202501", timeline_end="202603")
warm = scraper.fetch_warm_water()
cold = scraper.fetch_cold_water()
all_data = scraper.fetch_all()
# Override unit of measurement (warm water defaults to M3)
warm_kwh = scraper.fetch_warm_water(unit="kwh")
# Raw API response
all_raw = scraper.fetch_all_raw()
heating_raw = scraper.fetch_heating(raw=True)
# Force fresh login (skip session cache)
scraper.login(use_cache=False)
Session Caching
After a successful login the scraper saves session cookies and the token expiry timestamp to ~/.minol_session.json. On the next run, expired tokens are rejected immediately without a network request; still-valid tokens are restored from the cache, skipping the full SAML login. Pass --no-cache to force a fresh login.
Project details
Release history Release notifications | RSS feed
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 minol-1.0.1.tar.gz.
File metadata
- Download URL: minol-1.0.1.tar.gz
- Upload date:
- Size: 30.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c666e25ae5c15e1362095c61c8de088a8d3238aea9d3920ccb3539ad175ea91d
|
|
| MD5 |
f78497b136dd8c82fe09e998703637f8
|
|
| BLAKE2b-256 |
4ac039020d09bf5041c883267c57404c868450b9a2753e807d535e65e749bd7a
|
Provenance
The following attestation bundles were made for minol-1.0.1.tar.gz:
Publisher:
ci.yml on BastiOfBerlin/minol
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minol-1.0.1.tar.gz -
Subject digest:
c666e25ae5c15e1362095c61c8de088a8d3238aea9d3920ccb3539ad175ea91d - Sigstore transparency entry: 1102965041
- Sigstore integration time:
-
Permalink:
BastiOfBerlin/minol@5165a731410756a59e325cf0c3ecc07f132b6d91 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/BastiOfBerlin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@5165a731410756a59e325cf0c3ecc07f132b6d91 -
Trigger Event:
push
-
Statement type:
File details
Details for the file minol-1.0.1-py3-none-any.whl.
File metadata
- Download URL: minol-1.0.1-py3-none-any.whl
- Upload date:
- Size: 19.7 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 |
76397ee9651f437cdf70365169cab85232e5bf8cd9e410e22021bb6969d31159
|
|
| MD5 |
9df6f79e7dad69d3718b1831421a3540
|
|
| BLAKE2b-256 |
eecfd63b6049311beb40df476f1ba151f1b7fa67564a665dfa20fbc07748bf0f
|
Provenance
The following attestation bundles were made for minol-1.0.1-py3-none-any.whl:
Publisher:
ci.yml on BastiOfBerlin/minol
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minol-1.0.1-py3-none-any.whl -
Subject digest:
76397ee9651f437cdf70365169cab85232e5bf8cd9e410e22021bb6969d31159 - Sigstore transparency entry: 1102965122
- Sigstore integration time:
-
Permalink:
BastiOfBerlin/minol@5165a731410756a59e325cf0c3ecc07f132b6d91 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/BastiOfBerlin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@5165a731410756a59e325cf0c3ecc07f132b6d91 -
Trigger Event:
push
-
Statement type: