# NYC FDNY Contractor Leads & Hearing Radar (`lukehunter/fdny-violation-radar`) Actor

Fresh NYC FDNY/OATH fire-code summonses, filterable by borough and remediation trade, routed with hearing-date urgency for fire-protection contractors and compliance buyers. Buildings and businesses only.

- **URL**: https://apify.com/lukehunter/fdny-violation-radar.md
- **Developed by:** [Luke Hunter](https://apify.com/lukehunter) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 leads

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

## NYC FDNY Contractor Leads & Hearing Radar

**For fire-protection contractors, expediters and compliance firms who need fresh NYC leads.** One run returns a queue of recent NYC FDNY fire-code summonses (OATH Hearings Division), routed by likely remediation trade and hearing-date urgency — not another generic multi-agency violation dump.
Pay-per-result: **$0.005 per lead — 100 leads = $0.50.**
Try it free with Apify's monthly platform credit.

NYC publishes every FDNY OATH summons in one open dataset shared with a dozen other agencies (Sanitation, DOB, Police...), with no trade filter and no urgency signal of its own. This Actor isolates just the Fire Department rows, classifies each one's violation description into a routing trade, computes days-since-issue and days-until-hearing, and excludes cases already resolved — so what you get back is a queue you can prospect from directly, on a schedule.

### Quick start (2 minutes)

1. Open the **Input** tab (no account setup or API key needed — the source is public).
2. Use this prefill, adjusted to your target borough and trade:

```json
{"borough": "BROOKLYN", "remediationTrade": "fire_alarm", "issuedWithinDays": 7, "includeResolved": false, "maxItems": 100}
```

3. Click **Start**. In under a minute you'll have a dataset you can export to CSV/JSON or pull via the API into your CRM.

### Example output

Real output, mapped from a live row captured 2026-09-24:

| Field | Value |
|---|---|
| `ticketId` | `014274530L` |
| `buildingAddress` | `250 MADISON AVENUE` |
| `borough` | `MANHATTAN` |
| `violationDescription` | `FIRE PROTECTION SYSTEMS` |
| `issueDate` | `2026-09-19T00:00:00.000` |
| `hearingDate` | `2026-11-18T00:00:00.000` |
| `caseStatus` | `NEW ISSUANCE` |
| `daysSinceIssue` | `5` |
| `daysUntilHearing` | `55` |
| `derivedRemediationTrade` | `fire_protection_system` |
| `derivedWorkflowSignal` | `new_within_7_days` |
| `derivedPrioritySignal` | `recent` |

Full raw JSON for this row (all fields, including provenance):

```json
{
  "ticketId": "014274530L",
  "buildingAddress": "250 MADISON AVENUE",
  "borough": "MANHATTAN",
  "violationDescription": "FIRE PROTECTION SYSTEMS",
  "issueDate": "2026-09-19T00:00:00.000",
  "hearingDate": "2026-11-18T00:00:00.000",
  "caseStatus": "NEW ISSUANCE",
  "hearingResult": null,
  "daysSinceIssue": 5,
  "daysUntilHearing": 55,
  "derivedRemediationTrade": "fire_protection_system",
  "derivedWorkflowSignal": "new_within_7_days",
  "derivedPrioritySignal": "recent",
  "sourceUrl": "https://data.cityofnewyork.us/resource/jz4z-kudi.json",
  "sourceDatasetTotal": 22064435,
  "fetchedAt": "2026-09-24T12:00:00.000Z"
}
```

### Use cases

- **Fire-protection contractors** prospecting building owners who just got hit with a sprinkler/standpipe/alarm summons and need remediation before their hearing.
- **Fire alarm and inspection companies** filtering to `fire_alarm` to find buildings with active nuisance-alarm or detector violations.
- **Violation expediters and compliance consultants** working `hearing_within_14_days` cases first, where the client has the least time to act.
- **Insurance and risk teams** tracking fire-code exposure by borough for underwriting or portfolio review.

### Run it weekly

This is built for recurring use, not a one-off pull:

1. Open the Actor, set your filters, then click **Schedule** (top right of the run page) instead of Start — or create one under **Schedules** in the Apify Console.
2. Set it to run weekly (or daily for a fast-moving borough), keeping a short `issuedWithinDays` window (e.g. 7) so each run mostly returns new cases.
3. Keep `ticketId` as your dedupe key downstream (CRM, spreadsheet, webhook) so re-running the same window never double-counts a lead you already have.
4. Prioritize `hearing_within_14_days` and `new_within_7_days` rows for the outreach queue each run produces.

### Input

| Field | Default | Meaning |
|---|---:|---|
| `borough` | all | Optional NYC borough filter: `MANHATTAN`, `BRONX`, `BROOKLYN`, `QUEENS`, `STATEN ISLAND`. |
| `remediationTrade` | all | Optional derived trade filter: `fire_alarm`, `fire_protection_system`, `extinguisher`, `egress`, `fire_safety_documentation`, `other` — see classification below. |
| `issuedWithinDays` | 30 | Only summonses issued within this many days (1–365). |
| `includeResolved` | false | Include cases whose latest source status is resolved (paid, written off, dismissed, etc.). |
| `maxItems` | 100 | Hard cap on delivered and billed leads (1–5000). |

### Output fields

| Field | Meaning |
|---|---|
| `ticketId` | The OATH ticket number — stable, unique, safe to dedupe on across scheduled runs. |
| `buildingAddress`, `borough` | Source property location. `borough` is `null` on a small minority of rows the city itself leaves blank. |
| `violationDescription` | The city's own charge description(s), joined from every charge on the ticket. This is the real text — read it before treating `derivedRemediationTrade` as final. |
| `issueDate`, `hearingDate`, `caseStatus`, `hearingResult` | Source facts: when the summons was issued, its scheduled OATH hearing date (often not yet a real courtroom date this far out), its current status, and its hearing outcome once decided. `hearingResult` is populated on a small minority of rows (mostly ones already heard) — do not build logic that assumes it is there. |
| `daysSinceIssue`, `daysUntilHearing` | **Derived.** Age in days since the violation date; calendar days until the hearing date (`null` when the source has no hearing date yet). |
| `derivedRemediationTrade` | **Derived.** A routing guess from `violationDescription` — see below. Not an FDNY designation. |
| `derivedWorkflowSignal` | **Derived.** One of `hearing_within_14_days`, `new_within_7_days`, `aging_over_60_days`, `active` — a queue-sorting bucket, not an official urgency rating. |
| `derivedPrioritySignal` | **Derived.** A coarser age bucket (`recent`, `active`, `aging`), kept for simple sorting. |
| `sourceUrl`, `sourceDatasetTotal`, `fetchedAt` | Provenance: the NYC Open Data endpoint, the parent dataset's total row count at fetch time, and when this row was fetched. |

### How trade routing actually works (read this before filtering)

NYC's own OATH charge codes are a small, fixed set of ~30 broad regulatory categories (e.g. `BF12 FIRE PROTECTION SYSTEMS`, `BF20 INSPECTION AND TESTING`) — confirmed against a real, live 1,000-row FDNY batch. They are **not free text**, and NYC does not record which specific equipment (sprinkler, standpipe, fire pump, alarm panel) a "fire protection systems" summons is actually about. Because of that, this Actor does not offer a `sprinkler` or `commercial-kitchen-suppression` filter — an earlier version did, and it silently returned zero rows every time, which is worse than not offering it. What it does offer, based on what the source can actually distinguish:

- `fire_alarm` — the source explicitly calls out alarms (e.g. "FAIL TO PREVENT UNNECESSARY UNWARRANTED ALARMS") or the description otherwise mentions an alarm/detector/central-station term.
- `fire_protection_system` — a generic fire-protection-system violation (sprinkler, standpipe, suppression or unspecified) that the source does not further break down.
- `extinguisher`, `egress`, `fire_safety_documentation` (recordkeeping, certificates, permits, affidavits, signage) — each keyed off the source's own charge language.
- `other` — general maintenance, electrical, storage/materials and everything else that doesn't fit the above.

Classification is deterministic and reproducible from `violationDescription` alone (see `classifyRemediationTrade` in the source) — it is a routing aid, not a compliance or job-scope determination.

### Privacy: buildings and businesses only, fail-closed

The source dataset's respondent fields can name a business ("AM 260 MADISON LLC", "PRIME BNV INC") or, in principle, a private individual (a homeowner, a sole proprietor). This Actor:

- **Never outputs a respondent name field at all** — it isn't in the schema above, by design.
- **Excludes the entire lead** when the source record's respondent has a first name populated (the source only splits first/last name for a natural person — a business occupies the last-name field alone in every real row observed).
- **Excludes the lead** if the respondent's name has no recognizable business/entity marker (a legal suffix like LLC/Inc/Corp, an institutional word like Congregation/Church/School, or a numbered street-style business name) — fail-closed: if it can't be confirmed as a business, it's dropped rather than guessed at. This costs some real businesses without a clear suffix; that trade-off is deliberate.

### Pricing and cost control

Pay-per-event: **$0.005 per delivered lead**, benchmarked against comparable Apify Store Actors that deliver US government permit/violation/property records per row (building-permit and property-deed-record scrapers cluster at $0.002–$0.01/result; this sits at the median).

- `maxItems` is a hard cap on delivered and billed rows.
- Delivered rows always equal charged rows: if your run's own spending limit runs out mid-run, the Actor stops, finishes `SUCCEEDED`, and its status message says exactly how many leads were delivered and billed — never a silent under-charge, never a free row that should have been billed.
- If a platform migration restarts the run mid-delivery, rows already delivered and charged are never delivered or charged again.
- If the Actor is not monetized (e.g. running it yourself), every matching row is delivered free.

### Limitations — read before you build automation on this

- **Coarse trade routing.** See above — `fire_protection_system` covers sprinkler, standpipe and unspecified suppression-system violations together; the source cannot distinguish them.
- **Not every case has a `borough` or `hearingResult`.** Both are `null` on a small minority of real rows; do not assume either is always populated.
- **OATH is an adjudication feed, not an inspection census.** It reflects summonses referred to hearings, not every FDNY inspection or condition, and status/hearing data can lag or change after this Actor fetches it.
- **This does not determine severity, liability, remediation scope, job value, or whether work is still available** — it is a discovery and routing tool, not a compliance or estimating determination.
- **Some real businesses without a recognizable name suffix are excluded** by the privacy filter (see above) — a deliberate false-negative trade-off, not a bug.
- **10,000-row pagination ceiling per run**, well above any single `maxItems`, but a reason a very old backfill query could come up short.

### FAQ

**Do I need an NYC Open Data API key?** No. `data.cityofnewyork.us` is public and keyless.

**Why did a violation I know about not show up?** Either it's outside `issuedWithinDays`, its case is already resolved and `includeResolved` is false, it didn't match your `borough`/`remediationTrade` filter, or its respondent didn't pass the buildings/businesses privacy filter above.

**Can I get a specific trade like sprinklers only?** Not on its own — see "How trade routing actually works." Use `fire_protection_system` and read `violationDescription` to confirm equipment type.

**How fresh is the data?** NYC updates the underlying OATH dataset roughly daily; a 7-day `issuedWithinDays` window typically returns fresh, still-open cases.

**Can I schedule this?** Yes — that's the intended use. Schedule a borough/trade-specific 7-day lookback, keep `ticketId` in your CRM to dedupe across runs, and prioritize `hearing_within_14_days` records for near-term outreach.

### SEO / discovery terms

NYC FDNY violations, FDNY contractor leads, fire alarm leads NYC, OATH summonses, fire code remediation leads, FDNY hearing dates, NYC fire violation data, fire protection sales leads, NYC compliance API, fire code compliance leads.

### Related Actors

Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.

- **[Federal Register Rule Effective-Date Radar](https://apify.com/lukehunter/federal-rule-effective-date-radar)**: final federal rules about to take effect, with citations, dockets and urgency.
- **[Chicago Contract Recompete & Extension Radar](https://apify.com/lukehunter/chicago-contract-expiration-radar)**: expiring City of Chicago contracts with extension history, for recompete prospecting.
- **[Bank Branch Closings & Merger Radar (FDIC)](https://apify.com/lukehunter/bank-branch-merger-radar)**: new US bank branch closings, openings and mergers from the FDIC.
- **[Chicago New Business License Radar](https://apify.com/lukehunter/chicago-new-business-license-radar)**: newly licensed Chicago businesses as opening leads, grouped by category.
- **[RCRA Enforcement Response Leads](https://apify.com/lukehunter/rcra-enforcement-radar)**: new EPA hazardous-waste (RCRA) enforcement actions and penalties, by state.
- **[Emerging Federal Contractor Radar](https://apify.com/lukehunter/emerging-federal-contractor-radar)**: companies that just won their first federal contracts, from USAspending.gov.
- **[Hospital Price Transparency Enforcement Leads](https://apify.com/lukehunter/hospital-price-transparency-enforcement-leads)**: hospitals with recent CMS price transparency warning notices, CAP requests and CMP notices.
- **[Hospital Ownership Change Radar](https://apify.com/lukehunter/hospital-chow-radar)**: hospitals that just changed owner, with buyer, seller and effective date from CMS filings.
- **[New Healthcare Organization NPI Radar](https://apify.com/lukehunter/new-healthcare-organization-npi-radar)**: newly registered healthcare organizations from CMS's weekly NPI file, by provider type.
- **[County Multifamily Building Permits Surge Radar](https://apify.com/lukehunter/county-multifamily-permit-surge-radar)**: U.S. counties where 5+ unit apartment permits are surging year over year.
- **[Zillow ZIP Code Property Search Scraper](https://apify.com/lukehunter/zillow-zip-scraper)**: for-sale listings by ZIP code with prices, price cuts and days on Zillow.
- **[Walmart Category Scraper](https://apify.com/lukehunter/walmart-category-scraper)**: product names, prices, was-prices and ratings from Walmart category pages.

# Actor input Schema

## `borough` (type: `string`):

Optional NYC borough filter.

## `remediationTrade` (type: `string`):

Optional derived trade classification. Filters the delivered work queue; classification is inferred from the city-published violation description, not supplied by FDNY. NYC's own violation categories do not name specific equipment (e.g. sprinkler vs. standpipe), so those are grouped under fire\_protection\_system rather than offered as separate, mostly-empty filters.

## `issuedWithinDays` (type: `integer`):

Only summonses issued within this many days, 1-365, default 30.

## `includeResolved` (type: `boolean`):

Include cases whose latest source status is resolved/closed.

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

Hard cap on unique summons leads delivered and billed, 1-5000, default 100.

## Actor input object example

```json
{
  "issuedWithinDays": 30,
  "includeResolved": false,
  "maxItems": 100
}
```

# Actor output Schema

## `leads` (type: `string`):

No description

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("lukehunter/fdny-violation-radar").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("lukehunter/fdny-violation-radar").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 '{}' |
apify call lukehunter/fdny-violation-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lukehunter/fdny-violation-radar"
        }
    }
}
```

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/mOzClUE0bSHhpooZo/builds/1cvAWIXqvOM411jF1/openapi.json
