Skip to main content

esett-py

PyPI CI License: MIT Ruff Ruff ty Deptry Pytest

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 esett and 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 for pandas, polars or csv.

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, via resolution="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/Aggregate declares a 200 with no schema; it returns ImbalanceVolumeDTO[].
  • EXP17/Aggregate declares a single object; it returns an array.
  • EXP18/MBAOptions declares a nested array; it returns a flat array.
  • Timestamps are typed as plain string and resolution has no enum. The accepted values and the required yyyy-MM-dd'T'HH:mm:ss.SSSX format 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)

Source distribution for esett-py 0.1.0
File Size Uploaded
esett_py-0.1.0.tar.gz 14.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for esett-py 0.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page