myorlen-api 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. myorlen_api-0.1.0/.claude/settings.local.json +13 -0
  2. myorlen_api-0.1.0/.env.example +7 -0
  3. myorlen_api-0.1.0/.github/workflows/ci.yml +34 -0
  4. myorlen_api-0.1.0/.github/workflows/publish.yml +41 -0
  5. myorlen_api-0.1.0/.gitignore +23 -0
  6. myorlen_api-0.1.0/.mcp.json +9 -0
  7. myorlen_api-0.1.0/CHANGELOG.md +27 -0
  8. myorlen_api-0.1.0/CLAUDE.md +78 -0
  9. myorlen_api-0.1.0/LICENSE +9 -0
  10. myorlen_api-0.1.0/PKG-INFO +240 -0
  11. myorlen_api-0.1.0/README.md +205 -0
  12. myorlen_api-0.1.0/myorlen/__init__.py +27 -0
  13. myorlen_api-0.1.0/myorlen/_helpers.py +130 -0
  14. myorlen_api-0.1.0/myorlen/client.py +347 -0
  15. myorlen_api-0.1.0/myorlen/exceptions.py +22 -0
  16. myorlen_api-0.1.0/myorlen/mcp_server.py +271 -0
  17. myorlen_api-0.1.0/myorlen/models.py +70 -0
  18. myorlen_api-0.1.0/myorlen/py.typed +0 -0
  19. myorlen_api-0.1.0/myorlen/sync.py +77 -0
  20. myorlen_api-0.1.0/pyproject.toml +61 -0
  21. myorlen_api-0.1.0/scripts/smoke_test.py +72 -0
  22. myorlen_api-0.1.0/tests/__init__.py +0 -0
  23. myorlen_api-0.1.0/tests/conftest.py +43 -0
  24. myorlen_api-0.1.0/tests/fixtures/agreements.json +20 -0
  25. myorlen_api-0.1.0/tests/fixtures/balance.json +5 -0
  26. myorlen_api-0.1.0/tests/fixtures/invoices.json +35 -0
  27. myorlen_api-0.1.0/tests/fixtures/login_response.json +6 -0
  28. myorlen_api-0.1.0/tests/fixtures/ppg_list.json +21 -0
  29. myorlen_api-0.1.0/tests/fixtures/user_data.json +7 -0
  30. myorlen_api-0.1.0/tests/test_client.py +132 -0
  31. myorlen_api-0.1.0/tests/test_exceptions.py +31 -0
  32. myorlen_api-0.1.0/tests/test_helpers.py +236 -0
  33. myorlen_api-0.1.0/tests/test_mcp_server.py +319 -0
  34. myorlen_api-0.1.0/tests/test_models.py +68 -0
  35. myorlen_api-0.1.0/uv.lock +1189 -0
@@ -0,0 +1,13 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv sync:*)",
5
+ "Bash(uv run:*)",
6
+ "Bash(python3:*)"
7
+ ]
8
+ },
9
+ "enabledMcpjsonServers": [
10
+ "pgnig",
11
+ "myorlen"
12
+ ]
13
+ }
@@ -0,0 +1,7 @@
1
+ # OrlenID credentials (shared Orlen Group account — takes priority if both pairs are set)
2
+ ORLENID_USERNAME=you@example.com
3
+ ORLENID_PASSWORD=your-orlenid-password
4
+
5
+ # Native myORLEN eBOK credentials (formerly PGNiG)
6
+ # MYORLEN_USERNAME=you@example.com
7
+ # MYORLEN_PASSWORD=your-myorlen-password
@@ -0,0 +1,34 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test (Python ${{ matrix.python-version }})
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python-version: ["3.12", "3.13"]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v4
24
+ with:
25
+ enable-cache: true
26
+
27
+ - name: Install Python ${{ matrix.python-version }}
28
+ run: uv python install ${{ matrix.python-version }}
29
+
30
+ - name: Install dependencies
31
+ run: uv sync --group dev
32
+
33
+ - name: Run tests
34
+ run: uv run pytest -v
@@ -0,0 +1,41 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ publish:
10
+ name: Build and publish
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+
14
+ permissions:
15
+ id-token: write # required for Trusted Publisher OIDC
16
+ contents: read
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0 # required by hatch-vcs to derive version from git tags
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v4
25
+ with:
26
+ enable-cache: true
27
+
28
+ - name: Install Python
29
+ run: uv python install 3.12
30
+
31
+ - name: Install dependencies
32
+ run: uv sync --group dev
33
+
34
+ - name: Run tests
35
+ run: uv run pytest -v
36
+
37
+ - name: Build
38
+ run: uv build
39
+
40
+ - name: Publish to PyPI
41
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,23 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Secrets
13
+ .env
14
+
15
+ # HAR capture files (contain session tokens and credentials)
16
+ *.har
17
+
18
+ # Downloaded invoices
19
+ *.pdf
20
+
21
+ # Editor swap files
22
+ *.swp
23
+ *.swo
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "myorlen": {
4
+ "type": "stdio",
5
+ "command": "uv",
6
+ "args": ["run", "python", "-m", "myorlen.mcp_server"]
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-03-29
11
+
12
+ ### Added
13
+ - Async client (`MyOrlenClient`) with direct myORLEN eBOK login (`/auth/login`)
14
+ - OrlenID federated login (`use_orlenid=True`) — headless Keycloak form submission via `oid-ws.orlen.pl`
15
+ - Sync wrapper (`MyOrlenClientSync`) with a dedicated event loop for use in non-async contexts
16
+ - Transparent re-login on token expiry (tokens last ~1 hour; up to 2 silent re-login attempts)
17
+ - `get_balance()` — current balance and amount to pay
18
+ - `get_invoices(page, page_size)` — paginated invoice list with gas consumption data (`WearM3`, `WearKWH`)
19
+ - `get_invoice_pdf(invoice_number)` — download invoice PDF as raw bytes
20
+ - Cached account data at login: `user_info`, `agreements`, `metering_points`
21
+ - Typed models: `Address`, `MeteringPoint`, `Agreement`, `UserInfo`, `Balance`, `Invoice`
22
+ - Exception hierarchy: `MyOrlenError`, `MyOrlenAuthError`, `MyOrlenForbiddenError`, `MyOrlenNotFoundError`, `MyOrlenAPIError`
23
+ - MCP server (`myorlen.mcp_server`) exposing four tools: `get_account_info`, `get_balance`, `get_invoices`, `download_invoice`
24
+ - `mcp` optional dependency group: `pip install myorlen-api[mcp]`
25
+ - `myorlen-mcp` script entry point
26
+ - Credential auto-detection: `ORLENID_USERNAME`/`ORLENID_PASSWORD` (OrlenID, preferred) or `MYORLEN_USERNAME`/`MYORLEN_PASSWORD` (native)
27
+ - PEP 561 `py.typed` marker
@@ -0,0 +1,78 @@
1
+ # myorlen-api
2
+
3
+ Python client for the myORLEN eBOK self-care portal at `https://ebok.myorlen.pl` (gas accounts, formerly PGNiG).
4
+
5
+ ## Project structure
6
+
7
+ ```
8
+ myorlen/
9
+ ├── client.py # Async MyOrlenClient
10
+ ├── sync.py # MyOrlenClientSync (blocking wrapper)
11
+ ├── models.py # Dataclasses: Address, MeteringPoint, Agreement, UserInfo, Balance, Invoice
12
+ ├── exceptions.py # MyOrlenError hierarchy
13
+ ├── _helpers.py # Response parsers, generate_device_id, token_expires_at
14
+ ├── mcp_server.py # MCP server (requires myorlen-api[mcp])
15
+ └── __init__.py # Public exports
16
+ scripts/
17
+ └── smoke_test.py # End-to-end test, reads env vars
18
+ ```
19
+
20
+ ## Running
21
+
22
+ ```bash
23
+ uv sync
24
+ MYORLEN_USERNAME=... MYORLEN_PASSWORD=... uv run python scripts/smoke_test.py
25
+ ORLENID_USERNAME=... ORLENID_PASSWORD=... uv run python scripts/smoke_test.py
26
+ ```
27
+
28
+ ## Authentication
29
+
30
+ ### Direct login (MYORLEN_USERNAME / MYORLEN_PASSWORD)
31
+ - `POST /auth/login?api-version=3.0` with JSON body containing `identificator`, `accessPin`, `DeviceId`
32
+ - Returns `Token` + `DateExpirationUtc` directly in response body
33
+ - All subsequent requests use `AuthToken: {token}` header (not `Authorization: Bearer`)
34
+ - All requests include `?api-version=3.0`
35
+ - Server returns HTTP 401 + `Code: 1025 "Soft blocked."` for bad credentials
36
+
37
+ ### OrlenID login (ORLENID_USERNAME / ORLENID_PASSWORD)
38
+ Headless OIDC flow — no browser required:
39
+ 1. `POST /auth/oid/init-login` → server generates PKCE server-side, returns `RedirectUrl` to Keycloak
40
+ 2. `GET RedirectUrl` (oid-ws.orlen.pl) → Keycloak login form; accumulates session cookies
41
+ 3. `POST` credentials to form action with `Content-Type: application/x-www-form-urlencoded` → 302 to callback
42
+ 4. `GET /auth/oid/after-login-callback?code=...` → server exchanges code, stores token for DeviceId
43
+ 5. `GET /auth/get-auth-token?deviceId=...` → returns the token
44
+
45
+ ## Known gotchas
46
+
47
+ ### aiohttp session-level Content-Type override
48
+ The session is created with default `Content-Type: application/json`. For the Keycloak form POST,
49
+ this must be explicitly overridden per-request:
50
+ ```python
51
+ session.post(form_action, data={...}, headers={"Content-Type": "application/x-www-form-urlencoded"})
52
+ ```
53
+ Without this, aiohttp sends the wrong content type, Keycloak silently re-renders the form (HTTP 200)
54
+ instead of redirecting, and login fails with no obvious error.
55
+
56
+ ### OIDC callback URL contains IdP domain in query parameter
57
+ The successful redirect from Keycloak looks like:
58
+ ```
59
+ https://ebok.myorlen.pl/auth/oid/after-login-callback?state=...&iss=https%3A%2F%2Foid-ws.orlen.pl%2F...&code=...
60
+ ```
61
+ Checking `"oid-ws.orlen.pl" in location` is a false positive. Use `location.startswith(_BASE_URL)` instead.
62
+
63
+ ### Token expiry
64
+ Tokens expire after ~1 hour (`DateExpirationUtc`). No refresh endpoint exists — re-login on expiry.
65
+ Up to `_MAX_RELOGIN_ATTEMPTS = 2` transparent re-logins before raising `MyOrlenAuthError`.
66
+
67
+ ### MCP server credentials
68
+ Credential env vars (`ORLENID_USERNAME`/`ORLENID_PASSWORD` or `MYORLEN_USERNAME`/`MYORLEN_PASSWORD`)
69
+ are not listed in `.mcp.json` — the subprocess inherits them from the shell environment. This avoids
70
+ Claude Code's "missing environment variables" diagnostic, which fires for any `${VAR}` reference
71
+ whose var is unset. Export the pair you need in `~/.bashrc` / `~/.zshrc` before starting Claude Code.
72
+
73
+ ## Response format
74
+ All API responses wrap data in:
75
+ ```json
76
+ {"Code": 0, "Message": null, "TokenExpireDate": "...", ...data fields...}
77
+ ```
78
+ `Code != 0` means an error. The `check_response_code()` helper raises `MyOrlenAPIError` for these.
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pawel Rapkiewicz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: myorlen-api
3
+ Version: 0.1.0
4
+ Summary: Async/sync Python client for the myORLEN eBOK (formerly PGNiG) self-care portal
5
+ Project-URL: Homepage, https://github.com/vincentto13/myorlen-api
6
+ Project-URL: Source, https://github.com/vincentto13/myorlen-api
7
+ Project-URL: Bug Tracker, https://github.com/vincentto13/myorlen-api/issues
8
+ Author-email: Pawel Rapkiewicz <pawel.rapkiewicz@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Pawel Rapkiewicz
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
+ License-File: LICENSE
19
+ Keywords: api-client,gas,myorlen,orlen,pgnig,poland,utility
20
+ Classifier: Development Status :: 4 - Beta
21
+ Classifier: Framework :: AsyncIO
22
+ Classifier: Intended Audience :: Developers
23
+ Classifier: License :: OSI Approved :: MIT License
24
+ Classifier: Programming Language :: Python :: 3
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Topic :: Internet :: WWW/HTTP
28
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.12
31
+ Requires-Dist: aiohttp>=3.9
32
+ Provides-Extra: mcp
33
+ Requires-Dist: mcp>=1.0.0; extra == 'mcp'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # myorlen-api
37
+
38
+ Python client library for the [myORLEN eBOK](https://ebok.myorlen.pl) self-care portal (gas accounts, formerly PGNiG).
39
+ Reverse-engineered from browser traffic. Supports both async and sync usage.
40
+
41
+ ## Features
42
+
43
+ - Direct myORLEN eBOK login and OrlenID federated login (shared Orlen Group account)
44
+ - Automatic re-login on token expiry (tokens last ~1 hour, no refresh endpoint)
45
+ - Fetch account balance
46
+ - List invoices with pagination
47
+ - Download invoice PDFs
48
+ - MCP server for Claude and other AI assistants
49
+
50
+ ## Installation
51
+
52
+ ### From PyPI
53
+
54
+ ```bash
55
+ pip install myorlen-api
56
+ ```
57
+
58
+ ### From source (with [uv](https://docs.astral.sh/uv/))
59
+
60
+ ```bash
61
+ git clone https://github.com/vincentto13/myorlen-api.git
62
+ cd myorlen-api
63
+ uv sync
64
+ ```
65
+
66
+ ## Configuration
67
+
68
+ Set credentials for one of the following:
69
+
70
+ | Env vars | Login mode | Notes |
71
+ |---|---|---|
72
+ | `ORLENID_USERNAME` + `ORLENID_PASSWORD` | OrlenID (`oid-ws.orlen.pl`) | Shared across Orlen Group services. Takes priority if both pairs are set. |
73
+ | `MYORLEN_USERNAME` + `MYORLEN_PASSWORD` | Native myORLEN eBOK | Use if you have a standalone account (formerly PGNiG). |
74
+
75
+ ## Usage
76
+
77
+ ### Async
78
+
79
+ ```python
80
+ from myorlen import MyOrlenClient
81
+
82
+ # Native myORLEN eBOK credentials
83
+ async with MyOrlenClient("user@example.com", "password") as client:
84
+ ...
85
+
86
+ # OrlenID credentials (shared Orlen Group account)
87
+ async with MyOrlenClient("user@example.com", "orlenid-password", use_orlenid=True) as client:
88
+ print(client.user_info.first_name, client.user_info.last_name)
89
+
90
+ for agreement in client.agreements:
91
+ print(agreement.agreement_number, "active:", agreement.active)
92
+
93
+ for mp in client.metering_points:
94
+ print(mp.ppg_id, mp.tariff, mp.address)
95
+
96
+ balance = await client.get_balance()
97
+ print(balance.value, "PLN to pay:", balance.amount_to_pay, "PLN")
98
+
99
+ invoices = await client.get_invoices(page=1, page_size=12)
100
+ for inv in invoices:
101
+ print(inv.invoice_number, inv.issue_date, inv.amount, "PLN paid:", inv.is_paid)
102
+
103
+ # Download a PDF
104
+ pdf = await client.get_invoice_pdf(invoices[0].invoice_number)
105
+ ```
106
+
107
+ ### Sync
108
+
109
+ ```python
110
+ from myorlen import MyOrlenClientSync
111
+
112
+ with MyOrlenClientSync("user@example.com", "orlenid-password", use_orlenid=True) as client:
113
+ balance = client.get_balance()
114
+ invoices = client.get_invoices()
115
+ pdf = client.get_invoice_pdf(invoices[0].invoice_number)
116
+ ```
117
+
118
+ ## Development
119
+
120
+ ### MCP server — local setup with Claude Code
121
+
122
+ **1. Install the MCP extra**
123
+
124
+ ```bash
125
+ uv sync --extra mcp
126
+ ```
127
+
128
+ **2. Export your credentials**
129
+
130
+ ```bash
131
+ # OrlenID (recommended — works across all Orlen Group services)
132
+ export ORLENID_USERNAME=you@example.com
133
+ export ORLENID_PASSWORD=your-orlenid-password
134
+
135
+ # — or — native myORLEN eBOK credentials
136
+ export MYORLEN_USERNAME=you@example.com
137
+ export MYORLEN_PASSWORD=your-myorlen-password
138
+ ```
139
+
140
+ Persist in `~/.bashrc` / `~/.zshrc` so they're always available.
141
+
142
+ **3. Verify the server starts**
143
+
144
+ ```bash
145
+ uv run python -m myorlen.mcp_server
146
+ ```
147
+
148
+ The process should start and wait for MCP input on stdin (no output is normal — that's correct stdio behaviour). Press `Ctrl+C` to stop.
149
+
150
+ **4. Connect Claude Code**
151
+
152
+ The repo includes a `.mcp.json` that points Claude Code at the server automatically.
153
+ Open Claude Code from this project directory — it will pick up `.mcp.json` and prompt you to approve the server on first use.
154
+
155
+ Check the connection inside a Claude Code session:
156
+
157
+ ```
158
+ /mcp
159
+ ```
160
+
161
+ You should see `myorlen` listed as connected with 4 tools.
162
+
163
+ ### Running the smoke test (live API)
164
+
165
+ ```bash
166
+ # OrlenID credentials
167
+ ORLENID_USERNAME=you@example.com ORLENID_PASSWORD=orlenid-secret uv run python scripts/smoke_test.py
168
+
169
+ # Native myORLEN credentials
170
+ MYORLEN_USERNAME=you@example.com MYORLEN_PASSWORD=myorlen-secret uv run python scripts/smoke_test.py
171
+ ```
172
+
173
+ ## MCP Server
174
+
175
+ The library ships an [MCP](https://modelcontextprotocol.io) server that exposes your myORLEN gas account
176
+ as tools for Claude and other MCP-compatible AI assistants.
177
+
178
+ ### Install
179
+
180
+ ```bash
181
+ pip install myorlen-api[mcp]
182
+ ```
183
+
184
+ ### Available tools
185
+
186
+ | Tool | Description |
187
+ |---|---|
188
+ | `get_account_info` | User profile, agreements and metering points (cached, no network request) |
189
+ | `get_balance` | Current balance and amount to pay |
190
+ | `get_invoices` | Paginated invoice list with gas consumption data |
191
+ | `download_invoice` | Download a PDF invoice — saves to a temp file and returns the path |
192
+
193
+ ### Run standalone
194
+
195
+ ```bash
196
+ # OrlenID credentials
197
+ ORLENID_USERNAME=you@example.com ORLENID_PASSWORD=orlenid-secret uv run python -m myorlen.mcp_server
198
+
199
+ # Native myORLEN credentials
200
+ MYORLEN_USERNAME=you@example.com MYORLEN_PASSWORD=myorlen-secret uv run python -m myorlen.mcp_server
201
+ ```
202
+
203
+ ### Claude Desktop configuration
204
+
205
+ Add to `~/.config/claude/claude_desktop_config.json`:
206
+
207
+ ```json
208
+ {
209
+ "mcpServers": {
210
+ "myorlen": {
211
+ "command": "uv",
212
+ "args": ["run", "--project", "/path/to/myorlen-api", "python", "-m", "myorlen.mcp_server"],
213
+ "env": {
214
+ "ORLENID_USERNAME": "you@example.com",
215
+ "ORLENID_PASSWORD": "your-orlenid-password"
216
+ }
217
+ }
218
+ }
219
+ }
220
+ ```
221
+
222
+ Use `ORLENID_USERNAME`/`ORLENID_PASSWORD` for OrlenID, or `MYORLEN_USERNAME`/`MYORLEN_PASSWORD` for native login. If both are set, OrlenID takes priority.
223
+
224
+ > **Note:** Tokens expire after ~1 hour. The client re-logs in transparently up to 2 times before raising an error — no manual restart needed in most cases.
225
+
226
+ ### Example prompts
227
+
228
+ Ask Claude naturally:
229
+
230
+ - *"What's my gas balance?"*
231
+ - *"Show me my last 3 gas invoices"*
232
+ - *"Do I have any unpaid gas bills?"*
233
+ - *"What's my gas consumption for the last invoice?"*
234
+ - *"Download the latest invoice"*
235
+
236
+ ## Acknowledgements
237
+
238
+ This library was built with the help of [Claude](https://claude.ai) (Anthropic's AI assistant).
239
+ Claude assisted with reverse-engineering the authentication flow from browser HAR captures,
240
+ designing the library architecture, and implementing the async/sync client and MCP server.
@@ -0,0 +1,205 @@
1
+ # myorlen-api
2
+
3
+ Python client library for the [myORLEN eBOK](https://ebok.myorlen.pl) self-care portal (gas accounts, formerly PGNiG).
4
+ Reverse-engineered from browser traffic. Supports both async and sync usage.
5
+
6
+ ## Features
7
+
8
+ - Direct myORLEN eBOK login and OrlenID federated login (shared Orlen Group account)
9
+ - Automatic re-login on token expiry (tokens last ~1 hour, no refresh endpoint)
10
+ - Fetch account balance
11
+ - List invoices with pagination
12
+ - Download invoice PDFs
13
+ - MCP server for Claude and other AI assistants
14
+
15
+ ## Installation
16
+
17
+ ### From PyPI
18
+
19
+ ```bash
20
+ pip install myorlen-api
21
+ ```
22
+
23
+ ### From source (with [uv](https://docs.astral.sh/uv/))
24
+
25
+ ```bash
26
+ git clone https://github.com/vincentto13/myorlen-api.git
27
+ cd myorlen-api
28
+ uv sync
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ Set credentials for one of the following:
34
+
35
+ | Env vars | Login mode | Notes |
36
+ |---|---|---|
37
+ | `ORLENID_USERNAME` + `ORLENID_PASSWORD` | OrlenID (`oid-ws.orlen.pl`) | Shared across Orlen Group services. Takes priority if both pairs are set. |
38
+ | `MYORLEN_USERNAME` + `MYORLEN_PASSWORD` | Native myORLEN eBOK | Use if you have a standalone account (formerly PGNiG). |
39
+
40
+ ## Usage
41
+
42
+ ### Async
43
+
44
+ ```python
45
+ from myorlen import MyOrlenClient
46
+
47
+ # Native myORLEN eBOK credentials
48
+ async with MyOrlenClient("user@example.com", "password") as client:
49
+ ...
50
+
51
+ # OrlenID credentials (shared Orlen Group account)
52
+ async with MyOrlenClient("user@example.com", "orlenid-password", use_orlenid=True) as client:
53
+ print(client.user_info.first_name, client.user_info.last_name)
54
+
55
+ for agreement in client.agreements:
56
+ print(agreement.agreement_number, "active:", agreement.active)
57
+
58
+ for mp in client.metering_points:
59
+ print(mp.ppg_id, mp.tariff, mp.address)
60
+
61
+ balance = await client.get_balance()
62
+ print(balance.value, "PLN to pay:", balance.amount_to_pay, "PLN")
63
+
64
+ invoices = await client.get_invoices(page=1, page_size=12)
65
+ for inv in invoices:
66
+ print(inv.invoice_number, inv.issue_date, inv.amount, "PLN paid:", inv.is_paid)
67
+
68
+ # Download a PDF
69
+ pdf = await client.get_invoice_pdf(invoices[0].invoice_number)
70
+ ```
71
+
72
+ ### Sync
73
+
74
+ ```python
75
+ from myorlen import MyOrlenClientSync
76
+
77
+ with MyOrlenClientSync("user@example.com", "orlenid-password", use_orlenid=True) as client:
78
+ balance = client.get_balance()
79
+ invoices = client.get_invoices()
80
+ pdf = client.get_invoice_pdf(invoices[0].invoice_number)
81
+ ```
82
+
83
+ ## Development
84
+
85
+ ### MCP server — local setup with Claude Code
86
+
87
+ **1. Install the MCP extra**
88
+
89
+ ```bash
90
+ uv sync --extra mcp
91
+ ```
92
+
93
+ **2. Export your credentials**
94
+
95
+ ```bash
96
+ # OrlenID (recommended — works across all Orlen Group services)
97
+ export ORLENID_USERNAME=you@example.com
98
+ export ORLENID_PASSWORD=your-orlenid-password
99
+
100
+ # — or — native myORLEN eBOK credentials
101
+ export MYORLEN_USERNAME=you@example.com
102
+ export MYORLEN_PASSWORD=your-myorlen-password
103
+ ```
104
+
105
+ Persist in `~/.bashrc` / `~/.zshrc` so they're always available.
106
+
107
+ **3. Verify the server starts**
108
+
109
+ ```bash
110
+ uv run python -m myorlen.mcp_server
111
+ ```
112
+
113
+ The process should start and wait for MCP input on stdin (no output is normal — that's correct stdio behaviour). Press `Ctrl+C` to stop.
114
+
115
+ **4. Connect Claude Code**
116
+
117
+ The repo includes a `.mcp.json` that points Claude Code at the server automatically.
118
+ Open Claude Code from this project directory — it will pick up `.mcp.json` and prompt you to approve the server on first use.
119
+
120
+ Check the connection inside a Claude Code session:
121
+
122
+ ```
123
+ /mcp
124
+ ```
125
+
126
+ You should see `myorlen` listed as connected with 4 tools.
127
+
128
+ ### Running the smoke test (live API)
129
+
130
+ ```bash
131
+ # OrlenID credentials
132
+ ORLENID_USERNAME=you@example.com ORLENID_PASSWORD=orlenid-secret uv run python scripts/smoke_test.py
133
+
134
+ # Native myORLEN credentials
135
+ MYORLEN_USERNAME=you@example.com MYORLEN_PASSWORD=myorlen-secret uv run python scripts/smoke_test.py
136
+ ```
137
+
138
+ ## MCP Server
139
+
140
+ The library ships an [MCP](https://modelcontextprotocol.io) server that exposes your myORLEN gas account
141
+ as tools for Claude and other MCP-compatible AI assistants.
142
+
143
+ ### Install
144
+
145
+ ```bash
146
+ pip install myorlen-api[mcp]
147
+ ```
148
+
149
+ ### Available tools
150
+
151
+ | Tool | Description |
152
+ |---|---|
153
+ | `get_account_info` | User profile, agreements and metering points (cached, no network request) |
154
+ | `get_balance` | Current balance and amount to pay |
155
+ | `get_invoices` | Paginated invoice list with gas consumption data |
156
+ | `download_invoice` | Download a PDF invoice — saves to a temp file and returns the path |
157
+
158
+ ### Run standalone
159
+
160
+ ```bash
161
+ # OrlenID credentials
162
+ ORLENID_USERNAME=you@example.com ORLENID_PASSWORD=orlenid-secret uv run python -m myorlen.mcp_server
163
+
164
+ # Native myORLEN credentials
165
+ MYORLEN_USERNAME=you@example.com MYORLEN_PASSWORD=myorlen-secret uv run python -m myorlen.mcp_server
166
+ ```
167
+
168
+ ### Claude Desktop configuration
169
+
170
+ Add to `~/.config/claude/claude_desktop_config.json`:
171
+
172
+ ```json
173
+ {
174
+ "mcpServers": {
175
+ "myorlen": {
176
+ "command": "uv",
177
+ "args": ["run", "--project", "/path/to/myorlen-api", "python", "-m", "myorlen.mcp_server"],
178
+ "env": {
179
+ "ORLENID_USERNAME": "you@example.com",
180
+ "ORLENID_PASSWORD": "your-orlenid-password"
181
+ }
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ Use `ORLENID_USERNAME`/`ORLENID_PASSWORD` for OrlenID, or `MYORLEN_USERNAME`/`MYORLEN_PASSWORD` for native login. If both are set, OrlenID takes priority.
188
+
189
+ > **Note:** Tokens expire after ~1 hour. The client re-logs in transparently up to 2 times before raising an error — no manual restart needed in most cases.
190
+
191
+ ### Example prompts
192
+
193
+ Ask Claude naturally:
194
+
195
+ - *"What's my gas balance?"*
196
+ - *"Show me my last 3 gas invoices"*
197
+ - *"Do I have any unpaid gas bills?"*
198
+ - *"What's my gas consumption for the last invoice?"*
199
+ - *"Download the latest invoice"*
200
+
201
+ ## Acknowledgements
202
+
203
+ This library was built with the help of [Claude](https://claude.ai) (Anthropic's AI assistant).
204
+ Claude assisted with reverse-engineering the authentication flow from browser HAR captures,
205
+ designing the library architecture, and implementing the async/sync client and MCP server.