myorlen-ebok-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 (41) hide show
  1. myorlen_ebok_api-0.1.0/.gitignore +22 -0
  2. myorlen_ebok_api-0.1.0/AGENTS.md +91 -0
  3. myorlen_ebok_api-0.1.0/API.md +142 -0
  4. myorlen_ebok_api-0.1.0/LICENSE +21 -0
  5. myorlen_ebok_api-0.1.0/PKG-INFO +123 -0
  6. myorlen_ebok_api-0.1.0/README.md +101 -0
  7. myorlen_ebok_api-0.1.0/pyproject.toml +69 -0
  8. myorlen_ebok_api-0.1.0/src/myorlen_ebok_api/__init__.py +66 -0
  9. myorlen_ebok_api-0.1.0/src/myorlen_ebok_api/__main__.py +3 -0
  10. myorlen_ebok_api-0.1.0/src/myorlen_ebok_api/cli.py +184 -0
  11. myorlen_ebok_api-0.1.0/src/myorlen_ebok_api/client.py +519 -0
  12. myorlen_ebok_api-0.1.0/src/myorlen_ebok_api/exceptions.py +29 -0
  13. myorlen_ebok_api-0.1.0/src/myorlen_ebok_api/models.py +318 -0
  14. myorlen_ebok_api-0.1.0/src/myorlen_ebok_api/parsers.py +479 -0
  15. myorlen_ebok_api-0.1.0/src/myorlen_ebok_api/py.typed +0 -0
  16. myorlen_ebok_api-0.1.0/tests/__init__.py +0 -0
  17. myorlen_ebok_api-0.1.0/tests/conftest.py +63 -0
  18. myorlen_ebok_api-0.1.0/tests/fixtures/account_history.json +36 -0
  19. myorlen_ebok_api-0.1.0/tests/fixtures/agreement_details.json +105 -0
  20. myorlen_ebok_api-0.1.0/tests/fixtures/agreements.json +104 -0
  21. myorlen_ebok_api-0.1.0/tests/fixtures/billing_summary.json +11 -0
  22. myorlen_ebok_api-0.1.0/tests/fixtures/client_payments.json +193 -0
  23. myorlen_ebok_api-0.1.0/tests/fixtures/contact_history.json +11 -0
  24. myorlen_ebok_api-0.1.0/tests/fixtures/email_notif.json +13 -0
  25. myorlen_ebok_api-0.1.0/tests/fixtures/invoice_details.json +61 -0
  26. myorlen_ebok_api-0.1.0/tests/fixtures/invoices.json +532 -0
  27. myorlen_ebok_api-0.1.0/tests/fixtures/invoices_p2.json +102 -0
  28. myorlen_ebok_api-0.1.0/tests/fixtures/monthly.json +23 -0
  29. myorlen_ebok_api-0.1.0/tests/fixtures/note_details.json +40 -0
  30. myorlen_ebok_api-0.1.0/tests/fixtures/notifications.json +43 -0
  31. myorlen_ebok_api-0.1.0/tests/fixtures/ppg_list.json +46 -0
  32. myorlen_ebok_api-0.1.0/tests/fixtures/reading_dates.json +13 -0
  33. myorlen_ebok_api-0.1.0/tests/fixtures/readings.json +78 -0
  34. myorlen_ebok_api-0.1.0/tests/fixtures/related.json +22 -0
  35. myorlen_ebok_api-0.1.0/tests/fixtures/unread.json +10 -0
  36. myorlen_ebok_api-0.1.0/tests/fixtures/user_data.json +64 -0
  37. myorlen_ebok_api-0.1.0/tests/fixtures/yearly.json +43 -0
  38. myorlen_ebok_api-0.1.0/tests/test_cli.py +150 -0
  39. myorlen_ebok_api-0.1.0/tests/test_client.py +317 -0
  40. myorlen_ebok_api-0.1.0/tests/test_live.py +78 -0
  41. myorlen_ebok_api-0.1.0/tests/test_parsers.py +203 -0
@@ -0,0 +1,22 @@
1
+ # credentials and personal downloads - never commit
2
+ .env
3
+ .env.*
4
+ session.json
5
+ *.pdf
6
+ *.xlsx
7
+ *.csv
8
+
9
+ # python / tooling
10
+ __pycache__/
11
+ *.py[cod]
12
+ .venv/
13
+ build/
14
+ dist/
15
+ *.egg-info/
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ .coverage
20
+ coverage.xml
21
+ htmlcov/
22
+ .session/
@@ -0,0 +1,91 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for coding agents working on this repository.
4
+
5
+ ## What this is
6
+
7
+ `myorlen-ebok-api`: an unofficial Python SDK + CLI for the myORLEN gas eBOK
8
+ (https://ebok.myorlen.pl; previously PGNiG eBOK at ebok.pgnig.pl). The portal is a React SPA over a JSON API
9
+ on the same host; the SDK logs in with e-mail + password, keeps the returned token and calls
10
+ the same endpoints. **API.md is the endpoint reference**: read it before touching the client,
11
+ and update it whenever you learn something new about the site.
12
+
13
+ ## Layout
14
+
15
+ ```
16
+ src/myorlen_ebok_api/
17
+ client.py MyorlenClient: login, token handling, one method per feature (I/O only)
18
+ parsers.py pure functions JSON -> models; all field names of the API live here
19
+ models.py dataclasses (Decimal money, date dates), InvoiceFilter enum, to_jsonable()
20
+ exceptions.py MyorlenError > AuthenticationError | ApiError | ParseError | NotFoundError
21
+ cli.py `myorlen-ebok-api` argparse CLI, JSON on stdout
22
+ tests/
23
+ fixtures/ ANONYMISED captures of real API responses
24
+ test_parsers.py parsers against fixtures
25
+ test_client.py client flows with HTTP mocked by `responses` (asserts request params too)
26
+ test_cli.py CLI end to end with mocked HTTP
27
+ test_live.py read-only checks against the real site, skipped unless MYORLEN_LIVE=1
28
+ ```
29
+
30
+ ## Commands
31
+
32
+ ```bash
33
+ uv sync # install (uses uv.lock)
34
+ uv run pytest # offline test suite, must stay green
35
+ uv run ruff check . && uv run ruff format --check .
36
+ uv run mypy # strict mode on src/
37
+ MYORLEN_LIVE=1 uv run pytest -m live # needs MYORLEN_USER / MYORLEN_PASS (env or .env)
38
+ uv run myorlen-ebok-api --help # CLI
39
+ ```
40
+
41
+ Add dependencies with `uv add` / `uv add --dev` so `uv.lock` stays in sync. CI runs ruff,
42
+ mypy and pytest on Python 3.10–3.13 (`.github/workflows/ci.yml`).
43
+
44
+ ## How a feature works (pattern to follow)
45
+
46
+ 1. Find the route name in the web bundle (`https://ebok.myorlen.pl/static/js/main.js`): the
47
+ route table is `var H={auth:{…},crm:{…}}`, call sites look like
48
+ `Ct.getData("get_invoices_v2",{pageNumber:1,pageSize:12})` or `Ct.get("crm.get_ppg_list")`.
49
+ 2. Call it through `MyorlenClient._get()` (adds `api-version=3.0` and the `AuthToken` header,
50
+ re-logs in once on 401, raises `ApiError` on a non-zero `Code`) or `_download()` for files.
51
+ 3. Parse with a pure function in `parsers.py` into a dataclass from `models.py`.
52
+
53
+ Keep I/O in `client.py` and parsing in `parsers.py`. New parsers get a fixture + test in
54
+ `test_parsers.py`; new client methods get a `responses` test that also asserts the query sent
55
+ (see existing tests).
56
+
57
+ ## Site quirks worth knowing
58
+
59
+ - Every call needs `api-version=3.0`; the token goes in the `AuthToken` header, not a cookie.
60
+ - Wrong credentials give `403` with an empty body, not a JSON error.
61
+ - The token expires after about an hour (sliding, see `TokenExpireDateUtc` in every answer);
62
+ the SDK caches it in the session file with a stable `DeviceId`.
63
+ - `get-user-data` may answer `Code: 1` on success.
64
+ - `get-invoices-v2` rejects `type=` with 400; use the boolean filters (API.md).
65
+ - `PdfExists` in invoice lists is always false; ask the PDF endpoint directly. Interest-note PDFs
66
+ often come back as `text/plain` "Brak podglądu PDF…" instead of a PDF.
67
+ - `get-interest-note-details` uses different field names than invoices (`DocumentNumber`,
68
+ `DateCreated`, `DatePaymentDeadline`).
69
+
70
+ ## Releasing
71
+
72
+ Releases go to PyPI from GitHub Actions (`.github/workflows/release.yml`) via trusted
73
+ publishing; there is no token to manage.
74
+
75
+ 1. Bump the version: `uv version --bump patch` (or `minor` / `major`); this updates `pyproject.toml` and `uv.lock`.
76
+ 2. Commit, then tag and push: `git tag v$(uv version --short) && git push && git push --tags`.
77
+ 3. The workflow runs CI, checks the tag matches the version, builds and smoke-tests the wheel,
78
+ publishes to https://pypi.org/project/myorlen-ebok-api/ and creates a GitHub release with the files attached.
79
+
80
+ PyPI versions can never be re-uploaded, so a broken release needs a new version number.
81
+
82
+ ## Safety rules
83
+
84
+ - **Never commit personal data.** `.env` holds real credentials and is gitignored. Fixtures must
85
+ be anonymised: replace names, addresses, client / agreement / PPG / meter numbers, e-mails,
86
+ phone, PESEL, IBANs, document numbers, UUIDs and tokens before saving into `tests/fixtures/`.
87
+ Grep the fixtures for the real values before committing.
88
+ - Live exploration is read-only. Do not call payment endpoints (`pay-for-*`), reading
89
+ submission (`add-ppg-reading*`, `cancel-reading`), contact forms, agreement changes, e-invoice
90
+ toggles or account settings. If the SDK ever exposes a write endpoint, test it with mocks only.
91
+ - Keep request volume low when exploring (one login, reuse the token).
@@ -0,0 +1,142 @@
1
+ # myORLEN eBOK (ebok.myorlen.pl, formerly PGNiG eBOK): reverse-engineered API reference
2
+
3
+ Mapped in November 2026 against a live household account (one gas agreement, tariff W-2.1)
4
+ and the web app bundle (`/static/js/main.js?v=1.1646`). Unofficial: the operator can change
5
+ any of this without notice. Implemented in `src/myorlen_ebok_api/client.py` (HTTP) and
6
+ `src/myorlen_ebok_api/parsers.py` (JSON parsing). All example values below are fake.
7
+
8
+ > **Host.** `https://ebok.pgnig.pl/` now redirects (301) to `https://ebok.myorlen.pl/`.
9
+ > Only the new host is used.
10
+
11
+ ## Architecture
12
+
13
+ - `ebok.myorlen.pl` serves a **React SPA**; its JSON API lives on the same origin under
14
+ `/auth/…`, `/crm/…`, `/notification/…`, `/contact/…`, `/agreements/…`, `/cms/…`.
15
+ The bundle has the full route table (`var H={auth:{login:"/auth/login",…},crm:{…}}`).
16
+ - Every request carries the query parameter **`api-version=3.0`**.
17
+ - Authenticated requests send the token in the **`AuthToken`** request header (no cookies needed).
18
+ - JSON is PascalCase. Every answer has an envelope:
19
+ `Code` (0 = OK), `Message`, `DisplayToEndUser`, `EndUserMessage`, `TokenExpireDate`,
20
+ `TokenExpireDateUtc` (sliding token expiry), `ConfigUpdateDateUtc`.
21
+ `crm/get-user-data` can answer `Code: 1` on success (the web app treats it as 0).
22
+ - Timestamps: `2026-10-27T00:00:00.000000Z`. Query dates are sent as `Date.toISOString()`,
23
+ e.g. `2025-01-01T00:00:00.000Z`. Amounts are JSON numbers (PLN), volumes in m³, energy in kWh.
24
+ - Missing / invalid token → `401 {"Message": "Authorization has been denied for this request."}`.
25
+ - Bad request parameters → `400` with `Code: 1`.
26
+
27
+ ## Authentication
28
+
29
+ `POST /auth/login?api-version=3.0`, JSON body:
30
+
31
+ ```json
32
+ {"Identificator": "you@example.com", "AccessPin": "password",
33
+ "DeviceType": "Web", "DeviceId": "<uuid>", "DeviceName": "…"}
34
+ ```
35
+
36
+ Success → `200` with `Token`, `DateExpirationUtc` (≈1 h), `AutomaticLoginToken` (for
37
+ `/auth/login-automatic-token`, "remember me"), flags (`NewUser`, `ForceChangePasswordByInvalidPolicy`, …).
38
+ Wrong credentials → `403` with an empty body. Other failures carry `Code`: 1011 unauthorised
39
+ device, 1015 user not found, 1018 not activated, 1025 temporarily blocked (5 minutes).
40
+ No captcha on this endpoint (the site has a reCAPTCHA key, used by other forms).
41
+
42
+ `DeviceId` shows up in the account's device list (`/auth/get-devices`); the SDK keeps it stable
43
+ in the session file. Logout: `POST /auth/logout` with `{}` and the `AuthToken` header.
44
+
45
+ The site also has an OpenID flow (`/auth/oid/init-login` → `RedirectUrl`), used for accounts
46
+ migrated to the shared ORLEN identity; the SDK uses the classic login above.
47
+
48
+ ## Endpoints (all `GET`, all need `AuthToken`)
49
+
50
+ ### Account
51
+
52
+ | Endpoint | Params | Response |
53
+ |---|---|---|
54
+ | `/crm/get-user-data` | – | `Email`, `FirstName`, `LastName`, `Pesel` (masked `800********`), `Nip`, `Saldo`, `AmountToPay`, `Billing`, `HasPrepaid`, `Acceptances[]` (consent texts), … |
55
+ | `/auth/get-related-accounts` | – | `RelatedAccounts[]`: `PublicId`, `ClientNumber`, `Current`, `ClientName`, `Pesel` |
56
+ | `/crm/get-other-accounts` | – | `Results[]`: other eBOK logins for the same PESEL (masked e-mail) |
57
+ | `/crm/get-billing-summary` | – | `{Status: "Paid", Value: null}` (dashboard headline) |
58
+ | `/crm/get-agreements` | – | `Agreements[]` (active) |
59
+ | `/crm/get-all-agreements` | – | `Agreements[]` incl. inactive |
60
+ | `/crm/get-agreement-details` | `agreementNumber`, `clientNumber` | `{Agreement: {...}}`, superset of the list item (+ `AddressForCorrespondence`, `FirstName`, `LastName`, `IsOwner`, `NotExpiredChanges`) |
61
+ | `/crm/get-ppg-list` | – | `PpgList[]`: `IdPPG`, `MeterNumber`, `Address`, `ReadingDateUtc`, `ReadingValue`, `ContractNumber`, `ClientNumber`, `Tariff`, `IdLocal`, `HasHistory`, `AddReadingMode`, … |
62
+
63
+ Agreement item: `Type` (`PPG` gas), `AgreementNumber`, `ClientNumber`, `PpNumber` (PPG
64
+ number), `Address` / `ClientAddress` / `InvoiceAddress` (`Ulica`, `UlicaTyp`, `NrBudynku`,
65
+ `NrLokalu`, `KodPocztowy`, `Miejscowosc`), `Active`, `StartDate`, `EndDate`, `ClientName`,
66
+ `Iban` (individual account to pay into), `EcoInvoice`, `Saldo`, `Region`, `Tariff`,
67
+ `AgreementName`, `Owner`, `CollectionPoints[]` (`NumberOSD`, `IdCRD`, `Address*`, `Billing`).
68
+
69
+ ### Invoices
70
+
71
+ `/crm/get-invoices-v2`: `pageNumber`, `pageSize` (web app: 12; 200 for the payment dialog),
72
+ optional filters:
73
+
74
+ | Param | Meaning |
75
+ |---|---|
76
+ | `dateFrom`, `dateTo` | ISO timestamps, filter by issue date |
77
+ | `agreementNumber`, `clientNumber` | restrict to an agreement / customer |
78
+ | `paid=false` | unpaid only |
79
+ | `afterDeadline=true` | overdue |
80
+ | `loadAfter30Days=true` | forecast instalments due in more than 30 days |
81
+ | `excludeAfterDeadline=true` | not overdue ("upcoming" tab, combined with `paid=false`) |
82
+ | `beforeEndDate=true`, `forecastToPay=true`, `isHomeAssistant=true` | used by other screens |
83
+
84
+ `type=…` answers 400. Response: `InvoicesList[]`, `HasMoreRecords`, `AllowLoadAfter30Days`,
85
+ `AllowLoadAllInvoices`, `HasNonPaidForecast`. Item: `Number`, `Date`, `SellDate`,
86
+ `StartDate`/`EndDate` (billing period), `PayingDeadlineDate`, `GrossAmount`, `AmountToPay`,
87
+ `IsPaid`, `Status` (`Posted`), `Type`, `TempType` (`R` settlement, `P`/`B` forecast
88
+ instalments, empty for notes), `IsInterestNote`, `IsCreditNote`, `Correction`,
89
+ `AgreementNumber`, `IdPP`, `WearKWH`, `WearM3`, `Iban`, `PDFPrintAllowed`, `PdfExists`
90
+ (always false in practice; use the PDF endpoint).
91
+
92
+ | Endpoint | Params | Response |
93
+ |---|---|---|
94
+ | `/crm/get-invoice-details` | `invoiceNumber`, `excludePdfCheck=true` | `{Invoice: {...}}` + `Address`, `PdfVisibility`, `AutomaticPaymentDate`, `IsSubscribed` |
95
+ | `/crm/get-interest-note-details` | `interestNoteNumber`, `excludePdfCheck=true` | `{InterestNote: {DocumentNumber, DateCreated, DatePaymentDeadline, DateSell, GrossAmount, AmountToPay, IsPaid, …}}` |
96
+ | `/crm/get-invoice-pdf-visibility` | `invoiceNumber` | `{Visibility: "PdfVisible"}` |
97
+ | `/crm/get-invoice-pdf` | `invoiceNumber`, `mode=partial` | `application/pdf`, `Content-Disposition: inline; filename="p/…pdf"` |
98
+ | `/crm/get-interest-note-pdf` | `interestNoteNumber`, `mode=partial` | PDF, or `text/plain` "Brak podglądu PDF dla wybranej faktury." |
99
+
100
+ ### Payments & history
101
+
102
+ | Endpoint | Params | Response |
103
+ |---|---|---|
104
+ | `/crm/get-client-payments` | – | `Payments[]`: `DocumentDate`, `DocumentNumber` (`KP/RAP/…`), `Value`, `PaymentType` (`P`), `Description`, `ClientNumber`, `Transfer` |
105
+ | `/crm/get-compensations` | – | `Compensations[]` (empty on the test account) |
106
+ | `/crm/get-refunds` | – | `Refunds[]` (empty on the test account) |
107
+ | `/crm/get-account-history` | `pageNumber`, `pageSize` | `HistoryList[]`: `Date`, `Type` ("Zapłata za fakturę", …), `Description` |
108
+
109
+ ### Readings & consumption
110
+
111
+ | Endpoint | Params | Response |
112
+ |---|---|---|
113
+ | `/crm/get-all-ppg-readings-for-meter` | `idPpg`, `pageNumber`, `pageSize`, `ignoreStartDate=true` or `dateFrom`+`dateTo`, optional `type` | `MeterReadings[]`, `HasMoreRecords` |
114
+ | `/crm/get-ppg-readings-dates` | `idPpg` | `MeterReadingsDates[]` |
115
+ | `/crm/get-ppgs-readings-file` | `ppgIds` | xlsx attachment `Readings_YYYY_MM_DD_HH_MM_SS.xlsx` |
116
+ | `/crm/get-monthly-wear-chart-data` | `ppgIds`, `pageNumber`, `pageSize` | `ChartData: {"październik_2026": [{Label, Year, Value, Wear, Type, ReadingDateUtc, Period, ZoneLabel}]}` |
117
+ | `/crm/get-yearly-wear-chart-data` | `ppgIds` | `ComparisonPercent`, `IsPlusSignForPercent`, `ThisYearData[]` / `PreviousYearData[]`: `{Year, WearByYear, ChartItems[]}` |
118
+ | `/crm/get-wear-comparison-chart-data` | period params | not used |
119
+
120
+ Reading: `ReadingDateUtc`, `Value` (meter, m³), `Wear` (consumption since previous), `Value2/3`,
121
+ `Wear2/3` (multi-register meters), `Type` (`Real`, …), `Status` (`ROZL` settled,
122
+ `ANUL_KLIENT` cancelled by customer), `MeterNumber`, `PpId`, `ClientNumber`, `CanBeCancelled`.
123
+ The list only covers the current billing system's history (two readings on the test account).
124
+
125
+ ### Messages & settings
126
+
127
+ | Endpoint | Params | Response |
128
+ |---|---|---|
129
+ | `/notification/get-notifications` | – | `Notifications[]`: `Id`, `Title`, `Body`, `DateCreatedUtc`, `Type` (`Invoices`, …), `ObjectId` (invoice number) |
130
+ | `/notification/get-unreaded-count` | – | `{UnreadedCount}` |
131
+ | `/notification/email-settings` | – | `{Settings: {System, Invoices}}` |
132
+ | `/contact/get-contact-history-v2` | `pageIndex`, `pageSize` | `Contacts[]`, `AllowLoadMore` (item shape not observed: empty on the test account) |
133
+ | `/contact/get-contact-message` | `PublicId` | `Answers`, `ContactMessage` (not used) |
134
+
135
+ ## Write endpoints (not used by the SDK)
136
+
137
+ Mapped from the bundle only; never called while exploring:
138
+ `POST /crm/add-ppg-reading-v2` (submit a reading), `/crm/cancel-reading`,
139
+ `/crm/pay-for-invoice-v2` / `pay-for-multi-invoice-v2` (payments), `/notification/remove`,
140
+ `/agreements/set-agreement-name`, `/agreements/set-agreement-color`, `/cms/order-einvoice`,
141
+ `/crm/disable-eco-invoice`, `/contact/send-message-v2`, `/auth/change-password`,
142
+ `/auth/change-email-v2`, `/agreements/change-tariff`, `/agreements/send-terminate-agreement`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacek Grela
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,123 @@
1
+ Metadata-Version: 2.5
2
+ Name: myorlen-ebok-api
3
+ Version: 0.1.0
4
+ Summary: Unofficial Python SDK and CLI for the myORLEN gas eBOK (ebok.myorlen.pl, formerly PGNiG eBOK): agreements, invoices, PDFs, payments, meter readings, consumption.
5
+ Project-URL: Homepage, https://github.com/grelade/myorlen-ebok-api
6
+ Project-URL: Issues, https://github.com/grelade/myorlen-ebok-api/issues
7
+ Author-email: Jacek Grela <jacekgrela@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ebok,gas,invoices,myorlen,pgnig,sdk
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: Polish
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Home Automation
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: requests>=2.31
21
+ Description-Content-Type: text/markdown
22
+
23
+ # myorlen-ebok-api
24
+
25
+ Unofficial Python SDK and command-line client for the **myORLEN eBOK**
26
+ ([ebok.myorlen.pl](https://ebok.myorlen.pl)), the customer portal of the Polish gas retailer myORLEN.
27
+
28
+ > **Previous name: PGNiG eBOK.** The portal used to be the eBOK of PGNiG Obrót Detaliczny at
29
+ > `ebok.pgnig.pl`; after the rebranding to myORLEN that address redirects to `ebok.myorlen.pl`.
30
+ > Existing PGNiG eBOK logins work unchanged.
31
+
32
+ It logs in with your eBOK e-mail and password and gives typed access to what the portal shows:
33
+
34
+ - user data, balance and amount to pay, linked customer accounts
35
+ - supply agreements with tariff, address, IBAN, collection points; agreement details
36
+ - invoices, forecast instalments and interest notes (filters, paging), invoice details, **PDF downloads**
37
+ - payments booked on the account, eBOK action history
38
+ - gas meter points (PPG) with the last reading; reading history, Excel export
39
+ - consumption per settlement period and year-over-year comparison
40
+ - notifications, contact-form history, e-mail notification settings
41
+
42
+ > Not affiliated with PGNiG or ORLEN. The portal has no public API; this library calls the
43
+ > same JSON endpoints the web app uses and can break when they change.
44
+ > See [API.md](API.md) for the reverse-engineered endpoint reference.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install myorlen-ebok-api # or: uv add myorlen-ebok-api
50
+ ```
51
+
52
+ Latest unreleased code: `pip install git+https://github.com/grelade/myorlen-ebok-api`.
53
+
54
+ Requires Python 3.10+.
55
+
56
+ ## Command line
57
+
58
+ ```bash
59
+ export MYORLEN_USER=you@example.com MYORLEN_PASS='…' # or put them in ./.env (PGNIG_USER / PGNIG_PASS also work)
60
+ uvx myorlen-ebok-api balance
61
+ ```
62
+
63
+ | Command | Output |
64
+ |---|---|
65
+ | `user`, `balance`, `accounts` | user data, balance / amount to pay / payment status, linked accounts |
66
+ | `agreements [--all]` / `agreement [NUMBER]` | supply agreements / details of one |
67
+ | `meter-points` | meter points (PPG) with the last reading |
68
+ | `invoices [--from D] [--to D] [--filter all\|unpaid\|overdue\|forecast] [--agreement N] [--limit N]` | invoices |
69
+ | `invoice NUMBER` | invoice or interest-note details |
70
+ | `pdf NUMBER [--out DIR_OR_FILE]` | save an invoice PDF |
71
+ | `payments`, `history [--limit N]` | payments, eBOK action history |
72
+ | `readings [--from D] [--to D] [--xlsx PATH]` | meter readings (m³) or the Excel export |
73
+ | `monthly [--months N]`, `yearly` | consumption charts |
74
+ | `notifications`, `contacts`, `email-settings` | messages and settings |
75
+
76
+ Output is JSON (amounts are strings to keep `Decimal` precision). Global options: `--ppg ID`
77
+ (needed only for accounts with several meter points) and `--session-file PATH` (default
78
+ `~/.cache/myorlen-ebok-api/session.json`; the token is reused so repeated runs don't log in again).
79
+
80
+ ## Library
81
+
82
+ ```python
83
+ from myorlen_ebok_api import MyorlenClient
84
+
85
+ with MyorlenClient("you@example.com", "secret", session_file="~/.cache/myorlen-ebok-api/session.json") as c:
86
+ user = c.get_user()
87
+ print(user.balance, user.amount_to_pay)
88
+
89
+ for inv in c.iter_invoices(filter="unpaid"):
90
+ print(inv.number, inv.gross_amount, inv.due_date)
91
+
92
+ latest = next(c.iter_invoices())
93
+ pdf = c.download_invoice(latest) # DownloadedFile(filename, content_type, content)
94
+ open(pdf.filename, "wb").write(pdf.content)
95
+
96
+ for r in c.iter_readings():
97
+ print(r.date, r.value, r.consumption, "m³")
98
+ ```
99
+
100
+ All methods: `get_user`, `get_related_accounts`, `get_billing_summary`, `get_agreements`,
101
+ `get_agreement`, `get_meter_points`, `get_meter_point`, `get_invoices`, `iter_invoices`,
102
+ `get_invoice`, `download_invoice`, `get_payments`, `get_account_history`, `get_readings`,
103
+ `iter_readings`, `get_reading_dates`, `export_readings`, `get_monthly_consumption`,
104
+ `get_yearly_consumption`, `get_notifications`, `get_unread_count`, `get_email_settings`,
105
+ `get_contact_history`, `iter_contact_history`; session: `login`, `logout`, `save_session`.
106
+
107
+ Errors derive from `myorlen_ebok_api.MyorlenError` (`AuthenticationError`, `ApiError`, `ParseError`,
108
+ `NotFoundError`).
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ uv sync # creates .venv with dev tools
114
+ uv run pytest # offline tests (HTTP mocked, anonymised fixtures)
115
+ uv run ruff check . && uv run ruff format --check . && uv run mypy
116
+ MYORLEN_LIVE=1 uv run pytest -m live # read-only smoke tests against the real site (.env credentials)
117
+ ```
118
+
119
+ See [AGENTS.md](AGENTS.md) for the project layout and conventions.
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,101 @@
1
+ # myorlen-ebok-api
2
+
3
+ Unofficial Python SDK and command-line client for the **myORLEN eBOK**
4
+ ([ebok.myorlen.pl](https://ebok.myorlen.pl)), the customer portal of the Polish gas retailer myORLEN.
5
+
6
+ > **Previous name: PGNiG eBOK.** The portal used to be the eBOK of PGNiG Obrót Detaliczny at
7
+ > `ebok.pgnig.pl`; after the rebranding to myORLEN that address redirects to `ebok.myorlen.pl`.
8
+ > Existing PGNiG eBOK logins work unchanged.
9
+
10
+ It logs in with your eBOK e-mail and password and gives typed access to what the portal shows:
11
+
12
+ - user data, balance and amount to pay, linked customer accounts
13
+ - supply agreements with tariff, address, IBAN, collection points; agreement details
14
+ - invoices, forecast instalments and interest notes (filters, paging), invoice details, **PDF downloads**
15
+ - payments booked on the account, eBOK action history
16
+ - gas meter points (PPG) with the last reading; reading history, Excel export
17
+ - consumption per settlement period and year-over-year comparison
18
+ - notifications, contact-form history, e-mail notification settings
19
+
20
+ > Not affiliated with PGNiG or ORLEN. The portal has no public API; this library calls the
21
+ > same JSON endpoints the web app uses and can break when they change.
22
+ > See [API.md](API.md) for the reverse-engineered endpoint reference.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install myorlen-ebok-api # or: uv add myorlen-ebok-api
28
+ ```
29
+
30
+ Latest unreleased code: `pip install git+https://github.com/grelade/myorlen-ebok-api`.
31
+
32
+ Requires Python 3.10+.
33
+
34
+ ## Command line
35
+
36
+ ```bash
37
+ export MYORLEN_USER=you@example.com MYORLEN_PASS='…' # or put them in ./.env (PGNIG_USER / PGNIG_PASS also work)
38
+ uvx myorlen-ebok-api balance
39
+ ```
40
+
41
+ | Command | Output |
42
+ |---|---|
43
+ | `user`, `balance`, `accounts` | user data, balance / amount to pay / payment status, linked accounts |
44
+ | `agreements [--all]` / `agreement [NUMBER]` | supply agreements / details of one |
45
+ | `meter-points` | meter points (PPG) with the last reading |
46
+ | `invoices [--from D] [--to D] [--filter all\|unpaid\|overdue\|forecast] [--agreement N] [--limit N]` | invoices |
47
+ | `invoice NUMBER` | invoice or interest-note details |
48
+ | `pdf NUMBER [--out DIR_OR_FILE]` | save an invoice PDF |
49
+ | `payments`, `history [--limit N]` | payments, eBOK action history |
50
+ | `readings [--from D] [--to D] [--xlsx PATH]` | meter readings (m³) or the Excel export |
51
+ | `monthly [--months N]`, `yearly` | consumption charts |
52
+ | `notifications`, `contacts`, `email-settings` | messages and settings |
53
+
54
+ Output is JSON (amounts are strings to keep `Decimal` precision). Global options: `--ppg ID`
55
+ (needed only for accounts with several meter points) and `--session-file PATH` (default
56
+ `~/.cache/myorlen-ebok-api/session.json`; the token is reused so repeated runs don't log in again).
57
+
58
+ ## Library
59
+
60
+ ```python
61
+ from myorlen_ebok_api import MyorlenClient
62
+
63
+ with MyorlenClient("you@example.com", "secret", session_file="~/.cache/myorlen-ebok-api/session.json") as c:
64
+ user = c.get_user()
65
+ print(user.balance, user.amount_to_pay)
66
+
67
+ for inv in c.iter_invoices(filter="unpaid"):
68
+ print(inv.number, inv.gross_amount, inv.due_date)
69
+
70
+ latest = next(c.iter_invoices())
71
+ pdf = c.download_invoice(latest) # DownloadedFile(filename, content_type, content)
72
+ open(pdf.filename, "wb").write(pdf.content)
73
+
74
+ for r in c.iter_readings():
75
+ print(r.date, r.value, r.consumption, "m³")
76
+ ```
77
+
78
+ All methods: `get_user`, `get_related_accounts`, `get_billing_summary`, `get_agreements`,
79
+ `get_agreement`, `get_meter_points`, `get_meter_point`, `get_invoices`, `iter_invoices`,
80
+ `get_invoice`, `download_invoice`, `get_payments`, `get_account_history`, `get_readings`,
81
+ `iter_readings`, `get_reading_dates`, `export_readings`, `get_monthly_consumption`,
82
+ `get_yearly_consumption`, `get_notifications`, `get_unread_count`, `get_email_settings`,
83
+ `get_contact_history`, `iter_contact_history`; session: `login`, `logout`, `save_session`.
84
+
85
+ Errors derive from `myorlen_ebok_api.MyorlenError` (`AuthenticationError`, `ApiError`, `ParseError`,
86
+ `NotFoundError`).
87
+
88
+ ## Development
89
+
90
+ ```bash
91
+ uv sync # creates .venv with dev tools
92
+ uv run pytest # offline tests (HTTP mocked, anonymised fixtures)
93
+ uv run ruff check . && uv run ruff format --check . && uv run mypy
94
+ MYORLEN_LIVE=1 uv run pytest -m live # read-only smoke tests against the real site (.env credentials)
95
+ ```
96
+
97
+ See [AGENTS.md](AGENTS.md) for the project layout and conventions.
98
+
99
+ ## License
100
+
101
+ MIT
@@ -0,0 +1,69 @@
1
+ [project]
2
+ name = "myorlen-ebok-api"
3
+ version = "0.1.0"
4
+ description = "Unofficial Python SDK and CLI for the myORLEN gas eBOK (ebok.myorlen.pl, formerly PGNiG eBOK): agreements, invoices, PDFs, payments, meter readings, consumption."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Jacek Grela", email = "jacekgrela@gmail.com" }]
9
+ requires-python = ">=3.10"
10
+ keywords = ["pgnig", "myorlen", "ebok", "gas", "invoices", "sdk"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "Natural Language :: Polish",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Topic :: Home Automation",
19
+ "Typing :: Typed",
20
+ ]
21
+ dependencies = [
22
+ "requests>=2.31",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/grelade/myorlen-ebok-api"
27
+ Issues = "https://github.com/grelade/myorlen-ebok-api/issues"
28
+
29
+ [project.scripts]
30
+ myorlen-ebok-api = "myorlen_ebok_api.cli:main"
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "mypy>=1.10",
35
+ "pytest>=8",
36
+ "pytest-cov>=5",
37
+ "responses>=0.25",
38
+ "ruff>=0.6",
39
+ "types-requests>=2.31",
40
+ ]
41
+
42
+ [build-system]
43
+ requires = ["hatchling>=1.26"]
44
+ build-backend = "hatchling.build"
45
+
46
+ [tool.hatch.build.targets.sdist]
47
+ include = ["src", "tests", "API.md", "AGENTS.md", "README.md", "LICENSE"]
48
+
49
+ [tool.ruff]
50
+ line-length = 110
51
+ target-version = "py310"
52
+
53
+ [tool.ruff.lint]
54
+ select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF"]
55
+ ignore = ["RUF001", "RUF002", "RUF003"] # Polish text contains look-alike characters by design
56
+
57
+ [tool.mypy]
58
+ python_version = "3.10"
59
+ strict = true
60
+ files = ["src"]
61
+
62
+ [[tool.mypy.overrides]]
63
+ module = ["responses.*"]
64
+ ignore_missing_imports = true
65
+
66
+ [tool.pytest.ini_options]
67
+ testpaths = ["tests"]
68
+ addopts = "-ra"
69
+ markers = ["live: hits the real ebok.myorlen.pl (needs MYORLEN_USER/MYORLEN_PASS and MYORLEN_LIVE=1)"]
@@ -0,0 +1,66 @@
1
+ """Unofficial Python SDK for the myORLEN gas eBOK (ebok.myorlen.pl, formerly PGNiG eBOK)."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from .client import BASE_URL, MyorlenClient
6
+ from .exceptions import ApiError, AuthenticationError, MyorlenError, NotFoundError, ParseError
7
+ from .models import (
8
+ Address,
9
+ Agreement,
10
+ BillingSummary,
11
+ CollectionPoint,
12
+ ConsumptionEntry,
13
+ ContactMessage,
14
+ DownloadedFile,
15
+ EmailSettings,
16
+ HistoryEntry,
17
+ Invoice,
18
+ InvoiceFilter,
19
+ InvoicePage,
20
+ MeterPoint,
21
+ Notification,
22
+ Payment,
23
+ Reading,
24
+ ReadingPage,
25
+ RelatedAccount,
26
+ User,
27
+ YearConsumption,
28
+ YearlyConsumption,
29
+ )
30
+
31
+ try:
32
+ __version__ = version("myorlen-ebok-api")
33
+ except PackageNotFoundError: # pragma: no cover - running from a source tree
34
+ __version__ = "0.0.0"
35
+
36
+ __all__ = [
37
+ "BASE_URL",
38
+ "Address",
39
+ "Agreement",
40
+ "ApiError",
41
+ "AuthenticationError",
42
+ "BillingSummary",
43
+ "CollectionPoint",
44
+ "ConsumptionEntry",
45
+ "ContactMessage",
46
+ "DownloadedFile",
47
+ "EmailSettings",
48
+ "HistoryEntry",
49
+ "Invoice",
50
+ "InvoiceFilter",
51
+ "InvoicePage",
52
+ "MeterPoint",
53
+ "MyorlenClient",
54
+ "MyorlenError",
55
+ "NotFoundError",
56
+ "Notification",
57
+ "ParseError",
58
+ "Payment",
59
+ "Reading",
60
+ "ReadingPage",
61
+ "RelatedAccount",
62
+ "User",
63
+ "YearConsumption",
64
+ "YearlyConsumption",
65
+ "__version__",
66
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())