# MassOutage API guide for AI assistants (county emergency operations)

Last updated (live data poll): 2026-10-07T09:05:42.981Z
Guide generated: 2026-10-10T16:29:20.455Z

## 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.
