MassOutage API
A free, open-CORS JSON API for US power outages: live county status, municipalities, incidents and threshold alerts, plus a decade of history.
Paste this into your assistant’s system prompt, project instructions or knowledge base. It is regenerated on every load with the latest live poll time. Raw markdown for tools: /developers/ai-guide.md
ai-guide.md
# MassOutage API guide for AI assistants (county emergency operations)
Last updated (live data poll): 2026-10-07T09:05:42.981Z
Guide generated: 2026-10-10T15:46:30.969Z
## Purpose
You are helping a county Office of Emergency Management (OEM) or Emergency Operations Center (EOC) understand current power outages. MassOutage is an **unofficial aggregate** of public utility and government outage data for US counties. Use it for situational awareness, not as the authority for operational decisions.
## Base URL and conventions
- Base URL: `https://massoutage.com`
- No key required. Open CORS. GET only.
- Counties are addressed by 5-digit FIPS (state + county). Example: Monmouth County, NJ = `34025`.
- JSON responses are `{ "data": ..., "meta"?: ... }`. Errors are `{ "error": "message" }` with a 4xx/5xx status.
- Every response has headers `X-Request-Id` (quote it when reporting a problem), `X-API-Version` and `X-Data-Notice`.
- Full machine-readable spec: `https://massoutage.com/api/v1/openapi.json`
## Endpoints
### GET /api/v1/live/counties/{fips}
Headline status: customers out, incidents, per-utility counts with ETRs, freshness (`as_of`) and momentum.
Poll: every 5 minutes. Example: `https://massoutage.com/api/v1/live/counties/34025`
| Field | Type | Meaning |
| --- | --- | --- |
| `data` | object | |
| `data.fips` | string | 5-digit county FIPS. |
| `data.customers_out` | integer | Customers without power in the county right now (all live sources). |
| `data.customers_tracked` | integer, nullable | Customers served by sources that publish a denominator. |
| `data.pct` | number, nullable | customers_out / customers_tracked × 100, when a denominator exists. |
| `data.incident_count` | integer | Active outage incidents. |
| `data.utilities` | array | Per-utility breakdown of customers out. |
| `data.utilities[].id` | string | Utility identifier as reported by the live feed. |
| `data.utilities[].name` | string | Utility name. |
| `data.utilities[].customers_out` | integer | Customers out for this utility in the county. |
| `data.utilities[].etr` | string, nullable | Utility's estimated time of restoration (ISO 8601), the utility's estimate; it changes. |
| `data.worst_etr` | string, nullable | Latest utility ETR among active outages (ISO 8601). |
| `data.updated_at` | string, nullable | Time of the poll that produced this rollup. Null when nothing is out. |
| `data.as_of` | string, nullable | Oldest contributing source's fetch time. Older than updated_at means data is carried forward (stale). |
| `data.trend` | object, nullable | Outage momentum; null when nothing is out. |
| `data.trend.direction` | string enum | Momentum of the current outage. (rising, steady, improving) |
| `data.trend.duration_min` | integer | Minutes the current contiguous outage run has lasted. |
| `data.trend.peak_out` | integer | Peak customers out during the current run. |
| `data.trend.series` | array | Recent samples of the current run, oldest to newest. |
| `data.trend.span_min` | integer | Minutes covered by `series`. |
| `data.trend.normalcy` | string enum | How this outage compares with the county's history. (routine, elevated, major) |
| `data.trend.vs_record_pct` | number, nullable | Current customers out as % of this county's worst episode on record. |
### GET /api/v1/live/counties/{fips}/areas
Municipalities/townships per utility, including masked outages. Add `?active=true` for only affected towns, `?utility=jcp` to narrow.
Poll: every 5 minutes. Example: `https://massoutage.com/api/v1/live/counties/34025/areas`
| Field | Type | Meaning |
| --- | --- | --- |
| `data` | array | |
| `data[].utility_key` | string | Utility key (use with ?utility=). |
| `data[].utility_name` | string | Utility name. |
| `data[].area_type` | string | Area type as the utility reports it (e.g. Township, Borough, Municipality). |
| `data[].name` | string | Municipality/township name as the utility reports it. |
| `data[].customers_served` | integer, nullable | Customers the utility serves in this area. |
| `data[].customers_out` | integer | Customers out in this area. 0 may still hide masked outages. |
| `data[].masked_outages` | integer | Outages the utility reports as "<N" (count hidden for privacy). Unknown size, not zero. |
| `data[].incident_count` | integer, nullable | Incidents in this area. |
| `data[].pct_out` | number, nullable | customers_out / customers_served × 100. |
| `data[].etr` | string, nullable | Utility's estimated restoration for the area (ISO 8601). |
| `data[].bbox` | array, nullable | [minLon, minLat, maxLon, maxLat]. |
| `data[].as_of` | string | When the utility's feed was fetched. Rows older than 1 hour are excluded. |
| `as_of` | string, nullable | Latest feed time across the county's areas (before filters). |
| `meta` | object | Notice and echo of applied filters on county detail endpoints. |
| `meta.notice` | string | Terms of use for the data; same text as the X-Data-Notice header. |
| `meta.filters` | object | The filters that were applied (null/false when not set). |
### GET /api/v1/live/counties/{fips}/incidents
Individual outages with location, ETR, crew status and cause. `?min_customers=100` for the larger ones.
Poll: every 5 minutes. Example: `https://massoutage.com/api/v1/live/counties/34025/incidents`
| Field | Type | Meaning |
| --- | --- | --- |
| `data` | array | |
| `data[].utility_key` | string | Utility key. |
| `data[].utility_name` | string | Utility name. |
| `data[].lat` | number | Latitude of the outage marker (utility-provided, often approximate). |
| `data[].lon` | number | Longitude. |
| `data[].customers_out` | integer, nullable | Customers out; null when the utility masks the count. |
| `data[].customers_below` | integer, nullable | Upper bound when the utility reports "<N". |
| `data[].etr` | string, nullable | Utility's estimated time of restoration (ISO 8601). |
| `data[].crew_status` | string, nullable | Crew status text as the utility reports it. |
| `data[].cause` | string, nullable | Cause text as the utility reports it. |
| `data[].started_at` | string, nullable | When the outage started. |
| `data[].as_of` | string | When the utility's feed was fetched. |
| `as_of` | string, nullable | Latest feed time across the county's incidents (before filters). |
| `meta` | object | Notice and echo of applied filters on county detail endpoints. |
| `meta.notice` | string | Terms of use for the data; same text as the X-Data-Notice header. |
| `meta.filters` | object | The filters that were applied (null/false when not set). |
### GET /api/v1/live/counties/{fips}/alerts
Threshold evaluation (`?min_customers=`, `?min_pct=`) for county, utilities and towns; `connector_status`/`visible` tells you which utilities can be seen at all.
Poll: every 5 minutes. Example: `https://massoutage.com/api/v1/live/counties/34025/alerts`
| Field | Type | Meaning |
| --- | --- | --- |
| `data` | object | |
| `data.fips` | string | 5-digit county FIPS. |
| `data.thresholds` | object | |
| `data.thresholds.min_customers` | number, nullable | Applied min_customers. |
| `data.thresholds.min_pct` | number, nullable | Applied min_pct. |
| `data.thresholds.major_event_pct` | number | Major-event percentage line (10). |
| `data.county` | object | |
| `data.county.customers_out` | integer | County customers out (county-wide, not narrowed by ?utility). |
| `data.county.pct_out` | number, nullable | Percent of county customers out. |
| `data.county.above_threshold` | boolean | Crosses every threshold that was set; false when none is set. |
| `data.utilities` | array | |
| `data.utilities[].utility_key` | string | Utility key. |
| `data.utilities[].name` | string | Utility name. |
| `data.utilities[].connector_status` | string enum | How MassOutage sees this utility: county = reported by county every poll (0 out is a real all-clear); utility = total only; outage_only = appears only while it has outages; none = no feed. (county, utility, outage_only, none) |
| `data.utilities[].customers_out` | integer, nullable | Customers out in the county; null when not reported. |
| `data.utilities[].customers_served` | integer, nullable | Customers served in the county. |
| `data.utilities[].pct_out` | number, nullable | Percent of the utility's county customers out. |
| `data.utilities[].above_threshold` | boolean | Crosses every threshold that was set. |
| `data.utilities[].major_event` | boolean | At least 10% of the utility's county customers out (N.J.A.C. 14:5-8.9 line). |
| `data.utilities[].visible` | boolean | True only for connector_status county or utility. A non-visible utility can never be reported as clear. |
| `data.areas_above_threshold` | array | Municipalities crossing the thresholds, largest first (area rows also carry the other LiveArea fields). |
| `data.areas_above_threshold[].utility_key` | string | Utility key. |
| `data.areas_above_threshold[].utility_name` | string | Utility name. |
| `data.areas_above_threshold[].name` | string | Municipality. |
| `data.areas_above_threshold[].customers_out` | integer | Customers out. |
| `data.areas_above_threshold[].customers_served` | integer, nullable | Customers served. |
| `data.areas_above_threshold[].pct_out` | number, nullable | Percent out. |
| `data.as_of` | string, nullable | County live data time. |
| `meta` | object | Notice and echo of applied filters on county detail endpoints. |
| `meta.notice` | string | Terms of use for the data; same text as the X-Data-Notice header. |
| `meta.filters` | object | The filters that were applied (null/false when not set). |
### GET /api/v1/live/sources
Status endpoint: feed health in aggregate and how much of the picture is localized. Check before trusting a number.
Poll: every 5-10 minutes. Example: `https://massoutage.com/api/v1/live/sources`
| Field | Type | Meaning |
| --- | --- | --- |
| `data` | object | |
| `data.coverage` | object | |
| `data.coverage.sources` | integer | Configured live sources reporting this poll. |
| `data.coverage.sources_ok` | integer | Sources that fetched successfully this poll. |
| `data.coverage.customers_out` | integer | National total. |
| `data.coverage.county_level_out` | integer | Customers out localized to a county. |
| `data.coverage.not_localized_out` | integer | Customers out known only at utility/state level. |
| `data.coverage.tracked_customers` | integer | Measured floor of customers under live watch. |
| `data.feeds` | object | |
| `data.feeds.total` | integer | Live feeds configured. |
| `data.feeds.live` | integer | Feeds that answered on the last poll and are within their freshness window. |
| `data.feeds.stale` | integer | Feeds serving carried-forward last-good data. |
| `data.feeds.down` | integer | Feeds that failed with no recent data (their areas show a gap). |
| `data.feeds.customers_out_before_dedupe` | integer | Customers out summed across feeds, before overlapping feeds are merged. Higher than the national total. |
| `data.feeds.oldest_last_ok_ts` | string, nullable | Oldest successful fetch across all feeds. |
| `data.connector_coverage` | object | |
| `data.connector_coverage.total_customers` | integer | Customers on the wires of every utility with an EIA-861 service territory, counted once each (bundled + delivery). Retail-only suppliers are excluded; see Denominator. |
| `data.connector_coverage.county_customers` | integer | Customers of utilities reported by county every poll (0 out is a real all-clear). |
| `data.connector_coverage.utility_customers` | integer | Customers of utilities reported as a utility total every poll, not by county. |
| `data.connector_coverage.outage_only_customers` | integer | Customers of utilities that appear in feeds only while they have outages. |
| `data.connector_coverage.blind_customers` | integer | Customers of utilities with no live feed. |
| `data.connector_coverage.eia_year` | integer, nullable | EIA-861 year of the customer counts. |
| `data.denominator` | object | |
| `data.denominator.wires_customers` | integer | Customers on the wires of a utility with an EIA-861 service territory, the coverage denominator. |
| `data.denominator.delivery_customers` | integer | Of those, the ones buying energy from a competitive supplier. Their wires utility restores their outage, so they are counted there, once. |
| `data.denominator.supplier_duplicates` | integer | Customers retail suppliers file for themselves, already counted on their wires utility. Excluded; reported so the exclusion is visible. |
| `data.denominator.eia_year` | integer, nullable | EIA-861 year of the customer counts. |
### GET /api/v1/counties/{fips}
County name, state, serving utilities, a live snapshot and yearly reliability history for context.
Poll: hourly or less (history changes rarely). Example: `https://massoutage.com/api/v1/counties/34025`
| Field | Type | Meaning |
| --- | --- | --- |
| `data` | object | |
| `data.fips` | string | 5-digit county FIPS. |
| `data.name` | string | County name. |
| `data.state` | string | State name. |
| `data.state_abbr` | string | USPS state abbreviation. |
| `data.url` | string | Canonical MassOutage county page. |
| `data.live` | object | |
| `data.live.customers_out` | integer | Customers out right now. |
| `data.live.incident_count` | integer | Active incidents. |
| `data.latest_year` | object, nullable | Most recent EAGLE-I year (latest year of any source when none); null without history. |
| `data.latest_year.year` | integer | Calendar year. |
| `data.latest_year.source` | string | Dataset slug (eaglei = DOE/ORNL EAGLE-I, odin = live). |
| `data.latest_year.customer_minutes` | number | Total customer-minutes of outage. |
| `data.latest_year.peak_customers_out` | integer | Peak simultaneous customers out. |
| `data.latest_year.event_count` | integer | Outage episodes. |
| `data.latest_year.customers_tracked` | integer, nullable | Customers in the county (denominator). |
| `data.latest_year.avg_minutes_per_customer` | number, nullable | SAIDI-like average minutes without power per customer. |
| `data.yearly` | array | Yearly reliability history. |
| `data.yearly[].year` | integer | Calendar year. |
| `data.yearly[].source` | string | Dataset slug (eaglei = DOE/ORNL EAGLE-I, odin = live). |
| `data.yearly[].customer_minutes` | number | Total customer-minutes of outage. |
| `data.yearly[].peak_customers_out` | integer | Peak simultaneous customers out. |
| `data.yearly[].event_count` | integer | Outage episodes. |
| `data.yearly[].customers_tracked` | integer, nullable | Customers in the county (denominator). |
| `data.yearly[].avg_minutes_per_customer` | number, nullable | SAIDI-like average minutes without power per customer. |
| `data.utilities` | array | Utilities serving the county. |
| `data.utilities[].id` | string | Utility key. |
| `data.utilities[].name` | string | Utility name. |
| `meta` | object | Dataset-level provenance. Omitted when there is nothing to report. |
| `meta.sources` | array | Datasets this response draws on, for citation. |
| `meta.last_modified` | string, nullable | When the underlying data last changed (live poll time on live endpoints). |
## Polling schedule
- Live data refreshes about every 10 minutes. Poll live endpoints **no more than every 5 minutes**; faster polling returns the same data.
- Poll `/api/v1/counties/{fips}` hourly at most.
- On a 5xx or network error, keep the last good response, show its `as_of`, and retry with backoff (1, 2, 5 minutes).
## Stale-data handling
- Compare `as_of` (or `updated_at`) with the current time. If it is **older than 30 minutes**, say plainly: "MassOutage data may be stale (last updated <time>)."
- In `/api/v1/live/counties/{fips}`, `as_of` older than `updated_at` means at least one source is being carried forward.
- In `/api/v1/live/sources`, a non-zero `feeds.stale` or `feeds.down` means part of the national picture is incomplete. Say so rather than naming a utility: which feed is affected is not published.
- Area and incident rows older than 1 hour are dropped by the API, so an empty list can mean "no data", not "no outages". Check the utility's `connector_status`.
## Rules for AI
1. **Always cite the source and time.** Say "according to MassOutage (unofficial aggregate), as of <as_of>" and name the utility where the number comes from.
2. **Never say a utility is clear** (no outages) unless its `connector_status` is `county` or `utility` (`visible: true` in the alerts endpoint). For `outage_only` say "no outages reported"; for `none` say "MassOutage has no live feed for this utility."
3. **Masked counts are unknown, not zero.** `masked_outages` and `"<N"` values mean the utility hid a small count. Report them as "a small number (fewer than N)", never as 0.
4. **ETRs are the utility's estimate and change.** Present `etr` / `worst_etr` as "the utility currently estimates", with the time it was read.
5. **This is an unofficial aggregate.** For operational decisions, confirm with the utility's EOC liaison, the state OEM (for New Jersey, NJOEM) or DOE EAGLE-I.
6. **Life safety comes first.** If anyone reports a life-threatening situation, downed wires, or a medical device without power, tell them to call **911**.
7. **Do not invent numbers or restoration times.** If a field is null or missing, say it is not available.
8. **State coverage gaps plainly.** If a utility serving the county has `connector_status` `none` or `outage_only`, or a source is `stale`/`down`, say the total may be incomplete and name the gap.
## Attribution
Data is licensed CC BY 4.0. Credit "MassOutage (massoutage.com)" and the upstream sources listed in `meta.sources`.
Notice: Unofficial aggregate of public utility and government data; confirm with the utility or your OEM before acting. Emergencies: call 911.