# FEMA Disaster Declarations & ZIP Damage Data Scraper (`groundplanes/fema-disaster-zip-damage`) Actor

Scrape federal disaster declarations and ZIP-code-level FEMA housing inspection damage and Individual and Households Program (IHP) assistance totals from OpenFEMA v2.

- **URL**: https://apify.com/groundplanes/fema-disaster-zip-damage.md
- **Developed by:** [Groundplane Labs](https://apify.com/groundplanes) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.10 / 1,000 records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## FEMA Disaster Declarations & ZIP Damage Data Scraper

Pull federal disaster declarations and ZIP-code-level homeowner inspection damage and approved Individual and Households Program (IHP) financial assistance totals from the official OpenFEMA v2 API. Filter by dataset mode (`ZIP_DAMAGE` or `DECLARATIONS`), US state or territory, declaration date, incident type, and minimum inspected damage.

> **Disclaimer:** This Actor is an independent open-data integration tool and is not affiliated with, endorsed by, or sponsored by the Federal Emergency Management Agency (FEMA) or the U.S. Department of Homeland Security.

***

### What it does

- Queries two official OpenFEMA v2 datasets: `DisasterDeclarationsSummaries` and `HousingAssistanceOwners`.
- **`ZIP_DAMAGE` mode (default):** Joins disaster declaration metadata (`declarationTitle`, `incidentType`, `declarationDate`) with `HousingAssistanceOwners` records on `disasterNumber` to output ZIP-level homeowner registrations, FEMA property inspection counts, FEMA-inspected damage dollar estimates, official damage tier buckets (`$1–$10k`, `$10k–$20k`, `$20k–$30k`, `>$30k`), and approved IHP repair/replacement, rental, and Other Needs Assistance amounts.
- **`DECLARATIONS` mode:** Outputs county- and area-level federal disaster declarations (`DR`, `EM`, `FM`) with program flags (`ihProgramDeclared`, `iaProgramDeclared`, `paProgramDeclared`, `hmProgramDeclared`) and FIPS county codes.
- Suppresses small cells for privacy: `ZIP_DAMAGE` rows with `validRegistrations` below 3 are dropped before pushing, and the run status message reports how many rows were suppressed.
- Validates every output row against the schema before pushing, deduplicates on natural keys (`disasterNumber + zipCode + city + county` in `ZIP_DAMAGE` mode, matching the source grain, since one ZIP and city can span two counties; `femaDeclarationString + fipsCountyCode + designatedArea` in `DECLARATIONS` mode, since tribal designated areas share FIPS county code `000`), and sorts newest first by `declarationDate` descending and `totalDamage` descending.

### Who it's for

- **Property insurance & catastrophe risk teams** analyzing ZIP-level FEMA-inspected storm, flood, hurricane, and wildfire damage severity.
- **Building materials & restoration market analysts** sizing post-disaster repair demand across affected counties and postal codes.
- **Municipal resilience, housing & public policy researchers** tracking federal IHP assistance disbursements and inspection outcomes.
- **Real estate & mortgage risk analysts** monitoring recent federal disaster declarations by county and ZIP code.

***

### Input

| Field | Type | Default / Prefill | Description |
|---|---|---|---|
| `mode` | string (enum) | `"ZIP_DAMAGE"` | `"ZIP_DAMAGE"` for ZIP-level homeowner inspection damage and approved IHP assistance, or `"DECLARATIONS"` for county/area-level federal disaster declarations. |
| `states` | array of strings | `["FL"]` | Two-letter US state or territory postal codes (e.g. `["FL"]`, `["WV", "OH", "WI"]`). Leave empty for all states. |
| `declaredSince` | string | `""` (365 days ago) | Earliest `declarationDate` in `YYYY-MM-DD` format. When left blank in `ZIP_DAMAGE` mode, defaults to 365 days ago; if no ZIP damage rows are collected in that window, the lookback automatically widens (800 days, then 1095 days) and the run status message discloses the expansion. |
| `incidentTypes` | array of strings | `[]` | Optional OpenFEMA `incidentType` filters (e.g. `["Hurricane", "Severe Storm", "Flood"]`). |
| `minTotalDamageUsd` | number | `0` | Minimum ZIP-level `totalDamage` in USD (`ZIP_DAMAGE` mode only). |
| `maxItems` | integer | `200` | Maximum number of records to return, sorted newest first. |

#### Example input JSON

```json
{
  "mode": "ZIP_DAMAGE",
  "states": ["FL"],
  "declaredSince": "",
  "incidentTypes": [],
  "minTotalDamageUsd": 0,
  "maxItems": 200
}
```

#### Example run options (default max charge $50, 256 MB, 300 s timeout)

When starting a run via the Apify API or Console, set a default maximum charge limit of **$50** (`"maxTotalChargeUsd": 50`), **256 MB** of memory (`"memoryMbytes": 256`), and a **300 second** timeout (`"timeoutSecs": 300`):

```json
{
  "maxTotalChargeUsd": 50,
  "memoryMbytes": 256,
  "timeoutSecs": 300
}
```

The Actor checks your remaining spending limit before every dataset push and stops cleanly if the limit is reached.

***

### Output

#### Output fields (`ZIP_DAMAGE` mode)

| Field | Type | Description |
|---|---|---|
| `disasterNumber` | integer | FEMA disaster number (e.g. `4834`). |
| `declarationTitle` | string | null | Official FEMA disaster declaration title. |
| `incidentType` | string | null | FEMA incident type (`Hurricane`, `Severe Storm`, `Flood`, `Fire`, `Tornado`, etc.). |
| `declarationDate` | string | Disaster declaration date in `YYYY-MM-DD` format. |
| `state` | string | Two-letter US state or territory postal code. |
| `county` | string | null | County name. |
| `city` | string | null | City name. |
| `zipCode` | string | null | 5-digit postal ZIP code. |
| `validRegistrations` | integer | null | Count of valid FEMA Individual Assistance homeowner registrations in the ZIP/city. |
| `totalInspected` | integer | null | Total number of homeowner properties inspected by FEMA. |
| `totalDamage` | number | null | Total FEMA-inspected homeowner property damage in USD. |
| `averageFemaInspectedDamage` | number | null | Average FEMA-inspected damage per inspected registration in USD. |
| `noFemaInspectedDamage` | integer | null | Count of inspected homes with no FEMA-inspected damage. |
| `femaInspectedDamageBetween1And10000` | integer | null | Count of inspected homes with damage between $1 and $10,000. |
| `femaInspectedDamageBetween10001And20000` | integer | null | Count of inspected homes with damage between $10,001 and $20,000. |
| `femaInspectedDamageBetween20001And30000` | integer | null | Count of inspected homes with damage between $20,001 and $30,000. |
| `femaInspectedDamageGreaterThan30000` | integer | null | Count of inspected homes with damage greater than $30,000. |
| `approvedForFemaAssistance` | integer | null | Count of homeowner registrations approved for FEMA IHP financial assistance. |
| `totalApprovedIhpAmount` | number | null | Total approved Individual and Households Program (IHP) dollar amount in USD. |
| `repairReplaceAmount` | number | null | Approved housing repair and replacement assistance amount in USD. |
| `rentalAmount` | number | null | Approved temporary rental assistance amount in USD. |
| `otherNeedsAmount` | number | null | Approved Other Needs Assistance (ONA) amount in USD. |

#### Output fields (`DECLARATIONS` mode)

In `DECLARATIONS` mode, each row includes `disasterNumber`, `declarationTitle`, `incidentType`, `femaDeclarationString`, `declarationType`, `declarationDate`, `incidentBeginDate`, `incidentEndDate`, `state`, `designatedArea`, `fipsCountyCode`, `ihProgramDeclared`, `iaProgramDeclared`, `paProgramDeclared`, and `hmProgramDeclared`.

#### Sample output row (synthetic example)

```json
{
  "disasterNumber": 9901,
  "declarationTitle": "SYNTHETIC TROPICAL STORM EXAMPLE",
  "incidentType": "Hurricane",
  "declarationDate": "2026-09-18",
  "state": "FL",
  "county": "Example County (County)",
  "city": "EXAMPLE BAY",
  "zipCode": "33999",
  "validRegistrations": 4120,
  "totalInspected": 2840,
  "totalDamage": 18450230.5,
  "averageFemaInspectedDamage": 6496.56,
  "noFemaInspectedDamage": 633,
  "femaInspectedDamageBetween1And10000": 1520,
  "femaInspectedDamageBetween10001And20000": 410,
  "femaInspectedDamageBetween20001And30000": 185,
  "femaInspectedDamageGreaterThan30000": 92,
  "approvedForFemaAssistance": 2610,
  "totalApprovedIhpAmount": 14920800.0,
  "repairReplaceAmount": 9840500.0,
  "rentalAmount": 2150300.0,
  "otherNeedsAmount": 2930000.0
}
```

***

### How pricing works

Pay per result. You only pay for rows returned.

***

### Data source and terms

- **Official Source:** OpenFEMA API v2 (`https://www.fema.gov/api/open/v2/DisasterDeclarationsSummaries` and `https://www.fema.gov/api/open/v2/HousingAssistanceOwners`).
- **Update Frequency:** Updated daily by FEMA.
- **Attribution (OpenFEMA terms, verbatim):** "This product uses the Federal Emergency Management Agency's OpenFEMA API, but is not endorsed by FEMA. The Federal Government or FEMA cannot vouch for the data or analyses derived from these data after the data have been retrieved from the Agency's website(s)."
- **Dataset and endpoint version:** OpenFEMA API v2. This Actor cites and queries the v2 dataset endpoints `DisasterDeclarationsSummaries` v2 (`https://www.fema.gov/api/open/v2/DisasterDeclarationsSummaries`) and `HousingAssistanceOwners` v2 (`https://www.fema.gov/api/open/v2/HousingAssistanceOwners`). Cite the dataset version (v2) when you cite the data.
- **Retrieval date:** Each run retrieves data live from OpenFEMA at run time, and the Apify run record for that run records the retrieval date and time. Note the run date alongside any figure you publish, because FEMA revises and refreshes these datasets.
- **Terms of Use:** Use of OpenFEMA data is governed by the OpenFEMA terms and conditions (`https://www.fema.gov/about/openfema/terms-conditions`), not by an unrestricted grant. Under those terms, use the data for statistical research or as a reporting record only; do not attempt to reidentify any individual from the data, and do not use the data to make determinations affecting an individual's eligibility for any benefit, service, or other right. All records in `HousingAssistanceOwners` are aggregated by disaster, state, county, city, and ZIP code; no individual or household records are published, and this Actor further suppresses rows with `validRegistrations` below 3 to reduce reidentification risk (see Limits and FAQ).

***

### Troubleshooting (`PARTIAL:` status messages)

- If a run finishes with a status message starting with `PARTIAL:`, the Actor preserved and pushed all validated records collected before a paging cap, scan-time budget (`240s`), upstream HTTP retry exhaustion, or schema drift check was triggered.
- A `PARTIAL:` caused by a source problem (schema drift, source errors) ends with: *"If this keeps happening, please open an issue on this actor's Issues tab and we'll fix it fast."* A `PARTIAL:` caused only by your own limits (max items, charge limit, scan budget) does not carry that line.
- A `PARTIAL:` message naming `inspections not yet published for DR-xxxx (declared YYYY-MM-DD); N registration-only rows` means FEMA has published registrations for that disaster but no completed inspection results yet. The rows pushed are registration-only rows (all `totalInspected` values are 0), not damage findings. Re-run after FEMA publishes inspections for that disaster.
- A run whose filters match nothing finishes successfully with a `Complete: 0 rows matched your filters` status message naming the filters to widen.
- Found a problem or need a field? Open an issue on the Issues tab; we respond quickly.

### Limits and FAQ

- **Why does `ZIP_DAMAGE` mode only include certain disasters?** FEMA's `HousingAssistanceOwners` dataset only contains rows for disasters where the Individuals and Households Program (`ihProgramDeclared = true`) was activated and homeowner inspections have been processed. Fire Management Assistance (`FM`) or Public Assistance-only (`PA`) declarations appear in `DECLARATIONS` mode but do not generate homeowner inspection rows.
- **Why does a fresh disaster show registrations but zero inspected damage?** Inspection lag. For a newly declared disaster, FEMA publishes `HousingAssistanceOwners` registration rows before field inspections are completed and published, so `totalInspected`, `totalDamage`, and the damage tier buckets can all be 0 even though registrations exist. When every row for a disaster has `totalInspected` equal to 0, the run ends `PARTIAL:` with a reason of the form `inspections not yet published for DR-xxxx (declared YYYY-MM-DD); N registration-only rows`, naming each affected disaster, instead of reporting a silent all-zero `Complete`. Wait for FEMA to publish inspections for that disaster (often days to weeks after declaration) and re-run.
- **Why are some ZIP rows missing (small-cell suppression)?** To reduce reidentification risk under the OpenFEMA terms, `ZIP_DAMAGE` rows with `validRegistrations` below 3 are suppressed (dropped) and are not pushed to the dataset. The run status message reports how many rows were suppressed.
- **How are records deduplicated?** In `ZIP_DAMAGE` mode, records are deduplicated by `disasterNumber + zipCode + city + county`, which is the grain the source publishes (a ZIP code and city that cross a county line produce one row per county, and each row is kept). In `DECLARATIONS` mode, records are deduplicated by `femaDeclarationString + fipsCountyCode + designatedArea`, because tribal designated areas all carry FIPS county code `000` and are distinct rows.
- **Canary input:** `{ "mode": "ZIP_DAMAGE", "states": ["WV"], "declaredSince": "2026-01-01", "incidentTypes": [], "minTotalDamageUsd": 0, "maxItems": 5 }` returns 5 rows in a few seconds. A passing canary must also assert at least one returned row with `totalInspected` greater than 0; a canary where every row has `totalInspected` equal to 0 does not pass, because inspections for that disaster may not be published yet.

***

### Changelog

- **v0.1.2 (2026-11-01):** Fixes after Grok Red Team review: a disaster whose rows all have `totalInspected` equal to 0 now ends the run `PARTIAL:` naming the disaster and its declaration date (inspections not yet published) instead of a silent all-zero `Complete`; the verbatim OpenFEMA attribution statement, dataset endpoint version (v2) citation, retrieval-date note, and FEMA use conditions are documented and the earlier terms understatement is removed; small-cell rows with `validRegistrations` below 3 are suppressed and counted in the run status; default run options now set 256 MB memory and a 300 second timeout alongside the $50 max charge; the omitted `noFemaInspectedDamage` bucket is now output; source `id` is used as the paging tiebreak; `Retry-After` is honored in HTTP-date form as well as in seconds; the description now starts with "Scrape" and damage figures are described as FEMA-inspected estimates.

- **v0.1.1 (2026-10-31):** Fixes after review: dedupe keys now match the source grain (county added in `ZIP_DAMAGE` mode, designated area added in `DECLARATIONS` mode, so distinct rows are no longer dropped silently); a core-column check on the first record of every source page fails loudly on schema drift; runs where over 20% of rows fail validation now fail instead of shipping a partial dataset as complete; zero matches finish as `Complete: 0 rows matched your filters` instead of failing; the Issues-tab line is appended only to source-caused `PARTIAL:` messages; the charge-limit stop now reports as `PARTIAL:`; a lookback expansion beyond the default 365-day window is disclosed in the status message; status counts now report rows scanned per source and rows emitted separately.

- **v0.1 (2026-10-31):** Initial release supporting `ZIP_DAMAGE` and `DECLARATIONS` modes via OpenFEMA API v2 with schema validation, automatic lookback window expansion for default state runs, and pay-per-event spending limit protection.

# Actor input Schema

## `declaredSince` (type: `string`):

Earliest disaster declarationDate in YYYY-MM-DD format. Leave blank to default to 365 days ago. In ZIP_DAMAGE mode only, if no ZIP damage rows are collected in that window, the lookback automatically widens (800 days, then 1095 days) and the run status message discloses the expansion.

## `incidentTypes` (type: `array`):

Optional OpenFEMA incidentType filter values (e.g. \["Hurricane", "Severe Storm", "Flood", "Fire", "Tornado"]). Leave empty for all incident types.

## `maxItems` (type: `integer`):

Maximum number of records to return, sorted newest first by declarationDate and then totalDamage descending.

## `minTotalDamageUsd` (type: `number`):

Minimum ZIP-level totalDamage in USD (ZIP_DAMAGE mode only). Set to 0 to return all matching ZIP records.

## `mode` (type: `string`):

Select ZIP_DAMAGE (ZIP-level aggregated homeowner inspection damage and approved IHP assistance joined with disaster declaration metadata) or DECLARATIONS (county/area-level federal disaster declarations).

## `states` (type: `array`):

Two-letter US state or territory postal codes (e.g. \["FL"], \["WV", "OH", "WI"]). Leave empty for all states.

## Actor input object example

```json
{
  "declaredSince": "",
  "incidentTypes": [],
  "maxItems": 200,
  "minTotalDamageUsd": 0,
  "mode": "ZIP_DAMAGE",
  "states": [
    "FL"
  ]
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "declaredSince": "",
    "incidentTypes": [],
    "maxItems": 200,
    "minTotalDamageUsd": 0,
    "mode": "ZIP_DAMAGE",
    "states": [
        "FL"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("groundplanes/fema-disaster-zip-damage").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "declaredSince": "",
    "incidentTypes": [],
    "maxItems": 200,
    "minTotalDamageUsd": 0,
    "mode": "ZIP_DAMAGE",
    "states": ["FL"],
}

# Run the Actor and wait for it to finish
run = client.actor("groundplanes/fema-disaster-zip-damage").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "declaredSince": "",
  "incidentTypes": [],
  "maxItems": 200,
  "minTotalDamageUsd": 0,
  "mode": "ZIP_DAMAGE",
  "states": [
    "FL"
  ]
}' |
apify call groundplanes/fema-disaster-zip-damage --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,groundplanes/fema-disaster-zip-damage"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/M3yooVbl61cFdIdkc/builds/d1ssXOt5xjLQylQig/openapi.json
