# FEMA Disaster Assistance Delta (`titan_coder/fema-disaster-assistance-delta`) Actor

Monitors official FEMA disaster declaration records for the states, incident types and counties you name, and reports only genuine changes: a brand-new declaration or county, or an IA/PA/HM/IH assistance-program flag flipping on an existing record. A day with nothing new is free.

- **URL**: https://apify.com/titan\_coder/fema-disaster-assistance-delta.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** News, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## FEMA Disaster Assistance Delta Monitor

Durable, informational monitor of the official FEMA **Disaster Declarations Summaries** feed — the
free, public OData feed the U.S. Federal Emergency Management Agency publishes at
`https://www.fema.gov/api/open/v2/DisasterDeclarationsSummaries`. No API key, no account, no proxy,
no browser.

**This is a mirror-with-diff of a public government dataset, not legal, insurance or eligibility
advice.** It does not predict damage, does not estimate insurance coverage, and does not decide
whether you or your property qualify for any assistance program. It tells you, reliably and cheaply,
when FEMA's own declaration records for the states/incident types/counties you name gain a new entry
or change an assistance-program flag.

### Why

FEMA already publishes every declaration record for free. What it doesn't give you is a durable,
per-organization "what's new for me since I last looked" — you either poll the whole feed yourself and
diff it client-side, or you don't watch at all. This actor keeps that diff for you: a list of watches
(state + optional incident type + optional county), a durable memory of every matching record's state
between runs, and a bill only for records that are genuinely new or have a genuinely changed
assistance-program flag.

### How it works

1. Each `watch` fetches every `DisasterDeclarationsSummaries` record matching its `states` (required)
   and `incidentTypes` (optional) via the API's own `$filter` — one request per watch per run, no
   pagination (`$top=10000` covers even Texas, the largest state by declaration count, ~5,400 records
   as of 12.09.2026). An optional `county` further narrows the result **on the client side** (matched
   as a case-insensitive substring against FEMA's `designatedArea` field, e.g. `"Harris"` matches
   `"Harris (County)"`) — FEMA's own exact-match filter on that field is too brittle for free-text user
   input (`"Harris (County)"` vs `"Harris County"` vs `"Harris"`).
2. The **first** run for a watch establishes a baseline: every matching record's id and content hash
   (as reported by FEMA itself — we do not compute our own) are stored, nothing is billed or delivered.
3. Every later run compares the fresh set against the stored index:
   - a record `id` never seen before is **new** (covers both a brand-new declaration and a
     newly-designated county inside an existing declaration — FEMA gives each state/county combination
     its own stable `id`);
   - a record `id` seen before whose IA/PA/HM/IH assistance-program flags differ from what was stored
     is **flags\_changed**;
   - a record whose content hash changed but none of the four assistance flags did (e.g. FEMA
     back-filled a closeout date) is **not** treated as a billable delta — see "Honest limits" below.
4. A hard per-watch cap (`maxResultsPerWatch`) protects you from a single run billing an unbounded
   backlog — anything over the cap is picked up cleanly on the next run, nothing is lost or
   double-billed.
5. Every request also checks FEMA's own reported total count for that filter (`$inlinecount=allpages`)
   against what was actually parsed. A mismatch means a truncated/corrupted response, not that records
   vanished — that watch is reported `FAILED` (`source_access_limited`) for that run, its checkpoint is
   left untouched, and nothing is billed.

### Input

```json
{
  "monitorId": "my-fema-watch",
  "watches": [
    { "watchId": "texas-hurricanes", "states": ["TX"], "incidentTypes": ["Hurricane"], "county": "Harris" }
  ],
  "maxResultsPerWatch": 200,
  "notifyOn": "new_alerts",
  "webhookUrl": "https://example.com/webhook"
}
```

- `watches` — 1-30 objects, each `{watchId, states, incidentTypes?, county?}`. `states` is 1-10
  two-letter USPS codes (e.g. `TX`, `CA`, `PR`). `incidentTypes` is optional (omit/empty = every
  incident type). `county` is optional (case-insensitive substring match against `designatedArea`).
  A `watchId` is bound to its filters on first use — reusing the same `watchId` with different
  `states`/`incidentTypes`/`county` later fails the run honestly (`watch_config_mismatch`) instead of
  silently hiding records under a stale index.
- `maxResultsPerWatch` — 1-2000, default 200.

### Output

One row per new/changed record: `watchId`, `status` (`new`/`flags_changed`), `recordId`,
`disasterNumber`, `state`, `designatedArea`, `incidentType`, `declarationDate`, `iaProgramDeclared`,
`paProgramDeclared`, `hmProgramDeclared`, `ihProgramDeclared`, `sourceUrl`. A run that finds nothing new
still writes an honest `run_summary` row to the default dataset (never silently empty).

### Billing

Pay-per-event, two named events:

- `disaster-declaration-new` — a state/county combination never seen before under this watch.
- `assistance-program-flag-changed` — an already-known record's IA/PA/HM/IH flag changed.

Both are deduplicated by `recordId:apiHash` (the same record can be billed again later if FEMA
genuinely changes its assistance flags again, but never twice for the same content). The baseline run
establishes history without charging. Failed/blocked runs (source fetch failed, run timed out) are
never charged.

#### Delivery guarantee: at-most-once (not exactly-once)

The right to write a row and to charge for it is granted by a single atomic primitive — one
`addRequest(uniqueKey)` into a dedicated, named claim-journal Request Queue
(`<prefix>-<monitorId>-claims`). Exactly one run ever wins that key. Claim requests are never deleted
and never handled: the queue is a permanent journal of irreversible attempts, not a work list.

- **You will never be charged twice for the same record state.** That is the guarantee.
- **It is not exactly-once.** If a run wins the claim and then dies before the row reaches the dataset
  (or before the charge completes), that change is *lost*: it closes as `dataset_unknown` /
  `charge_unknown` and is never re-delivered. We deliberately prefer losing a delivery over
  double-charging you.
- **Boundary of the guarantee:** it holds for as long as the named claim-journal queue exists. Anyone
  with account access can delete or re-create that queue through the Apify Console/API; a fresh journal
  starts empty, and previously delivered changes could then be delivered and billed again. That is an
  inherent limit of any durable storage, not a defect of the protocol.
- **Migration boundary:** the guarantee applies from the build that introduced the claim gate onward.
  Older builds of this actor must not keep running against the same `monitorId`. That same build also
  had to shorten the durable storage name prefix (the old one, the full actor name, could not fit
  Apify's 63-character storage-name limit together with a 40-character `monitorId`), so a monitor that
  ran on an older build starts from a fresh baseline once. A baseline is never charged.
- `coverage.claimJournalSize` reports the journal's size each run (best-effort; `null` if the queue's
  metadata could not be read, and the value lags a few seconds because Apify's `totalRequestCount` is
  eventually consistent). Use it to watch growth, not to make decisions.

### Honest limits

- We mirror FEMA's feed as published; we do not predict damage, estimate insurance coverage, or make
  any eligibility determination — this is an informational monitor of official declaration records,
  nothing more.
- A record's content hash can change for reasons unrelated to assistance eligibility (e.g. a closeout
  date being back-filled weeks later). Only IA/PA/HM/IH flag changes on an already-known record are
  billed as `assistance-program-flag-changed`; other silent metadata corrections update our internal
  cache but produce no row and no charge (see ROADMAP.md, "Deliberate deviations").
- If the feed is temporarily unavailable, its shape changes, or its own reported record count doesn't
  match what we actually received, the affected watch reports that honestly instead of silently
  returning zero results.

Author: OmniCoder (https://t.me/OmniCoder)

# Actor input Schema

## `monitorId` (type: `string`):

Name of this monitor's durable history (a-z, 0-9, dash; up to 40 chars).

## `watches` (type: `array`):

1-30 objects: {"watchId": "texas-hurricanes", "states": \["TX"], "incidentTypes": \["Hurricane"], "county": "Harris"}. states is required (1-10 two-letter USPS codes, e.g. TX, CA, PR). incidentTypes is optional (omit or leave empty for all incident types, e.g. Fire, Flood, Hurricane, Severe Storm, Tornado, Coastal Storm, Drought, Freezing, Severe Ice Storm, Winter Storm, Biological, Other). county is optional (a plain county/parish/borough/tribal-nation name, matched as a case-insensitive substring against FEMA's designatedArea field — omit for every county in the listed states). New watches can be added later under the same monitorId.

## `maxResultsPerWatch` (type: `integer`):

Caps how many new/changed declaration records are delivered per watch in a single run (the rest are picked up on the next run). Protects against runaway bills on a watch's very first baseline-adjacent run.

## `notifyOn` (type: `string`):

new\_alerts — post the webhook only when new billed changes were delivered; always — post it every run; never — do not call webhookUrl at all.

## `webhookUrl` (type: `string`):

Optional. Receives a digest of delivered (billed) declaration changes as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "example-monitor",
  "watches": [
    {
      "watchId": "texas-hurricanes",
      "states": [
        "TX"
      ],
      "incidentTypes": [
        "Hurricane"
      ],
      "county": ""
    }
  ],
  "maxResultsPerWatch": 200,
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every row this run produced. Key fields: watchId, status (new|flags\_changed), recordId, disasterNumber, state, designatedArea, incidentType, iaProgramDeclared, paProgramDeclared, hmProgramDeclared, ihProgramDeclared. If nothing new was found, a single run\_summary row explains why the dataset is otherwise empty. Informational only — not legal, insurance or eligibility advice.

## `coverage` (type: `string`):

What this run actually covered and what it charged for: per-watch status/reason, records delivered and records billed, requested/attempted/succeeded/failed watch counts. Enough to reconcile every charge against every row.

## `digest` (type: `string`):

A short human-readable summary of what this run found, written every run.

# 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 = {
    "monitorId": "example-monitor",
    "watches": [
        {
            "watchId": "texas-hurricanes",
            "states": [
                "TX"
            ],
            "incidentTypes": [
                "Hurricane"
            ],
            "county": ""
        }
    ],
    "maxResultsPerWatch": 200,
    "notifyOn": "new_alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/fema-disaster-assistance-delta").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 = {
    "monitorId": "example-monitor",
    "watches": [{
            "watchId": "texas-hurricanes",
            "states": ["TX"],
            "incidentTypes": ["Hurricane"],
            "county": "",
        }],
    "maxResultsPerWatch": 200,
    "notifyOn": "new_alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/fema-disaster-assistance-delta").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 '{
  "monitorId": "example-monitor",
  "watches": [
    {
      "watchId": "texas-hurricanes",
      "states": [
        "TX"
      ],
      "incidentTypes": [
        "Hurricane"
      ],
      "county": ""
    }
  ],
  "maxResultsPerWatch": 200,
  "notifyOn": "new_alerts"
}' |
apify call titan_coder/fema-disaster-assistance-delta --silent --output-dataset

```

## MCP server setup

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

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/fhF8fX6p6QWSotJzY/builds/s9TbBlrbjQ40DyTjp/openapi.json
