# Open Electricity Documentation
---
# Introduction
An Introduction to Open Electricity
URL: /introduction
Open Electricity is currently in the process of being renamed from OpenNEM. There are still some repositories and projects yet to be renamed
Open Electricity (formerly OpenNEM) is a platform for tracking and visualizing energy data from across Australia.
The Open Electricity project aims to make energy network data accessible to a wider audience of users and developers through a website portal and data access API's and tools.
The Open Electricity project consists of two major components: the website and the API platform.
## Website
The [Open Electricity website](https://explore.openelectricity.org.au) is a public website that provides a tracker for energy data from across Australia.
* Explore energy data from across Australia
* [Facilities](https://explore.openelectricity.org.au/facilities/nem/): Map of Australian power station facilities and their data.
* [Compare regions](https://explore.openelectricity.org.au/compare/?range=all-12-mth-rolling&interval=1M&metric=renewablesProportion) - Compare energy data across regions.
* [Stripes](https://explore.openelectricity.org.au/stripes/nem/) - A visualization of the energy data history.
* [Emissions](https://explore.openelectricity.org.au/emissions/au/?interval=Year&projections=false&history=false) - Visualisation of Australia's emissions.
## API Platform
The Open Electricity Platform and client libraries are currently in beta and still under active development.
* [Open Electricity Platform](https://platform.openelectricity.org.au) - Signup for access to the API platform ([docs](/platform/introduction)).
* [Python client library](https://github.com/opennem/openelectricity-python) - Python client library for the Open Electricity API ([docs](/sdk/overview)).
* [TypeScript client library](https://github.com/opennem/openelectricity-typescript) - TypeScript client library for the Open Electricity API ([docs](/sdk/overview)).
## Supported Data Sets
Currently supported electricity networks and data sources:
* `NEM` - [Australia National Electricity Market](https://www.nemweb.com.au/) - energy network data for Queensland, New South Wales, Victoria, South Australia, Tasmania and the Australian Capital Territory
* `WEM` - [Australia Western Electricity Market](http://data.wa.aemo.com.au/) - energy network data for Western Australia
* `APVI` - [APVI](https://apvi.org.au) - Rooftop solar data for Australia
* `BoM` - [Bureau of Meteorology](https://www.bom.gov.au/climate/data/) - weather data for Australia
## About Open Electricity
Open Electricity is a project of [The Superpower Institute](https://www.superpowerinstitute.com.au/), a not-for-profit organisation
dedicated to accelerating the transition to a clean energy future. All [code](https://github.com/opennem) is open source and licensed under
the [MIT License](https://opensource.org/licenses/MIT). All data is made available under the [Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)](https://creativecommons.org/licenses/by-nc/4.0/) licence — see the [full licence](https://platform.openelectricity.org.au/license) for details.
## Next Steps
Follow the next steps based on being a user of Open Electricity, a developer using the API or a developer looking to contribute to the project.
Guides to the energy network and market, and how to use Open Electricity
Get started with the Open Electricity Platform
Open Electricity API Reference
Client libraries for the Open Electricity API in Python and TypeScript
---
# Community
Join the Open Electricity community to get help, share ideas, and contribute to the project.
URL: /community
## GitHub
The Open Electricity project is open source and hosted on GitHub. You can find the source code, report issues, and contribute to the project:
* [Open Electricity GitHub Organization](https://github.com/opennem)
* [Main Repository (opennem)](https://github.com/opennem/opennem)
* [Report Issues](https://github.com/opennem/opennem/issues)
## Social Media
Follow us on social media for updates and announcements:
* [Twitter: @opennem](https://twitter.com/opennem)
## Slack Community
Join our Slack workspace to connect with other users and developers:
* **Slack Workspace**: [opennem.slack.com](https://opennem.slack.com)
* **Get an Invite**: Email [inquiries@openelectricity.org.au](mailto:inquiries@openelectricity.org.au) to request an invitation
## Get Help
If you need help with Open Electricity:
1. Check the [documentation](/introduction) and [guides](/guides/networks)
2. Search existing [GitHub issues](https://github.com/opennem/opennem/issues)
3. Join our [Slack workspace](https://opennem.slack.com) (email [inquiries@openelectricity.org.au](mailto:inquiries@openelectricity.org.au) for an invite)
4. Create a new [GitHub issue](https://github.com/opennem/opennem/issues/new) if you find a bug or have a feature request
## Contributing
We welcome contributions to the Open Electricity project! Check out our [contribution guide](/contribute/overview) to get started.
---
# Networks and Regions
Understanding Australian electricity networks and regional structures
URL: /guides/networks
## Overview
Open Electricity tracks data across multiple Australian electricity networks. Each network has distinct characteristics, regions, and operational parameters.
Open Electricity currently supports the `NEM` and `WEM` as primary networks, which covers the majority of the Australian electricity market outside of the Northern Territory and North-West Australia.
Open Electricity also supports the `AEMO_ROOFTOP` and `APVI` as secondary networks, which covers the rooftop solar market in Australia.
## National Electricity Market (NEM)
The National Electricity Market (NEM) is Australia's primary electricity system, spanning approximately 5,000 kilometers from Port Douglas, Queensland, to Port Lincoln, South Australia. It supplies around 80% of Australia's electricity consumption, generating approximately 200 terawatt hours annually.
The NEM transports electricity via high-voltage transmission lines from generators to distributors, who deliver it to homes and businesses.
While the NEM covers much of the country, Western Australia and the Northern Territory operate independent electricity systems with separate regulatory frameworks.
## Wholesale Electricity Market (WEM)
The Wholesale Electricity Market (WEM) is the electricity market for the state of Western Australia. It is an isolated network, with no interconnections.
## Network Structure
### Primary Networks
- **National Electricity Market (`NEM`)**:
- 5-minute intervals for generation, price and demand for each region
- Timezone: `UTC+10`
- Regions: `NSW1`, `QLD1`, `SA1`, `TAS1`, `VIC1`
- Data since: `1998-12-06`
- **Wholesale Electricity Market (`WEM`)**:
- 5-minute intervals for generation, price and demand (since October 2023)
- Timezone: `UTC+8`
- Single region covering Western Australia
- Data since: `2006-09-19`
### Secondary Networks
- **AEMO Rooftop Solar (`AEMO_ROOFTOP`)**:
- 30-minute intervals
- Timezone: `UTC+10`
- Regions: Every Australian state
- Data since: `2018-03-01`
- **Australian Power System Virtual Interconnection (`APVI`)**:
- 15-minute intervals
- Timezone: `UTC+10`
- Regions: Every Australian state
- Data since: `2015-03-19`
## Network Regions
Networks are split into regions, which for the NEM represent each state a generation facility is located in.
Network regions have their own market pricing and demand data, as well as interconnector flows and generation facilities.
### NEM Regions
Each region represents a state-based jurisdiction:
- `NSW1`: New South Wales (including the ACT)
- `QLD1`: Queensland
- `SA1`: South Australia
- `TAS1`: Tasmania (since `2005-05-16`)
- `VIC1`: Victoria
- `SNOWY1`: Snowy Mountains region (deprecated)
### WEM Region
- `WEM`: Western Australia. Covering the southern half of the state on the SWIS grid.
### Region Properties
Each region contains:
- Unique timezone settings
- Price nodes
- Interconnector definitions
- Generation facilities
- Demand data
## Data Collection
### Interval Data
Networks collect data at different intervals:
- `NEM`:
- Dispatch: 5-minute intervals
- Price: 5-minute intervals
- Demand: 5-minute intervals
- Rooftop solar: 30-minute intervals
- `WEM`:
- Dispatch: 5-minute intervals
- Price: 5-minute intervals
- Demand: 5-minute intervals
- Rooftop solar: 30-minute intervals
- `APVI`:
- Dispatch: 15-minute intervals
## Network Flows
### NEM Interconnectors
Interconnectors link different regions and allow the import and export of generation between regions:
- `NSW1-QLD1`: Queensland to NSW
- `VIC1-NSW1`: Victoria to NSW
- `VIC1-SA1`: Victoria to South Australia
- `VIC1-TAS1`: Victoria to Tasmania (Basslink)
### WEM Interconnectors
WEM is an isolated network, with no interconnections.
---
# Power in Electricity Networks
URL: /guides/power
Power in electricity markets represents the instantaneous rate of electrical energy generation, transmission and consumption. It is measured in watts (W), though at grid scale it is typically expressed in megawatts (MW) or gigawatts (GW). Power plants generate electricity through various means:
- **Thermal Generation**: Coal, gas and nuclear plants generate electricity by heating water to create steam that drives turbines connected to generators. The spinning generators create electricity through electromagnetic induction.
- **Renewable Generation**: Wind turbines harness kinetic energy from wind, while solar panels convert sunlight directly into electricity through the photovoltaic effect. Hydroelectric plants use falling water to spin turbines.
- **Energy Storage**: Batteries and pumped hydro facilities store energy when supply exceeds demand and release it back as electrical power when needed. This helps balance supply and demand across the grid.
Power must be generated at the exact moment it is consumed since electricity cannot be easily stored at grid scale. This requires sophisticated systems to match generation with demand in real-time, coordinating hundreds of power plants across vast transmission networks.
The Australian electricity system operates on an alternating current (AC) system at 50 Hz frequency, requiring careful management of voltage, frequency and other technical parameters to maintain stable power delivery. Sophisticated control systems at power plants and across the transmission network work together to maintain these parameters within strict tolerances.
## Key Concepts
- **Power**: The rate at which electrical energy is transferred by an electric circuit. It is typically measured in megawatts (MW) in the context of electricity markets. Power measurements are instantaneous, reflecting the current rate of energy transfer.
- **Dispatch**: The process of determining which power plants will generate electricity at any given time to meet demand. This involves selecting the most cost-effective combination of available generation resources. Dispatch is also driven by the market price for each network region.
- **Generation**: The production of electricity from various energy sources such as coal, gas, wind, solar, and hydroelectric power. The mix of these sources can vary significantly between markets.
- **Demand**: The total amount of electricity required by consumers at any given time. Demand fluctuates based on factors like weather, time of day, and economic activity. It also varies by season, with higher demand typically occurring during extreme weather conditions in summer and winter. See the [demand guide](/guides/demand) for a detailed explanation of how demand is measured and reported.
## National Electricity Market (NEM) and Wholesale Electricity Market (WEM)
### Power Generation
Both the NEM and WEM rely on a diverse mix of energy sources, including coal, gas, wind, solar, and hydroelectric power. The generation mix is evolving with a significant increase in renewable energy sources in both markets.
### Scheduled vs Non-Scheduled Generation in the NEM
In the NEM, generation is classified as either scheduled or non-scheduled. Scheduled generators are typically larger and must submit bids to the market operator, AEMO, indicating how much electricity they can supply at different price points. Non-scheduled generators, often smaller renewable sources like wind and solar, are not required to submit bids and are dispatched based on their availability and capacity.
### Dispatch
Dispatch in both the NEM and WEM is managed by the Australian Energy Market Operator (AEMO). Generators submit bids to supply electricity, and AEMO dispatches the lowest-cost combination of available generation to meet demand every five minutes. This process ensures that electricity is supplied reliably and at the lowest possible cost, while also considering the market price for each network region.
There are three dispatch types in the NEM:
- **Load** - A load is a facility that is consuming power. It is represented as negative numbers in the generation data.
- **Generation** - A generation facility is a facility that is generating power. It is represented as positive numbers in the generation data.
- **Bidirectional** - A bidirectional facility is a facility that can both consume and generate power. It is represented as both positive and negative numbers in the generation data where negative numbers represent consumption and positive numbers represent generation.
### Demand
Demand in both markets is influenced by various factors, including weather conditions, time of day, and economic activity. AEMO forecasts demand and ensures that there is enough generation capacity to meet it. Demand patterns vary by time of day and season, with peak demand often occurring during the late afternoon and early evening, especially in summer and winter.
### Total Capacity and Generation
- **NEM**: The NEM has a total installed capacity of approximately 65,000 MW, with an average daily generation of around 600 GWh. It comprises over 300 power stations, ranging from large coal-fired plants to small renewable energy installations.
- **WEM**: The WEM has a total installed capacity of about 6,000 MW, with an average daily generation of approximately 60 GWh. It includes around 50 power stations, with a mix of traditional and renewable energy sources.
### Capacity Market in the WEM
The WEM operates as a capacity market, which means that generators are compensated not only for the electricity they produce but also for their availability to produce electricity when needed. This ensures that there is sufficient capacity to meet peak demand and provides financial incentives for maintaining and investing in generation capacity.
## Data
Open Electricity retrieves and stores power dispatch data for each power generation plant and unit from the NEM and WEM.
---
# Energy in Electricity Networks
URL: /guides/energy
Energy in the context of electricity networks refers to the total amount of electrical power consumed or generated over a specific period. It is typically measured in megawatt-hours (`MWh`) at facility scale and gigawatt-hours (`GWh`) at network and grid scale. Unlike power, which is an instantaneous measurement, energy accounts for the duration over which power is used or produced.
## Definition of Energy
Energy is the integral of power over time, representing the area under the power curve on a graph of power versus time. In simpler terms, it is the accumulation of power usage or generation over a given time interval. For example, if a device uses 1 kilowatt (kW) of power continuously for one hour, it consumes 1 kilowatt-hour (kWh) of energy.
## Difference Between Energy and Power
- **Power**: Power is the rate at which energy is generated or consumed at any given moment. It is an instantaneous measurement and is typically expressed in watts (`W`) or megawatts (`MW`).
- **Energy**: Energy is the total amount of power used or generated over a period of time. It is a cumulative measurement and is expressed in kilowatt-hours (`kWh`) or megawatt-hours (`MWh`).
In essence, power is about the rate of energy flow, while energy is about the total amount of energy transferred over time.
## Energy Calculation in Open Electricity
Open Electricity calculates energy for each interval by averaging the power generated during that interval and the previous interval. This method provides a more accurate representation of energy usage or generation over time.
### Calculation Method
The energy for a given interval is calculated using the following formula:
$$
E_i = \frac{P_i + P_{i-1}}{2} \times \Delta t
$$
Where:
$$E_i: \text{Energy during the interval (in MWh).} \\[8pt]$$
$$P_i: \text{Power during the current interval } i \text{ (in MW).} \\[8pt]$$
$$P_{i-1}: \text{Power during the previous interval } i-1 \text{ (in MW).} \\[8pt]$$
$$\Delta t: \text{Duration of the interval in hours (e.g., for 5 minutes, } \Delta t = \frac{1}{12} \text{).} \\[8pt]$$
### Calculation Example
Calculate the energy for a 5-minute interval where the power during the current interval is 60 `MW` and the power during the previous interval is 50 `MW`.
$$ P_i = 60 \, \text{MW} \, (\text{power during the current interval}) $$
$$ P_{i-1} = 50 \, \text{MW} \, (\text{power during the previous interval}) $$
$$ \Delta t = \frac{1}{12} \, \text{hours} \, (\text{5-minute interval}) $$
**Step 1: Average the Power Values**
$$\text{Average Power} = \frac{P_i + P_{i-1}}{2} = \frac{60 + 50}{2} = 55 \, \text{MW}$$
**Step 2: Divide by Interval Duration**
$$\text{Energy} = \text{Average Power} \times \Delta t = 55 \, \text{MW} \times \frac{1}{12} \, \text{hours} = 4.583 \, \text{MWh}$$
We get a result that is 4.583 MWh, the energy in `MWh` for that 5-minute interval.
---
# Renewable Energy Metrics
URL: /guides/renewables
Open Electricity provides several metrics for measuring renewable energy generation, including storage-adjusted variants that account for battery and pumped hydro contributions.
## Renewable Generation
**Metric:** `generation_renewable` (MW) / `generation_renewable_energy` (MWh)
Renewable generation is the sum of all generation from renewable fuel technology types, plus battery discharge and pumped hydro generation. Hybrid hydro-with-storage facilities are excluded from this metric.
- Bioenergy (biogas, biomass)
- Hydro (excluding hydro with storage facilities)
- Solar (utility-scale)
- Solar (rooftop) — added separately from the AEMO rooftop network
- Wind
- Battery discharging
- Pumps (excluding hydro with storage facilities)
### Hydro with Storage Exclusion
Some facilities are hybrid hydro and pumped storage — their generation includes both natural water inflow and water that has been pumped back uphill using grid electricity. Since we cannot separate these two sources at the metering level, hydro with storage facilities are excluded from base renewable generation and included only in the storage-adjusted metric below.
The `hydro_and_storage` fuel technology type classifies these facilities.
## Renewable Generation with Storage
**Metric:** `generation_renewable_with_storage` (MW) / `generation_renewable_with_storage_energy` (MWh)
This metric includes everything in renewable generation plus hydro with storage facilities. It represents the total renewable output including hybrid hydro-storage generation.
$$
\text{generation\_renewable\_with\_storage} = \text{generation\_renewable} + \text{hydro with storage generation}
$$
## Renewable Proportion
**Metric:** `renewable_proportion` (%)
The percentage of gross demand met by renewable generation:
$$
\text{renewable\_proportion} = \frac{\text{generation\_renewable}}{\text{demand\_gross}} \times 100
$$
Where `demand_gross` = operational demand + rooftop solar generation.
## Renewable with Storage Proportion
**Metric:** `renewable_with_storage_proportion` (%)
The percentage of gross demand met by renewable generation including storage:
$$
\text{renewable\_with\_storage\_proportion} = \frac{\text{generation\_renewable\_with\_storage}}{\text{demand\_gross}} \times 100
$$
## Energy Calculation
All energy metrics (MWh) are derived from power readings (MW) using trapezoidal integration. See the [Energy guide](/guides/energy) for the calculation methodology.
## Comparison with AEMO
AEMO publishes two renewable share figures in their Quarterly Energy Dynamics reports:
| AEMO metric | Open Electricity equivalent |
|---|---|
| Renewable energy share (including storage) | `renewable_with_storage_proportion` |
| Storage-adjusted renewable energy share | `renewable_proportion` (closest equivalent) |
Our `renewable_proportion` differs from AEMO's storage-adjusted metric in that we include battery discharge and non-hybrid pumped hydro in the renewable numerator, while AEMO excludes all storage from their adjusted figure. Our approach counts battery and pumped hydro discharge as renewable since the stored energy originated from the grid's renewable mix.
## Available API Metrics
| Metric | Unit | Description |
|---|---|---|
| `generation_renewable` | MW | Renewable power generation |
| `generation_renewable_energy` | MWh | Renewable energy generation |
| `generation_renewable_with_storage` | MW | Renewable power including hydro with storage |
| `generation_renewable_with_storage_energy` | MWh | Renewable energy including hydro with storage |
| `renewable_proportion` | % | Renewable share of gross demand |
| `renewable_with_storage_proportion` | % | Renewable+storage share of gross demand |
---
# Price and Market Data
Understanding electricity market pricing and trading data
URL: /guides/price
## Overview of the NEM Spot Market
The National Electricity Market (NEM) operates as a centralized exchange where the Australian Energy Market Operator (AEMO) matches electricity supply and demand in real time.
- **Bidding and Dispatch**: Generators submit bids indicating the price at which they are willing to supply electricity. AEMO stacks these bids from least to most expensive and dispatches the least-cost mix of generators required to meet demand.
- **Five-Minute Settlement**: Since October 2021, the NEM settles on a five-minute basis. Generators receive the spot price for their electricity based on each five-minute interval.
---
## NEM Price Caps and Floors
To manage market volatility and ensure reliability, the NEM enforces the following price limits:
- **Price Cap (Maximum)**: $15,500 per MWh
- **Price Floor (Minimum)**: -$1,000 per MWh
These limits define the maximum and minimum spot prices that can occur during any five-minute interval.
---
## Market Value Calculation in Open Electricity
Open Electricity calculates the market value of electricity generation using the following methodology:
1. For each generator and interval, the value is determined by multiplying the electricity generated by the spot price in the generator's network region.
2. The total market value for a period is the sum of these values across all intervals and generators.
### Formula
Let:
- `g[i, j]` = Electricity generated by generator `i` in interval `j` (in MWh)
- `p[j]` = Spot price in the network region for interval `j` (in $/MWh)
- `N` = Total number of generators
- `M` = Total number of intervals
The total market value (`V`) is calculated as:
V = \sum_{j=1}^M \sum_{i=1}^N \left( g_{i,j} \cdot p_j \right)
### Explanation
- The formula aggregates the contribution of each generator during every interval.
- By summing across all generators and intervals, the total market value reflects the overall economic activity within the NEM.
# Price and Market Data
## Overview
Open Electricity tracks electricity market prices and trading data across Australian electricity markets, providing detailed price information and market metrics.
## Price Data Structure
### Core Price Components
Price data is stored in the balancing summary table with:
- Interval prices
- Dispatch prices
- Forecast prices
## Market Intervals
### Trading Periods
- `NEM`:
- 5-minute dispatch intervals
- Historical 30-minute trading intervals (pre-Oct 2021)
- `WEM`:
- 30-minute trading intervals
- 30-minute balancing market intervals
## Price Types
### Market Price
The market price is the price at which electricity is traded in the market.
### Market Value
`market_value` is a volume-weighted price calculated for each generator in each interval and then summed up to the total market value.
---
# Demand in Electricity Networks
URL: /guides/demand
We're working on a new **gross demand** metric and **renewable proportion** calculation. Track progress in [#398](https://github.com/opennem/opennem/issues/398).
## Overview
**Demand** is how much electricity is being pulled from the grid at any moment, measured in megawatts (MW). Over time, it becomes energy, measured in megawatt-hours (MWh).
Everything plugged in contributes: factories, trains, streetlights, air conditioners, your kettle. Add it all up across a region and that's the demand number AEMO has to match with generation.
### Daily shape
Demand has a pretty consistent daily shape. It's lowest overnight when most people are asleep and businesses are shut. Morning brings a ramp as the country wakes up. Then around midday, something interesting happens: rooftop solar kicks in hard, and the demand that centralised power stations have to meet drops into a trough. This is the [duck curve](https://en.wikipedia.org/wiki/Duck_curve) you'll see referenced in energy commentary and pictured above on Open Electricity.
At sunset, solar output falls off a cliff, but people are arriving home, cooking, running heaters or AC. The grid has to ramp conventional generators fast to cover that gap. It's the steepest climb of the day and the period most likely to cause problems.
### Seasons
Summer heatwaves drive the absolute peaks, mostly from air conditioning. Winter heating pushes demand up too, especially in Victoria and Tasmania. Spring and autumn tend to be quieter.
### Why it matters
AEMO dispatches generation to match demand every five minutes. If generation falls short, you get load shedding (rolling blackouts). If there's too much, frequency drifts and the system becomes unstable. Either way, bad.
### Rooftop Solar and Home Batteries
Rooftop solar and home batteries have made demand harder to measure. A house with panels running the air conditioner at midday might draw nothing from the grid, so from AEMO's perspective that demand doesn't exist. But the person is still consuming electricity. It's just being met behind the meter, invisible to the dispatch engine.
As more rooftop solar gets installed, and more home batteries rolled out, the gap between what Open Electricity can see (scheduled demand) and what's actually being consumed (underlying demand) keeps widening, especially during the the day.
---
## Demand Metrics in Open Electricity
Open Electricity sources demand from AEMO's `DISPATCHREGIONSUM` table:
| Metric | AEMO Source Field | Description |
|---|---|---|
| `demand` | `TOTALDEMAND` | Scheduled demand, met by scheduled + semi-scheduled generation and interconnector flows |
| `demand_energy` | Calculated | Energy derived from demand over the interval (MWh) |
### Scheduled vs operational vs underlying demand
"Demand" means different things depending on how much generation you can see:
- **Scheduled demand** (`TOTALDEMAND`) is what NEMDE dispatches against. It only includes load visible to the dispatch engine: large industrial consumers and distribution network load as seen at transmission connection points. This is the demand figure Open Electricity uses.
- **Operational demand** (`DEMAND_AND_NONSCHEDGEN`) adds non-scheduled generation that AEMO has telemetry for (smaller wind and solar farms). It's the total demand being met by all grid-connected generation AEMO can observe.
- **Underlying (or native) demand** adds embedded generation that AEMO can't see, mostly rooftop solar. This is the closest measure to true total consumption, including load being met behind the meter.
The gap between scheduled and underlying demand has grown as more rooftop solar gets installed. During midday in sunny states, the difference can be substantial.
---
## How AEMO calculates TOTALDEMAND
AEMO calculates `TOTALDEMAND` per region per 5-minute dispatch interval. It combines two inputs:
1. `INITIALSUPPLY` — measured generation output from scheduled and semi-scheduled generators in the region, adjusted for interconnector flows
2. A forecast adjustment to account for time lag between physical metering and dispatch
Roughly:
$$
\text{TOTALDEMAND} = \text{INITIALSUPPLY} + \text{forecast adjustment}
$$
So `TOTALDEMAND` is a near-real-time estimate of the demand being served by scheduled generation and interconnectors. It doesn't include demand met by non-scheduled generators or behind-the-meter generation like rooftop solar.
For the full technical spec, see AEMO's [Demand Terms in EMMS Data Model](https://www.aemo.com.au/-/media/Files/Electricity/NEM/Security_and_Reliability/Dispatch/Policy_and_Process/Demand-terms-in-EMMS-Data-Model.pdf).
---
## NEM vs WEM Demand
### NEM
The NEM provides demand directly via `DISPATCHREGIONSUM` at 5-minute resolution. Open Electricity uses `TOTALDEMAND` from this source.
### WEM
The WEM is trickier. Its public data sources (`referenceTradingPrices`) only publish **prices, not demand**. The `balancing_summary` table has `price` but `demand` and `generation_total` are NULL.
Since the WEM is an isolated grid with no interconnectors, demand roughly equals total generation. Open Electricity derives WEM demand by summing generation from all WEM facilities in the `facility_scada` table.
WEM demand can lag NEM by up to 24 hours because WEM facility generation data is published daily, not in near-real-time.
---
## Demand response
Demand response (DR) programs pay consumers to reduce consumption during peak periods or high-price events. Since `TOTALDEMAND` is derived from `INITIALSUPPLY` (actual generation output), it already reflects demand *after* DR has taken effect. When DR kicks in, consumption drops, less generation gets dispatched, and `TOTALDEMAND` falls with it.
### Wholesale Demand Response (WDR)
Since October 2021, AEMO's [Wholesale Demand Response Mechanism](https://www.aemo.com.au/initiatives/trials-and-initiatives/past-trials-and-initiatives/wholesale-demand-response-mechanism) lets aggregators bid load reductions directly into NEM dispatch. DR providers submit availability and price bids just like generators, and when dispatched, the load reduction shows up as reduced `TOTALDEMAND`.
Open Electricity doesn't distinguish between organic consumption changes and DR-driven reductions. A drop in `TOTALDEMAND` could be people using less electricity, DR activation, or both.
For more on demand measurement, see the [WattClarity demand explainer](https://wattclarity.com.au/articles/2025/02/3measures-demand/) and [AEMO Operational Demand Data](https://www.aemo.com.au/energy-systems/electricity/national-electricity-market-nem/data-nem/operational-demand-data).
---
## Accessing demand data
Demand data is available via the market endpoint:
```
POST /v4/market/network/{network_code}
```
Available demand metrics: `demand`, `demand_energy`
```python Python
from openelectricity import OEClient
from openelectricity.types import MarketMetric
from datetime import datetime, timedelta
with OEClient() as client:
response = client.get_market(
network_code="NEM",
metrics=[MarketMetric.DEMAND, MarketMetric.DEMAND_ENERGY],
interval="5m",
date_start=datetime.now() - timedelta(days=1),
primary_grouping="network_region"
)
for timeseries in response.data:
print(f"Metric: {timeseries.metric} ({timeseries.unit})")
for result in timeseries.results:
print(f" {result.name}: {len(result.data)} intervals")
```
```typescript TypeScript
import { OpenElectricityClient } from "openelectricity"
const client = new OpenElectricityClient()
const { datatable } = await client.getMarket("NEM", ["demand", "demand_energy"], {
interval: "5m",
dateStart: "2024-01-01T00:00:00",
dateEnd: "2024-01-02T00:00:00",
primaryGrouping: "network_region",
})
```
---
## Data intervals
| Network | Interval | Source |
|---|---|---|
| NEM | 5 min | AEMO `DISPATCHREGIONSUM` (`TOTALDEMAND`) |
| WEM | 30 min | Derived from `facility_scada` generation totals |
---
# Curtailment
Understanding renewable energy curtailment in the NEM
URL: /guides/curtailment
Curtailment data is currently only supported on the NEM network
## What is Curtailment?
Curtailment occurs when renewable energy generators (wind and solar) are instructed to reduce their output below what they could potentially generate given the available wind and solar resources. This typically happens when there is more renewable generation available than the grid can accommodate at that moment.
Think of it like having a water tap that could flow at full pressure, but you're only opening it halfway because your bucket would overflow otherwise. The renewable resources (wind and sunshine) are available, but the electricity isn't needed or can't be transported through the grid at that time.
## Why Does Curtailment Happen?
Curtailment occurs for several reasons:
### 1. **Oversupply of Generation / Low Spot Prices**
When demand is low or moderate and renewable generation is plentiful, spot prices fall to low or even negative levels. If other generators can’t reduce output far enough, then renewable sources will also curtail. This form of curtailment – referred to as “self-curtailment” or “economic offloading” - is a normal feature of the NEM’s pricing mechanism, and the curtailed generators will be those who have offered their output at higher prices than their competitors. This type of curtailment often happens during:
- Sunny weekend afternoons when solar generation is high but demand is low
- Windy nights when wind generation peaks but overnight demand is minimal
### 2. **Network Constraints**
Transmission lines have capacity limits. Even if there's demand elsewhere in the grid, renewable generators may need to curtail if the transmission lines are at capacity. This is particularly common in:
- Remote renewable energy zones with limited transmission capacity
- Areas with rapid renewable development that has outpaced transmission infrastructure
### 3. **System Security**
The grid operator (AEMO) may curtail generation to maintain system stability, including:
- Managing system strength and inertia requirements
- Maintaining frequency within safe limits
- Ensuring adequate reserves for unexpected events
## How Open Electricity Reports Curtailment
Open Electricity calculates curtailment by comparing what renewable generators could have produced (unconstrained generation) with what they actually produced (actual generation). This data comes from AEMO's dispatch data, which provides target values and unconstrained intermittent generation forecasts (UIGF) for wind and solar.
### Open Electricity Website
Curtailment is displayed in the network generation views on the [tracker of the Open Electricity website](https://explore.openelectricity.org.au/energy/nem/?range=7d&interval=30m&view=discrete-time&group=Detailed).
Each of wind and solar curtailment can be seen in the chart for both power and energy views, and are accounted for in the table on the right.
### Available Metrics
Open Electricity provides curtailment data through several metrics:
#### Power Metrics (MW)
- **`curtailment`** - Total curtailed power across all renewable sources
- **`curtailment_solar_utility`** - Utility solar generation curtailed
- **`curtailment_wind`** - Wind generation curtailed
These show the instantaneous power that could have been generated but wasn't, measured in megawatts (MW). Use these for:
- Real-time monitoring
- Understanding current grid conditions
- 5-minute and hourly interval analysis
#### Energy Metrics (MWh)
- **`curtailment_energy`** - Total curtailed energy over a time period
- **`curtailment_solar_utility_energy`** - Utility solar energy curtailed
- **`curtailment_wind_energy`** - Wind energy curtailed
These show the cumulative energy that was curtailed over time, measured in megawatt-hours (MWh). Use these for:
- Daily, weekly, or monthly summaries
- Understanding total renewable energy lost
- Economic impact analysis
## Understanding AEMO's Timestamp Convention
AEMO uses an "interval-ending" timestamp convention where timestamps represent the end of each 5-minute dispatch interval.
For example, a timestamp of 14:35 represents the period from 14:30 to 14:35.
AEMO's dispatch data contains two fundamentally different types of measurements that occur at different points in time:
1. SCADA Actual Generation (`INITIALMW`): These are snapshot measurements taken at the beginning of each dispatch interval. For the interval timestamped 14:35, the SCADA reading was actually taken at 14:30.
2. Dispatch Targets (`CLEAREDMW)`: These are forward-looking targets set at the beginning of an interval for generators to achieve by the end of that interval. For the interval timestamped 14:35, these targets
were set at 14:30 to be achieved by 14:35.
The difference is most evident when dealing with aligning curtailment with actual generation.
### Open Electricity Solution to Timestamp Alignment
Curtailment is calculated as the difference between what generators could produce (UIGF - Unconstrained Intermittent Generation Forecast)
and what they were dispatched to produce (`CLEAREDMW`). Both `UIGF` and `CLEAREDMW` are **targets for the end of the interval**.
To properly align curtailment with actual generation:
- Actual generation for interval 14:35 (measured at 14:30)
- Must be compared with curtailment targets from interval 14:30 (which were targets set at 14:25 for achievement by 14:30)
This requires shifting the curtailment data forward by one interval (5 minutes) so that:
- The curtailment target that was set for time point T
- Aligns with the actual generation measured at time point T
### Implementation
Open Electricity addresses this by storing curtailment fields with a timestamp shifted forward by 5 minutes from their source dispatch data.
interval. This ensures that when you view data for any given timestamp, you're seeing:
- The actual generation that occurred at that moment
- The curtailment that was targeting that same moment
This alignment is critical for accurate analysis of renewable energy curtailment patterns and their relationship to actual generation output.
## Understanding Curtailment Patterns
### Daily Patterns
Solar curtailment typically peaks during midday hours when solar generation is highest, particularly on mild, sunny days when air conditioning demand is low. Wind curtailment can occur at any time but is often higher during overnight periods when demand is lowest.
### Seasonal Patterns
- **Spring**: Often sees the highest curtailment due to mild weather (low demand) combined with good renewable conditions
- **Summer**: High solar generation but also high cooling demand can lead to midday curtailment
- **Winter**: Lower solar generation but wind curtailment may increase during storms
- **Autumn**: Similar to spring with moderate curtailment levels
### Price Correlation
Curtailment typically correlates with low or negative electricity prices. When renewable generation is curtailed, it often indicates an oversupply situation that drives prices down. Understanding this relationship helps explain:
- Why negative prices occur
- The economic impact on renewable generators
- Investment signals for storage and transmission
## Impact and Implications
### Economic Impact
Curtailed energy represents lost revenue for renewable generators and lost clean energy for the grid. As renewable penetration increases, curtailment is expected to grow unless addressed through:
- **Energy storage**: Batteries can store excess renewable generation
- **Transmission expansion**: Better connections between renewable zones and demand centers
- **Demand response**: Shifting demand to periods of high renewable generation
- **Hydrogen production**: Using excess renewables for green hydrogen
### Environmental Impact
Every MWh of curtailed renewable energy is clean energy that could have displaced fossil fuel generation. Reducing curtailment is key to maximizing emissions reductions from renewable investments.
### Market Signals
High curtailment levels signal:
- Opportunities for energy storage investment
- Need for transmission infrastructure upgrades
- Potential for new flexible demand (like hydrogen electrolyzers)
- Locations where new renewable investment may face challenges
## Data Sources
Open Electricity calculates curtailment using AEMO's official dispatch data:
- **DISPATCHREGIONSUM** table provides regional target and unconstrained generation values
- **Semi-scheduled generator data** includes wind and solar farm constraints
- Updates every 5 minutes with NEM dispatch intervals
The calculation methodology:
1. **Unconstrained Generation (UIGF)**: What could have been generated based on available resources
2. **Actual Generation**: What was actually generated (cleared MW)
3. **Curtailment**: The difference between potential and actual generation
This approach provides a comprehensive view of renewable curtailment across the NEM, helping stakeholders understand and respond to the challenges and opportunities of renewable integration.
## Further Reading
- [AEMO's Quarterly Energy Dynamics reports](https://aemo.com.au/energy-systems/major-publications/quarterly-energy-dynamics-qed) include detailed curtailment analysis
- [Renewable Integration Study](https://aemo.com.au/energy-systems/major-publications/renewable-integration-study-ris) examines future curtailment scenarios
- [Semi-Scheduled Generation](https://aemo.com.au/energy-systems/electricity/national-electricity-market-nem/participate-in-the-market/registration) explains how renewable generators participate in the NEM
---
# Capacity
Capacity types and how they are displayed in Open Electricity
URL: /guides/capacity
## Overview
Capacity refers to the maximum amount of electrical power that a generation unit can produce, typically measured in megawatts (MW). Understanding the different types of capacity measurements helps in analysing the true potential and operational constraints of electricity generators.
## Types of Capacity
In the Australian electricity market, there are two primary capacity measurements tracked by AEMO (Australian Energy Market Operator):
### Registered Capacity
Registered capacity represents the normal operational capacity of a generation unit. This is the capacity registered with AEMO for standard market operations.
- For solar farms, this typically reflects the aggregate capacity of all solar panels installed
- For fossil fuel plants, this often represents the design capacity or Maximum Continuous Rating
- This is the standard measure used for most capacity analysis
### Maximum Capacity
Maximum capacity is used by AEMO for bid validation in market systems and may differ from registered capacity.
- For solar farms, this is often lower than registered capacity due to grid connection constraints
- For fossil fuel plants, this may represent a temporary higher output achievable at reduced efficiency
- This capacity might only be sustained for limited periods
## Why Do These Capacities Differ?
The differences between registered and maximum capacity vary by generation technology:
**Solar Farms** often have registered capacity greater than maximum capacity due to:
- Intentional oversizing of solar panel installations
- Grid connection limitations that constrain output
- Economic optimisation considering varying solar conditions
**Fossil Fuel Plants** sometimes have maximum capacity greater than registered capacity because:
- They can temporarily operate above normal ratings
- Higher outputs may come with reduced efficiency
- Operators typically avoid this except during high-price events
It is worth noting these values can differ considerably and may have implications for analysis. Consider how solar farm capacity has varied with time.

On the y-axis we have the ratio of registered to maxium capacity, and it is evident many facilities have a much larger registered capacity. Consequently, it is important to keep in mind which capacity metric is used for a given purpose as they may yield meaningfully different results. This ratio for fossil fuels is often less dramatic, though the decision may still be important. Take the generation at a Bayswater unit (BW03).

BW03 had a registered capacity of 660MW during 2024, however for 2154 hours, or over 24% of the time, it averaged generation above this value, whilst other coal facilities will have a substantially different profile. For instance Eraring's ER01 did not exceed its registered capacity for any hourly period in 2024, despite having a maximum capacity 30MW greater than its registered capacity.

For a detailed technical discussion of these capacity measures, see WattClarity's analysis: [Analytical Challenge – choosing what measure to use, for 'Installed Capacity'](https://wattclarity.com.au/articles/2022/09/analyticalchallenge-installedcapacity/)
## Accessing Capacity Data
Open Electricity provides capacity information through multiple interfaces:
### Facility Pages
Individual facility pages display detailed capacity information. For example, the Bayswater Power Station page shows the unit's capacity in its capacity chart:
[View Bayswater Power Station](https://explore.openelectricity.org.au/facility/au/NEM/BAYSW/?range=3d&interval=30m)
The facility page displays:
- Current maximum capacity for each generation unit
- Unit-level details including fuel technology
- Operational status and performance metrics
- Capacity factors for each unit
Open Electricity displays **maximum capacity** on facility pages and in capacity charts. This is the value AEMO uses for bid validation and reflects the actual upper bound on a unit's output, rather than the nameplate figure registered at market entry. The registered value remains available through the API.
This is better demonstrated when viewing battery facilities, such as the Waratah Super Battery in New South Wales.
[View Waratah Super Battery](https://explore.openelectricity.org.au/facility/au/NEM/WTAHB/?range=3d&interval=30m)
Generation tracks closely against the unit's maximum capacity.
### Capacity Charts
Open Electricity's capacity charts provide comprehensive views of capacity across networks and fuel technologies:
[Explore Capacity Charts](https://explore.openelectricity.org.au/capacity/au/)
These advanced charts offer:
- Historical maximum-capacity trends by fuel technology
- Combined view of both NEM and WEM markets
- Interactive filtering and time range selection
- Breakdown by renewable and non-renewable sources
As with the facility pages, the aggregated fleet values are summed from each unit's **maximum capacity**.
Advanced charting features allow you to view capacity history by fueltech over time in proportion view, change view, and more.
### API Access
For programmatic access, capacity data is available through the Open Electricity API at the facilities endpoint. The API provides:
- Real-time capacity information
- Historical capacity data
- Both registered and maximum capacity values (web UI uses maximum)
- Detailed unit-level information
## Capacity Factors
Capacity factor is a crucial metric that measures how much electricity a generator actually produces compared to what it could produce if operating at full capacity continuously. It's expressed as a percentage:
**Capacity Factor = (Actual Energy Output) ÷ (Maximum Possible Energy Output) × 100%**
### What Capacity Factors Tell You
A capacity factor reveals the operational characteristics and performance of a power station:
- **High capacity factors** (70-90%) indicate baseload generators that run continuously
- **Moderate capacity factors** (30-60%) suggest peaking plants or weather-dependent renewables
- **Low capacity factors** (10-30%) typically indicate backup generators or highly variable renewables
### Typical Capacity Factors by Fuel Technology
**Carbon-based**
- **Coal**: 70-85% - Designed for continuous operation
- **Gas**: 10-70% - Combined cycle plants often run as mid-merit generation or as peaking plants
- **Diesel**: 0-10% - Used in emergency situations
**Renewables**
- **Hydro**: 30-50% - Varies significantly based on water availability and market needs
- **Wind**: 30-45% - Dependent on wind resource quality and location
- **Solar (Utility)**: 25-35% - Limited by daylight hours and weather often better oriented and placed
- **Solar (Rooftop)**: 15-20% - Lower than utility
**Storage**
- **Battery**: 10-30% - Used for arbitrage and grid stabilisation rather than continuous output
- **Pumped Hydro**: 20-40% - Cycles between generation and pumping modes
### Interpreting Capacity Factors
Capacity factors help assess:
1. **Economic Performance**: Higher capacity factors generally mean better return on investment
2. **Resource Quality**: For renewables, higher factors indicate more optimal unit locations
3. **Operational Strategy**: Low factors may be intentional for peaking plants
4. **Maintenance Patterns**: Declining factors might indicate ageing equipment
5. **Market Conditions**: Economic factors such as price spikes or surges in demand influence when generators operate
For more detailed facility information and real-time generation data, explore the [Facilities Guide](/guides/facilities).
---
# Emissions
URL: /guides/emissions
# Understanding Emissions Data
Open Electricity provides comprehensive emissions data for the Australian electricity grid, calculating CO₂ emissions from electricity generation using facility-specific emissions intensity factors. This guide explains how emissions are calculated, data sources, and how to access the data.
## What are Emissions?
Emissions in the electricity sector refer to greenhouse gases, primarily carbon dioxide (CO₂), released during electricity generation. Different generation technologies have vastly different emissions profiles:
- **Coal**: High emissions (0.7-1.4 tCO₂/MWh)
- **Natural Gas**: Moderate emissions (0.4-0.6 tCO₂/MWh)
- **Renewables**: Zero operational emissions (solar, wind, hydro)
- **Biomass**: Variable emissions depending on fuel source
## How Open Electricity Calculates Emissions
Open Electricity calculates emissions at the unit level using the following formula:
$$\text{Emissions} = \text{Energy Generated} \times \text{Emissions Intensity Factor}$$
Where:
- **Emissions** are measured in tonnes of CO₂ (tCO₂)
- **Energy Generated** is measured in megawatt-hours (MWh)
- **Emissions Intensity Factor** is measured in tCO₂/MWh
### Detailed Calculation
For each generation unit at each time interval:
$$E_{unit} = G_{unit} \times EF_{unit}$$
Where:
- $E_{unit}$ = Emissions from the unit (tCO₂)
- $G_{unit}$ = Energy generated by the unit (MWh)
- $EF_{unit}$ = Unit's emissions factor (tCO₂/MWh)
Total emissions for a facility, region, or network are calculated by summing individual unit emissions:
$$E_{total} = \sum_{i=1}^{n} E_{unit,i}$$
The aggregate emissions intensity for a region or network is:
$$EI_{aggregate} = \frac{\sum_{i=1}^{n} E_{unit,i}}{\sum_{i=1}^{n} G_{unit,i}}$$
### Implementation Details
The emissions calculation is implemented in the facility aggregation pipeline (`opennem/aggregates/facility_interval.py`):
```sql
-- Calculate emissions for each unit
CASE
WHEN sum(fs.energy) > 0 THEN
coalesce(round(sum(u.emissions_factor_co2 * fs.energy), 4), 0)
ELSE 0
END as emissions,
-- Calculate emissions intensity
CASE
WHEN sum(fs.energy) > 0 THEN
coalesce(round(sum(u.emissions_factor_co2 * fs.energy) / sum(fs.energy), 4), 0)
ELSE 0
END as emissions_intensity
```
## Data Sources
Open Electricity sources emissions intensity factors from authoritative Australian government agencies:
### Primary Sources
1. **AEMO (Australian Energy Market Operator)**
- Provides emissions factors for NEM generators
- Updated annually in the Integrated System Plan (ISP)
- Includes technology-specific and unit-specific factors
2. **Clean Energy Regulator (CER)**
- National Greenhouse and Energy Reporting (NGER) scheme data
- Facility-level emissions reporting
- Verified emissions data for major generators
3. **State Environmental Agencies**
- WEM (Western Australia) emissions data
- State-specific emissions reporting
### Data Quality
Emissions factors are assigned with source attribution:
- `aemo`: Direct from AEMO publications
- `cer`: From Clean Energy Regulator NGER data
- `opennem_estimated`: Estimated based on fuel type and technology
## Available Metrics
Open Electricity provides emissions data at multiple levels:
### Network Level
- Total emissions by network (NEM, WEM)
- Emissions intensity (tCO₂/MWh)
- Historical trends and patterns
### Regional Level
- Emissions by network region (NSW1, QLD1, VIC1, SA1, TAS1)
- Regional emissions intensity
- Inter-regional emissions flows
### Facility Level
- Individual facility emissions
- Unit-level emissions where available
- Technology-specific emissions profiles
## Accessing Emissions Data
### Via the API
Emissions data is available through multiple API endpoints:
**Network Emissions:**
```typescript
const response = await client.getNetworkData(
"NEM",
["emissions", "emissions_intensity"],
{
interval: "1h",
dateStart: "2024-01-01",
dateEnd: "2024-01-31",
primaryGrouping: "network_region"
}
)
```
**Facility Emissions:**
```typescript
const response = await client.getFacilityData(
"NEM",
["ERARING", "BAYSW1"],
["emissions"],
{
interval: "1d",
dateStart: "2024-01-01"
}
)
```
### Via the Website
Emissions data is displayed on:
- **Network pages**: Total grid emissions and intensity
- **Facility pages**: Individual facility emissions profiles
- **Technology pages**: Emissions by generation technology
## Use Cases
### Carbon Accounting
Track the carbon intensity of electricity consumption for:
- Corporate sustainability reporting
- Scope 2 emissions calculations
- Time-of-use optimization
### Grid Analysis
Understand emissions patterns:
- Daily and seasonal variations
- Impact of renewable generation
- Regional differences
### Policy Analysis
Support decision-making with:
- Emissions reduction tracking
- Technology transition monitoring
- Market mechanism evaluation
## Important Considerations
### Scope Limitations
- Open Electricity reports **operational emissions** only (Scope 1)
- Does not include lifecycle emissions from construction or decommissioning
- Does not include upstream fuel extraction emissions
### Time Resolution
- 5-minute intervals: Instantaneous emissions rates
- Hourly/Daily: Total emissions for the period
- Use appropriate metrics for your analysis timeframe
### Interconnector Flows
- Emissions can be "imported" or "exported" between regions
- Regional emissions include local generation only
- Consider interconnector flows for consumption-based accounting
## Future Enhancements
Open Electricity continues to improve emissions data:
- More granular unit-level factors
- Lifecycle emissions estimates
- Consumption-based emissions tracking
- International emissions factors for comparison
## Related Resources
- [Energy Guide](/guides/energy) - Understanding energy vs power metrics
- [Networks Guide](/guides/networks) - Regional structure and interconnections
- [API Reference](/api-reference/data/get-network-data) - Detailed API documentation
---
# Pollution Data
URL: /guides/pollution
# Understanding Pollution Data
Open Electricity provides comprehensive pollution data from the National Pollutant Inventory (NPI) for Australian electricity generation facilities. This guide explains the pollution data available, how it's sourced, and what it means for understanding the environmental impact of electricity generation beyond just CO₂ emissions.
## What is NPI Pollution Data?
The National Pollutant Inventory (NPI) is Australia's national database of pollutant emissions. It tracks emissions of 93 substances to air, water, and land from industrial facilities, including power stations. Unlike greenhouse gas emissions (CO₂) which contribute to climate change, NPI tracks pollutants that affect local air quality and human health.
### Key Pollutants from Power Generation
Power stations, particularly coal-fired facilities, emit various pollutants:
- **Sulfur Dioxide (SO₂)**: Causes acid rain and respiratory issues
- **Nitrogen Oxides (NOₓ)**: Contributes to smog and respiratory problems
- **Particulate Matter (PM10, PM2.5)**: Fine particles that affect air quality and health
- **Carbon Monoxide (CO)**: Toxic gas that reduces oxygen delivery
- **Heavy Metals**: Mercury, lead, arsenic from coal combustion
- **Volatile Organic Compounds (VOCs)**: Contribute to ground-level ozone
## How Pollution Differs from Emissions
It's important to understand the distinction:
### CO₂ Emissions (Climate Impact)
- **Source**: Calculated from generation data and emissions factors
- **Impact**: Global climate change
- **Measurement**: Tonnes of CO₂ per MWh generated
- **Data frequency**: Real-time (5-minute intervals)
- **Reporting**: National Greenhouse and Energy Reporting (NGER)
### NPI Pollution (Local Impact)
- **Source**: Reported annually by facilities to NPI
- **Impact**: Local air quality and human health
- **Measurement**: Kilograms or tonnes per year
- **Data frequency**: Annual reporting
- **Reporting**: National Pollutant Inventory
## Data Collection and Methodology
### How NPI Data is Collected
Facilities report their pollution data annually to the NPI using various methods:
1. **Direct Measurement**: Continuous emissions monitoring systems
2. **Engineering Calculations**: Based on fuel consumption and emission factors
3. **Mass Balance**: Tracking inputs and outputs
4. **Emission Factors**: Standard factors for fuel types and processes
### Data Quality Indicators
Each pollution measurement includes a data quality indicator:
- **Measured**: Direct measurement from monitoring equipment
- **Calculated**: Engineering calculations based on operations
- **Estimated**: Estimation using emission factors
- **Mass Balance**: Calculated from material flows
### Reporting Thresholds
Not all facilities report all pollutants. Reporting is required when:
- The facility uses more than the threshold amount of a substance
- Emissions exceed reporting thresholds
- The facility burns more than specified amounts of fuel
## Understanding the Data
### Annual Reporting Cycles
NPI data is reported annually, with facilities submitting data for the previous financial year (July to June). This means:
- Data is historical, not real-time
- Represents total annual emissions
- Published several months after the reporting period
## Interpreting Pollution Data
### Scale and Context
When viewing pollution data, consider:
- **Magnitude**: Major coal plants emit thousands of tonnes of SO₂ annually
- **Comparison**: Gas plants typically have much lower pollution than coal
- **Trends**: Pollution often decreases as plants install control equipment
- **Closure Impact**: Retiring coal plants significantly reduces regional pollution
### Health and Environmental Impacts
Different pollutants have different impacts:
**Air Quality Pollutants:**
- NOₓ and VOCs form ground-level ozone (smog)
- SO₂ causes acid rain and respiratory issues
- Particulate matter affects breathing and cardiovascular health
**Heavy Metals:**
- Mercury bioaccumulates in food chains
- Lead affects neurological development
- Arsenic is carcinogenic
**Regional Pollutants:**
- Affect local communities more than distant areas
- Concentrate in valleys and during inversions
- Impact varies with weather patterns
## Available Data
### Facility Coverage
NPI pollution data is available for facilities that:
- Meet reporting thresholds for pollutant emissions
- Are registered with the NPI program
- Have submitted annual reports
This typically includes:
- All major coal-fired power stations
- Large gas-fired power stations
- Larger diesel generators
- Some industrial cogeneration facilities
### Pollutant Categories
Open Electricity organizes pollutants into categories:
**Air Pollutants:**
- Sulfur dioxide (SO₂)
- Nitrogen oxides (NOₓ)
- Carbon monoxide (CO)
- Particulate matter (PM10, PM2.5)
- Volatile organic compounds (VOCs)
- Ammonia (NH₃)
- Hydrochloric acid (HCl)
**Heavy Metals:**
- Mercury (Hg) and compounds
- Lead (Pb) and compounds
- Arsenic (As) and compounds
- Cadmium (Cd) and compounds
- Chromium (Cr) compounds
- Nickel (Ni) and compounds
**Organic Compounds:**
- Polycyclic aromatic hydrocarbons (PAHs)
- Dioxins and furans
- Benzene
- Formaldehyde
## Using Pollution Data
### Understanding Facility Impact
Pollution data helps understand the full environmental impact of electricity generation:
- Compare facilities beyond just CO₂ emissions
- Identify high-polluting facilities
- Track improvements from upgrades or closures
- Assess local vs global environmental trade-offs
### Policy and Planning
The data supports:
- Community awareness of local air quality impacts
- Planning decisions for new generation
- Health impact assessments
- Environmental justice considerations
## Accessing the Data
### On the Website
Pollution data will be available soon on the facilities page of the Open Electricity website.
### Via the API
Access pollution data programmatically:
```typescript
// Get pollution data for a facility
const pollution = await client.getFacilityPollution({
facility_code: ["YALLOURN"],
pollutant_category: ["air_pollutant"],
dateStart: "2020-01-01",
dateEnd: "2024-12-31"
})
```
## Important Considerations
### Data Limitations
- **Annual Resolution**: Data is yearly, by financial year, not real-time
- **Reporting Thresholds**: Smaller facilities may not report
- **Historical Data**: Available from 1998 onwards
- **Estimation Methods**: Not all data is directly measured
### Comparing Facilities
When comparing pollution between facilities:
- Consider generation output (pollution per MWh)
- Account for fuel types and quality
- Note pollution control equipment
- Consider plant age and technology
### Renewable Energy
Most renewable facilities don't appear in NPI data because:
- Solar and wind have no operational air emissions
- Hydro has no combustion emissions
- Battery storage has no direct emissions
- They don't meet reporting thresholds
## Related Resources
- [Emissions Guide](/guides/emissions) - Understanding CO₂ emissions data
- [Facilities Guide](/guides/facilities) - Facility structure and data
- [National Pollutant Inventory](http://www.npi.gov.au/) - Official NPI website
- [API Reference](/api-reference/pollution/get-facility-pollution) - Pollution API documentation
---
# Fueltechs and Fueltech Groups
Documentation of fuel technologies and their groupings in the OpenNEM system
URL: /guides/fueltechs
# Overview
The OpenNEM system uses a hierarchical classification system for different types of electricity generation technologies:
1. **Fueltechs** - Individual generation technologies (e.g., Coal Black, Solar Utility, Wind)
2. **Fueltech Groups** - Broader categories that group similar technologies (e.g., Coal, Solar, Wind)
This classification system is used throughout the OpenNEM API and visualisations to provide consistent grouping and coloring of generation types.
# Fueltech Groups
Fueltech groups are the high-level categories used to group similar generation technologies. Each group has a distinct color used in visualisations.
| Group Code | Label | Renewable |
|---------------------|----------------------|-----------|
| coal | Coal | FALSE |
| gas | Gas | FALSE |
| wind | Wind | TRUE |
| solar | Solar | TRUE |
| battery_charging | Battery (Charging) | FALSE |
| battery_discharging | Battery (Discharging)| FALSE |
| hydro | Hydro | TRUE |
| distillate | Distillate | FALSE |
| bioenergy | Bioenergy | TRUE |
| pumps | Pumps | FALSE |
# Fueltechs
Fueltechs represent specific generation technologies. Each fueltech belongs to a fueltech group and inherits properties like color from its group.
| Fueltech Code | Label | Fueltech Group | Renewable |
|-----------------------|----------------------|---------------------|-----------|
| battery_charging | Battery (Charging) | battery_charging | FALSE |
| battery_discharging | Battery (Discharging)| battery_discharging | FALSE |
| bioenergy_biogas | Biogas | bioenergy | TRUE |
| bioenergy_biomass | Biomass | bioenergy | TRUE |
| coal_black | Coal (Black) | coal | FALSE |
| coal_brown | Coal (Brown) | coal | FALSE |
| distillate | Distillate | distillate | FALSE |
| gas_ccgt | Gas (CCGT) | gas | FALSE |
| gas_ocgt | Gas (OCGT) | gas | FALSE |
| gas_recip | Gas (Reciprocating) | gas | FALSE |
| gas_steam | Gas (Steam) | gas | FALSE |
| gas_wcmg | Gas (Coal Mine Waste)| gas | FALSE |
| hydro | Hydro | hydro | TRUE |
| pumps | Pumps | pumps | FALSE |
| solar_rooftop | Solar (Rooftop) | solar | TRUE |
| solar_thermal | Solar (Thermal) | solar | TRUE |
| solar_utility | Solar (Utility) | solar | TRUE |
| wind | Wind | wind | TRUE |
| wind_offshore | Offshore Wind | wind | TRUE |
| aggregator_vpp | Aggregator (VPP) | - | TRUE |
| aggregator_dr | Aggregator (DR) | - | TRUE |
| nuclear | Nuclear | - | FALSE |
| imports | Network Import | - | FALSE |
| exports | Network Export | - | FALSE |
| interconnector | Interconnector | - | FALSE |
| battery | Battery | - | FALSE |
# Special Categories
Some fueltechs don't belong to a specific fueltech group as they represent special cases:
- **Aggregators** - Virtual Power Plants (VPP) and Demand Response (DR) units that combine multiple smaller generators
- **Network** - Imports, exports and interconnector flows between regions
- **Battery** - Generic battery category (distinct from charging/discharging)
- **Nuclear** - Currently not used in Australia but maintained for international compatibility
---
# Facilities and Generation
Understanding electricity generation facilities and their data structures
URL: /guides/facilities
Australia's electricity network is made up of over 500 generation facilities in the National Electricity Market (NEM) and the Wholesale Electricity Market (WEM). These facilities have different fuel input technologies and generation profiles, and operate under various statuses from commissioning through to retirement.
The Open Electricity platform provides comprehensive tracking and analysis of these facilities, making this data accessible through both our interactive website and API.
## Understanding Facilities
### Network Structure
An electricity network consists of:
- **Multiple facilities** distributed across regions
- **Different fuel technologies** powering generation
- **Various operational statuses** (operating, commissioning, retired, etc.)
- **Geographic distribution** across states and territories
### Facility Composition
A single facility is made up of multiple generation units. Each unit:
- Has a specific [fuel technology type](/guides/fueltechs)
- Operates independently within the facility
- Can have different capacities and characteristics
Importantly, a facility can comprise units with a mixture of fuel technologies. For example:
- Solar + battery storage combinations
- Coal plants with diesel backup generators
- Wind farms with battery integration
## Exploring Facilities
Visit the [Open Electricity facilities explorer](https://explore.openelectricity.org.au/facilities/au/) to browse all Australian electricity generation facilities.

The facilities explorer provides powerful features to understand Australia's electricity infrastructure:
- **Browse all facilities** — View a comprehensive list of over 500 tracked facilities
- **Filter by fuel technology** — Select specific technologies like wind, solar, or coal
- **Filter by operating status** — Show only operating, retired, or commissioning facilities
- **Interactive map view** — See geographic distribution of generation assets
- **Capacity information** — View maximum capacity for each facility
- **Technology mix** — Quickly identify the fuel technology for each facility
- **Regional distribution** — Understand generation capacity by state and region
Each row in the facilities list shows key information including facility name, location, region, technology type, and total capacity. Clicking on any facility takes you to its detailed page.
## Individual Facility Details
Each facility has a dedicated page with comprehensive information. For example, the [Golden Plains Wind Farm](https://explore.openelectricity.org.au/facility/au/NEM/GPWFEST/?range=3d&interval=30m):

The facility page displays:
- **Real-time generation** — Live power output graph showing generation patterns over time
- **Facility description** — Detailed information about the project and location
- **Interactive maps** — Both regional overview and satellite imagery of the actual facility
- **Unit breakdown** — Individual generation units with their specifications
- **Performance metrics**:
- Maximum capacity (MW) for each unit (see the [Capacity guide](/guides/capacity) for the distinction between maximum and registered)
- Current power output (MW)
- Capacity factor (%) showing utilisation
- **Time range controls** — View generation data over different periods (1D, 3D, 7D, 30D, 1Y, ALL)
- **Data intervals** — Adjust granularity from 5-minute to monthly intervals
## Facility Data Fields
### Facility Fields
| Field | Data Type | Description |
|-------|-----------|-------------|
| `code` | string | Unique facility identifier (e.g., `GPWFEST`) |
| `name` | string | Display name of the facility |
| `network_id` | string | Network the facility operates in (`NEM` or `WEM`) |
| `network_region` | string | Network region code (e.g., `VIC1`, `NSW1`) |
| `description` | string/HTML | Detailed facility description |
| `location` | object | Geographic coordinates (lat/lng) |
| `npi_id` | string | National Pollutant Inventory identifier |
| `units` | array | List of generation units within the facility |
### Unit Fields
| Field | Data Type | Description |
|-------|-----------|-------------|
| `code` | string | Unique unit identifier (e.g., `GPWFEST1`) |
| `fueltech_id` | string | [Fuel technology type](/guides/fueltechs) (e.g., `wind`, `solar_utility`) |
| `status_id` | string | Current operational status |
| `capacity_registered` | number | Registered generation capacity in MW |
| `capacity_maximum` | number | Maximum achievable capacity in MW |
| `capacity_storage` | number | Storage capacity for battery units in MWh |
| `emissions_factor_co2` | number | CO2 emissions factor (tonnes per MWh) |
| `dispatch_type` | string | Unit dispatch type (`GENERATOR`, `LOAD`, `BIDIRECTIONAL`) |
| `data_first_seen` | datetime | First time unit generation data appeared |
| `data_last_seen` | datetime | Most recent data point for unit generation data |
| `commencement_date` | datetime | When the unit commenced operation |
| `commencement_date_serialized` | string | Human-readable commencement date |
| `closure_date` | datetime | When the unit was closed |
| `closure_date_serialized` | string | Human-readable closure date |
| `expected_operation_date` | datetime | Expected date to begin operation |
| `expected_operation_date_serialized` | string | Human-readable expected operation date |
| `expected_closure_date` | datetime | Expected closure date |
| `expected_closure_date_serialized` | string | Human-readable expected closure date |
| `construction_start_date` | datetime | When construction began |
| `construction_start_date_serialized` | string | Human-readable construction start date |
| `project_approval_date` | datetime | When the project was approved |
| `project_approval_date_serialized` | string | Human-readable approval date |
| `project_lodgement_date` | datetime | When the project was lodged |
| `created_at` | datetime | When the unit record was created |
| `updated_at` | datetime | Last modification time |
Note: The serialized date fields provide human-friendly date formats that respect the level of date specificity available (e.g., "2024-08" for month-level precision, "2055" for year-only precision).
## Status Types
Generation units are tracked through various operational states:
- **Committed** — The facility has reached FID or started construction
- **Commissioning** — The facility has recorded generation and is yet to generate at 90% of its maximum capacity
- **Operating** — The facility has generated 90% of its maximum capacity and is currently generating
- **Retired** — Permanently closed and no longer generating
These definitions are unique to Open Electricity. The commissioning process can be broken down further into a series of tests conducted with AEMO and it is possible for facilities to be considered "operating" under Open Electricity's definition whilst still progressing through these commissioning steps. The commissioning process for each facility is not comprehensively documented and made public by AEMO, hence the difference in our definitions. For more information on AEMO's process, visit [here](https://www.aemo.com.au/energy-systems/electricity/national-electricity-market-nem/participate-in-the-market/network-connections/connections-scorecard).
There are facilities that may behave unusually despite being considered operating by Open Electricity. Consider the Eraring BESS. The battery reached 90% of maximum capacity in September 2025, but only completed AEMO's commissioning process in [December 2025](https://www.originenergy.com.au/about/who-we-are/what-we-do/generation/eraring-projects/battery/). We can see very low generation in October and November as the battery is not operating as a commissioned facility despite it meeting the Open Electricity threshold.

On the other hand, there are facilities that will operate in a reasonable manner and contribute considerably to the grid despite not being commissioning by AEMO's definition. Consider the Gangarri Solar Farm. The facility officially reached full output according to AEMO in [Q1 2025](https://www.aemo.com.au/-/media/files/electricity/nem/network_connections/connections-scorecard/2025/march-2025.pdf?rev=fba164b227c848abafe9cfa0176658a3&sc_lang=en), though it had generated at considerable rates for years prior. In fact, in October 2023, it had generated more electricity than during any month in 2025.

As such, the 90% of maximum capacity is a useful threshold to give an indication of the amount of capacity available to the grid, capacity factor calculations and similar uses. The commissioning status given by Open Electricity is indicative and users may find alternative definitions better suit their purposes.
## API Access
Facilities data is available through the Open Electricity API. The facilities endpoint provides programmatic access to all facility and unit information shown on the website.
For detailed API documentation and examples, see the [Facilities API Reference](/api-reference/facilities/get-facilities).
## Example API Response
Here's an example of facility data returned from the API:
```json
{
"code": "GPWFEST",
"name": "Golden Plains East",
"network_id": "NEM",
"network_region": "VIC1",
"units": [
{
"code": "GPWFEST1",
"fueltech_id": "wind",
"status_id": "operating",
"capacity_registered": 248,
"dispatch_type": "GENERATOR",
"commencement_date": "2024-09-01T00:00:00",
"commencement_date_serialized": "2024-09",
"expected_closure_date": "2055-01-01T00:00:00",
"expected_closure_date_serialized": "2055"
}
]
}
```
## Next Steps
Explore more about facilities, their technologies, and how to access their data programmatically.
Explore the interactive facilities map to browse all Australian electricity generation facilities
Learn about different fuel technologies used in electricity generation
Access facility data programmatically via the Open Electricity API
View real-time power data and understand power output patterns
---
# Batteries in the Australian Electricity Network
URL: /guides/batteries
Batteries play a crucial role in the Australian electricity network by providing energy storage solutions that enhance grid stability, support renewable energy integration, and improve energy security. This guide explores the purpose and benefits of batteries within the network.
## Purpose of Batteries
1. **Energy Storage**: Batteries store excess energy generated during periods of low demand or high renewable output, such as solar and wind. This stored energy can be released back into the grid during peak demand times, ensuring a stable and reliable electricity supply.
2. **Grid Stability**: By providing rapid response capabilities, batteries help maintain grid frequency and voltage levels, which are essential for the stable operation of the electricity network.
3. **Renewable Integration**: Batteries facilitate the integration of renewable energy sources by smoothing out the variability and intermittency associated with solar and wind power. This helps in reducing reliance on fossil fuels and lowering carbon emissions.
4. **Energy Security**: Batteries enhance energy security by providing backup power during outages or emergencies, ensuring continuous electricity supply to critical infrastructure and services.
5. **Cost Efficiency**: By reducing the need for expensive peaking power plants and minimizing transmission losses, batteries contribute to a more cost-effective electricity system.
## Battery Specifications and Performance
### Key Battery Specifications
Grid-scale batteries are defined by two key specifications:
1. **Power Capacity (MW)**: The maximum rate at which the battery can charge or discharge power. This determines how quickly the battery can respond to grid demands. For example, a 100 MW battery can provide or absorb up to 100 megawatts of power instantaneously.
2. **Energy Storage Capacity (MWh)**: The total amount of energy the battery can store. This determines how long the battery can sustain its power output. For example, a 100 MWh battery can provide 100 megawatts of power for one hour, or 50 megawatts for two hours.
The relationship between these specifications is often expressed as a ratio. For example, a battery with 100 MW power capacity and 200 MWh storage capacity has a 2:1 storage-to-power ratio, meaning it can operate at full power for 2 hours.
### Rapid Dispatch Capabilities
Batteries excel at rapid power dispatch, with several key characteristics:
- **Response Time**: Grid-scale batteries can respond to dispatch signals within milliseconds, compared to minutes or hours for conventional generators.
- **Ramp Rate**: Batteries can ramp from zero to full power almost instantaneously, providing crucial grid support during sudden changes in supply or demand.
- **Bidirectional Operation**: Batteries can switch between charging and discharging rapidly, making them ideal for frequency control and grid stabilisation services.
For example, the Hornsdale Power Reserve can deliver its full 150 MW capacity in less than 250 milliseconds, providing critical frequency control services to maintain grid stability.
### Operational Example
Consider a 100 MW / 150 MWh battery:
- It can provide 100 MW of power for 1.5 hours at maximum discharge
- It can operate at 50 MW for 3 hours
- It can provide frequency control services by rapidly varying its output between -100 MW (charging) and +100 MW (discharging)
This flexibility allows batteries to serve multiple grid functions:
- Peak shaving during high demand periods
- Grid frequency stabilisation
- Renewable energy integration
- Emergency backup power
## Growth of Battery Storage in Australia
The Australian electricity grid has undergone significant transformation with the rapid deployment of grid-scale batteries. This expansion has been driven by:
- Declining battery costs
- Government support and incentives
- Growing need for grid stability services
- Increasing renewable energy penetration
### Current Battery Deployment
As of 2024, Australia has over 1.5 GW of operational grid-scale battery storage capacity across more than 30 projects. Notable installations include:
- [Waratah Super Battery](https://explore.openelectricity.org.au/facility/au/NEM/WTAHB/?range=1y&interval=1w) (850 MW/1680 MWh) in South Australia
- [Hornsdale Power Reserve](https://explore.openelectricity.org.au/facility/au/NEM/HORN/?range=1y&interval=1w) (150 MW/194 MWh) in South Australia
- [Victorian Big Battery](https://explore.openelectricity.org.au/facility/au/NEM/VICB/?range=1y&interval=1w) (300 MW/450 MWh) in Victoria
- Wallgrove Grid Battery (50 MW/75 MWh) in New South Wales
## Bidirectional Batteries
### Introduction
The NEM (National Electricity Market) changed its approach to managing battery facilities in October 2024. Historically, batteries were represented as two separate units (DUIDs): one for charging (considered load) and one for discharging (considered generation). With the new change, AEMO transitioned battery facilities into a single bidirectional unit, where:
- Positive generation values represent discharging (export to the grid).
- Negative generation values represent charging (import from the grid).
This change simplifies implementation for AEMO by consolidating batteries into a single unit, but it introduces challenges for Open Electricity (formerly OpenNEM), which relies on the legacy method of splitting batteries into two distinct units.
The reasons for splitting the new bidirectional units back into two separate units in Open Electricity are:
- **Visual Clarity**: Separating charging and discharging makes it easier for users to visualize and understand battery behavior, as they can clearly see when batteries are importing versus exporting power to the grid.
- **Net Calculations**: Separating load and generation values enables accurate net calculations and regional demand analysis. This is particularly important when calculating total regional demand and generation, as battery charging (load) needs to be included in demand calculations while discharging (generation) contributes to supply calculations.
- **Granular Representation**: Splitting the values allows for clear and consistent representation of battery performance, ensuring better understanding and reporting of energy data.
### How Open Electricity Splits Bidirectional Units
The splitting of bidirectional units into two distinct units (for load and generation) is being achieved through a series of steps in the Open Electricity backend. Here’s a detailed breakdown of the process:
A new dispatch type named `BIDIRECTIONAL` was added to identify batteries that use the single-unit model. This allows Open Electricity to distinguish between legacy units and new bidirectional units.
In the generation pipeline, which processes incoming real-time power data from AEMO, is intercepted in memory and transformed. This enables quick and efficient data processing before persisting the results to the main database.
The splitting logic is implemented as follows:
- Positive generation values (representing discharging) are assigned to a generation unit.
- Negative generation values (representing charging) are assigned to a load unit.
For example, consider the battery facility BHB1 (Broken Hill Battery), which was previously represented as:
- `BHBG1` for generation (`battery_discharging` fueltech type).
- `BHBL1` for load (`battery_charging` fueltech type).
After the AEMO change, BHB1 becomes a single unit with bidirectional values. Open Electricity re-splits it in memory as:
- `BHBG1` → Generation unit (positive values).
- `BHBL1` → Load unit (negative values).
The old unit DUIDs are retained even though they do not exist in new generation data.
### Example Splitting Bidirectional Units
For a single bidirectional unit, let’s say BHB reports the following generation data:
| Timestamp | Generation (MW) |
|-----------|-----------------|
| `10:00 AM` | `-10` |
| `10:05 AM` | `15` |
Here:
- **-10 MW** indicates charging (import from grid).
- **15 MW** indicates discharging (export to grid).
The data is split into two separate units in memory:
1. **BHBL1 (Load Unit)**:
- `10:00 AM` → `10 MW` (absolute value of `-10 MW`).
- `10:05 AM` → `0 MW` (no charging when generation is positive).
2. **BHBG1 (Generation Unit)**:
- `10:00 AM` → `0 MW` (no discharging when generation is negative).
- `10:05 AM` → `15 MW`.
Resulting table after splitting:
| Timestamp | BHBL1 (MW) | BHBG1 (MW) |
|-----------|------------|------------|
| `10:00 AM` | `10` | `0` |
| `10:05 AM` | `0` | `15` |
# Battery State of charge
Open Electricity stores the current state of charge for a battery as the total MWh of energy stored in the battery. This is stored against the bidirectional battery unit for each facility, but is also available in the series for both the charging and discharging units.
Combined with the `capacity_storage` field from facility data, this can be used to calculate the state of charge as a percentage of the total storage capacity.
## Web Interface
Battery state of charge will be available on the Open Electricity facility pages in the near future
## API Access
Battery state of charge is available through the API for bidirectional units.
---
# Weather and Environmental Data
Weather station data and environmental metrics
URL: /guides/weather
## Overview
Open Electricity integrates weather data from Bureau of Meteorology (BoM) stations and tracks environmental metrics for generation facilities.
## Weather Stations
### Station Data
Stored in `bom_station`:
- Station code and name
- Geographic location
- Altitude
- State jurisdiction
- Priority level
### Observations
Stored in `bom_observation`:
- Temperature (apparent and air)
- Wind speed and direction
- Pressure
- Humidity
- Cloud cover
## Data Collection
### Weather Metrics
Observations include:
- `temp_air`: Air temperature
- `temp_apparent`: Feels-like temperature
- `wind_spd`: Wind speed
- `wind_dir`: Wind direction
- `press_qnh`: Pressure
- `humidity`: Relative humidity
### Collection Intervals
- Regular interval observations
- Quality controlled measurements
- Station-specific collection frequencies
## Environmental Impact
### Emissions Tracking
Tracked in `at_facility_intervals`:
- `emissions`: CO2 equivalent emissions
- `emissions_intensity`: Emissions per MWh
- Facility-specific factors
- Regional emissions intensity
## Data Access
### API Endpoints
```
GET /v4/stats/emissionfactor/network/{network_code} - Get emission factors
GET /v4/stats/emissionfactor/network/{network_code}/{network_region_code} - Get regional factors
```
## Best Practices
1. Verify weather station proximity to facilities
2. Check observation quality flags
3. Consider seasonal variations
4. Account for missing data
5. Validate emissions calculations
---
# Interconnectors
URL: /guides/interconnectors
# Interconnectors in the National Electricity Market (NEM)
Interconnectors are high-capacity transmission lines that enable electricity to flow between the five NEM regions: South Australia, Victoria, Tasmania, New South Wales, and Queensland. They play a vital role in balancing supply and demand, ensuring reliability, and optimizing electricity costs across the market.
Interconnectors are critical infrastructure within the NEM, enabling efficient energy sharing across regions, improving price stability, and enhancing system reliability. As demand for renewable energy grows, ongoing upgrades and new interconnectors will play an even more significant role in shaping Australia's energy landscape.
## Interconnector Map
The map below shows the major interconnectors between NEM regions:
## Role of Interconnectors
- **Energy Transfers**: Interconnectors allow electricity to move from regions with surplus (and lower spot prices) to regions with higher demand (and higher spot prices). This often equalizes prices between regions, improving market efficiency.
- **Capacity and Constraints**: Each interconnector has a nominal capacity defining its optimal power transfer capability under normal conditions. However, actual capacity can vary based on network conditions, thermal limits, or stability requirements.
- **Reducing Local Reliance**: By importing electricity, interconnectors can reduce the need for additional local generation capacity, acting as a substitute for regional generation investments.
## Key Interconnectors in the NEM
Below is a summary of the main interconnectors and their nominal capacities:
### Terranora Interconnector (N-Q-MNSP1)
- **Connection**: Queensland ↔ New South Wales
- **Capacity**:
- Queensland to NSW: 210 MW
- NSW to Queensland: 107 MW
### Queensland to New South Wales Interconnector (QNI)
- **Connection**: Queensland ↔ New South Wales
- **Capacity**:
- Queensland to NSW: 1,300 MW
- NSW to Queensland: 850 MW
### Victoria to New South Wales Interconnector (VIC1-NSW1)
- **Connection**: Victoria ↔ New South Wales
- **Capacity**:
- Victoria to NSW: 400–1,700 MW
- NSW to Victoria: 400–1,450 MW
### Basslink (T-V-MNSP1)
- **Connection**: Tasmania ↔ Victoria
- **Capacity**:
- Tasmania to Victoria: 594 MW
- Victoria to Tasmania: 478 MW
### Heywood Interconnector (V-SA)
- **Connection**: Victoria ↔ South Australia
- **Capacity**:
- Victoria to South Australia: 600 MW
- South Australia to Victoria: 550 MW
### Murraylink (V-S-MNSP1)
- **Connection**: Victoria ↔ South Australia
- **Capacity**:
- Victoria to South Australia: 220 MW
- South Australia to Victoria: 200 MW
## Future Interconnectors
- **Project Energy Connect**: A new interconnector between New South Wales and South Australia, with a planned capacity of up to 800 MW by 2026.
- **Marinus Link**: A second interconnector between Tasmania and Victoria, to be built in two stages, each with 750 MW capacity.
---
# Introduction
Introduction to the Open Electricity Platform
URL: /platform/introduction
Along with the [tracker website](https://explore.openelectricity.org.au), Open Electricity also provides a platform for programmatically accessing energy data.
The platform provides a REST API and a suite of client libraries for accessing the data, and a dashboard to monitor usage and manage API keys.
You can signup for access to the platform at [platform.openelectricity.org.au](https://platform.openelectricity.org.au).
Open Electricity API Reference
Client libraries for the Open Electricity API in Python and TypeScript
---
# Changelog
Product updates and release notes for Open Electricity
URL: /changelog
Stay up to date with the latest features, improvements, and bug fixes in Open Electricity.
## Records
- **False daily demand low records removed** - From early August the records page reported a new NEM daily demand low every few days, at values between 479 and 567 GWh, when the true record is 384 GWh set on 27 December 2025. The same false lows appeared for every NEM region and for WEM. The v4.5.9 rebuild of demand energy records used a query that could never emit a low record: it hard-coded the per-period interval count to 1 while the completeness guard for lows requires a full period (288 intervals for a day). Every demand low chain came back empty, and the five-minute record check then treated the first day it saw as a record and every lower day after it as another. Demand records have been rebuilt with full history, and the repair now fails if any period is missing either its high or its low chain ([#640](https://github.com/opennem/opennem/issues/640))
- **Record times inside daylight saving corrected** - Interval records between October and April were reported one hour later than the same peak in the API and tracker. The June re-ingest that removed the daylight saving shift from NEM history (v4.5.4) rebuilt the aggregates and exports but not the records table, so records generated before it still carried the shifted timestamps. All records have been regenerated from the corrected data. The 27 November 2018 battery discharge high, for example, now reads 09:45 as it does in the API rather than 10:45 ([#641](https://github.com/opennem/opennem/issues/641))
- **WEM demand energy records were six times too high** - Every WEM demand energy record was wrong, with annual values around 105 TWh against a real WEM total near 18 TWh. See below ([#642](https://github.com/opennem/opennem/issues/642))
## Bug Fixes
- **WEM demand energy six times too high in the market summary** - Energy for WEM was computed as if each row covered thirty minutes, while the market summary has been on a five-minute grid since 2012. All eleven energy and market value columns were affected, including the `au.wem.demand.energy` and `au.wem.demand.market_value` series in the static energy exports, which reported around 306 GWh a day for 2025 rather than about 51. The stored history has been rescaled, the daily and monthly rollups rebuilt, and the WEM energy exports regenerated ([#642](https://github.com/opennem/opennem/issues/642))
- **Duplicate WEM region rows** - Between September 2023 and December 2024 the market summary carried a second, identical set of WEM rows under the transitional `WEMDE` region code, so any total across regions counted WEM twice in that window. The 19 February 2024 daily demand record read 968 GWh for that reason. Region rows are now restricted to each network's declared regions, and the duplicate rows have been removed ([#642](https://github.com/opennem/opennem/issues/642))
## Reliability
- **Record rebuilds are now protected from the live record check** - Rebuilding the records table takes several minutes, and the five-minute record check running against a partially rebuilt table was the mechanism behind the false lows above. A rebuild now holds a database lock for its duration and the live check skips its run while the lock is held. A rehearsal on the development environment confirmed the failure mode: one unguarded run minted 3,748 false records, the guarded run minted none ([#640](https://github.com/opennem/opennem/issues/640))
## Facility Locations and Boundaries
- **Facility coordinates and site boundaries improved** - Facility geography was matched against OpenStreetMap and every proposed change reviewed by hand before it was applied. 151 facilities were updated: 119 gained a link to their OpenStreetMap site, and 32 had their coordinate corrected without a link change. 381 facilities now carry a site boundary, up from 316. Several coordinates were materially wrong rather than merely imprecise, including Bango Wind Farm, which sat 24 km from the wind farm it describes. Every proposal was accepted or rejected individually, and 94 were rejected, most often because the nearest OpenStreetMap object belonged to a different plant sharing the site. A battery co-located at a solar farm is a common case: the two are separate units and only one of them owns the polygon ([#481](https://github.com/opennem/opennem/issues/481))
- **OpenStreetMap relations resolve correctly** - Site geometry is stored against an OpenStreetMap id whose sign indicates whether it refers to a way or a relation, and that convention had not held in the stored data. Because most large wind and solar farms are mapped as relations, the lookup was failing for exactly the largest sites. Ids are now resolved as either type before failing ([#481](https://github.com/opennem/opennem/issues/481), [#634](https://github.com/opennem/opennem/pull/634))
- **MacIntyre coordinate corrected** - Australia's largest wind farm was positioned 42 km east of itself, near Warwick rather than the Herries Range. There is no enclosing area mapped for the site, so the coordinate is now the centroid of its 100 mapped turbines ([#481](https://github.com/opennem/opennem/issues/481))
- **A facility that could never reach the database** - Facility codes are compared with a leading zero stripped, because that prefix marks a code OpenNEM generates rather than one AEMO issues. Winton North's generated code, `0WNSF`, strips to `WNSF`, which is a real and entirely different facility, Wangaratta. The two collided and whichever was seen second was dropped, so Winton North's location was correct in the CMS and unreachable from the API. A generated prefix is now kept whenever stripping it would collide with another facility ([#481](https://github.com/opennem/opennem/issues/481), [#637](https://github.com/opennem/opennem/pull/637))
- **Removing a facility's OpenStreetMap link now takes effect** - Clearing the link in the CMS had no effect on the database, so an incorrect match could never be withdrawn. Picton in Western Australia was linked to Pindari Power Station in New South Wales and served that boundary, 3,392 km from the facility, after the link had already been removed. Clearing a link now also clears the site boundary derived from it ([#481](https://github.com/opennem/opennem/issues/481), [#639](https://github.com/opennem/opennem/pull/639))
Wind farms remain a known gap. Their turbines are mapped in OpenStreetMap as individual points rather than an enclosing area, so a site relation for a wind farm often encloses no polygon at all and no boundary can be derived from it. Per-turbine geometry is tracked separately ([#635](https://github.com/opennem/opennem/issues/635)).
A related gap is still open: some facilities now share a boundary with a neighbouring plant rather than holding their own ([#638](https://github.com/opennem/opennem/issues/638)).
## Bug Fixes
- **Parkeston no longer reported as consuming 355 GWh a year** - A 110 MW gas peaker in Western Australia was publishing negative generation in 97% of its intervals, which distorted its energy, its capacity factor, and any WEM fleet total derived from them. Its metering point changed meaning at the WEMDE transition in late 2023: it now reads the whole site, so the co-located Kalgoorlie load is netted off the generator's output. Gross generation cannot be recovered from that feed. A negative reading on a one-way generator is site load measured at the same point rather than negative generation, and is now recorded as zero across the aggregation. Storage and pumps are unaffected, since charging is genuine consumption. The correction is 1,562 GWh against 2.2 million GWh of positive generation, and Parkeston is 1,009 GWh of it ([#613](https://github.com/opennem/opennem/issues/613), [#636](https://github.com/opennem/opennem/pull/636))
- **NEM data now starts at market open** - Facility data for the NEM began at 1 January 1999, six days after the market itself. The records were always in the database (706,205 intervals across 134 units and all four mainland regions from 7 December 1998), but the network's `data_first_seen` was pinned to 1999, which bounded the ClickHouse backfill and left December 1998 unaggregated and unservable. That month is now aggregated and available through the API and exports, and market summary (demand and price) now starts at 13 December 1998, the first interval AEMO published ([#615](https://github.com/opennem/opennem/issues/615), [#621](https://github.com/opennem/opennem/pull/621))
- **Bouldercombe and Dalrymple battery history restored** - Both batteries were missing large parts of their generation history. AEMO retired their single-direction DUIDs in favour of paired generation/load codes, and the paired codes only exist from the point the bidirectional DUID appears, October 2024 for Bouldercombe and August 2024 for Dalrymple, leaving a ten-month void where the retired codes held data and the current ones did not. Separately, all of 2024 was present in Postgres but absent from ClickHouse for all three derived codes. The retired DUIDs are now carried forward onto their paired codes as a one-to-one alias, verified interval by interval against the overlap period, and the affected windows re-aggregated. Bouldercombe 2024 discharge goes from 7.6 GWh to 33.6 GWh, charge from 9.1 GWh to 41.8 GWh ([#603](https://github.com/opennem/opennem/issues/603), [#625](https://github.com/opennem/opennem/pull/625))
- **Unit first-seen dates corrected** - A unit's `data_first_seen` was computed from its first interval of non-zero output, so units that sat idle when they first reported were dated from whenever they first generated instead. Mount Piper 4 was out by 418 days. It now reflects the first telemetered reading of any value, recomputed across all 854 units ([#615](https://github.com/opennem/opennem/issues/615))
- **Network fueltech totals reconciled with per-unit data** - Network-level fueltech series could report less energy than the sum of the individual units, most visibly for battery discharge. Daily aggregates are versioned by completeness so a partial update cannot overwrite a complete one; the side effect was that data arriving for a day after it was first aggregated produced an update that lost the comparison and was discarded, leaving the network-level daily figure stuck at its earlier value. The affected daily aggregates have been rebuilt, resolving 18 months and 41 GWh of battery discharge divergence, with network and per-unit series now identical. Historical re-aggregations now rebuild those aggregates automatically, so the drift cannot reappear ([#592](https://github.com/opennem/opennem/issues/592), [#627](https://github.com/opennem/opennem/pull/627))
- **Missing intervals return null instead of vanishing** - When a series had no data for an interval inside its own lifetime, the point was omitted from the response entirely, leaving consumers unable to distinguish "no data" from a genuine zero, or from a unit that had not yet been commissioned. Those points now return an explicit `null`. Only interior gaps are filled, so nothing is invented before a unit's first reading or after its last and commissioning and retirement boundaries are unchanged ([#615](https://github.com/opennem/opennem/issues/615), [#621](https://github.com/opennem/opennem/pull/621))
- **Historical month queries on the v3 stats endpoint** - The deprecated v3 Power Network Region by Fueltech endpoint ignored its `month` parameter and always returned the last seven days, because the parameter was defaulted to today before the static-file redirect was evaluated. Historical months are now served as documented. The endpoint remains deprecated in favour of the v4 API ([#393](https://github.com/opennem/opennem/issues/393), [#626](https://github.com/opennem/opennem/pull/626))
## Reliability
- **Alerting on silently dropped facility data** - The ClickHouse ingest joins facility data to units, facilities and fueltech, and any code failing one of those joins was discarded with no warning. This is the mechanism that hid roughly 19,900 GWh of WEM history until it was found by hand earlier this year. A daily check now reports any code carrying energy that the ingest would drop, covering both unmapped codes and units missing a fueltech or facility ([#604](https://github.com/opennem/opennem/issues/604), [#624](https://github.com/opennem/opennem/pull/624))
- **Alerting on frozen generation telemetry** - When a unit's telemetry link fails, AEMO republishes the last value it saw rather than a gap, so the reading stays within the unit's capacity and is present in every interval, passing both range and completeness checks. A solar farm frozen this way reports full output through the night, inflating its energy for as long as the fault lasts. A daily check now looks for solar units generating overnight, which no working plant does. Run across eleven years of NEM history it identifies 22 such incidents totalling roughly 8.6 GWh of generation that never occurred. Those historical figures are unchanged for now, as no corrected source exists to restore them from ([#544](https://github.com/opennem/opennem/issues/544), [#628](https://github.com/opennem/opennem/pull/628))
## Bug Fixes
- **Tracker curtailment display** - Hotfix for a v4.5.9 regression: curtailment on tracker views longer than 7 days read 1,000x too high. The v3 static export serves every energy series in GWh, but curtailment series were exported as raw values labelled MWh — masked until v4.5.9 because the stored values happened to be GWh-scale. Curtailment energy series are now exported in GWh like every other energy series, and all affected static exports (2020 onward, including per-region files) have been regenerated. The v4 API `curtailment_*_energy` metrics were unaffected and remain in MWh ([#607](https://github.com/opennem/opennem/issues/607), [#614](https://github.com/opennem/opennem/pull/614))
## Data Quality
- **Demand energy unit fix** - The market summary's energy columns (demand, gross demand, renewable generation and curtailment energy, plus their derived market values) were stored 1,000x too low — values were gigawatt hours but labelled megawatt hours. All seven columns are now stored in true MWh, ClickHouse aggregates and rollup views have been rebuilt from 1999 to present, and static energy exports regenerated. Demand energy records were purged and rebuilt with uniform MWh units — the series previously mixed unit regimes, freezing record highs for ~15 years. July 2026 is confirmed as a genuine new Victorian monthly demand record (4,744,905 MWh, ahead of 4,707,075 MWh set in 2010) ([#605](https://github.com/opennem/opennem/issues/605), [#606](https://github.com/opennem/opennem/pull/606))
- **WEM historical generation restored** - WEM (Western Australia) facility generation was almost entirely null before December 2013, as AEMO published no instantaneous output quantity in that era. Generation is now derived from published energy, with history backfilled and all aggregates rebuilt — WEM facility series are now continuous from market start ([#598](https://github.com/opennem/opennem/issues/598), [#600](https://github.com/opennem/opennem/pull/600))
## API
- **Commissioning unit status** - The facilities endpoint now derives a `commissioning` status: operating units whose maximum observed generation is at or below 90% of capacity report `status_id=commissioning` and are excluded from `operating` filters. The status is computed on read and mirrors the Open Electricity website rule ([#602](https://github.com/opennem/opennem/pull/602))
## Reliability
- **Milestone social cards** - Fixed the production container environment so automated milestone social card images render again ([#597](https://github.com/opennem/opennem/pull/597))
## Data Quality
- **Curtailment history gaps filled** - Solar and wind curtailment series showed recurring blocks of `0` in months where AEMO's semi-scheduled curtailment fields were missing from ingestion, rather than a genuine zero. We re-ingested `DISPATCHREGIONSUM` from the AEMO MMSDM archives across the 24 affected months (September 2020 to August 2024), re-aggregated the market summary and regenerated every static export; curtailment is now continuous from September 2020 — the point at which AEMO began publishing the semi-scheduled fields ([#578](https://github.com/opennem/opennem/issues/578))
## Reliability
- **AEMO archive ingestion** - AEMO changed its MMSDM historical-archive filename format in August 2024 (`PUBLIC_ARCHIVE##FILE01#…`, where `#` is served url-encoded). The MMS crawlers now recognise both the legacy and new naming, so historical backfills from August 2024 onward no longer silently skip files ([#593](https://github.com/opennem/opennem/issues/593), [#594](https://github.com/opennem/opennem/pull/594))
## Data Quality
- **Rooftop solar edge gaps** - Rooftop solar generation and forecast series no longer show intermittent nulls at the edges of a request window; forecast and generation windows are snapped to the 30-minute grid and rooftop is carried forward to the core-generation edge ([#579](https://github.com/opennem/opennem/issues/579), [#587](https://github.com/opennem/opennem/pull/587), [#591](https://github.com/opennem/opennem/pull/591))
- **Battery storage excluded from renewables** - Battery storage discharge is no longer counted toward renewable and fossil generation totals in the milestone/record materialized views, correcting an erroneous WA fossil-share record ([#585](https://github.com/opennem/opennem/issues/585), [#589](https://github.com/opennem/opennem/pull/589))
## Reliability
- **Worker stability** - Interval-check ClickHouse writes now run off the async event loop, preventing the worker from starving its Postgres connection pool during incremental aggregation ([#572](https://github.com/opennem/opennem/issues/572), [#588](https://github.com/opennem/opennem/pull/588))
- **Crawler noise reduction** - Rolled-off rooftop intervals now age out of missing-interval detection, and expected NEMWeb fetch messages are downgraded from errors, cutting re-read thrash and log noise ([#590](https://github.com/opennem/opennem/pull/590))
- **CMS webhook resilience** - Fixed a Sanity seen-range SQL alias and quieted expected webhook errors ([#586](https://github.com/opennem/opennem/pull/586))
## Reliability
- **Milestone social posts restored** - Milestone and weekly-summary social posts were silently failing to render in the production container due to a Chromium sandbox flag; the renderer now passes the required flags so automated posts publish again ([#583](https://github.com/opennem/opennem/pull/583))
## Bug Fixes
- **No more spurious zeros on the latest interval** - The most recent 5-minute interval is provisional while AEMO data is still settling, which could briefly surface a `0` for renewable proportion or emissions that healed on the next request. Live `/v4/data` and `/v4/market` responses now stop at the last fully-settled interval, and renewable proportion returns `null` (not `0`) when gross demand has not landed yet ([#575](https://github.com/opennem/opennem/issues/575), [#576](https://github.com/opennem/opennem/pull/576), [#577](https://github.com/opennem/opennem/pull/577))
## Reliability
- **ClickHouse memory guard** - The API serving path now caps per-query memory and spills large aggregations to disk, so a single heavy request can no longer exhaust the database ([#564](https://github.com/opennem/opennem/pull/564))
- **CMS sync resilience** - Facility sync now retries transient Sanity CMS timeouts and Postgres deadlocks instead of failing the run ([#566](https://github.com/opennem/opennem/pull/566), [#568](https://github.com/opennem/opennem/pull/568))
- **Export upload retries** - Static export uploads to R2 storage now retry on transient network errors ([#570](https://github.com/opennem/opennem/pull/570))
## Documentation
- **Metrics known-issues refreshed** - Removed the stale MARKET MW summing and HOUR-interval analyzer-error warnings from the [metrics reference](/api-reference/metrics); both were fixed in v4.5.1 and the page now documents the corrected aggregation behaviour ([#562](https://github.com/opennem/opennem/issues/562))
## Data Quality & Backlog Repair
A major historical data repair campaign: a full audit of June 2009 to present, with gaps filled, bad values corrected and every aggregate rebuilt from source.
- **Daylight-saving ingestion repair** - Fixed a +1 hour timestamp shift affecting roughly 2–3 weeks of NEM data after every October daylight-saving changeover from 2007 to 2023; affected periods were re-ingested from AEMO MMSDM archives and all downstream aggregates rebuilt ([#559](https://github.com/opennem/opennem/issues/559))
- **Rooftop solar backfill (2015–2016)** - The synthetic rooftop backfill series is now included in generation and market value aggregates, making NEM rooftop solar continuous from March 2015 and closing a ~16-month gap in solar series
- **WA rooftop gap fill** - Recovered 287 missing days of APVI rooftop data across 2015–2016, and fixed a client crash on older API payloads that was the original cause of the gaps
- **Demand gaps** - Filled missing NEM demand intervals from NEMWeb
- **Negative generation clamp** - Renewable generation totals no longer count negative unit scada intervals (auxiliary loads while a unit is powered but not generating); applies to renewable energy calculations, records and milestones ([#561](https://github.com/opennem/opennem/issues/561))
- **Renewable proportion validation** - Rooftop solar is now included in gross demand and renewable generation in the market summary, renewable proportion records are bounded and validated against the rebuilt data, and a facility network assignment that leaked WA hydro into NEM totals was corrected ([#558](https://github.com/opennem/opennem/issues/558))
- **Full re-aggregation** - All aggregates and rollup views rebuilt from June 2009 to present; all static energy, monthly and daily exports regenerated
## Bug Fixes
- **Facility storage capacity** - Stale unit storage capacity values persisted after removal in the facility CMS; the sync now clears them, fixing a wind farm reporting battery storage ([#560](https://github.com/opennem/opennem/issues/560))
- **Crawler efficiency** - Missing-interval detection now aligns to each crawler's native interval, so 30-minute and hourly crawlers (e.g. rooftop) no longer re-read their entire window every run
- **API data range** - API keys with extended data-range access were incorrectly capped at the default range ([#555](https://github.com/opennem/opennem/issues/555), [#557](https://github.com/opennem/opennem/pull/557))
- **Historical NEM ingestion** - Chunked dispatch region upserts and tolerate pre-2020 AEMO files missing curtailment fields ([#559](https://github.com/opennem/opennem/issues/559))
## Bug Fixes
- **Full facility history** - Facility energy and power endpoints with `period=all` now return complete history from the network's first data (1999 for the NEM) instead of silently clipping to the most recent ~20 years ([#543](https://github.com/opennem/opennem/issues/543))
- **Pre-2009 demand-weighted price** - Hardened the pre-July-2009 NEM price calculation so the 30-minute trading price is carried forward across the 5-minute buckets (`locf`), keeping FY05–FY09 demand market value aligned with AER published figures ([#309](https://github.com/opennem/opennem/issues/309))
## Documentation
- **New docs platform** - Our [docs](https://docs.openelectricity.org.au) have moved from Mintlify to [Tangly](https://tangly.dev), a self-hosted open-source docs framework that renders the existing project unmodified, with a direct branch-to-environment deploy pipeline
## Data & Pipeline
- **Monthly demand accuracy** - Corrected May demand and gas figures: monthly demand is now aggregated from the daily materialized view instead of a stale monthly view, with a month-aligned backfill window ([#548](https://github.com/opennem/opennem/issues/548))
- **Calendar gap-fill** - Monthly and yearly series are now gap-filled onto the calendar grid, keeping market-value and weather coverage aligned ([#548](https://github.com/opennem/opennem/issues/548), [#549](https://github.com/opennem/opennem/issues/549))
## Bug Fixes
- **Status grouping** - Fixed `secondary_grouping=status` returning a 500 on `/v4/data/network/`; data can now be grouped by facility status (operating, committed, retired) like the other secondary groupings ([#539](https://github.com/opennem/opennem/issues/539), [#550](https://github.com/opennem/opennem/pull/550))
## Major Features
- **Real-time WEM data** - Western Australia generation and 5-minute dispatch pricing are now near-real-time via the new WEMDE dispatch-solution feed, replacing the previous ~24-hour lag ([#527](https://github.com/opennem/opennem/issues/527), [#530](https://github.com/opennem/opennem/pull/530), [#533](https://github.com/opennem/opennem/pull/533), [#538](https://github.com/opennem/opennem/pull/538))
## Plans & Data Access
- **Extended historical access for Community** - Community plans can now query the last 2 years of data (previously 1 year). Academic and Enterprise plans retain full history back to 1999.
- **Data licensing clarified** - Plan comparisons now state usage terms: Community and Academic are licensed for non-commercial use; commercial use requires an Enterprise plan.
See [Data Limits](/api-reference/data-limits) for the full breakdown.
## API Improvements
- **Accurate power aggregation** - Power (MW) is now averaged across intervals instead of summed, fixing inflated figures at every grouping level ([#523](https://github.com/opennem/opennem/issues/523), [#525](https://github.com/opennem/opennem/issues/525))
- **OpenAPI spec** - Enriched with a generic response envelope, field metadata, and per-language code samples
- **Capacity history** - Now sums `capacity_maximum` with a fallback to registered capacity, fixing NULL WEM coal, gas and wind capacity ([#528](https://github.com/opennem/opennem/issues/528))
## Data & Pipeline
- **Gap-filled series** - Chart series are reindexed onto the interval grid so upstream data gaps no longer misalign timestamps ([#534](https://github.com/opennem/opennem/issues/534), [#535](https://github.com/opennem/opennem/issues/535))
## Bug Fixes
- **WEM price aggregation** - Confined to WEM with a hardened parser; `locf()` interpolation split into separate WEM and NEM columns ([#541](https://github.com/opennem/opennem/issues/541), [#542](https://github.com/opennem/opennem/issues/542))
## Notifications
- **Record debouncing** - Interval milestone records are debounced to stop notification bursts, and battery-charging significance was lowered below the alert threshold to stop charging-driven alert floods
## SDK Releases
### [Python client](https://github.com/opennem/openelectricity-python) — `openelectricity` 0.11.2
- Proxy and custom TLS/certificate support on sync and async clients ([#22](https://github.com/opennem/openelectricity-python/issues/22), [#29](https://github.com/opennem/openelectricity-python/pull/29))
- New renewable-with-storage and gross-demand market metrics ([#31](https://github.com/opennem/openelectricity-python/pull/31))
- Notebook-safe sync client that works inside an existing event loop (Jupyter / IPython) ([#16](https://github.com/opennem/openelectricity-python/issues/16), [#32](https://github.com/opennem/openelectricity-python/pull/32))
- Restored Python 3.10+ support ([#28](https://github.com/opennem/openelectricity-python/issues/28)); `unit_code` ([#27](https://github.com/opennem/openelectricity-python/pull/27)) and `network_region` ([#35](https://github.com/opennem/openelectricity-python/pull/35)) parity across sync and async
### [TypeScript client](https://github.com/opennem/openelectricity-typescript) — `openelectricity` 0.9.1
- Full type parity with the Python SDK, adding the `renewable_proportion` metric and `status` secondary grouping ([#12](https://github.com/opennem/openelectricity-typescript/pull/12))
- Stronger error handling (403/404/500 surface as typed errors) ([#18](https://github.com/opennem/openelectricity-typescript/pull/18)) and `DataTable` group-by and filter correctness fixes ([#15](https://github.com/opennem/openelectricity-typescript/pull/15), [#19](https://github.com/opennem/openelectricity-typescript/pull/19))
The 4.5.0 release moves the analytics pipeline to ClickHouse, rewrites interconnector flow tracing, and introduces a unified plan system for platform.
## Major Features
- **Flow Tracing v4** - Rewritten interconnector flow solver with a dynamic, ClickHouse-backed topology model that handles circular flows and per-flow emissions, exposed through the API and the homepage ([#470](https://github.com/opennem/opennem/issues/470))
- **Automated weekly & monthly summaries** - Generation and record summaries are now published automatically to X, Bluesky and LinkedIn behind a Slack approval workflow ([#64](https://github.com/opennem/opennem/issues/64))
- **Renewable-with-storage metrics** - New market metrics that account for storage when computing renewable proportion
## API Improvements
- **ClickHouse-backed stats** - v4 station and facility stats endpoints restored and served from ClickHouse
- **Facility endpoint** - Added a `unit_code` filter, required authentication, and corrected no-data responses to 404 (previously 416)
- **Cleaner `/v4/me`** - Trimmed response and removed the dead `with_clerk` parameter from the OpenAPI spec
## Data & Pipeline
- **Postgres to ClickHouse** - The analytics and export pipeline (power, energy, demand, flows) was migrated to ClickHouse and the legacy Postgres aggregation pipeline retired
- **WEM demand** - Derived from facility SCADA generation, since the public WEM feed provides price only
- **Crawler improvements** - AEMO sources moved to polling with backoff and parallelized crawls, with gap-aware catchup that skips redundant work
- **Materialized views** - Completeness-versioned daily and monthly views with full-day backfill and AEST-aligned refresh windows
## Bug Fixes
- **API key authentication** - New API keys no longer return 401 across all endpoints ([#487](https://github.com/opennem/opennem/issues/487))
- **Double-counting** - ClickHouse `FINAL` scoping corrected to stop duplicate-row double-counting in market and price queries
- **Energy calculation** - Corrected the trapezoidal integration window and quality-flag handling; batteries excluded from generation queries
- **Export boundaries** - All series are truncated at the core generation boundary so demand, price and rooftop never extend past it
- **RecordReactor** - Moved to incremental milestone detection with automatic backfill of gaps
## SDK Releases
### [Python client](https://github.com/opennem/openelectricity-python) — `openelectricity` 0.10.1
- `unit_code` parameter on `get_facility_data`
- Flow market metrics; `fueltech` and `status` parameters now accept plain strings
- New bidirectional battery example
### [TypeScript client](https://github.com/opennem/openelectricity-typescript) — `openelectricity` 0.8.1
- `unitCodes` parameter on `getFacilityData`
- Flow and renewable-with-storage market metrics, plus the `hydro_and_storage` fueltech
- `getFacilities` now throws `NoDataFound` on 404 (previously mishandled)
## Major Features
- **Renewable Energy Proportion Tracking** - Real-time renewable energy percentage tracking across the network ([#423](https://github.com/opennem/opennem/issues/423))
- New milestone type in RecordReactor for tracking [renewable proportion](https://github.com/opennem/opennem/issues/398)
- Significance scoring for renewable energy records
- Exposed via API endpoints for programmatic access
- **Max Generation Tracking** - Historical peak generation capacity tracking per facility unit ([#461](https://github.com/opennem/opennem/issues/461))
- New `max_generation` and `max_generation_interval` fields on facility units
- Available via Facilities API endpoints
## Infrastructure & Technology Upgrades
- **Python 3.14 Support** - Platform upgraded to Python 3.14 for improved performance and compatibility ([#468](https://github.com/opennem/opennem/issues/468))
- Updated python-sanity dependency with async client and HTTP/2 support
- Migrated from clickhouse-connect to clickhouse-http driver
- Enhanced async patterns and event loop handling
- **Unified HTTP Client** - New rnet-based HTTP client replacing legacy libraries ([#466](https://github.com/opennem/opennem/issues/466), [#467](https://github.com/opennem/opennem/issues/467), [#475](https://github.com/opennem/opennem/issues/475))
- Resolve crawl request errors with new rust-based HTTP client for all requests
- **Granian ASGI Server** - Application server upgraded from Hypercorn to Granian ([#476](https://github.com/opennem/opennem/issues/476))
- Better resource utilization
## API Improvements
- **Facility API Enhancements** - Expanded facility endpoint capabilities ([#464](https://github.com/opennem/opennem/issues/464))
- Filter facilities by fueltech group
- Unit datetime metadata fields synchronized from CMS
- Timezone-aware datetime handling per network
- Improved serialization excluding None values
## Bug Fixes
- **Curtailment Data Alignment** - Fixed AEMO timestamp handling for curtailment data ([#455](https://github.com/opennem/opennem/issues/455))
- **Interval Limits** - Increased max interval days for better data coverage ([#454](https://github.com/opennem/opennem/issues/454))
- **Data Storage** - Fixed empty price data storage to prevent null records
- **Market Summary** - Fixed market summary refresh and column alignment
- **Worker Stability** - Catchup worker now runs all aggregates correctly
- **Battery Mapping** - Fixed battery unit mapping schema parsing issues
- **Export Controller** - Resolved ClickHouse query iteration and record processing
- **API Responses** - Fixed unset field handling by setting response_model_exclude_unset to false
- **Logging** - Reduced debug noise from httpcore and hpack, improved Logfire integration
## Documentation
- Enhanced documentation for AEMO timestamp handling in curtailment data
- Updated API documentation with new endpoints and fields
- Infrastructure upgrade documentation and migration guides
## Major Features
- **Pollution Data (NPI)** - Comprehensive pollution and emissions tracking from the National Pollutant Inventory now available across the platform ([#436](https://github.com/opennem/opennem/issues/436))
- Track greenhouse gases, air pollutants, and heavy metals from power generation facilities
- Details in documentation at the [Pollution Guide](https://docs.openelectricity.org.au/guides/pollution)
- Access via API endpoints for programmatic integration
- Example implementations:
- [Python client example](https://github.com/opennem/openelectricity-python/blob/main/examples/pollution.py)
- [TypeScript client example](https://github.com/opennem/openelectricity-typescript/blob/main/examples/pollution.ts)
- **Battery State of Charge** - Real-time battery storage level tracking showing current energy stored in battery facilities ([#434](https://github.com/opennem/opennem/issues/434))
- Monitor state of charge (SoC) at unit, facility, region and network levels
- View battery storage docs in the [Battery Storage Guide](https://docs.openelectricity.org.au/guides/battery-storage)
- New API endpoints for battery-specific data
- Example implementations:
- [Python client example](https://github.com/opennem/openelectricity-python/blob/main/examples/battery_storage.py)
- [TypeScript client example](https://github.com/opennem/openelectricity-typescript/blob/main/examples/battery-storage.ts)
- **Facility API Improvements** - Enhanced facility endpoint with additional fields ([#453](https://github.com/opennem/opennem/issues/453))
- Added NPI ID field for pollution data mapping
- Added location data for facilities
## Improvements and Bug Fixes
- **CPI Data Update** - Consumer Price Index data import and tracking ([#450](https://github.com/opennem/opennem/issues/450))
- **Curtailment Tracking** - Renamed `curtailment_solar` to `curtailment_solar_utility` for clarity ([#433](https://github.com/opennem/opennem/issues/433))
- **Battery Management** - Improved battery unit handling and aggregation ([#434](https://github.com/opennem/opennem/issues/434))
- **Record Tracking** - Fixed duplicate records with identical rounded values ([#451](https://github.com/opennem/opennem/issues/451))
- **Demand Energy** - Demand energy now sourced from analytics table ([#390](https://github.com/opennem/opennem/issues/390))
- **Unit Metadata** - Cleaned out obsolete unit metadata for better data integrity
## Documentation
- New comprehensive guides for pollution data and battery storage
- Updated API documentation with new endpoints
- Added code examples for accessing new features in Python and TypeScript clients
## Major Features
- **Curtailment Data** - Comprehensive curtailment tracking for renewable energy generation now available across the platform ([#433](https://github.com/opennem/opennem/issues/433))
- View curtailment data on the [Open Electricity website](https://explore.openelectricity.org.au)
- Access via API endpoints for programmatic integration
- Full documentation available in the [Curtailment Guide](https://docs.openelectricity.org.au/guides/curtailment)
- Example implementations:
- [Python client example](https://github.com/opennem/openelectricity-python/tree/main/examples/curtailment)
- [TypeScript client example](https://github.com/opennem/openelectricity-typescript/tree/main/examples/curtailment)
## Bug Fixes
- **Historic Price Data** - Fixed issues with historic price data retrieval and accuracy ([#309](https://github.com/opennem/opennem/issues/309))
## Documentation
- New comprehensive guide on understanding curtailment data
- Updated API documentation with curtailment endpoints
- Added code examples for accessing curtailment data in Python and TypeScript clients
## Major Features
- **Capacity History Tracking** - New comprehensive capacity history output that includes rooftop solar and applies unit history tracking ([#415](https://github.com/opennem/opennem/issues/415))
- **CMS Integration** - Import CMS IDs to database for facilities and units for better tracking
- **APVI Capacity History** - Controller to import APVI capacity history as unit histories ([#432](https://github.com/opennem/opennem/issues/432))
- **Unit Histories** - Enhanced unit histories with time axes for fields ([#431](https://github.com/opennem/opennem/issues/431))
## Improvements
- Battery split static map improvements and bug fixes ([#420](https://github.com/opennem/opennem/issues/420))
- Rooftop capacity storage enhancements ([#430](https://github.com/opennem/opennem/issues/430))
- Decimal precision improvements in unit_history table ([#431](https://github.com/opennem/opennem/issues/431))
- Gap-fill improvements for rooftop capacity history ([#415](https://github.com/opennem/opennem/issues/415))
## Bug Fixes
- Fixed WEM crawlers to include APVI month by default
- Resolved postcode prefix mapping (1 is NSW in APVI client)
- Fixed capacity chart task imports ([#415](https://github.com/opennem/opennem/issues/415))
- Corrected WEMDE trading price crawler length sanity checks
- Resolved issues with DRX facilities in first seen checks
- Fixed padding in query for rooftop capacities in capacity chart ([#415](https://github.com/opennem/opennem/issues/415))
## Documentation
- Updated migration documentation for v4.1
- Enhanced API documentation
## 🎉 Platform Beta Launch
We're excited to announce the beta launch of the **Open Electricity Platform**!
- Access at [platform.openelectricity.org.au](https://platform.openelectricity.org.au)
- Sign up for early access and explore enhanced data analytics capabilities
- Advanced visualizations and custom data exports
## SDK Releases
### Python Client
- Official Open Electricity Python client library now available
- Install via pip: `pip install openelectricity`
- Full documentation and examples included
### TypeScript Client
- Official Open Electricity TypeScript/JavaScript client library released
- Install via npm: `npm install @opennem/client`
- Type-safe API access with comprehensive type definitions
## API Enhancements
- **Performance Improvements** - ORjson serialization on data and market endpoints for improved performance
- **Milestones API** - New milestones endpoints with optimized caching
- **Record Reactor** - Enhanced record reactor with Slack and Twitter alerts for significant events
- **Solar Gap Filling** - Improved solar 5-minute gap fill handling for APVI interval sizes
## Social Integration
- **Bluesky Client** - Added Bluesky social media integration
- **Twitter Client** - Enhanced Twitter client for automated updates
- **Record Alerts** - Automatic social media alerts for significant generation records
## Infrastructure
- API v3 deprecation notice ([#424](https://github.com/opennem/opennem/issues/424))
- Sanity CMS syncing to database rewrite ([#422](https://github.com/opennem/opennem/issues/422))
- Enhanced facility and unit integrity monitoring ([#429](https://github.com/opennem/opennem/issues/429))
## Bug Fixes
- Fixed NEM interval check re-ordering and increased interval window
- Corrected battery charge/discharge significance scoring
- Resolved issues with regional demand calculations ([#412](https://github.com/opennem/opennem/issues/412))
- Fixed static historic records returning strings as data ([#403](https://github.com/opennem/opennem/issues/403))
---
# Overview
Documentation for the Open Electricity REST API - Australian Electricity Market Data
URL: /api-reference/overview
## Introduction
The Open Electricity API provides programmatic access to Australian electricity market data, including real-time generation, demand, price information and historical data across the National Electricity Market (NEM) and Western Australian Wholesale Electricity Market (WEM).
## Data Licence
Unless stated otherwise, the data provided by the Open Electricity API is licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) (non-commercial). See the [full licence](https://platform.openelectricity.org.au/license) for attribution requirements and commercial licensing options.
## Authentication
All API endpoints require authentication using an API key. You'll need to include your API key in the Authorization header of your requests:
```bash
Authorization: Bearer your_api_key_here
```
To obtain and manage your API key, please [register for an account](https://platform.openelectricity.org.au) at the Open Electricity Platform.
## Base URL
The base URL for all API endpoints is:
```
https://api.openelectricity.org.au/v4
```
## Response Format
All responses are returned in JSON format. Successful responses will have a 2xx status code and follow this structure:
```json
{
"version": "4.0",
"created_at": "2024-03-20T10:00:00Z",
"success": true,
"data": [
// Response data
],
"total_records": 100
}
```
## Error Handling
Errors are returned with appropriate HTTP status codes and include detailed error messages:
```json
{
"error": {
"code": "validation_error",
"message": "Invalid parameters",
"details": [
{
"loc": ["parameter_name"],
"msg": "Error description",
"type": "error_type"
}
]
}
}
```
Common error status codes:
- `400` Bad Request - Invalid parameters or request
- `401` Unauthorized - Missing or invalid API key
- `403` Forbidden - Insufficient permissions
- `422` Validation Error - Invalid input parameters
- `500` Internal Server Error - Server-side error
---
# Metrics & Aggregation
Metric definitions, units, and how each metric aggregates across time intervals
URL: /api-reference/metrics
The Open Electricity API exposes time-series data for the NEM and WEM networks at multiple intervals: 5-minute (`interval`), hourly (`hour`), daily (`day`), weekly (`week`), monthly (`month`), quarterly (`quarter`), seasonal (`season`), and yearly (`year`). The **5-minute interval is the source resolution** — every longer interval is aggregated from those 5-minute records.
How a metric aggregates depends on whether it is a **rate** (e.g. power in MW — an instantaneous measurement) or a **quantity** (e.g. energy in MWh — accumulated over an interval).
- **Rates aggregate with averaging.** Twelve 5-minute power readings, averaged, give the average power over the hour.
- **Quantities aggregate with summing.** Twelve 5-minute energy values, summed, give the total energy in the hour.
This page lists every metric returned by the API along with its unit, type, and per-interval aggregation rule.
## Generation data — `/v4/data/network/{network_code}` and `/v4/data/facilities/{network_code}`
| Metric | Unit | Type | 5-min source | Aggregation (hour+) |
|--------|------|------|--------------|---------------------|
| `power` | MW | Rate | `unit_intervals.generated` | **Average** of source values over the interval |
| `energy` | MWh | Quantity | `unit_intervals.energy` | **Sum** of source values |
| `emissions` | tCO₂ | Quantity | `unit_intervals.emissions` | **Sum** of source values |
| `market_value` | AUD | Quantity | `unit_intervals.market_value` | **Sum** of source values |
| `storage_battery` | MWh | Rate (state of charge) | `unit_intervals.energy_storage` | **Average** of source values |
### Aggregation details
- **Hourly (`hour`)** queries read from the 5-minute base table (`unit_intervals`) and apply `avg()` or `sum()` directly across the twelve 5-minute records in each hour.
- **Daily and longer (`day`, `week`, `month`, `quarter`, `season`, `year`)** queries read from the daily materialised view (`unit_intervals_daily_mv`). The daily MV stores `generated` as the daily **sum** of 5-minute power values plus an `interval_count` column. For `power`, the API computes an interval-weighted average across the queried daily rows:
```
sum(generated) / sum(interval_count)
```
This is equivalent to the average of the original 5-minute readings over the queried window, including partial first/last days where `interval_count < 288`.
- **`energy`, `emissions`, `market_value`** sum across the queried daily rows directly.
- **`storage_battery`** uses an interval-weighted average over the daily MV's `energy_storage_sum / energy_storage_count` columns.
### How 5-minute energy values are produced
Each 5-minute `energy` value is computed by Open Electricity from the corresponding `generated` (MW) readings using the trapezoidal rule (area under the power curve):
```
energy_i (MWh) = (generated_i + generated_{i-1}) / 2 × Δt
```
where `Δt` is the interval length in hours (e.g. `5/60` for NEM 5-minute data). At crawl time a placeholder value of `generated × Δt` is written immediately so freshly-ingested intervals have a value, then the energy worker (`opennem/workers/energy.py`) replaces it with the trapezoidal calculation and stamps `energy_quality_flag = 2`. The API returns the post-worker value; it does not re-derive energy from power at query time.
See the [Energy guide](/guides/energy) for the full derivation and worked example.
## Market data — `/v4/market/network/{network_code}`
| Metric | Unit | Type | Aggregation (hour+) |
|--------|------|------|---------------------|
| `price` | AUD/MWh | Rate | **Interval-weighted average** |
| `demand` | MW | Rate | **Sum** * |
| `demand_energy` | MWh | Quantity | **Sum** |
| `demand_gross` | MW | Rate | **Sum** * |
| `demand_gross_energy` | MWh | Quantity | **Sum** |
| `generation_renewable` | MW | Rate | **Sum** * |
| `generation_renewable_energy` | MWh | Quantity | **Sum** |
| `generation_renewable_with_storage` | MW | Rate | **Sum** * |
| `generation_renewable_with_storage_energy` | MWh | Quantity | **Sum** |
| `curtailment` | MW | Rate | **Sum** * |
| `curtailment_solar_utility` | MW | Rate | **Sum** * |
| `curtailment_wind` | MW | Rate | **Sum** * |
| `curtailment_energy` | MWh | Quantity | **Sum** |
| `curtailment_solar_utility_energy` | MWh | Quantity | **Sum** |
| `curtailment_wind_energy` | MWh | Quantity | **Sum** |
| `flow_imports` | MW | Rate | **Sum** * |
| `flow_exports` | MW | Rate | **Sum** * |
| `flow_imports_energy` | MWh | Quantity | **Sum** |
| `flow_exports_energy` | MWh | Quantity | **Sum** |
| `renewable_proportion` | % | Ratio | `sum(generation_renewable) / sum(demand_gross) × 100` over the queried window |
| `renewable_with_storage_proportion` | % | Ratio | `sum(generation_renewable_with_storage) / sum(demand_gross) × 100` over the queried window |
### `price` aggregation
`price` is averaged because it is a per-MWh rate. For hourly aggregation, the API takes the simple average of the 5-minute prices in the hour. For daily and longer, it uses an interval-weighted average from the market_summary daily materialised view: `sum(price_sum) / sum(price_count)`.
### Proportion metrics
`renewable_proportion` and `renewable_with_storage_proportion` are ratios computed over the *whole* queried window per row, not averaged from the underlying 5-minute proportions. The sums of the numerator and denominator are taken first, then divided.
## Aggregation correctness
All endpoints aggregate metrics correctly at every interval. MW-class metrics are averaged across the 5-minute intervals in each bucket, and energy and market-value metrics are summed — per the table above — on `/v4/market/network/{network_code}`, `/v4/data/network/{network_code}` and `/v4/data/facilities/{network_code}`, including batteries (`storage_battery`).
Two earlier issues have been resolved and need no workaround:
- **MARKET MW metrics are now averaged** above the 5-minute interval (previously summed, returning values 12× too large at `interval=hour`). Fixed in [opennem#525](https://github.com/opennem/opennem/issues/525), the MARKET follow-up to the DATA/FACILITY fix in [opennem#523](https://github.com/opennem/opennem/issues/523), by restructuring the query builder so an inner subquery pre-aggregates per 5-minute interval and the outer query applies the bucket average.
- **Proportion and generation metrics can be requested together** at the HOUR interval. The earlier ClickHouse analyzer error — when `renewable_proportion` (or `renewable_with_storage_proportion`) was requested alongside `generation_renewable`, `generation_renewable_with_storage` or `demand_gross` — no longer occurs: both read shared per-interval sums from the same subquery.
## See also
- [Data Limits](/api-reference/data-limits) — maximum date ranges per interval
- [Energy guide](/guides/energy) — what energy is and how it differs from power
- [Power guide](/guides/power) — instantaneous generation rate
- [Demand guide](/guides/demand) — how demand is measured and reported
---
# Data Limits
Documentation for the Open Electricity REST API - Data Range Limits
URL: /api-reference/data-limits
These limits are currently under review and may be increased or decreased in the future.
The Open Electricity API enforces maximum date range limits for data queries to ensure optimal performance and prevent excessive database load and payload sizes. These limits vary based on the data interval requested.
## Current Limits
### Standard Users
| Interval | Maximum Range | Description |
|----------|--------------|-------------|
| 5-minute (`interval`) | 8 days | High-resolution 5-minute interval data |
| Hourly (`hour`) | 32 days | Hourly aggregated data |
| Daily (`day`) | 366 days | Daily aggregated data |
| Weekly (`week`) | 366 days | Weekly aggregated data |
| Monthly (`month`) | 732 days (~2 years) | Monthly aggregated data |
| Quarterly (`quarter`) | 1830 days (~5 years) | Quarterly aggregated data |
| Seasonal (`season`) | 1830 days (~5 years) | Seasonal aggregated data |
| Yearly (`year`) | 3700 days (~10 years) | Yearly aggregated data |
## Historical Data Access
How far back you can query (`date_start`) depends on your plan:
| Plan | Historical window |
|------|-------------------|
| Community (default) | Last 2 years |
| Academic | Full history (from 1999) |
| Enterprise | Full history (from 1999) |
Requests with a `date_start` earlier than your plan's window return a `400` error. Academic and Enterprise plans can access the complete dataset back to 1999. Anonymous requests are treated as Community.
## Data Licensing
| Plan | Usage |
|------|-------|
| Community / Academic | Non-commercial use |
| Enterprise | Commercial use |
Commercial use of the data requires an Enterprise plan. Community and Academic access is licensed for non-commercial use only. [Get in touch](/community) about commercial licensing.
## Error Response
When a date range exceeds the maximum allowed for an interval, the API returns a 400 error:
```json
{
"detail": "Date range too large for hour interval. Maximum range is 32 days."
}
```
## Working with Range Limits
To retrieve data beyond the maximum range, iterate through the data in chunks that respect the limits.
### Example: Fetching 60 Days of Hourly Data
Since the hourly interval limit is 30 days, you'll need to make two requests.
### Simple Example: Getting Power Data
```typescript example.ts
import { OpenElectricityClient } from 'openelectricity';
const client = new OpenElectricityClient({
apiKey: process.env.OPENELECTRICITY_API_KEY
});
// Get power generation data for NEM network
const response = await client.getNetworkData(
'NEM',
['power'],
{
interval: 'hour',
dateStart: '2024-01-01',
dateEnd: '2024-01-30'
}
);
console.log(`Retrieved ${response.datatable.rows.length} data points`);
```
```python example.py
from openelectricity import OEClient
from openelectricity.types import NetworkCode, DataMetric, DataInterval
from datetime import datetime
client = OEClient() # Uses OPENELECTRICITY_API_KEY env var
# Get power generation data for NEM network
response = client.get_network_data(
network_code=NetworkCode.NEM,
metrics=[DataMetric.POWER],
interval=DataInterval.HOUR,
date_start=datetime(2024, 1, 1),
date_end=datetime(2024, 1, 30)
)
print(f"Retrieved {len(response.data)} data points")
```
### Handling Range Limits: Fetching 60 Days of Hourly Data
Since the hourly interval limit is 30 days, you'll need to make multiple requests to fetch 60 days of data.
```typescript chunked-fetch.ts
import { OpenElectricityClient } from 'openelectricity';
import { DataInterval } from 'openelectricity/types';
const client = new OpenElectricityClient({
apiKey: process.env.OPENELECTRICITY_API_KEY
});
async function fetchDataInChunks(
startDate: Date,
endDate: Date,
interval: DataInterval,
maxDays: number
) {
const allData = [];
let currentStart = new Date(startDate);
while (currentStart < endDate) {
const currentEnd = new Date(currentStart);
currentEnd.setDate(currentEnd.getDate() + maxDays - 1);
// Ensure we don't exceed the requested end date
const chunkEnd = currentEnd > endDate ? endDate : currentEnd;
try {
const response = await client.getNetworkData(
'NEM',
['power'],
{
interval: interval,
dateStart: currentStart.toISOString().split('T')[0],
dateEnd: chunkEnd.toISOString().split('T')[0]
}
);
allData.push(...response.datatable.rows);
} catch (error) {
if (error.statusCode === 400) {
console.error('Date range too large:', error.details);
break;
}
throw error;
}
// Move to next chunk
currentStart = new Date(chunkEnd);
currentStart.setDate(currentStart.getDate() + 1);
}
return allData;
}
// Usage: Fetch 60 days of hourly data
const startDate = new Date('2024-01-01');
const endDate = new Date('2024-03-01');
const data = await fetchDataInChunks(startDate, endDate, 'hour', 30);
console.log(`Retrieved ${data.length} total data points`);
```
```python chunked_fetch.py
from openelectricity import OEClient
from openelectricity.types import NetworkCode, DataMetric, DataInterval
from datetime import datetime, timedelta
client = OEClient()
def fetch_data_in_chunks(
start_date: datetime,
end_date: datetime,
interval: DataInterval,
max_days: int
):
"""
Fetch data in chunks respecting API range limits.
Args:
start_date: Start date for data retrieval
end_date: End date for data retrieval
interval: Data interval enum value
max_days: Maximum allowed days for the interval
Returns:
Combined list of all time series data
"""
all_data = []
current_start = start_date
while current_start < end_date:
# Calculate chunk end date
current_end = min(
current_start + timedelta(days=max_days - 1),
end_date
)
try:
# Make API request using the client
response = client.get_network_data(
network_code=NetworkCode.NEM,
metrics=[DataMetric.POWER],
interval=interval,
date_start=current_start,
date_end=current_end
)
# Extend with the data from this chunk
all_data.extend(response.data)
except Exception as e:
if "Date range too large" in str(e):
print(f"Date range error: {e}")
break
raise
# Move to next chunk (add 1 day to avoid overlap)
current_start = current_end + timedelta(days=1)
return all_data
# Usage: Fetch 60 days of hourly data
from datetime import datetime
start_date = datetime(2024, 1, 1)
end_date = datetime(2024, 3, 1)
data = fetch_data_in_chunks(
start_date,
end_date,
DataInterval.HOUR,
30
)
print(f"Retrieved {len(data)} total data points")
```
## Best Practices
1. **Choose the Right Interval**: Use larger intervals (daily, weekly) for longer time periods to reduce the number of requests needed.
2. **Cache Results**: Store retrieved data locally to avoid repeated API calls for the same date ranges.
3. **Handle Rate Limits**: Implement appropriate delays between requests if making many consecutive calls.
4. **Error Handling**: Always implement proper error handling for cases where date ranges exceed limits.
5. **Date Format**: Ensure dates are provided in the correct format (YYYY-MM-DD) and are timezone-naive in the network's local time.
## Default Behavior
If no date range is specified:
- `date_end` defaults to the last completed interval for the network
- `date_start` defaults to half the maximum allowed range before `date_end`
For example, for hourly data with a 30-day maximum:
- Default range would be the last 15 days of available data
## Future Improvements
These limits are designed to balance API performance with data accessibility. As our infrastructure scales, we plan to review and potentially increase these limits. Check back periodically for updates to these restrictions.
---
# api-reference/data/get-network-data
URL: /api-reference/data/get-network-data
---
# Get Facility Data
URL: /api-reference/data/get-facility-data
# Get Facility Data
---
# api-reference/market/get-network-data
URL: /api-reference/market/get-network-data
---
# api-reference/facilities/get-facilities
URL: /api-reference/facilities/get-facilities
---
# api-reference/user/get-user-me
URL: /api-reference/user/get-user-me
---
# Overview
URL: /sdk/overview
Open Electricity provides SDKs to access the API in TypeScript / JavaScript or Python.
# Typescript SDK
The Typescript SDK is available on [NPM](https://www.npmjs.com/package/openelectricity).
It supports accessing the API synchronously and asynchronously, and includes a built in data and record table tools for analysing data.
## Installation
The Typescript SDK is [available on NPM at openelectricity](https://www.npmjs.com/package/openelectricity).
Install the SDK using your preferred package manager:
```bash npm
npm install openelectricity
```
```bash yarn
yarn add openelectricity
```
```bash pnpm
pnpm add openelectricity
```
```bash bun
bun add openelectricity
```
## Quick Start
```typescript
import { OpenElectricityClient } from 'openelectricity'
// Initialize the client
const client = new OpenElectricityClient()
// Get energy data for the NEM
const { datatable } = await client.getNetworkData('NEM', ['energy'], {
interval: '1h',
dateStart: '2024-01-01T00:00:00',
dateEnd: '2024-01-02T00:00:00',
primaryGrouping: 'network_region',
})
// Analyze the data
console.table(datatable.toConsole())
```
# Python SDK
The Python SDK is available on [PyPI](https://pypi.org/project/openelectricity/).
It supports accessing the API synchronously and asynchronously, and exporting results to either pandas or polars DataFrames.
## Installation
The Python SDK is [available on PyPI at openelectricity](https://pypi.org/project/openelectricity/).
Install the SDK using your preferred package manager:
```bash pip
pip install openelectricity
```
```bash uv
uv add openelectricity
```
## Quick Start
```python
from openelectricity import OpenElectricityClient
# Initialize the client
client = OpenElectricityClient()
# Get energy data for the NEM
datatable = client.get_network_data('NEM', ['energy'], {
interval: '1h',
dateStart: '2024-01-01T00:00:00',
dateEnd: '2024-01-02T00:00:00',
primaryGrouping: 'network_region',
})
```
Open Electricity Python SDK
Open Electricity TypeScript SDK
---
# Configuration
URL: /sdk/configuration
The Open Electricity SDKs are configured using environment variables. While you can apply settings directly in the code when initialising a client, this
is not recommended for security and maintainability.
## Environment Variables
The following environment variables are supported:
| Variable | Required | Description | Default |
|----------|----------|-------------|---------|
| `OPENELECTRICITY_API_KEY` | Yes | Your API authentication key | `None` |
| `OPENELECTRICITY_API_URL` | No | The API endpoint | `https://api.openelectricity.org.au/v4` |
## Setting Environment Variables
The following code shows how to set the environment variables in your operating system:
```bash macos
export OPELECTRICITY_API_KEY=your-api-key
```
```bash windows
set OPELECTRICITY_API_KEY=your-api-key
```
## Environment Variables in Apps
Most applications in Python, Javascript and Typescript will support the use of environment variables. You can manage your environment variables
in `.env` files in the root of your project. Python supports loading environment variables from `.env` files using the `python-dotenv` package.
In Javascript and Typescript you can use the `dotenv` package to load environment variables from a `.env` file.
For loading environment variables on your system that are only relevant to a folder, it is recommended to use the [direnv tool](https://direnv.net/).
---
# Open Electricity TypeScript Client
URL: /sdk/typescript/overview
The Open Electricity TypeScript client is currently in beta and still under active development.
The Open Electricity TypeScript client is the official library for accessing the Open Electricity API, providing simplified access to Australian
electricity network data. This client supports both browser and Node.js environments, offering a type-safe interface to work with
electricity data from the National Electricity Market (NEM) and Western Australian Electricity Market (WEM).
## Features
- Cross-platform support (Browser & Node.js)
- Built-in data analysis tools
- Real-time electricity data access
- Timezone-aware date handling
- Time series data manipulation
- Facility and unit information
- Market data access
## Getting Started
### Installation
The Typescript SDK is [available on NPM](https://www.npmjs.com/package/openelectricity).
```bash npm
npm install openelectricity
```
```bash yarn
yarn add openelectricity
```
```bash pnpm
pnpm add openelectricity
```
```bash bun
bun add openelectricity
```
### Quick Start
```typescript
import { OpenElectricityClient } from 'openelectricity'
// Initialize the client
const client = new OpenElectricityClient()
// Get energy data for the NEM
const { datatable } = await client.getNetworkData('NEM', ['energy'], {
interval: '1h',
dateStart: '2024-01-01T00:00:00',
dateEnd: '2024-01-02T00:00:00',
primaryGrouping: 'network_region',
})
// Analyze the data
console.table(datatable.toConsole())
```
## Data Analysis
The Open Electricity client includes a powerful `DataTable` class that provides a pandas/polars-like interface for analyzing time series data.
### DataTable Overview
The `DataTable` class provides methods for:
- Filtering and selecting data
- Grouping and aggregating
- Sorting and ordering
- Statistical analysis
- Data transformation
### Core Methods
#### Accessing Data
```typescript
// Get all rows
const rows = datatable.getRows()
// Get available grouping columns
const groupings = datatable.getGroupings()
// Get metrics and their units
const metrics = datatable.getMetrics()
// Get the latest timestamp
const latestTime = datatable.getLatestTimestamp()
```
#### Filtering
Filter rows based on a condition:
```typescript
// Filter for high power periods
const highPower = datatable.filter(row => (row.power as number) > 1000)
// Filter for specific region
const nswData = datatable.filter(row => row.network_region === "NSW1")
```
#### Selecting Columns
Select specific columns to work with:
```typescript
// Select only power and region columns
const powerByRegion = datatable.select(["interval", "power", "network_region"])
```
#### Grouping and Aggregation
Group data and calculate aggregates:
```typescript
// Calculate sum by region
const totalByRegion = datatable.groupBy(["network_region"], "sum")
// Calculate mean by region and fuel technology
const avgByRegionAndFuel = datatable.groupBy(
["network_region", "fueltech"],
"mean"
)
```
Supported aggregation methods:
- `"sum"`: Calculate the sum of values
- `"mean"`: Calculate the arithmetic mean
#### Sorting
Sort data by one or more columns:
```typescript
// Sort by power, descending
const highestFirst = datatable.sortBy(["power"], false)
// Sort by region, then by power
const sortedData = datatable.sortBy(["network_region", "power"])
```
#### Statistical Analysis
Calculate summary statistics for numeric columns:
```typescript
const stats = datatable.describe()
```
The `describe()` method returns statistics including:
- `count`: Number of non-null values
- `mean`: Arithmetic mean
- `std`: Standard deviation
- `min`: Minimum value
- `q25`: 25th percentile
- `median`: 50th percentile
- `q75`: 75th percentile
- `max`: Maximum value
### Performance Considerations
The `DataTable` class includes several optimizations:
1. **Caching**: Results of grouping and sorting operations are cached
2. **Indexed Filtering**: Simple equality filters use column indexes
3. **Single-Pass Operations**: Many operations are optimized to process data in a single pass
4. **Memory Efficiency**: Data structures are reused where possible
## Type Reference
### Network Types
#### NetworkCode
Represents the supported electricity networks:
```typescript
type NetworkCode = "NEM" | "WEM" | "AU"
```
- `NEM`: National Electricity Market (Eastern and Southern Australia)
- `WEM`: Western Australian Electricity Market
- `AU`: Australia-wide (defaults to NEM timezone)
#### DataInterval
Supported time intervals for data aggregation:
```typescript
type DataInterval = "5m" | "1h" | "1d" | "7d" | "1M" | "3M" | "season" | "1y" | "fy"
```
### Metric Types
#### DataMetric
Metrics available for network and facility data:
```typescript
type DataMetric = "power" | "energy" | "emissions" | "market_value"
```
#### MarketMetric
Metrics available for market data:
```typescript
type MarketMetric = "price" | "demand" | "demand_energy"
```
### Response Types
#### ITimeSeriesResponse
Standard response type for time series data:
```typescript
interface ITimeSeriesResponse {
response: IAPIResponse // Raw API response
datatable?: DataTable // Processed data table
}
```
#### FacilityResponse
Response type for facility queries:
```typescript
interface FacilityResponse {
response: IAPIResponse // Raw API response
table: RecordTable // Processed facility records
}
```
## Common Patterns and Examples
### Basic Data Retrieval
```typescript
import { OpenElectricityClient } from '@openelectricity/client'
// Initialize client
const client = new OpenElectricityClient()
// Get energy data for the NEM network
const { response, datatable } = await client.getNetworkData("NEM", ["energy"], {
interval: "1h",
dateStart: "2024-01-01T00:00:00",
dateEnd: "2024-01-02T00:00:00",
primaryGrouping: "network_region",
})
```
### Analyzing Generation Mix
```typescript
// Get power data with fuel technology grouping
const { datatable } = await client.getNetworkData("NEM", ["power"], {
interval: "5m",
primaryGrouping: "network_region",
secondaryGrouping: "fueltech",
})
// Calculate renewable vs non-renewable generation
const renewableFueltechs = new Set(["solar", "wind", "hydro", "pumps", "bioenergy"])
const latestData = datatable.filter(row => row.interval.getTime() === datatable.getLatestTimestamp())
let renewable = 0, total = 0
latestData.getRows().forEach(row => {
const power = row.power as number
total += power
if (renewableFueltechs.has(row.fueltech as string)) {
renewable += power
}
})
console.log(`Renewable: ${(renewable / total * 100).toFixed(1)}%`)
```
### Calculating Emission Factors
```typescript
// Get emissions and energy data
const { datatable } = await client.getNetworkData("NEM", ["emissions", "energy"], {
interval: "1d",
primaryGrouping: "network_region",
})
// Calculate regional emission factors
const avgByRegion = datatable
.groupBy(["network_region"], "mean")
.getRows()
.map(row => ({
network_region: row.network_region,
avg_emission_factor: ((row.emissions as number) / (row.energy as number)).toFixed(3),
}))
```
## Error Handling
The client throws specific error types:
- `OpenElectricityError`: General API errors
- `NoDataFound`: When no data matches the query (416 status)
```typescript
try {
const result = await client.getNetworkData("NEM", ["energy"])
} catch (error) {
if (error instanceof OpenElectricityError) {
console.error("API Error:", error.message)
} else if (error instanceof NoDataFound) {
console.error("No matching data found")
}
}
```
## Support
- [GitHub Issues](https://github.com/opennem/openelectricity-client/issues)
- [API Documentation](https://docs.openelectricity.org.au)
---
# Typescript Client Reference
URL: /sdk/typescript/reference
# Client Initialization
```typescript
import { OpenElectricityClient } from 'openelectricity'
const client = new OpenElectricityClient({
apiKey?: string, // Optional: API key (recommended to use OPENELECTRICITY_API_KEY env var)
baseUrl?: string // Optional: API endpoint (recommended to use OPENELECTRICITY_API_URL env var)
})
```
# Network Data
Fetches the endpoint at (/v4/data/network)[/api/data/get-network-data]
## getNetworkData
Fetch network-level time series data for power, energy, emissions, and market value metrics.
```typescript
async getNetworkData(
networkCode: NetworkCode, // "NEM" | "WEM" | "AU"
metrics: DataMetric[], // Array of metrics to fetch
params?: INetworkTimeSeriesParams // Optional parameters
): Promise
```
**Parameters:**
- `networkCode`: The network to fetch data for
- `metrics`: Array of metrics (e.g., ["power", "energy", "emissions"])
- `params`: Optional parameters:
- `interval`: Time interval (e.g., "5m", "1h", "1d")
- `dateStart`: Start date (timezone-naive)
- `dateEnd`: End date (timezone-naive)
- `primaryGrouping`: Primary grouping field (`"network"` or `"network_region"`)
- `secondaryGrouping`: Array of secondary grouping fields (`"fueltech"`, `"fueltech_group"`, `"status"`, `"renewable"`)
- `network_region`: Filter to a specific region (e.g., `"NSW1"`)
- `fueltech`: Array of `UnitFueltechType` to filter by
- `fueltech_group`: Array of `UnitFueltechGroupType` to filter by
**Example:**
```typescript
const { datatable } = await client.getNetworkData("NEM", ["energy"], {
interval: "1h",
dateStart: "2024-01-01T00:00:00",
dateEnd: "2024-01-02T00:00:00",
primaryGrouping: "network_region",
secondaryGrouping: ["fueltech"]
})
```
# Facility Data
Fetches the endpoint at (/v4/data/facility)[/api/data/get-facility-data]
## getFacilityData
Fetch facility-specific time series data.
```typescript
async getFacilityData(
networkCode: NetworkCode, // "NEM" | "WEM" | "AU"
facilityCodes: string | string[], // Single facility or array of facilities
metrics: DataMetric[], // Array of metrics to fetch
params?: IFacilityTimeSeriesParams // Optional parameters
): Promise
```
**Parameters:**
- `networkCode`: The network containing the facilities
- `facilityCodes`: Single facility code or array of codes
- `metrics`: Array of metrics to fetch
- `params`: Optional parameters:
- `interval`: Time interval
- `dateStart` / `dateEnd`: Timezone-naive dates in network time
- `unitCodes`: Single unit code or array, narrows to specific units within the facilities
**Example:**
```typescript
const { datatable } = await client.getFacilityData(
"NEM",
["BAYSW1", "ERARING"],
["power", "emissions"],
{
interval: "5m",
dateStart: "2024-01-01T00:00:00",
dateEnd: "2024-01-02T00:00:00"
}
)
```
# Market Data
Fetches the endpoint at (/v4/market)[/api/data/get-network-data]
## getMarket
Fetch market-related metrics like price, demand, and curtailment.
```typescript
async getMarket(
networkCode: NetworkCode, // "NEM" | "WEM" | "AU"
metrics: MarketMetric[], // Array of market metrics
params?: IMarketTimeSeriesParams // Optional parameters
): Promise
```
**Parameters:**
- `networkCode`: The market to fetch data for
- `metrics`: Array of market metrics including:
- Price and demand: `"price"`, `"demand"`, `"demand_energy"`, `"demand_gross"`, `"demand_gross_energy"`
- Renewable generation: `"generation_renewable"`, `"generation_renewable_with_storage"`, `"renewable_proportion"`, `"renewable_with_storage_proportion"` (and their `_energy` variants)
- Curtailment power (MW): `"curtailment"`, `"curtailment_solar_utility"`, `"curtailment_wind"`
- Curtailment energy (MWh): `"curtailment_energy"`, `"curtailment_solar_utility_energy"`, `"curtailment_wind_energy"`
- Flows: `"flow_imports"`, `"flow_exports"` (and their `_energy` variants)
- `params`: Optional parameters:
- `interval`, `dateStart`, `dateEnd`
- `primaryGrouping`: `"network"` or `"network_region"`
- `network_region`: Filter to a specific region (e.g., `"NSW1"`)
**Example - Price and Demand:**
```typescript
const { datatable } = await client.getMarket("NEM", ["price", "demand"], {
interval: "30m",
dateStart: "2024-01-01T00:00:00",
dateEnd: "2024-01-02T00:00:00",
primaryGrouping: "network_region"
})
```
**Example - Curtailment Power (5-minute intervals):**
```typescript
// Fetch real-time curtailment power data (MW)
const { datatable } = await client.getMarket(
"NEM",
["curtailment_solar_utility", "curtailment_wind", "curtailment"],
{
interval: "5m",
dateStart: "2024-01-01T00:00:00",
// Omit dateEnd to get latest available data
primaryGrouping: "network_region"
}
)
```
**Example - Curtailment Energy (Daily totals):**
```typescript
// Fetch daily curtailment energy totals (MWh)
const { datatable } = await client.getMarket(
"NEM",
["curtailment_solar_utility_energy", "curtailment_wind_energy", "curtailment_energy"],
{
interval: "1d",
dateStart: "2024-01-01",
dateEnd: "2024-01-31",
primaryGrouping: "network_region"
}
)
```
# Facility Information
## getFacilities
Fetches the endpoint at (/v4/facilities)[/api/facilities/get-facilities]
Get information about generation facilities and their units.
```typescript
async getFacilities(
params?: IFacilityParams // Optional filter parameters
): Promise
```
**Parameters:**
- `params`: Optional filters:
- `status_id`: Array of facility statuses
- `fueltech_id`: Array of fuel technologies
- `network_id`: Network code or array of codes
- `network_region`: Specific network region
**Example:**
```typescript
const { table } = await client.getFacilities({
status_id: ["operating"],
fueltech_id: ["solar_utility", "wind"],
network_id: "NEM"
})
```
# Pollution Data
## getFacilityPollution
Fetches the endpoint at `/v4/pollution/facilities`.
Get time-series pollution data from the National Pollutant Inventory for facilities with NPI tracking.
```typescript
async getFacilityPollution(
params?: IFacilityPollutionParams
): Promise
```
**Parameters:**
- `params`: Optional filters:
- `facility_code`: Array of facility codes
- `pollutant_code`: Array of `PollutantCode` values (`"nox"`, `"so2"`, `"co"`, etc.)
- `pollutant_category`: Array of `PollutantCategory` values (`"air_pollutant"`, `"heavy_metal"`, etc.)
- `dateStart` / `dateEnd`: Timezone-naive dates
When the response contains no rows, `datatable` is `undefined`.
**Example:**
```typescript
const { response, datatable } = await client.getFacilityPollution({
facility_code: ["ERARING"],
pollutant_code: ["nox", "so2"],
})
```
# Metrics Discovery
## getAvailableMetrics
Fetches the endpoint at `/v4/metrics`.
Discover which metrics the API supports along with their units, descriptions, default aggregations, and which endpoints they are valid for.
```typescript
async getAvailableMetrics(): Promise
```
Errors are surfaced as `OpenElectricityError` (404 → `NoDataFound`, 403 → permission-denied).
**Example:**
```typescript
const { metrics, endpoints } = await client.getAvailableMetrics()
console.log(endpoints.data) // metrics valid on /data
console.log(metrics.power.unit) // "MW"
```
# User Information
## getCurrentUser
Fetches the endpoint at (/v4/user)[/api/user/get-user-me]
Get information about the current API user.
```typescript
async getCurrentUser(): Promise>
```
**Example:**
```typescript
const { data: user } = await client.getCurrentUser()
console.log(user.plan) // "COMMUNITY" | "BASIC" | "PRO" | "ENTERPRISE"
console.log(user.roles) // optional OpenNEMRolesType[]
console.log(user.rate_limit) // optional number
```
---
# Type Reference
TypeScript type definitions for the Open Electricity client
URL: /sdk/typescript/types
# Type Reference
The Open Electricity client uses TypeScript to provide type safety and better developer experience. This page documents all the types used in the client.
## Network Types
### NetworkCode
Represents the supported electricity networks:
```typescript
type NetworkCode = "NEM" | "WEM" | "AU"
```
- `NEM`: National Electricity Market (Eastern and Southern Australia)
- `WEM`: Western Australian Electricity Market
- `AU`: Australia-wide (defaults to NEM timezone)
### DataInterval
Supported time intervals for data aggregation:
```typescript
type DataInterval = "5m" | "1h" | "1d" | "7d" | "1M" | "3M" | "season" | "1y" | "fy"
```
- `5m`: 5-minute intervals
- `1h`: Hourly intervals
- `1d`: Daily intervals
- `7d`: Weekly intervals
- `1M`: Monthly intervals
- `3M`: Quarterly intervals
- `season`: Seasonal intervals
- `1y`: Yearly intervals
- `fy`: Financial year intervals
## Metric Types
### DataMetric
Metrics available for network and facility data:
```typescript
type DataMetric =
| "power"
| "energy"
| "emissions"
| "market_value"
| "pollution"
| "renewable_proportion"
| "storage_battery"
```
- `power`: Instantaneous power output (MW)
- `energy`: Energy generated (MWh)
- `emissions`: CO2 equivalent emissions (tCO2e)
- `market_value`: Market value ($)
- `pollution`: Pollutant emissions (kg), used with the `/pollution/facilities` endpoint
- `renewable_proportion`: Renewable share of generation (%)
- `storage_battery`: Battery storage state of charge (MWh)
### MarketMetric
Metrics available for market data:
```typescript
type MarketMetric =
| "price"
| "demand"
| "demand_energy"
| "demand_gross"
| "demand_gross_energy"
| "generation_renewable"
| "generation_renewable_energy"
| "generation_renewable_with_storage"
| "generation_renewable_with_storage_energy"
| "renewable_proportion"
| "renewable_with_storage_proportion"
| "curtailment"
| "curtailment_energy"
| "curtailment_solar_utility"
| "curtailment_solar_utility_energy"
| "curtailment_wind"
| "curtailment_wind_energy"
| "flow_imports"
| "flow_exports"
| "flow_imports_energy"
| "flow_exports_energy"
```
**Price and Demand:**
- `price`: Market price ($/MWh)
- `demand`: Operational demand (MW)
- `demand_energy`: Operational demand energy (MWh)
- `demand_gross`: Gross demand including rooftop solar (MW)
- `demand_gross_energy`: Gross demand energy including rooftop solar (MWh)
**Renewable Generation:**
- `generation_renewable`: Renewable generation including battery discharge and pumps, excluding TUMUT3 (MW)
- `generation_renewable_energy`: Renewable energy (MWh)
- `generation_renewable_with_storage`: Renewable generation including TUMUT3 hybrid hydro+storage (MW)
- `generation_renewable_with_storage_energy`: Renewable energy including TUMUT3 (MWh)
- `renewable_proportion`: Renewable share of gross demand (%)
- `renewable_with_storage_proportion`: Renewable+storage share of gross demand (%)
**Curtailment:**
- `curtailment` / `curtailment_energy`: Total curtailment (MW / MWh)
- `curtailment_solar_utility` / `curtailment_solar_utility_energy`: Solar curtailment (MW / MWh)
- `curtailment_wind` / `curtailment_wind_energy`: Wind curtailment (MW / MWh)
**Interconnector Flows:**
- `flow_imports` / `flow_imports_energy`: Region imports (MW / MWh)
- `flow_exports` / `flow_exports_energy`: Region exports (MW / MWh)
## Grouping Types
### DataPrimaryGrouping
Primary grouping options for data aggregation:
```typescript
type DataPrimaryGrouping = "network" | "network_region"
```
### DataSecondaryGrouping
Secondary grouping options for data aggregation:
```typescript
type DataSecondaryGrouping =
| "fueltech"
| "fueltech_group"
| "status"
| "renewable"
```
## Unit Types
### UnitFueltechType
Fuel technologies for a unit:
```typescript
type UnitFueltechType =
| "battery"
| "battery_charging"
| "battery_discharging"
| "bioenergy_biogas"
| "bioenergy_biomass"
| "coal_black"
| "coal_brown"
| "distillate"
| "gas_ccgt"
| "gas_ocgt"
| "gas_recip"
| "gas_steam"
| "gas_wcmg"
| "hydro"
| "hydro_and_storage"
| "pumps"
| "solar_rooftop"
| "solar_thermal"
| "solar_utility"
| "nuclear"
| "other"
| "solar"
| "wind"
| "wind_offshore"
| "imports"
| "exports"
| "interconnector"
| "aggregator_vpp"
| "aggregator_dr"
```
A matching `FuelTech` const is exported for autocomplete (`FuelTech.SOLAR_UTILITY`, etc.).
### UnitFueltechGroupType
```typescript
type UnitFueltechGroupType =
| "coal"
| "gas"
| "wind"
| "solar"
| "battery"
| "battery_charging"
| "battery_discharging"
| "hydro"
| "distillate"
| "bioenergy"
| "pumps"
| "renewable"
| "fossil"
| "other"
```
A matching `FuelTechGroup` const is exported.
### UnitStatusType
```typescript
type UnitStatusType = "committed" | "operating" | "retired"
```
A matching `UnitStatus` const is exported.
### UnitDispatchType
```typescript
type UnitDispatchType =
| "GENERATOR"
| "LOAD"
| "BIDIRECTIONAL"
| "INTERCONNECTOR"
```
### UnitDateSpecificity
How specific a unit-related date is:
```typescript
type UnitDateSpecificity = "year" | "month" | "quarter" | "day"
```
## Parameter Types
### IFacilityTimeSeriesParams
Parameters for facility data queries:
```typescript
interface IFacilityTimeSeriesParams {
interval?: DataInterval
dateStart?: string
dateEnd?: string
unitCodes?: string | string[]
}
```
### IMarketTimeSeriesParams
Parameters for market data queries:
```typescript
interface IMarketTimeSeriesParams extends IFacilityTimeSeriesParams {
primaryGrouping?: DataPrimaryGrouping
network_region?: string
}
```
### INetworkTimeSeriesParams
Parameters for network data queries:
```typescript
interface INetworkTimeSeriesParams extends IMarketTimeSeriesParams {
secondaryGrouping?: DataSecondaryGrouping[]
fueltech?: UnitFueltechType[]
fueltech_group?: UnitFueltechGroupType[]
}
```
### IFacilityParams
Parameters for facility queries:
```typescript
interface IFacilityParams {
status_id?: UnitStatusType[]
fueltech_id?: UnitFueltechType[]
network_id?: NetworkCode | NetworkCode[]
network_region?: string
}
```
## Response Types
### ITimeSeriesResponse
Standard response type for time series data:
```typescript
interface ITimeSeriesResponse {
response: IAPIResponse
datatable?: DataTable
}
```
### INetworkTimeSeries
Network time series data structure:
```typescript
interface INetworkTimeSeries {
network_code: string
metric: Metric
unit: string
interval: DataInterval
start: string
end: string
groupings: DataPrimaryGrouping[] | DataSecondaryGrouping[]
results: ITimeSeriesResult[]
network_timezone_offset: string
}
```
### IFacility
Facility information structure:
```typescript
interface IFacility {
code: string
name: string
network_id: string
network_region: string
description: string | null
npi_id: string | null
location: ILocation | null
units: IUnit[]
created_at?: string | null
updated_at?: string | null
}
```
### IFacilityPollutionParams
Parameters for the `/pollution/facilities` endpoint:
```typescript
interface IFacilityPollutionParams {
facility_code?: string[]
pollutant_code?: PollutantCode[]
pollutant_category?: PollutantCategory[]
dateStart?: string
dateEnd?: string
}
```
### PollutantCategory
```typescript
type PollutantCategory =
| "air_pollutant"
| "water_pollutant"
| "heavy_metal"
| "organic"
```
### PollutantCode
```typescript
type PollutantCode =
// Air pollutants
| "nox" | "so2" | "co" | "pm10" | "pm2_5" | "voc" | "ammonia" | "hcl"
// Heavy metals
| "as" | "cd" | "cr3" | "cr6" | "cu" | "hg" | "ni" | "pb" | "zn"
// Organic compounds
| "benzene" | "formaldehyde" | "pah" | "dioxins"
// Other
| "fluoride"
```
### IFacilityDataRow
Structure for facility data rows:
```typescript
interface IFacilityDataRow {
time: string
value: number
facility_code: string
facility_name: string
facility_network: string
facility_region: string
unit_code: string
unit_fueltech: UnitFueltechType | null
unit_status: UnitStatusType | null
unit_capacity: number | null
unit_emissions_factor: number | null
unit_first_seen: string | null
unit_last_seen: string | null
unit_dispatch_type: UnitDispatchType
}
```
## Data Analysis Types
### IDataTableRow
Structure for data table rows:
```typescript
interface IDataTableRow {
interval: Date
[key: string]: Date | string | number | boolean | null
}
```
## Error Types
### OpenElectricityError
Custom error type for API errors:
```typescript
class OpenElectricityError extends Error {
constructor(
message: string,
public response?: IAPIErrorResponse
)
}
```
### NoDataFound
Error type thrown when no data matches the query (HTTP 404):
```typescript
class NoDataFound extends Error {
constructor(message: string = "No data found")
}
```
## API Response Types
### IAPIResponse
Standard envelope returned by the API:
```typescript
interface IAPIResponse {
version?: string
created_at?: string
success: boolean
error: string | null
data: T
total_records?: number
}
```
`version` and `created_at` are optional. The `/me` endpoint may omit them.
### IValidationErrorDetail
Structured validation error details surfaced on `OpenElectricityError.details`:
```typescript
interface IValidationErrorDetail {
error?: string
supported_metrics?: string[]
requested_metrics?: string[]
invalid_metrics?: string[]
hint?: string
[key: string]: unknown
}
```
### IMetricsResponse
Response shape from `getAvailableMetrics()`:
```typescript
interface IMetricMetadata {
name: string
unit: string
description: string
default_aggregation: string
precision: number
}
interface IMetricsResponse {
metrics: Record
total: number
endpoints: {
market: string[]
data: string[]
}
}
```
## User Types
### IUser
```typescript
interface IUser {
id: string
full_name: string
email: string
owner_id: string
plan: UserPlan
meta: IUserMeta
rate_limit?: number
unkey_meta?: Record
roles?: OpenNEMRolesType[]
}
interface IUserMeta {
remaining: number
}
```
### UserPlan
```typescript
type UserPlan = "COMMUNITY" | "BASIC" | "PRO" | "ENTERPRISE"
```
### OpenNEMRolesType
```typescript
type OpenNEMRolesType =
| "admin"
| "pro"
| "academic"
| "user"
| "anonymous"
```
A matching `OpenNEMRoles` const is also exported for autocomplete:
```typescript
import { OpenNEMRoles } from "openelectricity"
OpenNEMRoles.ADMIN // "admin"
OpenNEMRoles.PRO // "pro"
```
---
# Utility Functions
Helper functions and utilities for working with the Open Electricity client
URL: /sdk/typescript/utilities
# Utility Functions
The Open Electricity client provides several utility functions to help you work with dates, timezones, and other common operations. These utilities are designed to handle the complexities of working with different electricity networks and their specific timezone requirements.
## Date and Time Utilities
### Network Timezones
The client handles data from different electricity networks in Australia, each with their own timezone:
- NEM (National Electricity Market): AEST/UTC+10
- WEM (Western Australia): AWST/UTC+8
- AU (Australia): AEST/UTC+10 (default)
### Timezone Functions
#### getNetworkTimezone
Get the timezone offset in hours for a specific network.
```typescript
function getNetworkTimezone(network: NetworkCode): number
```
**Parameters:**
- `network`: Network code (`"NEM"` | `"WEM"` | `"AU"`)
**Returns:**
- Number representing timezone offset in hours (e.g., 10 for AEST/UTC+10)
**Example:**
```typescript
import { getNetworkTimezone } from 'openelectricity'
const nemOffset = getNetworkTimezone("NEM") // Returns 10 (AEST/UTC+10)
const wemOffset = getNetworkTimezone("WEM") // Returns 8 (AWST/UTC+8)
```
#### getNetworkTimezoneOffset
Get timezone offset in milliseconds for a network.
```typescript
function getNetworkTimezoneOffset(network: NetworkCode): number
```
**Parameters:**
- `network`: Network code (`"NEM"` | `"WEM"` | `"AU"`)
**Returns:**
- Number representing timezone offset in milliseconds
**Example:**
```typescript
import { getNetworkTimezoneOffset } from 'openelectricity'
const nemOffsetMs = getNetworkTimezoneOffset("NEM") // Returns 36000000 (10 hours in ms)
```
### Date Handling Functions
#### isAware
Check if a date string contains timezone information.
```typescript
function isAware(dateStr: string | Date): boolean
```
**Parameters:**
- `dateStr`: Date string or Date object to check
**Returns:**
- Boolean indicating if the date string contains timezone information
**Example:**
```typescript
import { isAware } from 'openelectricity'
isAware("2024-03-15T10:00:00Z") // Returns true
isAware("2024-03-15T10:00:00+10:00") // Returns true
isAware("2024-03-15T10:00:00") // Returns false
isAware(new Date()) // Returns false
```
#### makeAware
Make a date timezone aware by adding the network's timezone offset.
```typescript
function makeAware(date: string | Date, network: NetworkCode): string
```
**Parameters:**
- `date`: Date string or Date object to make timezone aware
- `network`: Network code to get timezone from
**Returns:**
- ISO string with timezone information
**Example:**
```typescript
import { makeAware } from 'openelectricity'
const awareDate = makeAware("2024-03-15T10:00:00", "NEM")
// Returns "2024-03-15T10:00:00+10:00"
```
#### stripTimezone
Remove timezone information from a date string.
```typescript
function stripTimezone(dateStr: string): string
```
**Parameters:**
- `dateStr`: Date string to strip timezone from
**Returns:**
- Date string without timezone information
**Example:**
```typescript
import { stripTimezone } from 'openelectricity'
const naiveDate = stripTimezone("2024-03-15T10:00:00+10:00")
// Returns "2024-03-15T10:00:00"
```
### Interval Functions
#### getLastCompleteInterval
Get the last complete 5-minute interval for a network.
```typescript
function getLastCompleteInterval(network: NetworkCode): string
```
**Parameters:**
- `network`: Network code to get timezone from
**Returns:**
- ISO string of the last complete 5-minute interval in network time (without timezone information)
**Example:**
```typescript
import { getLastCompleteInterval } from 'openelectricity'
// If current time is 2024-03-15T10:07:30+10:00
const lastInterval = getLastCompleteInterval("NEM")
// Returns "2024-03-15T10:00:00"
```
## Best Practices
### Working with Timezones
1. **Network-Specific Times**: Always use the appropriate network timezone when working with dates:
```typescript
const networkAwareDate = makeAware(localDate, "NEM")
```
2. **API Submissions**: The API expects timezone-naive dates in network time:
```typescript
const apiReadyDate = stripTimezone(networkAwareDate)
```
3. **Interval Data**: Use `getLastCompleteInterval` for real-time data:
```typescript
const lastInterval = getLastCompleteInterval("NEM")
```
### Date Validation
1. **Check Timezone Information**:
```typescript
if (isAware(dateStr)) {
// Handle timezone-aware date
} else {
// Handle naive date
}
```
2. **Network-Specific Processing**:
```typescript
const offset = getNetworkTimezone(network)
const offsetMs = getNetworkTimezoneOffset(network)
```
## Common Patterns
### Real-time Data Retrieval
```typescript
import { getLastCompleteInterval } from '@openelectricity/client'
async function getLatestData() {
const lastInterval = getLastCompleteInterval("NEM")
const { datatable } = await client.getNetworkData("NEM", ["power"], {
dateStart: lastInterval,
interval: "5m"
})
return datatable
}
```
### Date Conversion Pipeline
```typescript
import { makeAware, stripTimezone } from 'openelectricity'
function prepareDateForAPI(date: Date, network: NetworkCode) {
// Add network timezone
const networkAware = makeAware(date, network)
// Strip for API submission
return stripTimezone(networkAware)
}
```
### Timezone Validation
```typescript
import { isAware, makeAware } from 'openelectricity'
function ensureNetworkAware(date: string | Date, network: NetworkCode) {
if (!isAware(date)) {
return makeAware(date, network)
}
return date.toString()
}
```
---
# Introduction
The Open Electricity Python SDK
URL: /sdk/python/overview
Open Electricity provides a Python SDK for accessing the API and analysing data.
The Python SDK is available on [PyPI](https://pypi.org/project/openelectricity/).
### Features
- Synchronous and asynchronous API clients
- Fully typed with comprehensive type annotations
- Automatic request retries and error handling
- Context manager support
- Modern Python (3.10+) with full type annotations
- Direct conversion to Pandas and Polars DataFrames for analysis
### Installation
```bash uv
uv add openelectricity
```
```bash pip
pip install openelectricity
```
### Project Source
The project source code is available on GitHub at [https://github.com/opennem/openelectricity-python](https://github.com/opennem/openelectricity-python).
You can file issues and contribute to the project there.
---
# Python Client Reference
URL: /sdk/python/reference
# Client Initialization
The Python SDK provides both synchronous and asynchronous clients.
## Synchronous Client
```python
from openelectricity import OEClient
# Initialize with environment variables (recommended)
client = OEClient()
# Or explicitly pass credentials
client = OEClient(
api_key="your-api-key",
base_url="https://api.openelectricity.org.au/v4"
)
# Use as context manager
with OEClient() as client:
# Make API calls
pass
```
## Asynchronous Client
```python
from openelectricity import AsyncOEClient
import asyncio
async def main():
async with AsyncOEClient() as client:
# Make async API calls
pass
asyncio.run(main())
```
## Proxies and Custom Certificates
For corporate networks that route traffic through a proxy or intercept TLS, both `OEClient` and `AsyncOEClient` accept the following keyword-only options:
| Option | Type | Description |
|--------|------|-------------|
| `proxy` | `str` | Proxy URL, e.g. `http://proxy.corp:8080`. Credentials may be embedded (`http://user:pass@host:port`). |
| `proxy_auth` | `aiohttp.BasicAuth` | Proxy credentials, as an alternative to embedding them in `proxy`. |
| `ssl_context` | `ssl.SSLContext` | A pre-built SSL context, e.g. for a corporate CA. |
| `ca_cert` | `str` | Path to an additional CA certificate bundle. Added to the system trust store. |
| `verify_ssl` | `bool` | Verify TLS certificates. Defaults to `True`; set to `False` to disable (not recommended). |
| `trust_env` | `bool` | Read proxy settings and `.netrc` from the environment (`HTTP_PROXY` / `HTTPS_PROXY`). Defaults to `False`. |
`ssl_context` and `ca_cert` are mutually exclusive, and neither can be combined with `verify_ssl=False`.
**Route requests through a proxy:**
```python
from openelectricity import OEClient
# Credentials embedded in the URL
client = OEClient(proxy="http://user:pass@proxy.corp:8080")
# Or supplied separately
from aiohttp import BasicAuth
client = OEClient(
proxy="http://proxy.corp:8080",
proxy_auth=BasicAuth("user", "pass"),
)
```
**Pick up proxy settings from the environment:**
```python
# Uses the HTTP_PROXY / HTTPS_PROXY environment variables
client = OEClient(trust_env=True)
```
**Trust a corporate CA certificate:**
```python
# For networks that intercept TLS with their own certificate authority
client = OEClient(ca_cert="/etc/ssl/corp-ca.pem")
```
# Network Data
Fetch network-level time series data for power, energy, emissions, and market value metrics.
## get_network_data
```python
def get_network_data(
network_code: str, # "NEM" | "WEM" | "AU"
metrics: list[DataMetric], # List of metrics to fetch
interval: str = "5m", # Time interval
date_start: datetime | None = None, # Start date
date_end: datetime | None = None, # End date
primary_grouping: str | None = None, # Primary grouping
secondary_grouping: str | None = None # Secondary grouping
) -> TimeSeriesResponse
```
**Example:**
```python
from openelectricity.types import DataMetric
from datetime import datetime, timedelta
response = client.get_network_data(
network_code="NEM",
metrics=[DataMetric.POWER, DataMetric.ENERGY],
interval="1h",
date_start=datetime.now() - timedelta(days=1),
date_end=datetime.now(),
primary_grouping="network_region",
secondary_grouping="fueltech"
)
# Access data
for timeseries in response.data:
print(f"Metric: {timeseries.metric}")
for result in timeseries.results:
for data_point in result.data:
print(f" {data_point.timestamp}: {data_point.value}")
```
# Market Data
Fetch market-related metrics including price, demand, and curtailment.
## get_market
```python
def get_market(
network_code: str, # "NEM" | "WEM" | "AU"
metrics: list[MarketMetric], # List of market metrics
interval: str = "5m", # Time interval
date_start: datetime | None = None, # Start date
date_end: datetime | None = None, # End date (omit for latest)
network_region: str | None = None, # Specific region filter
primary_grouping: str | None = None # Primary grouping
) -> TimeSeriesResponse
```
### Available Market Metrics
**Price and Demand:**
- `MarketMetric.PRICE` - Electricity spot price ($/MWh)
- `MarketMetric.DEMAND` - Operational demand (MW)
- `MarketMetric.DEMAND_ENERGY` - Operational demand energy (MWh)
- `MarketMetric.DEMAND_GROSS` - Gross demand including rooftop solar (MW)
- `MarketMetric.DEMAND_GROSS_ENERGY` - Gross demand energy including rooftop solar (MWh)
**Renewable Generation:**
- `MarketMetric.GENERATION_RENEWABLE` - Renewable generation including battery discharge and pumps, excluding TUMUT3 (MW)
- `MarketMetric.GENERATION_RENEWABLE_ENERGY` - Renewable energy (MWh)
- `MarketMetric.GENERATION_RENEWABLE_WITH_STORAGE` - Renewable generation including TUMUT3 hybrid hydro+storage (MW)
- `MarketMetric.GENERATION_RENEWABLE_WITH_STORAGE_ENERGY` - Renewable energy including TUMUT3 (MWh)
- `MarketMetric.RENEWABLE_PROPORTION` - Renewable share of gross demand (%)
- `MarketMetric.RENEWABLE_WITH_STORAGE_PROPORTION` - Renewable+storage share of gross demand (%)
**Curtailment Power (MW):**
- `MarketMetric.CURTAILMENT` - Total curtailment across all renewables
- `MarketMetric.CURTAILMENT_SOLAR_UTILITY` - Utility solar generation curtailed
- `MarketMetric.CURTAILMENT_WIND` - Wind generation curtailed
**Curtailment Energy (MWh):**
- `MarketMetric.CURTAILMENT_ENERGY` - Total curtailed energy
- `MarketMetric.CURTAILMENT_SOLAR_UTILITY_ENERGY` - Utility solar energy curtailed
- `MarketMetric.CURTAILMENT_WIND_ENERGY` - Wind energy curtailed
**Interconnector Flows:**
- `MarketMetric.FLOW_IMPORTS` / `MarketMetric.FLOW_IMPORTS_ENERGY` - Region imports (MW / MWh)
- `MarketMetric.FLOW_EXPORTS` / `MarketMetric.FLOW_EXPORTS_ENERGY` - Region exports (MW / MWh)
### Examples
**Fetch Real-time Curtailment (5-minute intervals):**
```python
from openelectricity.types import MarketMetric
from datetime import datetime, timedelta
import pandas as pd
# Get latest curtailment data (omit date_end for latest)
response = client.get_market(
network_code="NEM",
metrics=[
MarketMetric.CURTAILMENT_SOLAR_UTILITY,
MarketMetric.CURTAILMENT_WIND,
MarketMetric.CURTAILMENT
],
interval="5m",
date_start=datetime.now() - timedelta(days=1),
# date_end omitted to get latest data
primary_grouping="network_region"
)
# Convert to DataFrame
data = []
for timeseries in response.data:
for result in timeseries.results:
region = result.name.split("_")[-1] # Extract region from name
for data_point in result.data:
data.append({
"timestamp": data_point.timestamp,
"region": region,
"metric": timeseries.metric,
"value": data_point.value,
"unit": timeseries.unit
})
df = pd.DataFrame(data)
```
**Fetch Daily Curtailment Energy:**
```python
# Get daily curtailment energy totals (MWh)
response = client.get_market(
network_code="NEM",
metrics=[
MarketMetric.CURTAILMENT_SOLAR_UTILITY_ENERGY,
MarketMetric.CURTAILMENT_WIND_ENERGY,
MarketMetric.CURTAILMENT_ENERGY
],
interval="1d",
date_start=datetime.now() - timedelta(days=30),
date_end=datetime.now(),
primary_grouping="network_region"
)
# Process results
for timeseries in response.data:
print(f"\nMetric: {timeseries.metric} ({timeseries.unit})")
for result in timeseries.results:
region = result.name.split("_")[-1]
total = sum(dp.value for dp in result.data if dp.value)
print(f" {region}: {total:,.0f} {timeseries.unit}")
```
**Price and Curtailment Correlation:**
```python
# Fetch price and curtailment for correlation analysis
response = client.get_market(
network_code="NEM",
metrics=[
MarketMetric.PRICE,
MarketMetric.CURTAILMENT
],
interval="5m",
date_start=datetime.now() - timedelta(hours=24),
primary_grouping="network_region"
)
# Calculate correlation by region
import pandas as pd
data = []
for timeseries in response.data:
for result in timeseries.results:
region = result.name.split("_")[-1]
for data_point in result.data:
data.append({
"timestamp": data_point.timestamp,
"region": region,
"metric": timeseries.metric,
"value": data_point.value
})
df = pd.DataFrame(data)
pivot_df = df.pivot_table(
index=["timestamp", "region"],
columns="metric",
values="value"
).reset_index()
# Calculate correlation
for region in pivot_df["region"].unique():
region_df = pivot_df[pivot_df["region"] == region]
correlation = region_df["price"].corr(region_df["curtailment"])
print(f"{region}: {correlation:.3f}")
```
# Facility Data
## get_facility_data
Fetch facility-specific time series data.
```python
def get_facility_data(
network_code: str, # "NEM" | "WEM" | "AU"
facility_codes: str | list[str], # Single or multiple facility codes
metrics: list[DataMetric], # List of metrics
interval: str = "5m", # Time interval
date_start: datetime | None = None, # Start date
date_end: datetime | None = None # End date
) -> TimeSeriesResponse
```
**Example:**
```python
response = client.get_facility_data(
network_code="NEM",
facility_codes=["BAYSW1", "ERARING"],
metrics=[DataMetric.POWER, DataMetric.EMISSIONS],
interval="1h",
date_start=datetime.now() - timedelta(days=1),
date_end=datetime.now(),
)
```
# Facility Information
## get_facilities
Get information about generation facilities and their units.
```python
def get_facilities(
status_id: list[str] | None = None, # Filter by status
fueltech_id: list[str] | None = None, # Filter by fuel technology
network_id: str | list[str] | None = None, # Filter by network
network_region: str | None = None # Filter by region
) -> FacilityResponse
```
**Example:**
```python
from openelectricity.types import UnitStatusType, UnitFueltechType
# Get all operating solar and wind facilities in NSW
response = client.get_facilities(
status_id=[UnitStatusType.OPERATING],
fueltech_id=[
UnitFueltechType.SOLAR_UTILITY,
UnitFueltechType.WIND
],
network_id=["NEM"],
network_region="NSW1"
)
for facility in response.data:
print(f"{facility.code}: {facility.name}")
for unit in facility.units:
print(f" {unit.code}: {unit.capacity_mw} MW")
```
# Data Analysis
## Converting to DataFrames
The SDK provides built-in support for converting responses to Pandas and Polars DataFrames.
### Pandas Integration
```python
import pandas as pd
# Get market data
response = client.get_market(
network_code="NEM",
metrics=[MarketMetric.PRICE, MarketMetric.DEMAND],
interval="1h",
date_start=datetime.now() - timedelta(days=1),
date_end=datetime.now(),
primary_grouping="network_region"
)
# Convert to DataFrame
data = []
for timeseries in response.data:
for result in timeseries.results:
for data_point in result.data:
data.append({
"timestamp": data_point.timestamp,
"metric": timeseries.metric,
"value": data_point.value,
"unit": timeseries.unit
})
df = pd.DataFrame(data)
# Analyze
print(df.groupby("metric")["value"].describe())
```
### Polars Integration
```python
import polars as pl
# Same data structure as above
df = pl.DataFrame(data)
# Fast aggregations with Polars
result = (
df.lazy()
.groupby(["metric"])
.agg([
pl.col("value").mean().alias("avg"),
pl.col("value").max().alias("max"),
pl.col("value").min().alias("min")
])
.collect()
)
```
# Error Handling
The SDK provides comprehensive error handling with detailed error messages.
```python
from openelectricity.exceptions import OpenElectricityError
try:
response = client.get_market(
network_code="NEM",
metrics=[MarketMetric.PRICE],
interval="invalid_interval" # Invalid parameter
)
except OpenElectricityError as e:
print(f"API Error: {e}")
if hasattr(e, 'response'):
print(f"Details: {e.response}")
```
# Best Practices
1. **Use environment variables** for API credentials:
```bash
export OPENELECTRICITY_API_KEY="your-api-key"
export OPENELECTRICITY_API_URL="https://api.openelectricity.org.au/v4"
```
2. **Use context managers** to ensure proper resource cleanup:
```python
with OEClient() as client:
# API calls
pass
```
3. **Omit date_end** to get the latest available data:
```python
# Gets data from start_date to latest available
response = client.get_market(
network_code="NEM",
metrics=[MarketMetric.CURTAILMENT],
date_start=datetime.now() - timedelta(days=1)
# date_end omitted
)
```
4. **Choose appropriate intervals**:
- Use `"5m"` for real-time power monitoring
- Use `"1h"` for hourly aggregations
- Use `"1d"` for daily energy totals
- Use energy metrics (MWh) for longer periods
5. **Handle large datasets** efficiently:
```python
# Use generators for large datasets
def process_large_dataset(client, start_date, end_date):
current = start_date
while current < end_date:
chunk_end = min(current + timedelta(days=7), end_date)
response = client.get_market(
network_code="NEM",
metrics=[MarketMetric.PRICE],
date_start=current,
date_end=chunk_end
)
yield response
current = chunk_end
```
---
# Chart Styling
Create charts with Open Electricity branding and colors
URL: /sdk/python/styling
The Open Electricity Python SDK includes a comprehensive styling module that helps you create professional charts matching the Open Electricity brand guidelines.
## Installation
The styling module requires additional visualization dependencies:
```bash pip
pip install openelectricity[charts]
```
```bash uv
uv add openelectricity[charts]
```
Or install the dependencies separately:
```bash
pip install matplotlib seaborn pillow
```
## Quick Start
```python Basic Setup
from openelectricity import styles
import matplotlib.pyplot as plt
# Apply Open Electricity styling globally
styles.set_openelectricity_style()
# Create a styled figure
fig, ax = styles.create_styled_figure(figsize=(12, 6))
# Your plotting code here
ax.plot(data)
# Format with Open Electricity branding
styles.format_chart(
ax,
title="My Energy Chart",
ylabel="Generation (MW)",
add_logo=True # Adds watermark
)
plt.show()
```
```python With Data
from openelectricity import OEClient, styles
from openelectricity.types import DataMetric
import pandas as pd
# Fetch data
with OEClient() as client:
response = client.get_network_data(
network_code="NEM",
metrics=[DataMetric.POWER],
interval="1h",
primary_grouping="fueltech"
)
# Create styled chart
fig, ax = styles.create_styled_figure()
# Plot with fuel technology colors
for fueltech in fueltechs:
color = styles.get_fueltech_color(fueltech)
ax.plot(data[fueltech], color=color, label=fueltech)
styles.format_chart(ax, title="Generation by Technology")
plt.show()
```
## Fuel Technology Colors
The module includes the official color palette for all Australian energy fuel technologies.
### Color Functions
```python Single Color
# Get color for a specific fuel technology
color = styles.get_fueltech_color("solar_rooftop")
# Returns: "#FFD700" (gold)
```
```python Multiple Colors
# Get colors for multiple fuel technologies
fueltechs = ["solar", "wind", "coal", "gas"]
colors = styles.get_fueltech_palette(fueltechs)
# Returns: ["#FFA500", "#4A8E3C", "#2C2C2C", "#D2691E"]
```
### Available Colors
| Technology | Color | Hex Code |
|------------|-------|----------|
| Solar Rooftop | 🟡 Gold | `#FFD700` |
| Solar Utility | 🟠 Orange | `#FFA500` |
| Wind | 🟢 Forest Green | `#4A8E3C` |
| Hydro | 🔵 Light Blue | `#4A90E2` |
| Technology | Color | Hex Code |
|------------|-------|----------|
| Battery Discharging | 🟣 Indigo | `#6366F1` |
| Battery Charging | 🟪 Purple | `#8B5CF6` |
| Pumps | 🔷 Cyan | `#06B6D4` |
| Technology | Color | Hex Code |
|------------|-------|----------|
| Coal Black | ⚫ Very Dark Gray | `#2C2C2C` |
| Coal Brown | 🟤 Dark Brown | `#654321` |
| Gas CCGT | 🟫 Chocolate | `#D2691E` |
| Gas OCGT | 🟨 Burlesque | `#DEB887` |
| Gas Steam | 🟧 Sandy Brown | `#F4A460` |
| Technology | Color | Hex Code |
|------------|-------|----------|
| Distillate | 🔴 Crimson | `#DC143C` |
| Bioenergy | 🟫 Tan | `#8B7355` |
| Imports/Exports | ⚪ Gray | `#9CA3AF` |
## Logo Watermark
Add the official Open Electricity logo as a watermark to your charts.
```python
# Add watermark with defaults (15% size, 30% opacity)
styles.add_watermark(ax)
# Customize watermark
styles.add_watermark(
ax,
position=(0.98, 0.02), # Bottom-right (x, y in 0-1 range)
size=0.20, # 20% of figure width
alpha=0.3 # 30% transparency
)
```
The logo is automatically downloaded and cached from the official Open Electricity platform on first use.
## Chart Formatting
The `format_chart()` function applies consistent Open Electricity branding:
```python
styles.format_chart(
ax,
title="NEM Generation by Technology",
xlabel="Date",
ylabel="Power (MW)",
add_logo=True, # Add watermark
logo_position=(0.98, 0.02),
logo_size=0.15, # 15% of figure width
logo_alpha=0.3 # 30% transparency
)
```
### Features Applied
- **Typography**: DM Sans font family with appropriate sizes
- **Colors**: Clean white background with subtle gray gridlines
- **Grid**: Light gray (30% alpha) horizontal and vertical lines
- **Spines**: Only left and bottom axes visible (cleaner look)
- **Logo**: Optional watermark in bottom-right corner
## Complete Examples
### Stacked Area Chart
Recreate the Open Electricity website's generation chart:
```python
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
from openelectricity import OEClient, styles
from openelectricity.types import DataMetric
# Apply styling
styles.set_openelectricity_style()
# Fetch data
with OEClient() as client:
response = client.get_network_data(
network_code="NEM",
metrics=[DataMetric.POWER],
interval="30m",
date_start=datetime.now() - timedelta(days=3),
primary_grouping="fueltech"
)
# Process to DataFrame
data = []
for timeseries in response.data:
for result in timeseries.results:
fueltech = result.name.replace("power.", "")
for dp in result.data:
if dp.value and dp.value >= 0:
data.append({
"timestamp": dp.timestamp,
"fueltech": fueltech,
"power": dp.value
})
df = pd.DataFrame(data)
pivot_df = df.pivot_table(
index="timestamp",
columns="fueltech",
values="power",
fill_value=0
)
# Create chart
fig, ax = styles.create_styled_figure(figsize=(14, 8))
# Order fuel technologies (fossil fuels bottom, renewables top)
fuel_order = [
"coal_black", "coal_brown", # Coal at bottom
"gas_steam", "gas_ccgt", "gas_ocgt", # Gas
"hydro", "battery_charging", # Storage
"battery_discharging", "wind", # Renewables
"solar_utility", "solar_rooftop" # Solar on top
]
# Filter to available columns
ordered_cols = [col for col in fuel_order if col in pivot_df.columns]
colors = styles.get_fueltech_palette(ordered_cols)
# Create stacked area chart
ax.stackplot(
pivot_df.index,
*[pivot_df[col].values for col in ordered_cols],
labels=[col.replace("_", " ").title() for col in ordered_cols],
colors=colors,
alpha=0.9
)
# Format chart
styles.format_chart(
ax,
title=f"NEM Generation - {pivot_df.iloc[-1].sum():,.0f} MW",
ylabel="Generation (MW)",
add_logo=True,
logo_size=0.20
)
# Add legend
ax.legend(loc='upper left', frameon=False, ncol=5)
plt.tight_layout()
plt.show()
```
### Daily Emissions Chart
```python
from openelectricity import OEClient, styles
from openelectricity.types import DataMetric
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
# Setup styling
styles.set_openelectricity_style()
with OEClient() as client:
# Get emissions data
response = client.get_network_data(
network_code="NEM",
metrics=[DataMetric.EMISSIONS],
interval="1d",
date_start=datetime.now() - timedelta(days=30),
primary_grouping="network_region"
)
# Create styled figure
fig, ax = styles.create_styled_figure(figsize=(12, 6))
# Plot each region with brand colors
regions = ["NSW1", "QLD1", "VIC1", "SA1", "TAS1"]
colors = plt.cm.Set2(range(len(regions)))
for i, region in enumerate(regions):
# Filter data for region
region_data = [...] # Process response
ax.plot(dates, values, label=region, color=colors[i], linewidth=2)
# Format with branding
styles.format_chart(
ax,
title="Daily Emissions by Region",
xlabel="Date",
ylabel="Emissions (tCO₂)",
add_logo=True
)
ax.legend(loc='upper right')
plt.show()
```
## Style Configuration
The module applies these consistent style settings:
| Element | Configuration |
|---------|--------------|
| **Font Family** | DM Sans, fallback to system sans-serif |
| **Background** | Clean white (#FFFFFF) |
| **Text Color** | Dark gray (#1A1A1A) |
| **Grid** | Light gray (#E0E0E0) at 30% opacity |
| **Grid Style** | Solid lines, 0.5pt width |
| **Spines** | Only bottom and left visible |
| **Title Size** | 14pt bold |
| **Label Size** | 11pt regular |
| **Tick Size** | 10pt regular |
## Best Practices
- Always use the provided fuel technology colors for energy data
- Use `get_fueltech_color()` for consistent coloring
- Group similar technologies with color families
- Position in bottom-right corner for minimal interference
- Use 15-20% size for optimal visibility
- Set 30% transparency to avoid obscuring data
- Stack fossil fuels at bottom, renewables on top
- Use thousands separators for large numbers
- Include clear time labels on x-axis
- Add units to axis labels (MW, MWh, tCO₂)
- Logo is downloaded once and cached
- Apply global styling once at start
- Use vectorized operations for large datasets
## API Reference
### Core Functions
| Function | Description |
|----------|------------|
| `set_openelectricity_style()` | Apply global matplotlib/seaborn styling |
| `create_styled_figure(figsize, dpi)` | Create pre-styled figure and axes |
| `format_chart(ax, **kwargs)` | Apply Open Electricity formatting to axes |
| `add_watermark(ax, **kwargs)` | Add logo watermark to axes |
### Color Functions
| Function | Description |
|----------|------------|
| `get_fueltech_color(fueltech)` | Get hex color for a fuel technology |
| `get_fueltech_palette(fueltechs)` | Get list of colors for multiple technologies |
| `get_color_map()` | Get complete fuel technology color dictionary |
| `get_brand_colors()` | Get Open Electricity brand color palette |
## Troubleshooting
If the logo fails to download, ensure you have internet connectivity. The module will continue without the watermark if the logo is unavailable.
For high-DPI displays, increase the `dpi` parameter in `create_styled_figure()` to 150 or 200 for sharper output.
## Related Resources
- [Python SDK Overview](/sdk/python/overview)
- [Python SDK Reference](/sdk/python/reference)
- [Example Scripts](https://github.com/opennem/openelectricity-python/tree/main/examples)
---
# Getting Started
Fetch and display power generation by fuel type from the NEM
URL: /howto/getting-started
This guide walks through fetching a day of power generation data from Australia's National Electricity Market (NEM), grouped by fuel technology, and displaying it as a table. By the end you'll have a working script in either Python or TypeScript.
## Prerequisites
You'll need an API key from the [Open Electricity Platform](https://platform.openelectricity.org.au). See [SDK configuration](/sdk/configuration) for how to set it up as an environment variable.
## Install the SDK
```bash uv
uv add openelectricity
```
```bash pip
pip install openelectricity
```
```bash npm
npm install openelectricity
```
```bash bun
bun add openelectricity
```
Both SDKs read your `OPENELECTRICITY_API_KEY` environment variable automatically — no need to pass credentials in code.
### Initialise the client
The client handles authentication, request retries and response parsing. See the [SDK overview](/sdk/overview) for more detail.
```python Python
from openelectricity import OEClient
client = OEClient()
```
```typescript TypeScript
import { OpenElectricityClient } from "openelectricity"
const client = new OpenElectricityClient()
```
### Build the date range
We'll query yesterday's full 24-hour window so the data is complete.
```python Python
from datetime import datetime, timedelta
yesterday = (datetime.now() - timedelta(days=1)).replace(
hour=0, minute=0, second=0, microsecond=0
)
today = yesterday + timedelta(days=1)
```
```typescript TypeScript
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
yesterday.setHours(0, 0, 0, 0)
const today = new Date(yesterday)
today.setDate(today.getDate() + 1)
const fmt = (d: Date) =>
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}T00:00:00`
```
Using yesterday ensures you always get a full day of settled data. Today's data may still be arriving.
### Request power data
Call `get_network_data` / `getNetworkData` with these parameters:
- **`network_code="NEM"`** — the [National Electricity Market](/guides/networks) covering eastern Australia
- **`metrics=[DataMetric.POWER]`** — instantaneous generation in MW. See the [power guide](/guides/power)
- **`interval="1h"`** — hourly aggregation
- **`secondary_grouping="fueltech_group"`** — break results down by [fuel technology group](/guides/fueltechs) (solar, wind, coal, etc.)
```python Python
from openelectricity.types import DataMetric
with OEClient() as client:
response = client.get_network_data(
network_code="NEM",
metrics=[DataMetric.POWER],
interval="1h",
date_start=yesterday,
date_end=today,
secondary_grouping="fueltech_group",
)
```
```typescript TypeScript
const { datatable } = await client.getNetworkData("NEM", ["power"], {
interval: "1h",
dateStart: fmt(yesterday),
dateEnd: fmt(today),
secondaryGrouping: ["fueltech_group"],
})
```
### Understand the response
The API returns time series data grouped by your requested dimensions. Each series contains:
| Field | Description |
|-------|-------------|
| **metric** | The metric name (`power`) |
| **unit** | Unit of measurement (`MW`) |
| **results** | Array of named series, one per fuel technology group |
Each result has a `data` array of `{ timestamp, value }` pairs at the requested interval.
### Display as a table
```python Python
# Option 1: Polars DataFrame
df = response.to_polars()
print(df)
# Option 2: Console-friendly records
for record in response.to_records():
print(record)
```
```typescript TypeScript
console.table(datatable.toConsole())
```
## Complete example
Runnable end-to-end scripts you can copy and execute directly.
```python Python
from datetime import datetime, timedelta
from openelectricity import OEClient
from openelectricity.types import DataMetric
yesterday = (datetime.now() - timedelta(days=1)).replace(
hour=0, minute=0, second=0, microsecond=0
)
today = yesterday + timedelta(days=1)
with OEClient() as client:
response = client.get_network_data(
network_code="NEM",
metrics=[DataMetric.POWER],
interval="1h",
date_start=yesterday,
date_end=today,
secondary_grouping="fueltech_group",
)
df = response.to_polars()
print(df)
```
```typescript TypeScript
import { OpenElectricityClient } from "openelectricity"
const yesterday = new Date()
yesterday.setDate(yesterday.getDate() - 1)
yesterday.setHours(0, 0, 0, 0)
const today = new Date(yesterday)
today.setDate(today.getDate() + 1)
const fmt = (d: Date) =>
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}T00:00:00`
const client = new OpenElectricityClient()
const { datatable } = await client.getNetworkData("NEM", ["power"], {
interval: "1h",
dateStart: fmt(yesterday),
dateEnd: fmt(today),
secondaryGrouping: ["fueltech_group"],
})
console.table(datatable.toConsole())
```
## Next steps
Full Python client API and DataFrame integrations
Full TypeScript client API and DataTable analysis tools
Explore all available API endpoints directly
Learn about networks, fuel technologies, emissions and more
---
# Overview
Contribute to the Open Electricity project
URL: /contribute/overview
## Project Overview
The OpenNEM project ([GitHub organization `opennem`](https://github.com/opennem/)) consists of three primary projects in separate source code repositories:
* `opennem` - [GitHub](https://github.com/opennem/opennem) - GitHub project name `opennem`. The primary backend stack that crawls all the data sources, parses them, generates outputs, the database schema and the API interface for integrations. The primary development language is Python.
* `openelectricity` - [GitHub](https://github.com/opennem/openelectricity) - The Open Electricity website.
* `opennem-fe` - [GitHub](https://github.com/opennem/opennem-fe) - This is the web frontend for the Open Electricity [data tracker website](https://explore.openelectricity.org.au)
---
# Development Setup
Getting started with Open Electricity development
URL: /contribute/backend/overview
# Development Guide
This guide will help you set up Open Electricity for local development. The project consists of two main components:
- A FastAPI web API server
- An Arq background worker for processing tasks
## Prerequisites
### Installing uv
We use [uv](https://github.com/astral-sh/uv) as our package manager and virtual environment tool. It's significantly faster than pip and provides better dependency resolution.
Install uv using one of these methods:
**macOS/Linux:**
```bash macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
```
```powershell Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
## Project Setup
1. Clone the repository:
```bash
git clone https://github.com/opennem/opennem.git
cd opennem
```
2. Create the virtual environment and install dependencies:
```bash macOS/Linux
uv install
source .venv/bin/activate # On Windows use: .venv\Scripts\activate
```
```powershell Windows
uv install
.venv\Scripts\activate
```
## Core Dependencies
Open Electricity uses several key libraries:
- **FastAPI** - Modern web framework for building APIs
- **SQLAlchemy 2.0** - SQL toolkit and ORM
- **asyncpg** - Async PostgreSQL driver
- **Alembic** - Database migration tool
- **Arq** - Async job queue and worker
- **Pydantic** - Data validation using Python type annotations
- **uvicorn** - ASGI server for running the API
- **TimescaleDB** - Time-series database extension for PostgreSQL
- **Redis** - In-memory data store used by Arq
## Running the Application
The application consists of two main processes that need to be run:
### 1. API Server
The FastAPI application serves the REST API endpoints. Run it with:
```bash
uv run api
```
This will start the API server at http://localhost:8000
The OpenAPI documentation will be available at:
- http://localhost:8000/docs (Swagger UI)
### 2. Background Worker
The Arq worker processes background tasks like data ingestion and exports. Run it with:
```bash
uv run worker
```
## Development Database
1. Install PostgreSQL 17 locally
2. Create a database:
```bash
createdb opennem
```
3. Run migrations:
```bash
uv run alembic upgrade head
```
## Local Services
We provide a Docker Compose configuration for running required services locally. Start the services with:
```bash
docker-compose up -d
```
This will start:
- PostgreSQL 17 with TimescaleDB extension
- Redis server for the task queue
## Environment Setup
1. Copy the example environment file:
```bash
cp .env.example .env
```
2. Edit the `.env` file with your local settings. The example file contains all required variables with sensible defaults for local development.
### Configuration Settings
All application settings are defined in `opennem/settings_schema.py` and are loaded from environment variables. Key settings include:
- Database connection details
- Redis configuration
- API settings
- Authentication settings
- Logging configuration
You can view all available settings and their documentation in the schema file. Each setting can be overridden using environment variables with the `OPENNEM_` prefix.
Example settings from settings_schema.py:
OPENNEM_DB_HOST: Database hostname (default: "localhost")
OPENNEM_REDIS_HOST: Redis hostname (default: "localhost")
OPENNEM_API_HOST: API server host (default: "0.0.0.0")
OPENNEM_API_PORT: API server port (default: 8000)
## Code Quality Tools
We use several tools to maintain code quality:
- **Ruff** - Fast Python linter and formatter
- **mypy** - Static type checker
- **pytest** - Testing framework
Run the quality checks:
uv run ruff check .
uv run mypy .
uv run pytest
## API Documentation
The API documentation is automatically generated from the OpenAPI schema. You can view it at:
- Local development: http://localhost:8000/docs
- Production: https://api.openelectricity.org.au/docs
## Getting Help
If you need help:
1. Check the [project documentation](https://docs.openelectricity.org.au)
2. Open an issue on GitHub
3. Join our community discussions
## Contributing
1. Create a new branch for your feature
2. Make your changes
3. Run the test suite
4. Submit a pull request
Please follow our coding standards:
- Use type hints for all function parameters and returns
- Write docstrings for all functions and modules
- Follow PEP 8 style guidelines
- Write tests for new functionality
---
# Command Line Interface
The OpenNEM Command Line Interface
URL: /contribute/backend/cli
# OpenNEM Command Line Interface
The OpenNEM CLI provides various commands for managing the OpenNEM platform. The CLI can be run using either:
# Using UV (recommended)
uv run opennem
# Using Python directly
python -m opennem.cli
## Command Groups
### Database Commands (db)
Commands for managing the OpenNEM database.
# Initialize database schema and tables
opennem db init
# Load initial data fixtures
opennem db fixtures
### Import Commands (import)
Commands for importing data into OpenNEM.
# Import facility data
opennem import facilities
# Import fuel technology data
opennem import fueltechs
# Import BOM weather station data
opennem import bom
### Crawler Commands (crawl)
Commands for managing data crawlers.
# List all available crawlers and their status
opennem crawl list
# Run a specific crawler
opennem crawl run \
# Run options:
# --all Run all available data for the crawler (default: False)
# --limit N Limit to N most recent records
# --reverse Reverse the order of the crawlers
# Examples:
opennem crawl run aemo
opennem crawl run wem --all --limit 100
opennem crawl run nem --reverse
# Flush crawler metadata
opennem crawl flush
opennem crawl flush --days 7 --crawler "my-crawler"
### Inspect Command
Inspect OpenNEM JSON data from a URL.
opennem inspect \
### Export Commands (export)
Commands for exporting data from OpenNEM.
# Currently no export commands implemented
### Task Commands (task)
Commands for managing background tasks.
# Currently no task commands implemented
## Error Handling
All commands include proper error handling and will:
1. Display meaningful error messages in red
2. Log errors appropriately
3. Exit with status code 1 on failure
4. Show debug information if DEBUG=true is set
## Environment Variables
The CLI respects the following environment variables:
- `DEBUG`: Enable debug output (default: false)
- Other OpenNEM settings as defined in opennem/settings_schema.py
## Development
When developing new CLI commands:
1. Use Typer for command implementation
2. Include proper type hints
3. Add comprehensive help text
4. Handle errors appropriately
5. Add new commands to the appropriate command group
6. Document new commands in this file
## Exit Codes
- `0`: Success
- `1`: General error
- `130`: User interrupted (Ctrl+C)
## Logging
The CLI uses the standard Python logging framework with:
- Log level controlled by environment variables
- Errors logged to stderr
- Info and debug messages to stdout
- Rich formatting for better readability
## Dependencies
The CLI requires the following key dependencies:
- `typer[all]`: Modern CLI framework
- `rich`: Terminal formatting
- `asyncio`: Async support
- Other OpenNEM dependencies as specified in pyproject.toml
## Best Practices
When using the CLI:
1. Use `uv run opennem` for better performance
2. Set appropriate environment variables before running commands
3. Check command help with `--help` flag
4. Use debug mode when troubleshooting
5. Monitor logs for detailed operation information
## Command Help
Every command supports the `--help` flag for detailed usage information:
# Show main help
opennem --help
# Show help for a command group
opennem db --help
opennem import --help
opennem crawl --help
# Show help for a specific command
opennem crawl run --help
opennem db init --help
## Common Workflows
### Initial Setup
# Initialize the database
opennem db init
# Load required fixtures
opennem db fixtures
# Import initial facility data
opennem import facilities
opennem import fueltechs
### Data Collection
# List available crawlers
opennem crawl list
# Run specific crawlers
opennem crawl run aemo
opennem crawl run wem
# Run with specific options
opennem crawl run nem --all --limit 1000
## Troubleshooting
If you encounter issues:
1. Enable debug mode:
export DEBUG=true
opennem \
2. Check logs:
tail -f logs/opennem.log
3. Verify database connection:
opennem db init
## Support
For issues with the CLI:
1. Check the logs using appropriate log level
2. Verify environment variables
3. Ensure database connectivity
4. Check the [GitHub issues](https://github.com/opennem/opennem/issues)
5. Join the [Open Electricity community](/community)
## Contributing
When adding new CLI commands:
1. Follow the existing command structure
2. Add comprehensive help text
3. Include error handling
4. Update this documentation
5. Add tests for new functionality
---
# CONTRIBUTING
URL: /CONTRIBUTING
# Contributing
Thanks for helping improve the Open Electricity docs. This repo holds the content for
**https://docs.openelectricity.org.au**, built with [Tangly](https://tangly.dev).
## Setup
```bash
bun install
bun run dev # live preview at http://localhost:9411
```
## Editing a page
Pages are MDX files (`.mdx`) at the repo root and under `guides/`, `sdk/`, `howto/`,
`platform/`, `contribute/`, and `api-reference/`. Each page starts with frontmatter:
```mdx
---
title: Page title
description: One-line summary used for SEO and social cards.
---
Body content in Markdown / MDX.
```
You can use the full set of [Tangly components](https://tangly.dev) (callouts, cards,
tabs, steps, code groups, accordions, API blocks, and more) directly in the body.
## Adding a page
1. Create the `.mdx` file in the right folder.
2. Add its path (without extension) to `navigation` in `docs.json` so it appears in the
sidebar. Pages not referenced in `docs.json` are reachable by URL but show as
orphans in `bun run check`.
3. Reference images from `images/` using absolute paths (`/images/...`).
## Before you open a PR
```bash
bun run check # validates docs.json, nav, links, and frontmatter (strict)
bun run build # full production build; catches MDX and asset errors
```
Both must pass. `check` is also run in CI and gates deploys.
## Pull request flow
1. Branch from `main`, make your change, push, open a PR.
2. CI builds a **preview deployment** and comments the URL on the PR. Use it to review
the rendered result.
3. Once approved and merged to `main`, the change **deploys automatically** to
production (docs.openelectricity.org.au).
## Style
- Keep titles and descriptions concise; the description feeds SEO and social cards.
- Prefer short sentences and active voice.
- Link to source, dashboards, and the API reference where it helps the reader.
## Configuration and theming
Site-wide settings (navigation, theme, colors, logo, API reference source) live in
`docs.json`. See the [Tangly schema reference](https://tangly.dev) for every option.
Larger structural or theming changes are worth a quick issue first.