# NYC Restaurant Inspections - Grades, Scores & Violations (`j0401/nyc-restaurant-inspections`) Actor

NYC DOHMH restaurant inspections (295k violation records / 31.5k restaurants): grade, score, critical flag, violation code, cuisine, borough, BBL/BIN. Search, aggregate by cuisine or borough, profile a restaurant, roll up one row per restaurant, or read the violation-code dictionary.

- **URL**: https://apify.com/j0401/nyc-restaurant-inspections.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Categories:** Business
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.03 / 1,000 nyc restaurant inspection records

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?

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 Restaurant Inspections - 295,292 Inspection & Violation Records

New York City's Department of Health and Mental Hygiene inspection results as a
queryable dataset: **295,292 inspection/violation records across 31,488
restaurants** (measured 10 November 2026), refreshed daily (newest record
**9 November 2026**, newest inspection **7 November 2026**). Records carry the letter grade, the inspection
score, the critical-violation flag, the violation code and description, the
cuisine, the borough and street address, BBL/BIN and coordinates **where the
city publishes them** - an inspection with no violation has no code or
description, and 51% of rows have no letter grade yet (both are counted
below).

### Low cost

**From $0.00005 per record, down to $0.00003 at Gold.** Pay only for the
records you receive - the full file stays queryable and you are never billed
for the scan behind it.

### What you get per record

| field | meaning |
|---|---|
| `restaurantId`, `restaurantName` | CAMIS id and the DBA name |
| `address`, `borough`, `zipcode` | building + street, borough and ZIP |
| `phone`, `cuisine` | the published business phone and cuisine |
| `inspectionDate`, `inspectionType`, `action` | when and what kind of inspection, and the action taken |
| `notYetInspected` | the string `"true"` for the 4,008 permit rows that have no inspection yet, else `"false"` |
| `violationCode`, `violationDescription` | the DOHMH violation code and its text |
| `criticalFlag` | Critical / Not Critical / Not Applicable |
| `score`, `grade`, `gradeDate` | inspection score, letter grade and the date it was issued |
| `recordDate` | the city's record stamp |
| `latitude`, `longitude` | point coordinates |
| `bbl`, `bin`, `communityBoard`, `councilDistrict`, `censusTract`, `nta` | property and district identifiers for joins |
| `totalMatched` | what the filters matched, by mode: violation rows (rows and aggregate), rows for that restaurant (profile), restaurants (restaurants), codes (codes) |

### Search it

- **Restaurant** - `name="pizza"`, `camis="40745618"`.
- **Area** - `boro="Brooklyn"`, `zipcode="11221"`, `bin="..."`, `bbl="..."`.
- **Cuisine** - `cuisine="Chinese"`, `"Pizza"`, `"Latin"`.
- **Grade** - `grade="C"` for the lowest published grade.
- **Food-safety severity** - `criticalFlag="Critical"`.
- **Violation** - `violationCode="04K"` (evidence of rats), `04L` (mice), `04M` (roaches), `04H` (food adulterated or contaminated).
- **Date window** - `dateFrom="2026-01-01", dateTo="2026-12-31"`.
- **Inspection type** - `inspectionType="Pre-permit"`.

### Example inputs

**Pizza places in Brooklyn** (3,250 matching rows)

```json
{ "name": "pizza", "boro": "Brooklyn", "maxResults": 100 }
```

**Critical violations recorded since 1 January 2026** (45,582 rows)

```json
{ "criticalFlag": "Critical", "dateFrom": "2026-01-01", "maxResults": 100 }
```

**One restaurant's full inspection history**

```json
{ "mode": "profile", "camis": "40745618" }
```

In `mode="profile"`, the top-level `grade`/`score`/`gradeDate` are the
restaurant's **latest issued grade** (the newest row that carries a grade and a
grade date); `profileLatestGrade`/`profileLatestScore`/`profileLatestGradeDate`
repeat the same values, and the raw newest row is still visible in the other
fields.

**Rows graded C**

```json
{ "grade": "C", "maxResults": 200 }
```

### Roll a filter up to one record per restaurant

`mode="restaurants"` folds the matching rows into one record per CAMIS: the
latest inspection in the matched set (`inspectionDate`, `inspectionType`,
`action`), the violation and critical-violation counts **across the matched
rows** (`profileViolations`, `profileCriticalViolations`), the latest grade in
the matched set (`grade`, `score`, `gradeDate`), the restaurant's inspection
count and its first/last inspection date.

```json
{ "mode": "restaurants", "boro": "Brooklyn", "cuisine": "Pizza", "maxResults": 500 }
```

That matches **4,416 records from 430 restaurants** and delivers all 430 of
them. `{ "mode": "restaurants", "grade": "C" }` matches **1,955 restaurants**;
`{ "mode": "restaurants", "boro": "Brooklyn" }` matches **8,027**. `maxResults`
caps how many restaurant records are delivered (default 50, max 5,000). The
whole matched set is always folded before delivery, and `totalMatched` reports
how many restaurants matched, so you can see how many a larger `maxResults`
would return.

Filters select rows, so every count and the grade shown are computed over the
rows that matched - with `grade="C"` you are looking at each restaurant's most
recent C-grade inspection, not necessarily the grade it holds today. Use
`mode="profile"` when you need one restaurant's current state.

Restaurants mode scans the matched rows to build the rollup, so it requires a
filter that narrows the scan and refuses to run rather than silently truncate: an
unfiltered call, or a match set above 80,000 rows, fails with a message asking
for a narrower filter (borough, ZIP, cuisine, grade, violation code or a date
window). Brooklyn alone is 74,577 rows and **Manhattan is 109,259 - above the
ceiling**, so pair a large borough with a cuisine or a date window. You are
billed per delivered restaurant record, not for the scan.

### Violation-code dictionary

```json
{ "mode": "codes" }
```

Returns the **146** DOHMH codes present in the file with `violationCode`,
`violationDescription`, `criticalFlag` and the number of records citing it.
Filters still apply, so they narrow the dictionary as well as the counts:
`boro="Brooklyn"` returns the 125 codes cited in Brooklyn with Brooklyn-only
counts, and `grade="C"` returns 63. Unfiltered, the most-cited code is `10F`
(non-food contact surface not kept clean) with 41,129 records, then `08A`
(vermin harbourage) 25,074 and `06C` (food not protected from contamination)
19,067.

### Aggregate it

```json
{ "mode": "aggregate", "groupBy": "cuisine", "metric": "count" }
```

`groupBy` = `cuisine` (default), `boro`, `grade`, `criticalFlag`,
`inspectionType`, `violationCode` or `zipcode`; `metric` = `count` (matching
rows), `avgScore` or `restaurants`.

**Use `metric="restaurants"` when the question is "how many restaurants".**
`count` counts violation rows; `restaurants` counts distinct CAMIS.

```json
{ "mode": "aggregate", "groupBy": "boro", "metric": "restaurants" }
```

Returns Manhattan 12,586, Brooklyn 8,027, Queens 7,010, Bronx 2,641, Staten
Island 1,121, plus 103 restaurants whose borough the city codes as `0`.

Measured records by borough: Manhattan 109,259, Brooklyn 74,577, Queens 74,216,
Bronx 27,514, Staten Island 9,428; by cuisine: American 43,691, Chinese 28,646,
Coffee/Tea 21,469, Pizza 16,896, Latin American 15,536, Mexican 12,136.
Row-weighted average score by cuisine: Bangladeshi 42.05, Pakistani 36.83,
African 36.28 - each violation row contributes its inspection score, so
inspections with many citations weigh more. (Rows with no cuisine description
average 37.65 and form their own group, so they are not counted inside any
cuisine above.)

### Row grain and how to read it

- **One row = one violation cited at one inspection of one restaurant.** An
  inspection that found nothing is published as one row with empty violation
  fields (7,095 such rows). `totalMatched` counts these rows, not restaurants.
- **`score` is inspection-level and repeats on every violation row of that
  inspection** - do not sum it across rows. Use `mode="aggregate",
  "metric":"avgScore"` for averages.
- **`grade` is blank on 150,638 rows** - inspections before a letter grade was
  issued, or not yet graded. Blank means "no grade", not an error. Grades
  `N` (not yet graded), `P` (pending) and `Z` (reopened) also appear; the
  published counts are A 97,746, B 17,842, C 12,862, N 10,296, Z 5,212, P 696.
  10,284 rows carry a grade but no grade date; a profile's latest grade uses
  only rows that have both.
- **`inspection_date = 1900-01-01` is a sentinel**, not a real date: 4,008 rows
  belong to restaurants that applied for a permit and have not been inspected
  yet. Those rows emit `inspectionDate` empty and `notYetInspected` `true`.
- **The feed contains active restaurants only.** A restaurant that closes drops
  out of the dataset, and its history with it - so historical coverage is not
  a census of every restaurant that ever operated.
- The source itself contains 233 exactly duplicated rows (0.08%); they are
  delivered as filed and each counts as one record.
- When `maxResults` or the run-level charge cap stops a query, the dataset also
  carries one unbilled `_meta` row with `truncated`, `delivered`,
  `totalMatched` and `stopReason`.
- 298 rows carry borough `0`, the source's code for an address the city could
  not place in a borough.
- **Not every row has every field**: 17,483 rows have no score, 1,639 have no
  latitude/longitude/BBL, 5,840 have no BIN and 4,088 have no cuisine
  description. A field being empty means the city did not publish it for that
  inspection.
- Cuisine has 90 distinct values, ZIP code 253, violation code 146.

### Source and licence

New York City Department of Health and Mental Hygiene, published on the
[NYC Open Data portal](https://data.cityofnewyork.us/Health/DOHMH-New-York-City-Restaurant-Inspection-Results/43nn-pn8j)
(public open data).

# Actor input Schema

## `mode` (type: `string`):

rows = inspection/violation records (default). aggregate = one row per group with a count, average score or restaurant count. profile = one restaurant's latest grade, score and counts (camis required). restaurants = one record per restaurant for a narrowed filter; it requires a filter and refuses a match set above 80,000 rows rather than truncating. codes = the DOHMH violation-code dictionary.

## `camis` (type: `string`):

The city's 8-digit restaurant id, e.g. "40745618". Use with mode=profile for one restaurant's full history.

## `name` (type: `string`):

Case-insensitive substring on the DBA name, e.g. "pizza".

## `boro` (type: `string`):

Borough name, e.g. Manhattan, Brooklyn, Queens, Bronx, Staten Island.

## `zipcode` (type: `string`):

Exact 5-digit ZIP code.

## `cuisine` (type: `string`):

Case-insensitive substring on the cuisine description, e.g. "Chinese", "Pizza", "Latin".

## `grade` (type: `string`):

Letter grade. A/B/C are the published grades; N = not yet graded, P = pending, Z = reopened without a grade. 150,638 rows carry no grade at all.

## `criticalFlag` (type: `string`):

Whether the cited violation is critical to food safety.

## `inspectionType` (type: `string`):

e.g. "Cycle Inspection", "Pre-permit", "Re-inspection", "Administrative".

## `violationCode` (type: `string`):

Exact DOHMH violation code, e.g. "02B", "04H", "10F".

## `bin` (type: `string`):

Building Identification Number (links to other NYC building datasets).

## `bbl` (type: `string`):

10-digit Borough-Block-Lot.

## `dateFrom` (type: `string`):

ISO date (YYYY-MM-DD).

## `dateTo` (type: `string`):

ISO date (YYYY-MM-DD).

## `minScore` (type: `number`):

Inspection score (higher = more violation points). The score is an inspection-level value repeated on each violation row.

## `maxScore` (type: `number`):

Inspection score upper bound.

## `groupBy` (type: `string`):

Used only with mode=aggregate.

## `metric` (type: `string`):

count (default) = matching rows; avgScore = row-weighted average inspection score (the score repeats on each violation row, so heavily-cited inspections weigh more); restaurants = distinct restaurants, which is the number to use when you want "how many restaurants", not "how many violation rows".

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

How many records to deliver in rows/profile/restaurants mode (default 50, max 5000). Aggregate and codes modes ignore this.

## Actor input object example

```json
{
  "mode": "",
  "grade": "",
  "criticalFlag": "",
  "groupBy": "",
  "metric": "",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

NYC restaurant inspection records - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (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("j0401/nyc-restaurant-inspections").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("j0401/nyc-restaurant-inspections").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 j0401/nyc-restaurant-inspections --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/nyc-restaurant-inspections"
        }
    }
}
```

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/FgRcfw5yUpUyM1Fii/builds/xscd5voayKuoNnAuC/openapi.json
