esett-py
A small Python client for the eSett open data API — the Nordic imbalance settlement data published by eSett Oy.
- No dependencies. Standard library only.
- No setup. The API is public and unauthenticated —
import esettand go. - Readable calls. Endpoint groups mirror the API docs, with snake case arguments and flexible dates.
- Long ranges just work. Multi-year queries are split into windows and stitched back together for you.
- Plain data out. Every call returns a
list[dict], ready forpandas,polarsorcsv.
Quickstart
Install
pip install esett-py
Requires Python 3.11 or newer.
Query data
import esett
# Which balance areas can I ask about?
esett.production.mba_options()
# Nordic production volumes for Finland, 15-minute resolution
rows = esett.production.values(
start="2024-01-01",
end="2024-01-02",
mba="10YFI_1________U",
)
rows[0]
# {'timestamp': '2024-01-01T01:00:00', 'timestampUTC': '2024-01-01T00:00:00Z',
# 'mba': 'FI', 'hydro': 300.63, 'nuclear': 1086.84, 'solar': 0.11,
# 'thermal': 456.49, 'wind': 699.42, 'windOffshore': None,
# 'energyStorage': None, 'other': 33.51, 'total': 2577.0}
Into a dataframe:
import pandas as pd
df = pd.DataFrame(rows)
Guide
Finding MBA codes
Most endpoints are filtered by metering balance area, identified by an EIC code rather than a friendly name. Each endpoint group lists the codes it accepts:
for country in esett.production.mba_options():
for area in country["mbas"]:
print(country["countryCode"], area["name"], area["code"])
# DK DK1 10YDK-1--------W
# FI FI 10YFI_1________U
# SE SE1 10Y1001A1001A44P
# ...
Pass one code, or several to have the API sum them into a single combined
series (the mba field of each row becomes e.g. "SE1,SE2"). Query the areas
separately if you want one series per area:
esett.prices.values(start="2024-01-01", end="2024-01-08", mba="10YFI_1________U")
# one combined series covering both areas, not two series
esett.prices.values(
start="2024-01-01",
end="2024-01-08",
mba=["10Y1001A1001A44P", "10Y1001A1001A45N"],
)
The API refuses any query whose result would exceed 100 000 rows, raising
ESettBadRequest with a rowLimit violation. Narrow the time range or the
filters if you hit it.
Dates and times
start and end accept a string, a date or a datetime. Naive values are
treated as UTC; aware values are converted to UTC. end is exclusive, so
start="2024-01-01", end="2024-02-01" is exactly the month of January.
from datetime import date, datetime
esett.consumption.values(start="2024-01-01", end=date(2024, 2, 1), mba=FI)
esett.consumption.values(start=datetime(2024, 1, 1, 6), end="2024-01-01T12:00", mba=FI)
values() vs aggregate()
Every time-series group exposes the same two methods:
values()— the series at the API's native resolution.aggregate()— the same data resampled server-side, viaresolution="year" | "month" | "week" | "day" | "hour".
esett.production.values(start="2024-01-01", end="2024-02-01", mba=FI)
esett.production.aggregate(start="2024-01-01", end="2024-02-01", mba=FI, resolution="day")
Long time ranges
A year of 15-minute data is ~35 000 rows and ~9 MB, so wide queries are slow and
easy to time out. values() therefore splits any range longer than
max_window (default 366 days) into consecutive requests and concatenates the
results in order. Because end is exclusive, the windows do not overlap and no
row is duplicated:
# transparently issued as several requests
rows = esett.prices.values(start="2015-01-01", end="2024-01-01", mba=FI)
aggregate() is never split — the server builds the buckets, so cutting the
range would change the answer.
Typed models
Responses are dictionaries with the API's original camelCase keys. If you prefer attribute access and snake case, every response shape is also available as a frozen dataclass:
from esett.models import ProductionVolumes
volumes = [ProductionVolumes.from_dict(row) for row in rows]
volumes[0].wind_offshore
volumes[0].timestamp_utc
Errors
An empty result (HTTP 204) is returned as [], not an error. Everything else
raises a subclass of esett.ESettError:
| Exception | Raised when |
|---|---|
ESettBadRequest |
HTTP 4xx — bad parameters. Exposes .status and .violations |
ESettServerError |
HTTP 5xx, after retries are exhausted |
ESettTransportError |
Network failure or timeout |
try:
esett.fees.history(country="XX", fee="NOPE")
except esett.ESettBadRequest as exc:
print(exc.status) # 400
print(exc.violations) # [{'field': ..., 'message': 'Unknown country: XX...'}]
Invalid arguments (an unknown resolution, an empty mba list, an unparseable
date) raise ValueError before any request is made.
Configuring a client
The module-level helpers use a shared default client. Create your own to change its behaviour:
from datetime import timedelta
with esett.Client(timeout=120, retries=5, max_window=timedelta(days=90)) as client:
rows = client.load_profile.values(start="2024-01-01", end="2024-04-01", mba=SE1)
| Argument | Default | Purpose |
|---|---|---|
base_url |
https://api.opendata.esett.com |
API root; must be http or https |
timeout |
60.0 |
Per-request socket timeout in seconds |
retries |
3 |
Extra attempts on transport errors and 5xx |
backoff |
0.5 |
Base delay for exponential retry backoff |
max_window |
366 days |
Longest span per request; None disables splitting |
Endpoints
| API group | Attribute | Methods |
|---|---|---|
| EXP01 Market Parties | market_parties |
balance_responsible_parties(), balance_service_providers(), distribution_system_operators(), retailers() |
| EXP03 Metering Grid Areas | metering_grid_areas |
areas(), mba_options() |
| EXP04 Retailer Balance Responsibility | retailer_balance_responsibility |
responsibilities(), mba_options() |
| EXP05 Fees | fees |
options(), all(), history(), latest() |
| EXP06 Settlement Banks | — | settlement_banks() |
| EXP08 Historical Two Balance Prices | two_balance_prices |
values(), mba_options() |
| EXP09 Historical Two Balance Volumes | two_balance_volumes |
values(), mba_options() |
| EXP13 Imbalance Volumes | imbalance_volumes |
values(), aggregate(), mba_options() |
| EXP14 Prices | prices |
values(), aggregate(), mba_options() |
| EXP15 Consumption | consumption |
values(), aggregate(), mba_options() |
| EXP16 Production | production |
values(), aggregate(), mba_options() |
| EXP17 Reconciliation Prices | reconciliation_prices |
values(), aggregate(), mba_options() |
| EXP18 Load Profile | load_profile |
values(), aggregate(), mba_options() |
Each attribute is available both on a Client instance and at module level
(esett.production.values(...)).
Master-data endpoints take optional filters instead of a time range:
esett.market_parties.retailers(country="FI", name="Helen")
esett.metering_grid_areas.areas(mba=FI, mga_type="Consumption")
esett.retailer_balance_responsibility.responsibilities(mba=FI, brp_name="Fortum")
esett.fees.latest(country="FI")
esett.settlement_banks()
Contributing
make setup # create the venv and install dev dependencies
make check # ruff lint, format check, ty type check, deptry
make test # fast offline tests
make test-live # end-to-end tests against the real API
make test never touches the network. The live suite is deselected by default
and exercises every endpoint group, the windowing logic, and the response shapes
against the vendored spec.
Keeping up with the API
Only src/esett/models.py is generated; the endpoint methods are hand-written.
That split is deliberate — the eSett spec defines no operationId, so generated
method names would be unusable, and several response schemas do not match the
live API.
make spec-check # fail if the published spec differs from spec/openapi.json
make spec # refresh the vendored spec
make models # regenerate src/esett/models.py from it
CI runs make spec-check weekly, so spec changes show up as a reviewable diff.
Adding a new endpoint is then a few lines in src/esett/_resources.py.
Known spec defects
Worked around in the hand-written layer, each verified against the live API:
EXP13/Aggregatedeclares a200with no schema; it returnsImbalanceVolumeDTO[].EXP17/Aggregatedeclares a single object; it returns an array.EXP18/MBAOptionsdeclares a nested array; it returns a flat array.- Timestamps are typed as plain
stringandresolutionhas no enum. The accepted values and the requiredyyyy-MM-dd'T'HH:mm:ss.SSSXformat were determined from the live API.
License
MIT — see LICENSE. This project is not affiliated with eSett Oy.
Release files for esett-py 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| esett_py-0.1.0.tar.gz | 14.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| esett_py-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.2 kB
Release files / esett_py-0.1.0.tar.gz
| Download URL | esett_py-0.1.0.tar.gz |
|---|---|
| Size | 14.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
250b16e7554ea9f608a08895aff67a6574ef322d42b70dfb524a6ee14d635773
|
|
BLAKE2b-256 checksum How to use checksums |
1ac1468620e3a5b3bf1feb2a68565561737ecb2a84781de4be7aa6fcbdb055c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency logRelease files / esett_py-0.1.0-py3-none-any.whl
| Download URL | esett_py-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b488995e9cc6d9364cb2bb9775b42d470fed6683450e8ebbe752035affe043c4
|
|
BLAKE2b-256 checksum How to use checksums |
2cecbba0771ac66dc831d9e6832921485516e38ddb462a2bea6e94234e81bb41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency log