Official Python client for accessing European electricity balancing market data via the Balancing Services REST API
Project description
Balancing Services Python Client
Python client library for the Balancing Services REST API. Access European electricity balancing market data with a modern, type-safe Python interface.
Features
- Type-safe: Full type hints for better IDE support and code quality
- Modern Python: Built with httpx and attrs for async support and clean data models
- Auto-generated: Generated from the official OpenAPI specification
- Comprehensive: Access to all API endpoints and data models
- Well-documented: Inline documentation and usage examples
Installation
uv pip install balancing-services
Or with pip:
pip install balancing-services
For development, install from source:
cd clients/python
uv pip install -e .
Quick Start
from balancing_services import AuthenticatedClient
from balancing_services.api.default import get_imbalance_prices
from balancing_services.models import Area
from datetime import datetime
# Create an authenticated client
client = AuthenticatedClient(
base_url="https://api.balancing.services/v1",
token="YOUR_API_TOKEN"
)
# Get imbalance prices for Estonia
response = get_imbalance_prices.sync_detailed(
client=client,
area=Area.EE,
period_start_at=datetime.fromisoformat("2025-01-01T00:00:00Z"),
period_end_at=datetime.fromisoformat("2025-01-02T00:00:00Z")
)
if response.status_code == 200:
data = response.parsed
print(f"Query period: {data.queried_period.start_at} to {data.queried_period.end_at}")
for imbalance_prices in data.data:
print(f"Area: {imbalance_prices.area}, Direction: {imbalance_prices.direction}")
for price in imbalance_prices.prices:
print(f" {price.period.start_at}: {price.price} {imbalance_prices.currency}")
Authentication
To obtain an API token:
- Email: info@balancing.services
- Website: https://balancing.services
Include your token when creating the client:
from balancing_services import AuthenticatedClient
client = AuthenticatedClient(
base_url="https://api.balancing.services/v1",
token="YOUR_API_TOKEN"
)
Usage Examples
Get Balancing Energy Bids with Pagination
from balancing_services import AuthenticatedClient
from balancing_services.api.default import get_balancing_energy_bids
from balancing_services.models import Area, ReserveType
from datetime import datetime
client = AuthenticatedClient(base_url="https://api.balancing.services/v1", token="YOUR_TOKEN")
# First page
response = get_balancing_energy_bids.sync_detailed(
client=client,
area=Area.EE,
period_start_at=datetime.fromisoformat("2025-01-01T00:00:00Z"),
period_end_at=datetime.fromisoformat("2025-01-02T00:00:00Z"),
reserve_type=ReserveType.AFRR,
limit=100
)
if response.status_code == 200:
data = response.parsed
print(f"Has more: {data.has_more}")
# Process first page
for bid_group in data.data:
for bid in bid_group.bids:
print(f"Bid: {bid.volume} MW @ {bid.price} {bid_group.currency}")
# Get next page if available
if data.has_more and data.next_cursor:
next_response = get_balancing_energy_bids.sync_detailed(
client=client,
area=Area.EE,
period_start_at=datetime.fromisoformat("2025-01-01T00:00:00Z"),
period_end_at=datetime.fromisoformat("2025-01-02T00:00:00Z"),
reserve_type=ReserveType.AFRR,
cursor=data.next_cursor,
limit=100
)
Async Usage
import asyncio
from balancing_services import AuthenticatedClient
from balancing_services.api.default import get_imbalance_prices
from balancing_services.models import Area
from datetime import datetime
async def fetch_prices():
client = AuthenticatedClient(
base_url="https://api.balancing.services/v1",
token="YOUR_TOKEN"
)
response = await get_imbalance_prices.asyncio_detailed(
client=client,
area=Area.EE,
period_start_at=datetime.fromisoformat("2025-01-01T00:00:00Z"),
period_end_at=datetime.fromisoformat("2025-01-02T00:00:00Z")
)
if response.status_code == 200:
return response.parsed
return None
# Run async function
prices = asyncio.run(fetch_prices())
Error Handling
from balancing_services import AuthenticatedClient
from balancing_services.api.default import get_imbalance_prices
from balancing_services.models import Area
from datetime import datetime
client = AuthenticatedClient(base_url="https://api.balancing.services/v1", token="YOUR_TOKEN")
response = get_imbalance_prices.sync_detailed(
client=client,
area=Area.EE,
period_start_at=datetime.fromisoformat("2025-01-01T00:00:00Z"),
period_end_at=datetime.fromisoformat("2025-01-02T00:00:00Z")
)
if response.status_code == 200:
data = response.parsed
# Process successful response
elif response.status_code == 400:
error = response.parsed
print(f"Bad request: {error.detail}")
elif response.status_code == 401:
print("Authentication failed - check your API token")
elif response.status_code == 429:
print("Rate limited - please retry later")
else:
print(f"Error {response.status_code}: {response.content}")
Data Models
All response and request models are fully typed using attrs. Key models include:
ImbalancePricesResponse,ImbalanceTotalVolumesResponseBalancingEnergyVolumesResponse,BalancingEnergyPricesResponse,BalancingEnergyBidsResponseBalancingCapacityBidsResponse,BalancingCapacityPricesResponse,BalancingCapacityVolumesResponse- Enums:
Area,ReserveType,Direction,Currency,ActivationType,BidStatus
Development
Regenerating the Client
The client is generated from the OpenAPI specification. To regenerate:
cd clients/python
./generate.sh
Running Tests
# Install development dependencies
uv pip install -e ".[dev]"
# Run tests
pytest
# Run tests with coverage
pytest --cov=balancing_services --cov-report=html
Code Quality
# Format code
ruff format .
# Lint code
ruff check .
# Type checking
mypy balancing_services
Automation Scripts
The Python client includes automation scripts for development and publishing:
generate-pyproject.sh
Generates pyproject.toml from the template with the current version from openapi.yaml.
Note: The version is stored in openapi.yaml (single source of truth). The pyproject.toml.draft template contains invalid TOML (version = __VERSION__ without quotes) to prevent accidental use without proper generation.
./generate-pyproject.sh
This script is automatically called by check.sh and test-and-publish.sh.
check.sh
Runs all quality checks in one command:
./check.sh
Performs:
- Generates
pyproject.tomlfrom draft - Runs tests with pytest
- Runs linting with ruff
- Runs type checking with mypy
- Verifies the package builds
test-and-publish.sh
Complete publishing workflow with TestPyPI validation:
Setup:
-
Copy
.env.sampleto.envand fill in your credentials:cp .env.sample .env # Edit .env and add: # BALANCING_SERVICES_API_KEY (required - for testing against live API) # UV_PUBLISH_TOKEN_TESTPYPI (required) # UV_PUBLISH_TOKEN_PYPI (required)
-
Run the publish script:
./test-and-publish.sh
Emergency option - skip API tests if the server is down:
./test-and-publish.sh --skip-api-tests
⚠️ This skips testing against the live API. Use only in emergencies!
Alternatively, export variables manually:
export BALANCING_SERVICES_API_KEY="your-api-key"
export UV_PUBLISH_TOKEN_TESTPYPI="your-testpypi-token"
export UV_PUBLISH_TOKEN_PYPI="your-pypi-token"
./test-and-publish.sh
What it does:
- Loads credentials from
.env(if present) - Verifies all required environment variables are set
- Runs quality checks
- Publishes to TestPyPI using
UV_PUBLISH_TOKEN_TESTPYPI - Creates isolated test sandbox
- Installs package from TestPyPI
- Runs all example scripts against live API using
BALANCING_SERVICES_API_KEY - Prompts to publish to PyPI using
UV_PUBLISH_TOKEN_PYPIif tests pass - Verifies production PyPI package by installing and running examples again
Get your credentials:
- API Key: https://balancing.services
- TestPyPI: https://test.pypi.org/manage/account/token/
- PyPI: https://pypi.org/manage/account/token/
For maintainers: See ../../scripts/README.md for the complete release workflow.
Troubleshooting
Authentication Errors (401)
Problem: Receiving 401 Unauthorized responses
Solutions:
- Verify your API token is correct
- Check that the token is being passed to
AuthenticatedClient - Ensure your token hasn't expired (contact support if needed)
Bad Request Errors (400)
Problem: Receiving 400 Bad Request responses
Common causes:
- Invalid date range (end date before start date)
- Date range too large
- Invalid area code or reserve type
- Malformed datetime strings
Solution: Check the error detail in the response for specific information:
if response.status_code == 400:
error = response.parsed
print(f"Error detail: {error.detail}")
Empty Results
Problem: Receiving 200 OK but no data
Possible reasons:
- No data available for the requested period
- Data not yet published for recent periods
- Requesting data for a period before data collection started
Solution: Try a different time period or check if data exists for your area
Timeout Issues
Problem: Requests timing out
Solutions:
- Increase the client timeout:
client = AuthenticatedClient(
base_url="https://api.balancing.services/v1",
token="YOUR_TOKEN",
timeout=30.0 # Increase from default
)
- Reduce the date range in your request
- Use pagination for large datasets
Type Errors with Enums
Problem: Type errors when passing area or reserve type
Solution: Use the provided enum classes:
from balancing_services.models import Area, ReserveType
# Correct
area=Area.EE
# Incorrect
area="EE" # String might work but not type-safe
Documentation
- API Documentation: https://api.balancing.services/v1/documentation
- OpenAPI Spec: openapi.yaml
- Main Repository: https://github.com/balancing-services/rest-api
Support
- Website: https://balancing.services
- Email: info@balancing.services
- Issues: GitHub Issues
License
MIT License - see LICENSE for details.
Changelog
See CHANGELOG.md for version history and changes.
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 balancing_services-1.5.0.tar.gz.
File metadata
- Download URL: balancing_services-1.5.0.tar.gz
- Upload date:
- Size: 32.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
312508c97fbc9626a106c81ea0233b01651f4a35aa12cdf2124d3ef9861643d3
|
|
| MD5 |
852be5f13bd504854a502291f88c8965
|
|
| BLAKE2b-256 |
a785917db9ffc1822a6eacfdbd083afb5f4600c733c98dfdb73b9b9d1af21cce
|
File details
Details for the file balancing_services-1.5.0-py3-none-any.whl.
File metadata
- Download URL: balancing_services-1.5.0-py3-none-any.whl
- Upload date:
- Size: 56.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e341d9150846946fe69b1446980ed7776dd175a157972d02a9aea8d629a92ab2
|
|
| MD5 |
9dea0031b76d355a2b360392144a5c1a
|
|
| BLAKE2b-256 |
fb361a74e16e33fa7494e61ea02ca789ca9284257095e0fb256676508571de3b
|