# NERIS Fire Department & Station Directory (`automation-lab/neris-fire-station-directory-scraper`) Actor

Search and export official public NERIS fire departments joined to station addresses, staffing, websites, and station counts.

- **URL**: https://apify.com/automation-lab/neris-fire-station-directory-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.25 / 1,000 item extracteds

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?

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

## NERIS Fire Department & Station Directory

Export the official public NERIS fire department and station directory as a clean, joined dataset. Each result combines department identity, headquarters address, staffing, website, station count, and one station's identity and address, so the output is ready for spreadsheets, maps, databases, and recurring public-safety directory refreshes.

The Actor reads the NERIS Public Fire Departments product published through an anonymous ArcGIS Feature Service. It does not require a NERIS login, scrape private account pages, or return incident reports.

### What can this NERIS scraper do?

- Search departments by name, NERIS ID, or legacy FDID.
- Filter departments by one or more US state or territory codes.
- Refresh exact departments by stable NERIS IDs.
- Export one joined row per published fire station.
- Preserve departments with no station record as an optional row with null station fields.
- Return department staffing type, workforce counts, website, station count, unit count, and protected population when NERIS publishes them.
- Process the national directory with deterministic pagination and a configurable result limit.
- Produce typed JSON, CSV, Excel, XML, RSS, or JSONL through Apify dataset exports.
- Run on a schedule for repeat directory snapshots and downstream change detection.

### Who is this fire station directory for?

**Public-safety analysts** can build current department and station inventories without manually joining two map layers.

**GIS and emergency-planning teams** can import normalized station addresses and stable source IDs into mapping or coverage workflows.

**Researchers and journalists** can compare department type, station count, workforce fields, and protected population across selected geographies.

**Data teams and integrators** can schedule repeat exports into a warehouse, spreadsheet, webhook, or automation platform.

**Vendors serving fire departments** can maintain a source-attributed organization directory while respecting responsible-use requirements.

### Why use this Actor?

NERIS publishes departments and stations as separate official feature layers. A usable directory normally requires filtering departments, paginating both layers, joining stations by department NERIS ID, normalizing nullable fields, and handling departments with no matching station.

This Actor performs that work in one run. The default dataset remains flat: a department with five published stations produces five rows. Stable department and station NERIS IDs make repeat snapshots easier to compare than name-only lists.

The Actor uses direct structured requests rather than a browser. That keeps runs lightweight and avoids downloading images, fonts, or interactive map assets.

### What NERIS data is extracted?

| Group | Fields |
| --- | --- |
| Department identity | `departmentNerisId`, `departmentName`, `legacyFdid`, `entityType` |
| Headquarters | `departmentAddressLine1`, `departmentAddressLine2`, `departmentCity`, `departmentState`, `departmentZipCode`, `departmentTimeZone` |
| Department profile | `departmentType`, `departmentWebsite`, `stationCount`, `departmentUnitCount`, `populationProtected` |
| Staffing | `careerFirefightersFullTime`, `careerFirefightersPartTime`, `volunteerFirefighters`, `emsOnlyCareerFullTime`, `emsOnlyCareerPartTime`, `emsOnlyVolunteers` |
| Station identity | `stationNerisId`, `stationName`, `stationUnitCount` |
| Station address | `stationAddressLine1`, `stationAddressLine2`, `stationCity`, `stationState`, `stationZipCode` |
| Provenance | `sourceUpdatedAt`, `sourceUrl`, `retrievedAt` |

NERIS fields can be null when a department has not supplied them or when the public release omits them. Null is different from zero.

### How to export a NERIS department and station directory

1. Open the Actor in Apify Console.
2. Enter a department name, select states, provide exact NERIS IDs, or leave all filters empty for national coverage.
3. Choose the maximum number of joined rows.
4. Keep **Include departments without published stations** enabled if every matched department should be represented.
5. Click **Start**.
6. Open the **Dataset** tab after the run succeeds.
7. Export the rows as CSV, Excel, JSON, JSONL, XML, or RSS.
8. Save the task or add a schedule when you need recurring snapshots.

### Input parameters

| Input | Type | Default | Description |
| --- | --- | --- | --- |
| `search` | string | none | Case-insensitive fragment of department name, NERIS ID, or legacy FDID. Minimum two characters. |
| `states` | string\[] | all | Two-letter headquarters state or territory codes, such as `TX` or `RI`. |
| `departmentNerisIds` | string\[] | all | Up to 500 exact fire-department NERIS IDs. |
| `includeDepartmentsWithoutStations` | boolean | `true` | Emit a department row with null station fields when no station is published. |
| `maxItems` | integer | `1000` | Maximum joined rows, from 1 through 100,000. |

All supplied filters are combined. For example, `search: "Houston"` with `states: ["TX"]` only returns matching departments whose headquarters state is Texas.

#### Search by department name

```json
{
  "search": "Houston",
  "maxItems": 25
}
```

#### Refresh exact NERIS departments

```json
{
  "departmentNerisIds": ["FD48201232"],
  "maxItems": 20
}
```

#### Export a state directory

```json
{
  "states": ["RI"],
  "includeDepartmentsWithoutStations": true,
  "maxItems": 1000
}
```

#### Request a national export

```json
{
  "maxItems": 100000,
  "includeDepartmentsWithoutStations": true
}
```

### Output example

A current joined result has this shape:

```json
{
  "departmentNerisId": "FD04007033",
  "departmentName": "Houston Mesa Fire Department",
  "departmentType": "COMBINATION",
  "departmentAddressLine1": "8139 West Mescalero Road",
  "departmentCity": "Payson",
  "departmentState": "AZ",
  "departmentZipCode": "85541 6154",
  "departmentWebsite": "http://www.houstonmesafire.org/",
  "stationCount": 1,
  "careerFirefightersFullTime": 1,
  "volunteerFirefighters": 12,
  "stationNerisId": "FD04007033S000",
  "stationName": "Houston Mesa Fire Department Station 31",
  "stationAddressLine1": "8139 West Mescalero Road",
  "stationCity": "Payson",
  "stationState": "AZ",
  "stationZipCode": "85541 6154",
  "sourceUpdatedAt": "2026-08-24T00:02:18.000Z",
  "sourceUrl": "https://www.arcgis.com/home/item.html?id=0ac459746be44023a1b33ba00bb5f628",
  "retrievedAt": "2026-08-24T20:11:19.946Z"
}
```

Additional documented fields are present with values or `null`. The dataset schema controls column order in Apify Console.

### Understanding the joined rows

`departmentNerisId` is the department join key. `stationNerisId` identifies one station belonging to that department.

When NERIS publishes multiple stations for a department, department-level values repeat on each station row. This flat shape works directly in a single CSV worksheet.

When `includeDepartmentsWithoutStations` is true and no station record exists, station fields are null. The row still counts as one produced dataset item because it is a useful department result.

`sourceUpdatedAt` comes from the official public layer export timestamp. `retrievedAt` records when this Actor fetched the row.

### How much does it cost to export NERIS fire station records?

The Actor uses pay-per-event pricing:

- a **$0.005 Start** event once per run;
- an **Item** event for each saved joined row;
- Item prices decrease by subscription tier, from $0.002392 per row on FREE to $0.0005824 per row on DIAMOND.

At the FREE tier, 25 rows cost about **$0.0648** including the Start event. A 1,000-row export costs about **$2.397**. Your active subscription tier can reduce the per-row amount shown in Apify Console.

Failed requests and discarded upstream records are not Item events. Infrastructure usage is covered by the event prices rather than billed as an extra Actor fee.

### Scheduling directory refreshes

Create a saved Task with stable filters, then add a daily, weekly, or monthly schedule. Each run creates a new dataset snapshot.

For change tracking, compare rows by `stationNerisId`. Use `departmentNerisId` for department-level changes and keep `sourceUpdatedAt` to identify the source release.

A downstream workflow can copy each successful dataset to object storage or a warehouse before the default retention period expires.

### Spreadsheet and data-pipeline integrations

- Export directly as CSV or Excel from the Dataset tab.
- Connect Apify to Google Sheets through Make or Zapier.
- Trigger a webhook when the run finishes and download the dataset through the API.
- Load JSONL into BigQuery, Snowflake, PostgreSQL, or another warehouse.
- Geocode or map the published addresses in a separate authorized workflow.
- Join internal records on stable NERIS IDs or a published legacy FDID.

The Actor returns addresses, not latitude and longitude. Use the official ArcGIS layer directly when your workflow specifically needs source geometry or jurisdiction polygons.

### Run from the Apify API with cURL

Replace `YOUR_APIFY_TOKEN` with your Apify API token:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~neris-fire-station-directory-scraper/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"states":["RI"],"maxItems":1000}'
```

To wait for completion and receive dataset items in one request:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~neris-fire-station-directory-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"search":"Houston","maxItems":25}'
```

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/neris-fire-station-directory-scraper').call({
  departmentNerisIds: ['FD48201232'],
  maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("automation-lab/neris-fire-station-directory-scraper").call(
    run_input={"states": ["RI"], "maxItems": 1000}
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use the Actor through MCP

Add the Apify MCP server to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/neris-fire-station-directory-scraper"
```

**Claude Desktop** can use this MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/neris-fire-station-directory-scraper"
    }
  }
}
```

**Cursor** users can add the same server URL under Settings → Tools & MCP. **VS Code** users can add the same JSON server entry to their MCP configuration and start it from the MCP Servers view.

Example prompts:

- “Run the NERIS fire station directory for Rhode Island and summarize department types.”
- “Refresh department FD48201232 and save its joined station rows.”
- “Find NERIS departments matching Houston and return their station addresses.”

### Reliability, limits, and failure behavior

The Actor uses the official NERIS Public ArcGIS service, deterministic object-ID ordering, 1,000-feature pages, bounded station batches, 45-second request timeouts, and retries for transient server or rate-limit failures.

A source authentication error, schema error, or persistent upstream failure fails the run instead of returning a misleading partial success. Already saved rows remain in the run dataset for diagnosis.

`maxItems` limits joined output rows, not source departments. A department with many stations can consume multiple result slots.

The public dataset is updated by NERIS and can change between runs. Published station counts and actual station rows can temporarily differ during source updates.

### Data scope and limitations

- Coverage follows the current NERIS Public Fire Departments product.
- The official product describes more than 30,000 departments and publishes separate station records.
- Some profile and staffing fields are null.
- Websites and addresses are source-provided and are not independently verified by this Actor.
- No private NERIS account, incident, user, unit-detail, or contact-person data is accessed.
- Jurisdiction boundary geometry is not included in the flat directory output.
- The Actor does not calculate historical changes; schedule snapshots and compare them downstream.
- State filtering uses the department headquarters state, not every station's state.

### Responsible use and legality

This Actor accesses an anonymous public dataset linked from NERIS Public. Review the NERIS Public and ArcGIS item terms before using or redistributing results.

Use the data for legitimate research, planning, directory maintenance, analysis, and integration. Respect applicable privacy, public-records, licensing, attribution, and anti-spam laws. Do not present the dataset as emergency dispatch guidance or assume every source field is complete or current.

The Actor is not affiliated with or endorsed by NERIS, FSRI, UL Research Institutes, the U.S. Fire Administration, Esri, or ArcGIS.

### Troubleshooting

#### Why did my search return no rows?

Check spelling and remove conflicting filters. `search`, `states`, and `departmentNerisIds` are combined. Try a broader name fragment or the exact uppercase NERIS ID.

#### Why are station fields null?

The matched department has no station feature in the current public layer and `includeDepartmentsWithoutStations` is enabled. Disable that option to omit such departments.

#### Why are staffing or website fields null?

NERIS did not publish a value for that department in the current public export. The Actor preserves null rather than guessing.

#### Why did a large run fail?

Review the run log for an upstream ArcGIS status or timeout. Retry later if the official service was temporarily unavailable. Use state filters for smaller independent refreshes when appropriate.

### FAQ

#### Can I export the complete national directory?

Yes. Leave search, state, and ID filters empty, set `maxItems` to 100,000, and keep departments without stations enabled. The exact number of rows changes with the source.

#### Does one row represent a department or a station?

Normally one row represents one station joined to its department. A department without a station can produce one department-only row with null station fields.

#### Can I search by NERIS ID?

Yes. Use `search` for an ID fragment or `departmentNerisIds` for exact repeatable IDs.

#### Does this Actor need a NERIS login or proxy?

No. It uses the anonymous NERIS Public ArcGIS Feature Service and does not configure a proxy.

#### Are coordinates included?

No. This product focuses on the requested flat identity, address, staffing, website, and station-count directory. The source ArcGIS item is linked in every result for workflows needing official geometry.

#### Can I use the result in Excel?

Yes. Download CSV or Excel from the Dataset tab. Repeated department fields keep all station records in one worksheet.

#### How often is the source updated?

The official ArcGIS item says it is updated daily. Actual publication timing and field completeness remain controlled by NERIS.

### Related automation-lab Actors

This Actor is designed as a standalone NERIS directory workflow. For broader location enrichment, search the `automation-lab` Store profile for public mapping and website extraction Actors that match your authorized downstream use.

### Support

If a run fails, include the run URL, non-secret input, expected department or state, and a short description of the missing or incorrect field. Do not include Apify tokens, NERIS credentials, or private operational data.

# Actor input Schema

## `search` (type: `string`):

Case-insensitive fragment of a department name, NERIS ID, or legacy FDID, such as Houston.

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

Optional two-letter US state or territory codes. A department matches when its headquarters state is selected.

## `departmentNerisIds` (type: `array`):

Optional list of exact fire-department NERIS IDs for repeatable entity-specific refreshes.

## `includeDepartmentsWithoutStations` (type: `boolean`):

Save one department row with null station fields when NERIS has no station record for that department.

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

Maximum department-station rows to save. Increase this to 100,000 for a complete national export.

## Actor input object example

```json
{
  "search": "Houston",
  "includeDepartmentsWithoutStations": true,
  "maxItems": 20
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing one joined row per published station, plus optional department-only rows.

# 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 = {
    "search": "Houston",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/neris-fire-station-directory-scraper").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 = {
    "search": "Houston",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/neris-fire-station-directory-scraper").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 '{
  "search": "Houston",
  "maxItems": 20
}' |
apify call automation-lab/neris-fire-station-directory-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/neris-fire-station-directory-scraper"
        }
    }
}

```

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/9hCfB92x0CK0Hyswy/builds/BTcdRBkBIQd4Gp565/openapi.json
