FEMA Disaster Assistance Delta
Pricing
Pay per event
FEMA Disaster Assistance Delta
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.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Radu Furtuna
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
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
- Each
watchfetches everyDisasterDeclarationsSummariesrecord matching itsstates(required) andincidentTypes(optional) via the API's own$filter— one request per watch per run, no pagination ($top=10000covers even Texas, the largest state by declaration count, ~5,400 records as of 12.09.2026). An optionalcountyfurther narrows the result on the client side (matched as a case-insensitive substring against FEMA'sdesignatedAreafield, 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"). - 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.
- Every later run compares the fresh set against the stored index:
- a record
idnever 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 stableid); - a record
idseen 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.
- a record
- 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. - 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 reportedFAILED(source_access_limited) for that run, its checkpoint is left untouched, and nothing is billed.
Input
{"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?}.statesis 1-10 two-letter USPS codes (e.g.TX,CA,PR).incidentTypesis optional (omit/empty = every incident type).countyis optional (case-insensitive substring match againstdesignatedArea). AwatchIdis bound to its filters on first use — reusing the samewatchIdwith differentstates/incidentTypes/countylater 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_unknownand 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-charactermonitorId), so a monitor that ran on an older build starts from a fresh baseline once. A baseline is never charged. coverage.claimJournalSizereports the journal's size each run (best-effort;nullif the queue's metadata could not be read, and the value lags a few seconds because Apify'stotalRequestCountis 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)