# FEMA Flood Zone Batch Lookup — No Login, $10/1k (`flamboyant_liner/flood-zone-batch-lookup`) Actor

Give it a list of US addresses or coordinates and get each one's FEMA flood zone, SFHA flag, base flood elevation, FIRM panel and effective date, LOMR and a risk label. Geocoding included. No login or API key. MCP-ready. $10 per 1,000 lookups.

- **URL**: https://apify.com/flamboyant\_liner/flood-zone-batch-lookup.md
- **Developed by:** [Khrystyna Skotte](https://apify.com/flamboyant_liner) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 flood zone lookups

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?

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 Flood Zone Batch Lookup

Give it a list of US addresses (or latitude/longitude points) and get back, for every one, the official FEMA flood zone, whether it sits in a Special Flood Hazard Area, the base flood elevation, the FIRM panel and its effective date, a plain-English risk label and a link to the FEMA Map Service Center. One row per input, in order, whether or not the address could be located. No API key, no browser, thousands of addresses per run.

Built for flood insurance quoting, mortgage flood-determination pre-screens, real-estate due diligence, portfolio risk reviews and property data enrichment.

### What you get per address

| Field | Example | Meaning |
|---|---|---|
| `inputAddress` | `4802 Braesvalley Dr, Houston, TX 77096` | What you sent |
| `id` | `nola-ae` | Your id, or the 1-based position in the list |
| `normalizedAddress` | `4802 BRAESVALLEY DR, HOUSTON, TX 77096` | USPS-style cleaned address |
| `matchedAddress` | `4802 BRAESVALLEY DR, HOUSTON, TX, 77096` | Address the geocoder matched |
| `latitude` / `longitude` | `29.6827` / `-95.4610` | Point used for the lookup |
| `geocoder` | `census` / `nominatim` / `input` / `none` | Which geocoder located it (`input` = you supplied coordinates) |
| `geocodeConfidence` | `1` | 1 exact Census match, 0.7 non-exact Census, 0.5 Nominatim, 0 not found |
| `floodZone` | `AE` | FEMA flood zone code |
| `zoneSubtype` | `0.2 PCT ANNUAL CHANCE FLOOD HAZARD` | Zone qualifier (shaded X, floodway, levee, etc.) |
| `inSFHA` | `true` | Inside a Special Flood Hazard Area (mandatory flood insurance for federally backed mortgages) |
| `staticBFE` | `-3` | Static base flood elevation in feet, when the zone carries one |
| `depth` | `2` | Flood depth in feet for AO zones |
| `verticalDatum` | `NAVD88` | Datum of the BFE |
| `dfirmId` | `48201C` | FEMA study (county) identifier |
| `firmPanel` | `48201C0865M` | Flood Insurance Rate Map panel number |
| `panelEffectiveDate` | `2019-05-02` | Panel effective date |
| `lomrCaseNumber` / `lomrEffectiveDate` | `19-04-6434P` | Most recent Letter of Map Revision covering the point (only with `includeLomr`) |
| `riskLabel` | `High` | `High`, `Moderate`, `Minimal`, `Undetermined`, `Open Water` or `Unmapped` |
| `femaMapUrl` | `https://msc.fema.gov/portal/search?AddressQuery=…` | FEMA Map Service Center search for this address |
| `lookupStatus` | `ok` | `ok`, `geocode_failed`, `not_mapped` (no NFHL coverage at this point) or `lookup_failed` (FEMA service error after retries) |
| `processedAt` | ISO timestamp | When the run happened |

### What the zones mean

| Zone | Risk label | Meaning |
|---|---|---|
| `A`, `AE`, `AH`, `AO`, `AR`, `A99` | High | 1% annual chance (100-year) floodplain. `AE` has base flood elevations, `AH`/`AO` are shallow flooding, `AR`/`A99` relate to levee restoration or construction. Flood insurance is mandatory for federally backed mortgages. |
| `V`, `VE` | High | Coastal 1% annual chance floodplain with wave action. Mandatory insurance, stricter building standards. |
| `X` with subtype `0.2 PCT ANNUAL CHANCE FLOOD HAZARD` (shaded X, formerly `B`) | Moderate | 0.2% annual chance (500-year) floodplain, or 1% floodplain with less than 1 ft depth. Insurance optional but recommended. |
| `X` with subtype `AREA WITH REDUCED FLOOD RISK DUE TO LEVEE` | Moderate | Protected by an accredited levee; residual risk if the levee fails. |
| `X` with subtype `AREA OF MINIMAL FLOOD HAZARD` (unshaded X, formerly `C`) | Minimal | Outside the 500-year floodplain. |
| `D` | Undetermined | Flood hazards possible but not studied. |
| `OPEN WATER` | Open Water | The point falls on a lake, river or the sea. |
| No polygon | Unmapped | Outside the National Flood Hazard Layer (some rural counties, tribal lands, most of Alaska). |

`inSFHA` comes straight from FEMA's `SFHA_TF` flag, not from the label, so it is the field to use for mandatory-purchase decisions.

### Input

| Field | Default | Notes |
|---|---|---|
| `addresses` | 5 sample properties | Array of strings (`"123 Main St, Austin, TX 78701"`), address objects (`{"id", "street", "city", "state", "zip"}`) or coordinate objects (`{"id", "latitude", "longitude"}`). Objects may also carry `{"id", "address"}` for a one-line string with your own id. |
| `includeFirmPanel` | `true` | Add FIRM panel number and effective date |
| `includeLomr` | `false` | Add the latest Letter of Map Revision covering the point |
| `fallbackToNominatim` | `true` | Try OpenStreetMap when the Census Geocoder cannot match an address (1 request/second) |
| `maxItems` | `10` | Process at most this many items |

```json
{
  "addresses": [
    "4802 Braesvalley Dr, Houston, TX 77096",
    { "id": "nola-ae", "street": "4900 Paris Ave", "city": "New Orleans", "state": "LA", "zip": "70122" },
    { "id": "denver-point", "latitude": 39.7392, "longitude": -104.9903 }
  ],
  "includeFirmPanel": true,
  "maxItems": 5000
}
```

### Sample output

```json
{
  "inputAddress": "4900 Paris Ave, New Orleans, LA 70122",
  "id": "nola-ae",
  "normalizedAddress": "4900 PARIS AVE, NEW ORLEANS, LA 70122",
  "matchedAddress": "4900 PARIS AVE, NEW ORLEANS, LA, 70122",
  "latitude": 30.0074667765,
  "longitude": -90.074569988723,
  "geocoder": "census",
  "geocodeConfidence": 1,
  "floodZone": "AE",
  "zoneSubtype": null,
  "inSFHA": true,
  "staticBFE": -3,
  "depth": null,
  "verticalDatum": null,
  "dfirmId": "22071C",
  "firmPanel": "22071C0114F",
  "panelEffectiveDate": "2016-09-30",
  "lomrCaseNumber": null,
  "lomrEffectiveDate": null,
  "riskLabel": "High",
  "femaMapUrl": "https://msc.fema.gov/portal/search?AddressQuery=4900%20PARIS%20AVE%2C%20NEW%20ORLEANS%2C%20LA%2C%2070122",
  "lookupStatus": "ok",
  "processedAt": "2026-09-28T18:00:00.000Z"
}
```

### How it works

1. **Normalise** every address to USPS style (suffix and directional abbreviations, state codes, ZIP+4 split).
2. **Geocode** with the US Census Bureau batch geocoder (up to 10,000 addresses per request), retry non-matches on the Census one-line endpoint with and without the unit number, then optionally fall back to OpenStreetMap Nominatim. Coordinate inputs skip this step.
3. **Query FEMA's National Flood Hazard Layer** (NFHL MapServer, layer 28 Flood Hazard Zones) with each point, 5 requests in flight. If the point sits on a boundary between two zones, the SFHA one is reported so a mandatory-purchase zone is never missed.
4. Optionally query the **FIRM Panels** (layer 3) and **LOMRs** (layer 1) layers, matched to the same FEMA study as the zone polygon, because neighbouring counties' panel grids overlap.

Throughput is roughly 5–10 lookups per second for geocodable addresses; the Nominatim fallback runs at 1 address per second.

### Sources

- FEMA National Flood Hazard Layer, `hazards.fema.gov/arcgis/rest/services/public/NFHL/MapServer` (the same data behind the FEMA Flood Map Service Center). Zones, BFEs, panels and LOMRs are the currently effective data at run time.
- US Census Bureau Geocoder, `Public_AR_Current` benchmark.
- OpenStreetMap Nominatim (optional fallback).

### Limits and caveats

- This is a point-in-polygon lookup at the geocoded address point. It is not a legal flood zone determination (Standard Flood Hazard Determination Form) and does not replace an elevation certificate. Large parcels can straddle zones; the row reports the zone at the rooftop point the geocoder returned.
- `staticBFE` is only populated where FEMA stores a static elevation on the zone polygon. Many AE zones carry BFEs on separate BFE lines or cross-sections instead, so a `null` BFE does not mean there is none.
- Geocoding accuracy follows the Census geocoder (street-segment interpolation). `geocodeConfidence` below 1 means the point may be a few tens of metres off; treat boundary cases with care.
- Not-yet-effective preliminary maps are not included.
- FEMA's service occasionally returns errors under load; each query is retried four times and the row is marked `lookup_failed` if it still fails, so you can re-run just those.

### Pricing

- $0.005 per run start
- $0.01 per input item (one output row each, charged whether or not the address could be located)

1,000 addresses cost $10.05.

# Actor input Schema

## `addresses` (type: `array`):

One item per property. Each item is a one-line string ("4802 Braesvalley Dr, Houston, TX 77096"), an address object {"id", "street", "city", "state", "zip"}, or a coordinate object {"id", "latitude", "longitude"}. Every item produces exactly one output row.

## `includeFirmPanel` (type: `boolean`):

Add the FIRM panel number and its effective date (one extra FEMA query per address).

## `includeLomr` (type: `boolean`):

Add the most recent Letter of Map Revision case number covering the point, if any (one extra FEMA query per address).

## `fallbackToNominatim` (type: `boolean`):

When the Census Geocoder finds no match, try OpenStreetMap Nominatim (rate-limited to 1 request per second, so large no-match sets are slow).

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

Process at most this many items from the list (each item is one charged lookup).

## Actor input object example

```json
{
  "addresses": [
    "4802 Braesvalley Dr, Houston, TX 77096",
    {
      "id": "nola-ae",
      "street": "4900 Paris Ave",
      "city": "New Orleans",
      "state": "LA",
      "zip": "70122"
    },
    "6000 Canal Blvd, New Orleans, LA 70124",
    "1600 Pennsylvania Ave NW, Washington, DC 20500",
    {
      "id": "denver-point",
      "latitude": 39.7392,
      "longitude": -104.9903
    }
  ],
  "includeFirmPanel": true,
  "includeLomr": false,
  "fallbackToNominatim": true,
  "maxItems": 10
}
```

# Actor output Schema

## `records` (type: `string`):

Dataset of flood zone lookups, one row per input address (JSON).

# 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 = {
    "addresses": [
        "4802 Braesvalley Dr, Houston, TX 77096",
        {
            "id": "nola-ae",
            "street": "4900 Paris Ave",
            "city": "New Orleans",
            "state": "LA",
            "zip": "70122"
        },
        "6000 Canal Blvd, New Orleans, LA 70124",
        "1600 Pennsylvania Ave NW, Washington, DC 20500",
        {
            "id": "denver-point",
            "latitude": 39.7392,
            "longitude": -104.9903
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("flamboyant_liner/flood-zone-batch-lookup").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 = { "addresses": [
        "4802 Braesvalley Dr, Houston, TX 77096",
        {
            "id": "nola-ae",
            "street": "4900 Paris Ave",
            "city": "New Orleans",
            "state": "LA",
            "zip": "70122",
        },
        "6000 Canal Blvd, New Orleans, LA 70124",
        "1600 Pennsylvania Ave NW, Washington, DC 20500",
        {
            "id": "denver-point",
            "latitude": 39.7392,
            "longitude": -104.9903,
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("flamboyant_liner/flood-zone-batch-lookup").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 '{
  "addresses": [
    "4802 Braesvalley Dr, Houston, TX 77096",
    {
      "id": "nola-ae",
      "street": "4900 Paris Ave",
      "city": "New Orleans",
      "state": "LA",
      "zip": "70122"
    },
    "6000 Canal Blvd, New Orleans, LA 70124",
    "1600 Pennsylvania Ave NW, Washington, DC 20500",
    {
      "id": "denver-point",
      "latitude": 39.7392,
      "longitude": -104.9903
    }
  ]
}' |
apify call flamboyant_liner/flood-zone-batch-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,flamboyant_liner/flood-zone-batch-lookup"
        }
    }
}
```

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/MnMNxw3MPmMLzApH9/builds/eDrgkLDQFlX9J4x8W/openapi.json
