Power Retail Sales API v1
Generated from
core.contract.describe_power_retail_sales_v1()and checked fixture-backed examples. Do not hand-edit the example JSON files.
Capability
- Capability:
power.retail_sales - Primitive:
query_power_retail_sales_v1 - Status:
available - Current source:
energy.eia.eia861 - Source publisher: U.S. Energy Information Administration
- Snapshot behavior:
as_of = latestresolves to an exact snapshot date in every response.
What It Can Answer
- Annual retail electricity sales (billed MWh) by utility, state, and customer sector.
- Annual retail revenue (thousand dollars) and customer counts at the same grain.
- Service-type structure (bundled vs energy-only vs delivery-only) per utility and state.
- State-level Adjustment and Withheld sentinel handling — kept in state totals, excluded from utility-keyed answers.
- Row-level detail records when
include_recordsis true. - Raw workbook cell evidence (down to the stacked sector/measure cell) for any returned row-level citation.
Represented Facts
EIA-861 reports annual retail electricity sold to ultimate customers — billed sales (MWh), revenue, and customer counts — by utility, state, and customer sectorEIA-861 reports the filing part and service type (bundled / energy-only / delivery-only) behind each utility×state row
Data Point Contract
- Data point:
power.retail_sales - Product spec:
blocks/power_retail_sales/card.md - Grain:
utility_state_sector_annual - Source basis:
energy.eia.eia861 - Represented fact: EIA-861 reports annual retail electricity sold to ultimate customers — billed sales (MWh), revenue (thousand dollars), and customer counts — by utility, state, customer sector (residential/commercial/industrial/transportation/total), filing part, and service type, as a final annual census. Sales are billed energy over the year, not load: they never reconcile with EIA-930 hourly demand (atom-catalog Rule 1/2).
Does not answer:
hourly or peak load — sales are billed MWh over a year, not load (EIA-930 is the demand atom)facility-level or data-center-specific loadcounty-level anything — service territory carries county names only (deferred decision)average retail price (cents/kWh) — deferred by explicit ratified choice (prices x national is its own PD)net metering / distributed generation, demand response, reliability, meters — deferred family membersthe ~1,700 small short-form (EIA-861S) utilities — measured at build, 0 of 1,687 short-form respondents appear in this workbook (their sales live in Short_Form, a deferred slice)monthly freshness — the EIA-861M sample is a deferred fast-follow facetgenerator capacity, plant generation, wholesale prices, transmissionreconciliation against EIA-930 demand or EIA-923 generation (atom-catalog Rule 2)
REST Surface
GET /v1/healthGET /v1/capabilitiesGET /v1/power/retail-sales/schemaPOST /v1/power/retail-sales/queryPOST /v1/evidence/source-row
MCP Surface
list_capabilities_v1describe_power_retail_sales_v1query_power_retail_sales_v1get_source_evidence_v1
Local MCP Setup
Some MCP clients launch servers from the user's home directory or ignore a configured cwd. Use uv run --directory so the server always starts from the repository project.
{
"command": "uv",
"args": [
"run",
"--directory",
"/absolute/path/to/OSINT",
"python",
"-m",
"core.mcp_server"
]
}
Leave EXASCALE_PARQUET_BASE / EXASCALE_RAW_BASE unset: the one server hosts every data point's tools, and with no override it resolves each block's promoted snapshots and raw archive from the repository layout. Setting either env var points ALL tools at one directory — a per-block path breaks every other block's tools. They exist only for single-source sandboxes and tests.
Request Schema
Filters:
as_ofdata_yearstatesectorpartservice_typeownershipba_codedata_typeeia_utility_idutility_nameis_adjustmentis_withheldincluded_in_default_us_metrics
Group by:
data_yearstatesectorpartservice_typeownershipba_codedata_typeeia_utility_idutility_name
Date range parameters:
Controls:
include_recordsinclude_evidencelimitorder_bytop_norder
Ranking (how order_by / top_n / order join — order_by ranks groups by a metric, never a group_by dimension; top_n needs both a group_by and an order_by):
{
"no_ranking": "Omit order_by and top_n to return all groups in group-key order.",
"order": {
"default": "desc",
"valid_values": [
"desc",
"asc"
]
},
"order_by": {
"accepts": "one of output.metrics",
"note": "Ranks the groups by a metric (a measure). Not a group_by dimension \u2014 rows already come back grouped by each group_by field.",
"requires": [
"group_by"
],
"valid_values": [
"sales_mwh",
"revenue_thousand_dollars",
"customers_count",
"distinct_utility_count",
"source_record_count"
]
},
"top_n": {
"note": "Keeps the top N groups by order_by; the rest fold into one (other) remainder (additive metrics sum into it, non-additive ones are nulled) so the result still reconciles to summary.totals.",
"requires": [
"group_by",
"order_by"
],
"type": "positive integer"
}
}
Output Schema
Aggregate metrics:
sales_mwhrevenue_thousand_dollarscustomers_countdistinct_utility_countsource_record_count
Metric groups:
{
"customers": [
"customers_count"
],
"entities": [
"distinct_utility_count"
],
"records": [
"source_record_count"
],
"revenue": [
"revenue_thousand_dollars"
],
"sales_mwh": [
"sales_mwh"
]
}
Response summary fields:
group_counttotals
Accepted fact policy:
- Query responses contain accepted, gate-passed facts only.
- Gate, monitor, and audit quality signals are internal controls, not agent-facing answer caveats.
- If a source snapshot is not fit to serve, the source must fail closed before it reaches this API.
Metric metadata:
| Metric | Category | Unit | Aggregation | Additive Across Groups | Authoritative Total | Definition |
|---|---|---|---|---|---|---|
sales_mwh |
sales_mwh | MWh | sum | true | summary.totals.sales_mwh |
Sum of EIA-861 annual billed retail electricity sales (MWh) within the current result scope. |
revenue_thousand_dollars |
revenue | thousand USD | sum | true | summary.totals.revenue_thousand_dollars |
Sum of EIA-861 annual retail revenue (thousand dollars) within the current result scope. |
customers_count |
customers | count | sum | true | summary.totals.customers_count |
Sum of EIA-861 annual ultimate-customer counts within the current result scope. |
distinct_utility_count |
entities | count | count_distinct | false | summary.totals.distinct_utility_count |
Count of distinct EIA utility IDs within the current result scope. |
source_record_count |
records | count | count source records | true | summary.totals.source_record_count |
Count of normalized source rows (measure-cell atoms) contributing to the current result scope. |
Rollup rules:
summary.totals.<metric>is the authoritative total for the full matched query.- Grouped row metrics may be summed only when
additive_across_groupsistrue. - Do not sum grouped values for these non-additive metrics:
distinct_utility_count.
Detail record fields returned when include_records is true:
source_idsheet_namesource_record_keyregion_scopeincluded_in_default_us_metricsis_adjustmentis_withhelddata_yearsource_row_numbereia_utility_idutility_namepartservice_typedata_typestateownershipba_codesectormeasureunitvalueas_ofraw_file_sha256citation
Row-level citation fields:
source_idsource_urlsource_filesheetsource_rowsource_colraw_file_sha256as_of
Aggregate citation fields:
source_idpublishersource_urlsource_fileraw_file_sha256as_ofsource_rows_countsource_rows_sampleverifylineage_filter
Codebooks
| Field | Coverage | Codes | Examples | Note |
|---|---|---|---|---|
sector |
complete_for_served_eia861_sectors | 5 | residential = Residential, commercial = Commercial, industrial = Industrial, transportation = Transportation, total = In-row TOTAL block (duplicates the four sectors; explicit opt-in) |
Default scope is the four customer sectors; the in-row total block duplicates them and is excluded unless sector names it explicitly (an unpinned sum would double-count). Filter values are case-normalized server-side. |
part |
complete_for_eia861_filing_parts | 4 | A = Bundled service (energy + delivery), B = Energy-only service (deregulated energy seller), C = Delivery-only service (wires company), D = Bundled, behind-the-meter third-party owners |
The file's own aggregation law (PD-026): state totals sum Parts A,B,C,D for revenue but only A,B,D for sales and customers — Part C (Delivery) re-counts energy that Part B (Energy) rows already carry. A result mixing service types carries a service_type_mix scope note with that exact remedy. |
data_type |
complete_for_eia861_data_type_codes | 2 | O = Observed, I = Imputed |
O = Observed, I = Imputed. In the 2024 census every imputed row is one of the state-level Adjustment sentinel rows (PD-023/PD-025). |
ownership |
complete_for_eia861_ownership_labels | 10 | Behind the Meter = Behind the Meter, Community Choice Aggregator = Community Choice Aggregator, Cooperative = Cooperative, Federal = Federal, Investor Owned = Investor Owned |
Sentinel rows (Adjustment/Withheld) carry a blank ownership, served as null. |
ba_code |
partial_for_eia861 | 81 | AEC = PowerSouth Energy Cooperative, AECI = Associated Electric Cooperative, Inc., AESO = Alberta Electric System Operator, AVA = Avista Corporation, AVRN = Avangrid Renewables, LLC |
EIA-861 reports this BA code on the utility×state row. exascale.build preserves it as source-reported data; it is not an independently validated physical BA-footprint assignment or corrected grid-operator attribution. A balancing authority is not a state. ERCO (ERCOT) lies wholly within Texas and covers ~90% of its load, but state=TX is the larger jurisdiction — it also includes non-ERCOT El Paso (WECC), the Panhandle (SPP), and East Texas (MISO), a material (~20%+) difference. Use balancing_authority_code for a grid and state for a jurisdiction; they are not interchangeable. When a phrase like 'the Texas grid' is ambiguous between the two, report both readings rather than guessing one (PD-009). |
The complete machine-readable codebooks are included in capability-schema.json.
Checked Examples
| Agent question | Request params | Checked output |
|---|---|---|
| What were 2024 retail sales, revenue, and customers by state and customer sector? | {"data_year": 2024, "group_by": ["state", "sector"]} |
retail-sales-by-state-and-sector.json |
| Which utilities sold retail power in Maine, and how much? | {"group_by": ["eia_utility_id", "utility_name"], "state": "ME"} |
utility-sales-in-maine.json |
| How do Maine's retail sales split by filing part and service type? | {"group_by": ["part", "service_type"], "state": "ME"} |
service-type-structure.json |
| What state-level adjustment corrections does the Maine census carry, by sector? | {"group_by": ["sector"], "is_adjustment": true, "state": "ME"} |
adjustment-corrections.json |
| Return one Maine retail-sales atom with a cell-level citation. | {"include_records": true, "limit": 1, "state": "ME"} |
retail-detail-with-citation.json |
| Verify the raw workbook row behind a returned citation | citations[ref].verify (aggregate) or records[0].citation (detail) |
source-row-evidence.json |
| Dogfood the tool sequence as an agent | list -> describe -> query -> evidence |
agent-dogfood-transcript.json |
The checked schema output is capability-schema.json.
Agent Workflow
- Call
list_capabilities_v1and selectpower.retail_sales. - Call
describe_power_retail_sales_v1to inspect valid filters, groupings, metrics, and citation fields. - Call
query_power_retail_sales_v1with bounded JSON params. - If the answer needs proof, pass a returned row-level
citationobject toget_source_evidence_v1. - Answer with the resolved
as_ofand relevant citations. Present returned metrics as authoritative for their declared source, snapshot, grain, and aggregation.