# FEMA NRI County Risk Profile — Asset Hazard Join (`malonestar/fema-nri-county-risk-profile`) Actor

Join any asset (address, lat/lon, or county FIPS) to FEMA's National Risk Index hazard profile at county or census-tract resolution: composite risk score, expected annual loss, social vulnerability, resilience, and ranked top-3 hazards across all 18 FEMA perils.

- **URL**: https://apify.com/malonestar/fema-nri-county-risk-profile.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Agents, Developer tools, Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.30 / 1,000 results

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/platform/actors/running/actors-in-store#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 NRI County Risk Profile — Asset Hazard Join

Join any list of assets — street addresses (via state+county), GPS coordinates, or
county FIPS codes — to FEMA's **National Risk Index (NRI)** hazard profile:
composite risk score, expected annual loss ($) **split by buildings, population
and agriculture**, annualized loss ratios, social vulnerability, community
resilience, and **two different top-3 hazard rankings** across all **18 FEMA
perils** (wildfire, hurricane, earthquake, tornado, inland flooding, drought,
and more). Resolve at **county** resolution (default, ~3,232 areas) or
**census-tract** resolution (~85,154 areas, finer-grained).

Source: FEMA National Risk Index Counties and Census Tracts FeatureServers
(ArcGIS, keyless). County/tract geocoding for lat/lon assets uses the keyless
FCC Census Block API. No API keys required.

### What changed in 1.1 (2026-08)

- **Every record now says who answered.** 1.0 rendered a *failed* NRI query as
  `match_status: "not_found"` — literally "no area matched the input" — and
  pushed it, and billed for it. A buyer could not tell an outage from a genuine
  miss. There are now `nri_lookup_status`, `geocode_status`,
  `tract_lookup_status` and `verified_no_match` on every row, a new
  `match_status` value `source_unavailable`, and the run **fails and bills
  nothing** when not one record resolved.
- **A live drift probe runs before any billable row.** The layer must resolve
  (a retired ArcGIS service answers HTTP 200 with an embedded error, not a 404),
  every requested field must still exist (an unknown `outFields` name returns
  **zero features**, not an error), the row count must sit in a band, a positive
  canary must return exactly Denver County, a **negative control must return
  exactly zero**, and every rating must be inside FEMA's published vocabulary.
- **The NRI release is now reported.** `nri_version` comes straight off the
  record (`NRI_VER`, currently **December 2025**). FEMA republishes the NRI under
  the same service alias, so without this a superseded vintage answers silently.
- **Stable tract paging.** 1.0 ordered *every* page by `STCOFIPS`, which is not
  unique on the tract layer — Denver County alone holds 178 tracts — and
  `resultOffset` paging over a non-unique sort is not stable in ArcGIS. Tract
  queries now order by `TRACTFIPS`.
- **Inventory truncation is visible** via `result_truncated` and
  `source_records_available` (the service's own count).
- **40 new fields** from columns the layer has always published. The county layer
  carries **467** fields; 1.0 read 48 of them.

### Who this is for

- **Insurers & reinsurers** scoring a book of business by county- or tract-level hazard exposure.
- **Real estate & REITs** screening acquisition targets for climate/hazard risk down to the neighborhood.
- **Corporate risk / business continuity teams** profiling facility and supply-chain locations.
- **Lenders & underwriters** adding a hazard-risk data point to due diligence.
- **AI agents** (via MCP) that need a quick "how risky is this location" tool call.

### Two top-hazard rankings, and why they differ

- `top_hazards` ranks by FEMA's **national percentile** hazard-type risk score.
- `top_hazards_by_expected_annual_loss` ranks by **modelled dollars**.

For Denver County these give different answers. By percentile: Hail, Lightning,
Tornado. By dollars: **Inland Flooding $93.6M**, Hail $72.4M, Tornado $28.0M.
An underwriter usually wants the dollars; a comparative-risk map usually wants
the percentile. Both ship on every row.

Hazards FEMA marks `Not Applicable`, `No Rating` or `Insufficient Data` are
excluded from both rankings.

### County vs. census-tract resolution

Set the `resolution` input to `"county"` (default) or `"tract"`:

- **County** — FEMA's county-level NRI roll-up, ~3,232 areas nationwide. Works with
  any of the three asset input types (fips, state+county, or lat/lon).
- **Tract** — FEMA's finer-grained census-tract NRI, ~85,154 areas nationwide.
  Only applies precisely to **lat/lon assets**: the point is geocoded to a
  15-digit Census block FIPS via the FCC API, and the tract FIPS (its first 11
  digits) is joined against the NRI tract layer. `fips` and `state+county`
  assets carry no tract-level signal, so they resolve at county level and say so
  (`tract_lookup_status: "no_tract_signal"`).
  If a tract lookup **misses**, the record falls back to its county profile with
  `tract_lookup_status: "no_tract_match"`. If the tract **service fails**, the
  run fails rather than silently substituting a coarser answer for the one you
  asked for.

**Honesty note on tract-level precision:** FEMA's National Risk Index tract data
is a finer-grained cut of the same underlying hazard/loss/vulnerability models,
not an independently-collected dataset — smaller geographic areas mean smaller
underlying sample sizes for some inputs, so treat tract-level scores as
directionally useful rather than as statistically tight as the county
aggregate, and expect more `Not Applicable` ratings for geographically-gated
hazards (e.g. avalanche, coastal flooding, tsunami) in small inland tracts.
When precision matters, cross-check the tract score against its parent county's
score (both are always included via `stcofips`/`county`).

### Two modes

#### 1. Asset mode (default) — join your locations to NRI

Provide an `assets` array. Each item can be **any one** of:

- `{ "fips": "08031" }` — 5-digit county FIPS code (always resolves at county level)
- `{ "state": "Colorado", "county": "Denver" }` — state + county name (always resolves at county level)
- `{ "lat": 39.7392, "lon": -104.9903 }` — geocoded via the FCC Census Block API (resolves at tract level when `resolution: "tract"`)

Add an optional `"label"` to identify each asset in the output.

```json
{
  "resolution": "tract",
  "assets": [
    { "lat": 39.7392, "lon": -104.9903, "label": "Denver HQ" },
    { "lat": 29.9511, "lon": -90.0715, "label": "New Orleans warehouse" },
    { "fips": "06037", "label": "LA County DC (county-level, no lat/lon)" }
  ]
}
```

#### 2. Inventory mode — full NRI dumps by state/county

Leave `assets` empty and set `states` and/or `counties` to pull full NRI profiles
for those areas, at the resolution set by `resolution` (all counties or tracts if
left fully empty, bounded by `maxResults`).

```json
{ "resolution": "tract", "states": ["CO"], "counties": ["Denver"] }
```

### Output fields (per record)

#### Identity & input echo

| Field | Meaning |
|---|---|
| `asset_label` | Label identifying the input asset. Null in inventory mode |
| `input_type` | `fips`, `state_county`, `lat_lon`, or `invalid` |
| `input_fips` | The 5-digit county FIPS supplied in the input |
| `input_state` | The normalized state name supplied in the input |
| `input_county` | The normalized county name supplied in the input |
| `input_lat` | The latitude supplied in the input |
| `input_lon` | The longitude supplied in the input |
| `geocoded_state` | State name returned by the FCC Census Block geocoder |
| `geocoded_county` | County name returned by the FCC geocoder |
| `geocoded_tract_fips` | The 11-digit tract FIPS derived from the FCC block FIPS |

#### Did anyone actually check? (read these before the numbers)

| Field | Meaning |
|---|---|
| `match_status` | `matched` / `not_found` / `source_unavailable` / `geocode_failed` / `invalid_input` |
| `nri_lookup_status` | `ok` / `unavailable` / `not_queried` |
| `nri_lookup_error` | Null when the NRI service answered; the failure message otherwise |
| `geocode_status` | `ok` / `failed` / `not_applicable` |
| `geocode_error` | Null when the geocode succeeded or was not needed |
| `tract_lookup_status` | `ok` / `no_tract_match` / `no_tract_signal` / `unavailable` / `not_requested` |
| `tract_lookup_error` | Null when the tract layer answered or was not queried |
| `verified_no_match` | `true` only when the service answered and genuinely holds no area. `null` means nobody checked — **not** "no risk" |
| `resolution_used` | `tract` or `county`, whichever actually produced this profile |
| `result_truncated` | Inventory mode: true when the filter matched more rows than `maxResults` |
| `source_records_available` | The FeatureServer's own count for the inventory filter |
| `nri_service` | Which FEMA NRI FeatureServer(s) answered |
| `nri_version` | The NRI release stamp on the record itself (e.g. `December 2025`) |
| `checked_at` | ISO timestamp of the run |

#### Area identity

| Field | Meaning |
|---|---|
| `state` | Matched area's full state name |
| `state_abbrev` | Two-letter postal abbreviation |
| `state_fips` | Two-digit state FIPS |
| `county` | Matched area's parent county name |
| `county_fips` | Three-digit county FIPS |
| `county_type` | County / Parish / Borough / Census Area / Municipio / City |
| `stcofips` | 5-digit state+county FIPS |
| `tract_fips` | 11-digit census tract FIPS (tract resolution only) |
| `tract` | Bare 6-digit tract number (tract resolution only) |
| `nri_id` | FEMA NRI identifier (`C`-prefixed county, `T`-prefixed tract) |
| `area_sq_mi` | Land area in square miles |
| `population` | Resident population used in the NRI exposure model |
| `building_value_usd` | Total building replacement value |
| `agriculture_value_usd` | Total agriculture value |

#### Composite risk

| Field | Meaning |
|---|---|
| `risk_score` | Composite Risk Index score (national percentile) |
| `risk_rating` | Composite risk category |
| `risk_value` | The raw composite Risk Index value the percentile derives from |
| `risk_percentile` | National percentile of the composite Risk Index |

#### Expected annual loss

| Field | Meaning |
|---|---|
| `expected_annual_loss_usd` | Composite modelled $ loss/year, all hazards |
| `expected_annual_loss_score` | Composite EAL percentile score |
| `expected_annual_loss_rating` | Composite EAL rating category |
| `expected_annual_loss_percentile` | National percentile of composite EAL |
| `eal_building_usd` | EAL attributable to **buildings** |
| `eal_population_equivalent_usd` | EAL to population, in dollar equivalence |
| `eal_population_count` | EAL to population, in population-equivalence units |
| `eal_agriculture_usd` | EAL attributable to **agriculture** |
| `annualized_loss_ratio_building` | Building EAL ÷ building value (scale-free) |
| `annualized_loss_ratio_population` | Population EAL ÷ population |
| `annualized_loss_ratio_agriculture` | Agriculture EAL ÷ agriculture value |
| `annualized_loss_ratio_percentile` | National percentile of the composite loss ratio |

#### Vulnerability & resilience

| Field | Meaning |
|---|---|
| `social_vulnerability_score` | CDC/ATSDR SVI-based percentile score |
| `social_vulnerability_rating` | Social vulnerability category |
| `social_vulnerability_percentile` | National percentile of the SVI input |
| `resilience_score` | Community resilience percentile score |
| `resilience_rating` | Community resilience category |
| `resilience_percentile` | National percentile of the resilience input |
| `resilience_value` | Raw community resilience value |
| `community_resilience_factor` | The factor actually applied to the risk equation (<1 reduces risk, >1 increases it) |

#### Hazards

| Field | Meaning |
|---|---|
| `top_hazards` | Top 3 of 18 by **national percentile**: `{code, hazard, score, rating}` |
| `top_hazards_by_expected_annual_loss` | Top 3 of 18 by **modelled dollars** |
| `hazards` | All 18, each with `code`, `hazard`, `score`, `rating`, `expected_annual_loss_usd`, `expected_annual_loss_rating`, `annualized_frequency`, `historic_event_count`, `exposure_usd` |
| `hazards_rated_count` | How many of the 18 carry a real rating here |
| `hazards_not_applicable_count` | How many FEMA marks `Not Applicable` |

### Inputs that populate the optional columns

- Everything above is populated for any matched county — e.g.
  `{"assets": [{"state": "Colorado", "county": "Denver"}]}`.
- `tract_fips`, `tract`, `geocoded_tract_fips`: `{"resolution": "tract",
  "assets": [{"lat": 39.7392, "lon": -104.9903}]}`.
- `result_truncated: true`, `source_records_available`:
  `{"states": ["Texas"], "maxResults": 10}` (Texas has 254 counties).
- `verified_no_match: true`: `{"assets": [{"fips": "99999"}]}`.
- `geocode_status: "failed"`: a coordinate outside US Census coverage, e.g.
  `{"assets": [{"lat": 0, "lon": 0}]}`.

### Use as an MCP tool

This actor is discoverable to AI agents (Claude, Cursor, etc.) via mcp.apify.com.
Field-level output descriptions make it a clean, chainable tool: an agent can pass
a `{state, county}` or `{lat, lon}` (optionally with `resolution: "tract"`) and get
back a structured hazard profile it can reason over directly. Agents should read
`nri_lookup_status` and `verified_no_match` before trusting any figure, and must
treat `verified_no_match: null` as "unknown", never as "no risk".

### Pricing

$6 per 1,000 results (single automatic Result/dataset-item event — one billed
record per asset in asset mode, or per county/tract in inventory mode). Records
are buffered and the billing gate runs **before** anything is pushed: if not one
location resolved against a source that answered, the run fails and nothing is
billed.

### FAQ

**Does this need an API key?** No — the FEMA NRI FeatureServers and the FCC
Census Block geocoder are all keyless public APIs.

**What if my county name doesn't match?** The join strips common suffixes
("County", "Parish", "Borough") and matches case-insensitively. If a county still
doesn't resolve, the record comes back with `match_status: "not_found"` and
`verified_no_match: true` — the service answered and genuinely has no such area.
If instead the service was unreachable you get `match_status: "source_unavailable"`
and `verified_no_match: null`.

**Why did my run fail instead of returning empty profiles?** Because an all-null
profile reads like "this location has no measured hazard risk". If the NRI layer
fails its live drift check, or not one record resolves, the run fails loudly and
bills nothing.

**Can I get census-tract-level risk for a street address without lat/lon?**
Not directly — tract resolution requires a coordinate (geocode the address to
lat/lon first, then pass it in). `fips` and `state+county` inputs only carry
county-level precision.

**What does "Inland Flooding" mean vs "Coastal Flooding"?** FEMA NRI's 18 hazard
types split flood risk into `IFLD` (Inland/riverine flooding) and `CFLD` (coastal
flooding, `Not Applicable` for landlocked areas) — both are in the `hazards`
array. Note the column prefix is `IFLD`, never `RFLD`.

**How current is the data?** FEMA NRI is a static, periodically-republished
dataset. This actor always queries the live FeatureServers and reports the
release stamp on every row as `nri_version` (currently `December 2025`), so a new
FEMA release can never change your numbers silently.

### Related actors

- **NFIP Flood Loss Risk Screener** — the backward-looking counterpart: actual
  historical NFIP paid claims near a coordinate, to pair with NRI's
  forward-looking modelled risk.
- **FHWA NBI Bridge Risk Monitor**, **EPA Contaminated Site Screener**,
  **Site Due Diligence Bundle** — the rest of a site-diligence stack.

Each is a screening tool, not a determination.

# Actor input Schema

## `assets` (type: `array`):

Locations to profile. Each item is EITHER {fips:"08031"} (5-digit county FIPS), OR {state:"Colorado", county:"Denver"}, OR {lat:39.7392, lon:-104.9903} (geocoded via the keyless FCC Census Block API). Add an optional "label" to identify each asset in the output. Leave empty to run inventory mode instead (see states/counties below).

## `resolution` (type: `string`):

Geographic resolution to join against: "county" (default — ~3,144 US counties) or "tract" (~85,000 US census tracts, finer-grained). Tract resolution only applies to lat/lon assets (geocoded to a tract via the FCC Census Block API) and to inventory-mode states/counties pulls; fips or state+county assets carry no tract signal and always use county data. If a tract lookup misses or the tract service errors, the record gracefully falls back to its county profile with resolution\_used="county" (never fails the run).

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

Used only when Assets is empty. Return full NRI risk profiles for these US states (2-letter postal codes or full names, e.g. CO or Colorado) at the resolution set above (county or tract). Leave empty (with Assets also empty) to return a small nationwide sample bounded by Max results.

## `counties` (type: `array`):

Used only when Assets is empty. Narrows the States filter above to specific bare county names (no "County"/"Parish" suffix), e.g. Denver. Applies at both county and tract resolution.

## `maxResults` (type: `integer`):

Maximum number of output records (each is one billed result). In asset mode this caps the number of assets processed; in inventory mode it bounds the row count returned (there are ~3,144 US counties and ~85,000 US census tracts total).

## Actor input object example

```json
{
  "assets": [
    {
      "state": "Colorado",
      "county": "Denver",
      "label": "Denver HQ"
    },
    {
      "lat": 29.9511,
      "lon": -90.0715,
      "label": "New Orleans warehouse"
    }
  ],
  "resolution": "county",
  "maxResults": 500
}
```

# Actor output Schema

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

The default dataset.

# 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 = {
    "assets": [
        {
            "state": "Colorado",
            "county": "Denver",
            "label": "Denver HQ"
        },
        {
            "lat": 29.9511,
            "lon": -90.0715,
            "label": "New Orleans warehouse"
        }
    ],
    "resolution": "county",
    "maxResults": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/fema-nri-county-risk-profile").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 = {
    "assets": [
        {
            "state": "Colorado",
            "county": "Denver",
            "label": "Denver HQ",
        },
        {
            "lat": 29.9511,
            "lon": -90.0715,
            "label": "New Orleans warehouse",
        },
    ],
    "resolution": "county",
    "maxResults": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/fema-nri-county-risk-profile").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "assets": [
    {
      "state": "Colorado",
      "county": "Denver",
      "label": "Denver HQ"
    },
    {
      "lat": 29.9511,
      "lon": -90.0715,
      "label": "New Orleans warehouse"
    }
  ],
  "resolution": "county",
  "maxResults": 500
}' |
apify call malonestar/fema-nri-county-risk-profile --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=malonestar/fema-nri-county-risk-profile",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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