eia-data
EIA Open Data Skill
Fetches and analyzes U.S. energy data from the EIA API v2 (api.eia.gov/v2).
API Key Handling
Resolution order (stop at first success):
~/.config/eia/credentials(default) — parseapi_key=<value>from this fileEIA_API_KEYenv var — fallback if credentials file is absent- User-provided in conversation — fallback if neither above is set
- Prompt the user — "Please provide your EIA API key. You can get one free at https://www.eia.gov/opendata/ — or store it in
~/.config/eia/credentialsasapi_key=YOUR_KEY(chmod 600)."
Never hardcode or log the key. Pass it as a query parameter ?api_key=<KEY>.
Reading the credentials file (bash):
KEY=$(grep '^api_key=' ~/.config/eia/credentials 2>/dev/null | cut -d= -f2)
[ -z "$KEY" ] && KEY="${EIA_API_KEY}"
Reading the credentials file (Go):
func resolveAPIKey() (string, error) {
if path, err := os.UserHomeDir(); err == nil {
creds := filepath.Join(path, ".config", "eia", "credentials")
if data, err := os.ReadFile(creds); err == nil {
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "api_key=") {
return strings.TrimPrefix(line, "api_key="), nil
}
}
}
}
if key := os.Getenv("EIA_API_KEY"); key != "" {
return key, nil
}
return "", fmt.Errorf("no EIA API key found; store in ~/.config/eia/credentials")
}
API Structure
Base URL: https://api.eia.gov/v2/
Self-describing hierarchy — query any route without /data to get its children and available facets:
GET /v2/ → top-level categories
GET /v2/electricity/ → electricity sub-routes
GET /v2/electricity/retail-sales/ → metadata + available facets
GET /v2/electricity/retail-sales/data/ → actual data
Key parameters for data requests:
| Parameter | Example | Notes |
|---|---|---|
api_key |
api_key=YOUR_KEY |
Required |
frequency |
frequency=monthly |
annual, quarterly, monthly, weekly, hourly |
data[] |
data[]=price |
which value columns to return |
facets[X][] |
facets[stateid][]=TX |
filter by dimension |
start |
start=2022-01 |
inclusive |
end |
end=2024-12 |
inclusive |
sort[0][column] |
sort[0][column]=period |
sort field |
sort[0][direction] |
sort[0][direction]=desc |
asc or desc |
offset |
offset=0 |
pagination start |
length |
length=5000 |
max 5000 per request |
Key Routes by Domain
See references/routes.md for full route catalog. High-priority routes:
Electricity
| Route | Description |
|---|---|
electricity/retail-sales/data/ |
Retail sales — price, revenue, sales by state + sector |
electricity/rto/region-data/data/ |
Hourly demand by balancing authority |
electricity/rto/fuel-type-data/data/ |
Hourly generation by fuel type |
electricity/electric-power-operational-data/data/ |
Monthly generation by plant type, state |
Common facets:
stateid: 2-letter state code (TX, CA, NY…) or US for nationalsectorid: RES (residential), COM (commercial), IND (industrial), TRA (transport), ALLfueltypeid: NG, COL, NUC, SUN, WND, WAT, OIL, OTH
Petroleum & Natural Gas
| Route | Description |
|---|---|
petroleum/pri/gnd/data/ |
Weekly gasoline & diesel retail prices |
petroleum/sum/sndw/data/ |
Weekly petroleum supply summary |
petroleum/stoc/wstk/data/ |
Weekly crude oil & petroleum product stocks |
petroleum/move/imp/data/ |
Crude oil imports |
natural-gas/pri/sum/data/ |
Natural gas prices (citygate, wellhead, retail) |
natural-gas/stor/sum/data/ |
Weekly natural gas storage |
natural-gas/prod/sum/data/ |
Monthly dry gas production |
Common facets:
duoarea: National (NUS), PADD regions (R10=PADD1, R20=PADD2…), state codesproduct: EPD2D (diesel), EPM0 (regular gasoline), EPM0R (reformulated)process: PRS (retail sales), PRW (wellhead), PRI (imports)
Workflow
Step 1 — Resolve Intent
Map the user's question to a route. If uncertain:
- Query the parent route (no
/data/) to inspect available sub-routes - Check
references/routes.mdfor common mappings
Step 2 — Fetch Metadata First (when needed)
curl "https://api.eia.gov/v2/<route>/?api_key=<KEY>"
Returns: available facets, frequency options, data columns, startPeriod, endPeriod.
Use this to validate facet values before data fetch.
Step 3 — Fetch Data
Build the URL with appropriate filters. Default behavior:
- Use
frequency=monthlyunless user asks for hourly/weekly/annual - Sort by
period descto get most recent first - Request
length=5000(max); paginate if response warns of truncation - Default time window: last 24 months unless user specifies
curl "https://api.eia.gov/v2/electricity/retail-sales/data/?api_key=<KEY>\
&frequency=monthly\
&data[]=price\
&facets[stateid][]=TX\
&facets[sectorid][]=RES\
&sort[0][column]=period\
&sort[0][direction]=desc\
&length=24"
Step 4 — Parse Response
Response structure:
{
"response": {
"total": 123,
"dateFormat": "YYYY-MM",
"frequency": "monthly",
"data": [ { "period": "2024-11", "stateid": "TX", "price": 11.23, "price-units": "cents per kilowatthour" } ]
},
"request": { ... },
"apiVersion": "2.1.9"
}
Always check response.total vs returned row count — paginate if total > length.
Step 5 — Produce Output
Format: Raw Data Table + Narrative
Present a markdown table of the most relevant rows (cap at ~20 rows for readability), then a narrative summary covering:
- Current state — most recent value(s) and date
- Trend — direction and magnitude over the time window (use % change)
- Notable features — peaks, troughs, inflection points
- Geographic or sector context if applicable
- Units and caveats — always state units; note if data is preliminary
Example output structure:
## Texas Residential Electricity Prices (Monthly, 2023–2024)
| Period | Price (¢/kWh) |
|----------|---------------|
| 2024-11 | 11.23 |
| 2024-10 | 11.45 |
| ... | ... |
**Summary:** Texas residential electricity averaged 11.23¢/kWh in November 2024,
down 1.9% from the prior month and 3.2% year-over-year. Prices peaked at 14.1¢/kWh
in August 2023 driven by summer demand, then declined through winter. Data is
preliminary for the most recent 2 months (EIA revision cycle).
Pagination
If response.total > length, paginate:
offset = 0
all_data = []
while offset < total:
# fetch with &offset=<offset>&length=5000
all_data.extend(response["response"]["data"])
offset += 5000
Warn the user if dataset is very large (>20k rows) and ask if they want a filtered subset.
Error Handling
| HTTP Code | Meaning | Action |
|---|---|---|
| 400 | Bad parameter | Log error body; fix facet/frequency and retry |
| 403 | Invalid API key | Prompt user to verify key |
| 404 | Route not found | Query parent route for valid children |
| 200 + warning | Partial result / row cap | Paginate or narrow filters |
Geo Reference
PADD Regions (Petroleum Administration for Defense Districts):
- PADD 1: East Coast (R10)
- PADD 2: Midwest (R20)
- PADD 3: Gulf Coast (R30)
- PADD 4: Rocky Mountain (R40)
- PADD 5: West Coast (R50)
RTO/ISO Balancing Authorities (electricity):
- CISO = CAISO (California)
- ERCO = ERCOT (Texas)
- MISO = Midcontinent ISO
- PJM = PJM Interconnection
- NYIS = NYISO
- ISNE = ISO New England
- SWPP = Southwest Power Pool
- US48 = Contiguous US aggregate
Implementation Notes
- Prefer
bash_toolorpythonin Claude's computer to make actual HTTP requests - Use
jqfor JSON parsing in bash pipelines when available - Golang client — see
references/golang_example.go(EIAClient,QueryParams,FetchAll) - Python client — see
references/python_example.py(EIAClient,QueryParams,fetch_all,paginate) - Response
dateFormatfield tells you the exact format for theperiodfield — don't assume - EIA updates: weekly data Wednesdays, monthly data ~6 weeks after period end
- Preliminary flag: most recent 1–2 months are often marked preliminary and subject to revision