v1.2.0 to v1.3.0

127 added, 262 removed. Audit A to A.

---
name: gridstatus-api
description: This skill should be used when the user asks to "get electricity data", "query grid data", "get LMP prices", "fetch load data", "get fuel mix", "query ERCOT data", "query CAISO data", "query PJM data", "get electricity prices", "analyze grid operations", "get ISO data", or mentions electricity market data (load, generation, pricing, LMP, fuel mix, ancillary services, etc.).
- version: 1.2.0
+ version: 1.3.0
---
# GridStatus API Skill
Query electricity grid data from US Independent System Operators (ISOs) using the GridStatus.io API. Access real-time and historical data for load, pricing (LMP), generation, fuel mix, and more.
## Quick Start
- The GridStatus API is at `api.gridstatus.io/v1`. Queries require:
- 1. **API Key**: Set `GRIDSTATUS_API_KEY` environment variable
- 2. **Dataset ID**: e.g., `ercot_load`, `caiso_lmp_real_time_5_min`
- 3. **Parameters**: Time range, filters, limit
-
- **API Key Setup**: Check for `GRIDSTATUS_API_KEY` in the user's `.env` file. If not present, instruct the user to:
- 1. Create account at https://www.gridstatus.io
- 2. Get API key from Settings page
- 3. Add to `.env`: `GRIDSTATUS_API_KEY=your_key_here`
-
- **Free tier limit**: 500,000 rows per month (250 requests max). Always use `limit` parameter to avoid exceeding quota.
-
- ## Quick Answers to Common Questions
-
- ### "How much electricity was consumed in [time period]?"
- 1. Query load data for the period (e.g., `pjm_load`, `ercot_load`)
- 2. Calculate energy using the formulas in the Energy Calculations section below
- 3. For 5-minute data: sum all intervals × (5/60) to get MWh
- 4. For hourly data: sum all load values directly = MWh
+ **IMPORTANT: Always use the Python SDK.** The direct curl API has issues with date filtering on the free tier and returns stale sample data. The Python SDK correctly handles all parameters.
- ### "What's the current load in [ISO]?"
- Query with `order=desc&limit=1` to get the most recent data point:
- ```bash
- curl "https://api.gridstatus.io/v1/datasets/pjm_load/query?order=desc&limit=1&timezone=market&api_key=$GRIDSTATUS_API_KEY"
- ```
+ ### Setup
- ### "What datasets are available for [ISO]?"
- Use the `/v1/datasets` metadata API to discover datasets:
+ 1. **Install SDK** (use uv for isolation):
```bash
- curl "https://api.gridstatus.io/v1/datasets?api_key=$GRIDSTATUS_API_KEY" | grep -i "ercot"
+ cd /tmp && uv venv -q && source .venv/bin/activate && uv pip install gridstatusio pandas -q
```
- ## Energy Calculations
-
- Load (MW) measures instantaneous power. Energy (MWh) = power × time.
+ 2. **API Key**: Check for `GRIDSTATUS_API_KEY` in the user's `.env` file. If not present, instruct the user to:
+ - Create account at https://www.gridstatus.io
+ - Get API key from Settings page
+ - Add to `.env`: `GRIDSTATUS_API_KEY=your_key_here`
- | Data Frequency | Energy Formula | Example |
- |----------------|----------------|---------|
- | 5-minute | `energy_MWh = load_MW × (5/60)` | 128,000 MW × 0.0833 = 10,667 MWh |
- | 15-minute | `energy_MWh = load_MW × (15/60)` | 128,000 MW × 0.25 = 32,000 MWh |
- | Hourly | `energy_MWh = load_MW × 1` | 128,000 MW × 1 = 128,000 MWh |
+ 3. **Query data**:
+ ```python
+ import os
+ os.environ['GRIDSTATUS_API_KEY'] = 'key_from_dotenv'
- **Total energy for a period:** Sum energy across all intervals
+ from gridstatusio import GridStatusClient
+ client = GridStatusClient()
- **Example: Total energy in last hour (5-min data, 12 intervals):**
- ```python
- # Each 5-min interval contributes: load_MW * (5/60) MWh
- total_mwh = sum(row['load'] * (5/60) for row in data)
- total_gwh = total_mwh / 1000
+ df = client.get_dataset(
+ dataset="isone_fuel_mix",
+ start="2026-01-25",
+ end="2026-01-26",
+ limit=100
+ )
+ print(df.tail())
```
+ **Free tier limit**: 500,000 rows per month. Always use `limit` parameter.
+
## Supported ISOs
| ISO | Region | Key Datasets |
|-----|--------|--------------|
| ERCOT | Texas | `ercot_load`, `ercot_spp_*`, `ercot_fuel_mix` |
| CAISO | California | `caiso_load`, `caiso_lmp_*`, `caiso_fuel_mix` |
- | PJM | Mid-Atlantic/Midwest | `pjm_standardized_hourly`, `pjm_standardized_5_min`, `pjm_load`, `pjm_lmp_*` |
+ | PJM | Mid-Atlantic/Midwest | `pjm_load`, `pjm_lmp_*`, `pjm_standardized_*` |
| MISO | Midwest | `miso_load`, `miso_lmp_*` |
| NYISO | New York | `nyiso_load`, `nyiso_lmp_*` |
- | ISO-NE | New England | `isone_load`, `isone_lmp_*` |
+ | ISO-NE | New England | `isone_load`, `isone_lmp_*`, `isone_fuel_mix` |
| SPP | Southwest/Central | `spp_load`, `spp_lmp_*` |
See `references/datasets-by-iso.md` for complete dataset catalog.
- ## Workflow
-
- ### Step 1: Clarify the Data Need
-
- Ask the user:
- - **Data type**: Load, prices (LMP), fuel mix, forecasts?
- - **ISO/Region**: Which electricity market?
- - **Time range**: Historical or recent? What date range?
- - **Granularity**: 5-min, 15-min, hourly, daily?
- - **Filters**: Specific locations, hubs, or zones?
+ ## Standard Query Template
- ### Step 2: Find the Dataset
+ Use this template for all queries. Run in a Python heredoc:
- **API-based discovery (recommended - always works):**
```bash
- # List all datasets (455+ available)
- curl "https://api.gridstatus.io/v1/datasets?api_key=$GRIDSTATUS_API_KEY"
-
- # Get specific dataset metadata including columns and data range
- curl "https://api.gridstatus.io/v1/datasets/pjm_load?api_key=$GRIDSTATUS_API_KEY"
- ```
-
- **Python SDK (may have numpy dependency issues on some systems):**
- ```python
- from gridstatusio import GridStatusClient
- client = GridStatusClient()
- client.list_datasets(filter_term="ercot") # Search by keyword
- ```
-
- **Dataset naming convention**: `{iso}_{data_type}_{frequency}`
- - `ercot_load` - ERCOT system load
- - `caiso_lmp_real_time_5_min` - CAISO 5-minute real-time LMPs
- - `pjm_lmp_day_ahead_hourly` - PJM day-ahead hourly LMPs
-
- **Search by keyword patterns:**
- - Load data: `*_load`, `*_standardized_*`
- - Prices: `*_lmp_*`, `*_spp_*`
- - Generation: `*_fuel_mix*`
-
- **Standardized datasets** (highly recommended for multi-metric analysis):
- - `{iso}_standardized_hourly` - Combines load, fuel mix, and forecasts in one table
- - `{iso}_standardized_5_min` - Same data at 5-minute resolution
- - Zone-specific data in columns like `load.comed`, `load.zone_name`, `fuel_mix.solar`
- - Example: `pjm_standardized_hourly` has 48 columns including all PJM zones
-
- See `references/datasets-by-iso.md` for curated list of common datasets.
-
- ### Step 3: Build the Query
+ cd /tmp && source .venv/bin/activate && python3 << 'EOF'
+ import os
+ os.environ['GRIDSTATUS_API_KEY'] = 'YOUR_KEY_HERE'
- **Python SDK (recommended for most use cases):**
- ```python
from gridstatusio import GridStatusClient
-
client = GridStatusClient()
- df = client.get_dataset(
- dataset="ercot_load",
- start="2024-01-01",
- end="2024-01-02",
- timezone="US/Central", # Optional: get local timestamps
- limit=1000
- )
- ```
- **curl (direct API - use when SDK has dependency issues):**
- ```bash
- curl -H "x-api-key: $GRIDSTATUS_API_KEY" \
- "https://api.gridstatus.io/v1/datasets/ercot_load/query?start_time=2024-01-01&end_time=2024-01-02&timezone=market&limit=1000"
- ```
-
- **CRITICAL: Different parameter names for SDK vs API**
- - **Python SDK**: Use `start` and `end` parameters
- - **Direct API (curl)**: Use `start_time` and `end_time` parameters
- - **Timezone**: Use `timezone="market"` for ISO's local time, or specific timezone like `"US/Central"`
-
- ### Step 4: Apply Filters and Transformations
-
- **Filtering by column:**
- ```python
df = client.get_dataset(
- dataset="ercot_spp_real_time_15_min",
- start="2024-01-01",
- end="2024-01-02",
- filter_column="location",
- filter_value="HB_HOUSTON",
- filter_operator="="
+ dataset="DATASET_NAME",
+ start="YYYY-MM-DD",
+ end="YYYY-MM-DD",
+ limit=100
)
+ print(df.to_string())
+ EOF
```
- **Filter operators**: `=`, `!=`, `>`, `<`, `>=`, `<=`, `in`
+ ## Common Query Patterns
- **Multiple values:**
+ ### Current Fuel Mix (What's generating now?)
```python
df = client.get_dataset(
- dataset="pjm_lmp_real_time_5_min",
- filter_column="location_type",
- filter_value=["HUB", "ZONE"],
- filter_operator="in"
+ dataset="isone_fuel_mix", # or ercot_fuel_mix, caiso_fuel_mix
+ start="2026-01-25",
+ end="2026-01-26",
+ limit=500
)
+ latest = df.iloc[-1]
+ total = latest[['coal','hydro','natural_gas','nuclear','oil','solar','wind','wood','refuse','other']].sum()
+ print(f"Oil: {latest['oil']:.0f} MW ({latest['oil']/total*100:.1f}%)")
```
- **Resampling (aggregation):**
+ ### System Load
```python
df = client.get_dataset(
- dataset="caiso_load",
- start="2024-01-01",
- end="2024-01-07",
- resample="1 hour",
- resample_function="mean"
+ dataset="pjm_load",
+ start="2026-01-25",
+ end="2026-01-26",
+ timezone="US/Eastern",
+ limit=100
)
+ print(f"Current load: {df.iloc[-1]['load']:,.0f} MW")
```
- See `references/api-reference.md` for complete parameter documentation.
-
- ### Step 5: Handle Response
-
- Results return as pandas DataFrame with columns specific to each dataset. Common columns:
- - `interval_start_utc` / `interval_end_utc` - Time range (UTC)
- - `interval_start_local` / `interval_end_local` - Local time (if timezone specified)
- - `load` - System load in MW
- - `lmp` - Locational marginal price ($/MWh)
- - `location` - Settlement point, hub, or zone name
-
- **For standardized datasets:**
- - Zone/area data is in nested columns: `load.comed`, `load.aep`, `load.zone_name`
- - Fuel mix in columns: `fuel_mix.solar`, `fuel_mix.wind`, `fuel_mix.coal`
- - These are NOT filterable fields - access them as DataFrame columns after fetching
- - Use column notation: `df['load.comed']` to access zone-specific data
-
- **Calculate energy consumption:**
- - For hourly data: `energy_mwh = load_mw * 1.0` (MW × 1 hour)
- - For 5-minute data: `energy_mwh = load_mw * (5/60)` (MW × 5/60 hours)
- - Convert to GWh: `energy_gwh = energy_mwh / 1000`
-
- ## Common Query Patterns
-
- ### System Load
+ ### Electricity Prices (LMP)
```python
- # Get recent load for an ISO
df = client.get_dataset(
- dataset="ercot_load",
- start="2024-01-01",
- end="2024-01-02",
- timezone="US/Central"
+ dataset="ercot_spp_real_time_15_min",
+ start="2026-01-25",
+ end="2026-01-26",
+ filter_column="location",
+ filter_value="HB_HOUSTON",
+ limit=100
)
+ print(f"Houston price: ${df.iloc[-1]['spp']:.2f}/MWh")
```
- ### Zone-Specific Load (using standardized datasets)
+ ### Zone-Specific Load (Standardized Datasets)
```python
- # Get load for a specific zone (e.g., ComEd in PJM)
df = client.get_dataset(
dataset="pjm_standardized_hourly",
- start="2026-01-12",
- end="2026-01-13",
+ start="2026-01-25",
+ end="2026-01-26",
timezone="market"
)
- # Zone data is in columns: df['load.comed'], df['load.aep'], etc.
- # Calculate total energy: (df['load.comed'] * 1.0).sum() / 1000 = GWh
+ # Zone data in columns: df['load.comed'], df['load.aep'], etc.
+ print(f"ComEd load: {df.iloc[-1]['load.comed']:,.0f} MW")
```
- ```bash
- # Same query using curl
- curl -H "x-api-key: $GRIDSTATUS_API_KEY" \
- "https://api.gridstatus.io/v1/datasets/pjm_standardized_hourly/query?start_time=2026-01-12&end_time=2026-01-13&timezone=market"
- ```
+ ## Dataset Discovery
- ### Electricity Prices (LMP)
+ ### Check Dataset Metadata (date range, columns)
```python
- # Real-time prices at a specific hub
- df = client.get_dataset(
- dataset="ercot_spp_real_time_15_min",
- start="2024-01-01",
- end="2024-01-02",
- filter_column="location",
- filter_value="HB_NORTH"
- )
- ```
+ # Get dataset info including available date range
+ import requests
+ import os
- ### Fuel Mix / Generation
- ```python
- # Generation by fuel type
- df = client.get_dataset(
- dataset="caiso_fuel_mix",
- start="2024-01-01",
- end="2024-01-02"
- )
+ api_key = os.environ.get('GRIDSTATUS_API_KEY')
+ r = requests.get(f"https://api.gridstatus.io/v1/datasets/pjm_load",
+ headers={"x-api-key": api_key})
+ meta = r.json()
+ print(f"Available: {meta['earliest_available_time_utc']} to {meta['latest_available_time_utc']}")
+ print(f"Columns: {[c['name'] for c in meta['all_columns']]}")
```
- ### Day-Ahead vs Real-Time Prices
+ ### List Datasets
```python
- # Day-ahead hourly prices
- da_df = client.get_dataset(
- dataset="pjm_lmp_day_ahead_hourly",
- start="2024-01-01",
- end="2024-01-02"
- )
-
- # Real-time 5-min prices
- rt_df = client.get_dataset(
- dataset="pjm_lmp_real_time_5_min",
- start="2024-01-01",
- end="2024-01-02"
- )
+ client.list_datasets(filter_term="ercot") # Search by keyword
+ datasets = client.list_datasets(filter_term="fuel_mix", return_list=True)
```
- See `references/common-queries.md` for more query patterns.
+ **Dataset naming convention**: `{iso}_{data_type}_{frequency}`
+ - `ercot_load` - ERCOT system load
+ - `caiso_lmp_real_time_5_min` - CAISO 5-minute real-time LMPs
+ - `pjm_lmp_day_ahead_hourly` - PJM day-ahead hourly LMPs
- ## Output Format
+ ## Energy Calculations
- Provide the user with:
- 1. **Dataset**: Name + description
- 2. **ISO**: Which market
- 3. **Time range**: Start/end dates
- 4. **Filters**: Any applied filters
- 5. **Query**: Python code or curl command
- 6. **Row limit note**: Reminder about free tier limits
+ Load (MW) measures instantaneous power. Energy (MWh) = power x time.
- ### Example Response Format
+ | Data Frequency | Energy Formula | Example |
+ |----------------|----------------|---------|
+ | 5-minute | `energy_MWh = load_MW * (5/60)` | 100,000 MW x 0.0833 = 8,333 MWh |
+ | 15-minute | `energy_MWh = load_MW * (15/60)` | 100,000 MW x 0.25 = 25,000 MWh |
+ | Hourly | `energy_MWh = load_MW * 1` | 100,000 MW x 1 = 100,000 MWh |
+
+ ```python
+ # Total energy for a period (5-min data)
+ total_mwh = (df['load'] * (5/60)).sum()
+ total_gwh = total_mwh / 1000
```
- Dataset: ercot_spp_real_time_15_min (ERCOT 15-minute real-time settlement point prices)
- ISO: ERCOT (Texas)
- Time Range: 2024-01-01 to 2024-01-02
- Query:
- from gridstatusio import GridStatusClient
- client = GridStatusClient()
- df = client.get_dataset(
- dataset="ercot_spp_real_time_15_min",
- start="2024-01-01",
- end="2024-01-02",
- filter_column="location",
- filter_value="HB_HOUSTON",
- limit=10000
- )
+ ## Query Parameters Reference
- Note: Free tier allows 500K rows/month. This query returns ~96 rows/day per location.
- ```
+ | Parameter | Type | Description |
+ |-----------|------|-------------|
+ | `dataset` | str | **Required**. Dataset ID (e.g., `ercot_load`) |
+ | `start` | str | Start date (YYYY-MM-DD or ISO format) |
+ | `end` | str | End date |
+ | `limit` | int | Max rows to return |
+ | `timezone` | str | `"market"` for ISO local time, or `"US/Central"` etc. |
+ | `filter_column` | str | Column to filter on |
+ | `filter_value` | str/list | Value(s) to match |
+ | `filter_operator` | str | `=`, `!=`, `>`, `<`, `>=`, `<=`, `in` |
+ | `resample` | str | Aggregate frequency (`"1 hour"`, `"1 day"`) |
+ | `resample_function` | str | `mean`, `sum`, `min`, `max` |
+ See `references/api-reference.md` for complete documentation.
+
## Troubleshooting
| Issue | Cause | Fix |
|-------|-------|-----|
- | 401 Unauthorized | Invalid/missing API key | Check `GRIDSTATUS_API_KEY` env var |
- | "Missing API Key" | Wrong header format in curl | Use `api_key` query param or lowercase `x-api-key` header |
- | Empty DataFrame | Wrong date parameters | Use `start_time`/`end_time` for curl, `start`/`end` for SDK |
- | Old data returned | Date not in dataset | Check dataset date range; recent data may not be available yet |
- | "Unknown column" error | Wrong filter on standardized dataset | Zone data is in columns, not filterable - use `df['load.comed']` after fetching |
- | 429 Rate Limited | Too many requests | Reduce query frequency, SDK auto-retries |
- | Row limit exceeded | Free tier quota | Add `limit` parameter, reduce date range |
- | ImportError (numpy) | Python SDK dependency conflict | **Use curl instead** (see below) |
+ | Old/stale data returned | Using curl directly | **Use Python SDK instead** - curl has date filtering issues on free tier |
+ | 401 Unauthorized | Invalid/missing API key | Check `GRIDSTATUS_API_KEY` is set correctly |
+ | Empty DataFrame | Date range outside coverage | Check dataset metadata for available date range |
+ | ModuleNotFoundError | SDK not installed | Run: `cd /tmp && uv venv -q && source .venv/bin/activate && uv pip install gridstatusio pandas -q` |
+ | "Unknown column" error | Wrong filter column | Zone data is in columns after fetch, not filterable. Use `df['load.comed']` |
+ | 429 Rate Limited | Too many requests | SDK auto-retries with backoff |
- ### Python SDK Dependency Issues
+ ### Why Not curl?
- The Python SDK (`gridstatusio`) may fail with numpy or pandas import errors on some systems. **When this happens, use curl directly** - it's reliable and works with any system:
+ The direct HTTP API has a critical issue on the free tier: **date parameters are ignored** and it returns sample data from years ago. The Python SDK correctly handles date filtering and returns current data.
+ If you must use curl (e.g., for dataset metadata), it works for non-query endpoints:
```bash
- # Works everywhere - no Python dependencies needed
- source .env # Load GRIDSTATUS_API_KEY
- curl "https://api.gridstatus.io/v1/datasets/pjm_load/query?\
- start_time=2026-01-25T17:00:00&end_time=2026-01-25T18:00:00&\
- timezone=market&api_key=$GRIDSTATUS_API_KEY"
- ```
-
- **Tip:** For complex data processing, fetch with curl and pipe to `jq` for JSON processing.
-
- **Data Availability Notes:**
- - Datasets have different coverage periods; check date range before querying
- - Recent data (last 1-2 days) may not be available yet in all datasets
- - Use `timezone="market"` to get local time and avoid UTC conversion issues
- - Most load datasets have 5-minute intervals (~128,000 MW for PJM total load)
-
- ## Rate Limiting
+ # This works - metadata endpoint
+ curl -s -H "x-api-key: $GRIDSTATUS_API_KEY" \
+ "https://api.gridstatus.io/v1/datasets/pjm_load"
- The SDK implements automatic exponential backoff for rate limits:
- - Default: 5 retries with 2s base delay
- - Configure: `GridStatusClient(max_retries=3, base_delay=1.0)`
+ # This returns stale data - query endpoint has issues
+ curl -s -H "x-api-key: $GRIDSTATUS_API_KEY" \
+ "https://api.gridstatus.io/v1/datasets/pjm_load/query?start=2026-01-25" # BROKEN
+ ```
## Additional Resources
### Reference Files
- **`references/datasets-by-iso.md`** - Complete dataset catalog by ISO
- **`references/api-reference.md`** - Full API parameter documentation
- **`references/common-queries.md`** - Ready-to-use query patterns
### Example Files
- **`examples/python-query.py`** - Python SDK examples
- - **`examples/curl-examples.sh`** - curl command templates