Restaurant Health Inspection Intelligence
Pricing
from $5.00 / 1,000 record scrapeds
Restaurant Health Inspection Intelligence
Find failed inspections, repeated violations, closures, and compliance trends from official public records (NYC, Chicago). Returns normalized records plus derived intelligence: risk score, repeat-offender flags, and likely service needs.
Pricing
from $5.00 / 1,000 record scrapeds
Rating
0.0
(0)
Developer
Aiden Mason
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
Find failed inspections, repeated violations, closures, reinspections, and restaurant compliance trends from official public records — and turn them into structured, commercially useful intelligence.
This Apify Actor collects restaurant health inspection records from official public open-data sources, normalizes them into one consistent schema, and adds a derived intelligence layer: risk scores, repeat-offender flags, declining-performance detection, reinspection opportunities, and likely service needs (pest control, deep cleaning, food-safety consulting, equipment maintenance, training).
It is positioned as inspection intelligence, not just a scraper. Every record includes a source URL and source record ID so you can verify it with the original agency.
Supported jurisdictions (v1)
| Jurisdiction | Source | Platform | Records | Updated |
|---|---|---|---|---|
| New York City (DOHMH) | DOHMH Restaurant Inspection Results (43nn-pn8j) | Socrata | ~296,000 | Daily |
| Chicago (CDPH) | Food Inspections (4ijn-s7e5) | Socrata | ~313,000 | Frequent |
Both are official, free, no-login public open-data endpoints accessed via direct HTTP (Socrata SODA API). No proxies, no browser automation, no CAPTCHA, no authentication required.
More Socrata jurisdictions (Seattle/King County, NYS, San Francisco, Dallas, Toronto) share the same connector and are on the expansion roadmap.
What it answers
- Which restaurants failed an inspection this week / month?
- Which restaurants have repeated violations?
- Which restaurants have declining scores?
- Which restaurant chains/locations have the most violations?
- Which businesses received emergency closures?
- Which restaurants were reinspected?
- Which newly inspected restaurants may need compliance services (pest control, cleaning, consulting, maintenance, training)?
Input
See .actor/input_schema.json for the full, UI-documented schema. Key fields:
| Field | Type | Description |
|---|---|---|
jurisdiction | enum | nyc or chicago (required) |
restaurantName | string | Case-insensitive contains match on business name |
zipCode | string | Filter to one ZIP/postal code |
dateFrom / dateTo | date (YYYY-MM-DD) | Inspection date range (leave empty for no bound) |
inspectionResults | multi-select | pass/fail/conditional/closed/pending/not_scored/unknown |
minScore / maxScore | number | Inspection score range (NYC; higher = more violations) |
violationCategories | multi-select | food_temp/food_protection/pest_control/sanitation/plumbing_water/equipment/hygiene/structural/signage/other |
criticalViolationsOnly | boolean | Keep only inspections with ≥1 critical violation |
closureOnly | boolean | Keep only closures |
reinspectionOpportunityOnly | boolean | Fail/conditional with no later pass |
maxResults | integer | Cap on returned records (default 1000; 0 = up to 100,000) |
sortOrder | enum | date_desc/date_asc/score_desc/risk_desc |
includeViolationDetails | boolean | Populate the nested violations array (default true) |
includeHistoricalInspections | boolean | Aggregate per-business history for intelligence (default true) |
keyword | string | Substring match on violation description text (e.g. rodent) |
socrataAppToken | string (secret) | Optional free token to raise rate limits; not required |
requestDelayMs | integer | Polite delay between paginated requests (default 150) |
Example input
{"jurisdiction": "nyc","dateFrom": "2026-06-01","dateTo": "2026-06-30","inspectionResults": ["fail", "conditional"],"criticalViolationsOnly": true,"maxResults": 1000,"sortOrder": "risk_desc"}
Output
Each dataset row is one inspection (NYC's per-violation source rows are aggregated into one record per inspection). Records use a stable normalized schema (see .actor/dataset_schema.json, which also defines the Apify Output-tab views). Unavailable values are null.
Core fields
record_id, source_record_id, source_url, jurisdiction, county, state, restaurant_name, legal_business_name, facility_type, chain_name, address, city, postal_code, latitude, longitude, license_number, inspection_id, inspection_date (ISO 8601), inspection_type, inspection_result, inspection_score, inspection_grade, risk_level, closure_status, closure_date, reopen_date, reinspection_required, reinspection_date, total_violations, critical_violation_count, noncritical_violation_count, violations[], inspector_notes, source_platform, scraped_at.
Violation objects (violations[])
violation_code, violation_description, violation_category, severity (critical/major/minor/informational/unknown), critical, corrected_on_site, repeat_violation.
Intelligence fields (derived, documented, configurable)
lead_category, risk_score (0–100), repeat_offender, recent_failure, recent_closure, declining_performance, reinspection_opportunity, likely_service_needs[], inspection_frequency_days, days_since_last_inspection, days_since_last_failure, location_inspection_count, chain_inspection_count, violation_trend (improving/stable/worsening/unknown), data_completeness_score.
Original source values are preserved in
_src_*fields (e.g._src_inspection_type,_src_action,_src_cuisine) so nothing is silently overwritten.
Intelligence rules (documented thresholds)
| Field | Rule | Default threshold |
|---|---|---|
recent_failure | Business has a failed inspection within N days of now | 30 days |
repeat_offender | ≥ N failed/conditional inspections within trailing M months | 2 / 12 months |
recent_closure | Business has a closure within N days of now | 90 days |
declining_performance | Latest score worse than average of prior two by ≥ Δ (NYC); or result rank worsens (Chicago) | Δ = 7 |
reinspection_opportunity | Latest inspection fail/conditional with no later pass in the returned set | — |
likely_service_needs | Violation categories in the last 90 days mapped to services | 90 days |
risk_score | Weighted: recent_failure +25, repeat_offender +20, recent_closure +15, declining +10, reinspection +15, critical-density up to +15, score up to +10 (capped 100) | — |
lead_category | Primary service need, or compliance_monitoring for closures, else research/none | — |
Category → service mapping: pest_control→pest_control, sanitation/structural→deep_cleaning, plumbing_water/equipment→equipment_maintenance, food_temp/food_protection→food_safety_consulting, hygiene→training, signage→compliance_monitoring.
Thresholds are configurable via input overrides (recentFailureDays, recentClosureDays, repeatMonths) and DEFAULT_THRESHOLDS in src/intelligence/index.js.
Derived fields are estimates ("likely"/"possible"). They never assert a business is unsafe. Always verify with the original agency.
Example output
See docs/example_output.json for five full enriched records. Example snippet:
{"record_id": "nyc:50188053:2026-07-09:preopening","restaurant_name": "LILEO","city": "BROOKLYN","state": "NY","inspection_date": "2026-07-09","inspection_result": "pending","inspection_grade": "N","inspection_score": 33,"critical_violation_count": 2,"risk_score": 45,"recent_failure": false,"likely_service_needs": ["food_safety_consulting"],"lead_category": "food_safety_consulting","source_url": "https://data.cityofnewyork.us/Health/DOHMH-New-York-City-Restaurant-Inspection-Results/43nn-pn8j","source_record_id": "camis:50188053","scraped_at": "2026-07-13T17:00:00.000Z"}
Run locally
Requirements: Node.js 18+.
npm install# Unit + integration tests (Node test runner)npm test# Live validation against the NYC source (writes docs/05-validation-report.md)node scripts/validate.js# Run the Actor locally (uses Apify local storage)APIFY_LOCAL_STORAGE_DIR=./storage node src/main.js # provide input via storage/default_value.json
Example local input (storage/key_value_stores/default/INPUT.json):
{ "jurisdiction": "nyc", "dateFrom": "2026-06-01", "dateTo": "2026-06-30", "maxResults": 500 }
Deploy
apify loginapify create # or push this folderapify push
The Dockerfile builds on apify/actor-node:20. Dependencies are production-only in the image.
Example API call
curl -X POST "https://api.apify.com/v2/acts/<your-username>/restaurant-health-inspection-intelligence/runs?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"jurisdiction":"nyc","inspectionResults":["fail"],"criticalViolationsOnly":true,"dateFrom":"2026-06-01","maxResults":1000}'
Architecture
src/main.js # Actor entry: input validation, orchestration, dataset push, OUTPUT summarysources/socrata.js # Reusable Socrata SODA client (pagination, retries, rate limiting)nyc.js # NYC config + per-violation -> per-inspection aggregation + normalizationchicago.js # Chicago config + violation-blob parser + normalizationindex.js # Source registry + collect()normalization/dates.js # ISO 8601 date normalizationresults.js # Result/severity/inspection-type controlled vocabulariesviolations.js # Violation parsing + service-category classificationaddresses.js # Address/postal/dedup-key helpersintelligence/index.js # Risk scoring, repeat offenders, trends, likely service needsutils/filters.js # Post-aggregation filtering + sorting + maxResultsmonitoring.js # Source-change / data-quality warnings.actor/ # Apify Actor definition (publish config)actor.json # Name, version, memory limits, input/output/dataset schema wiringinput_schema.json # UI-documented input schemadataset_schema.json # Dataset item JSON Schema + Output-tab views (overview/leads/compliance)output_schema.json # OUTPUT run-summary schemadocs/ # Research + validation deliverablestests/ # 122 automated tests (Node test runner)scripts/validate.js # Live 100-record validation + 1000-record smoke test
Source-specific extraction is kept separate from shared normalization and intelligence logic, so new Socrata jurisdictions plug in via a config + normalizer without touching the core.
Required Actor features
Input schema ✓ · Dataset output ✓ · Pagination ✓ · Request retries ✓ · Rate limiting ✓ · Duplicate detection (record_id) ✓ · Date filtering ✓ · Result filtering ✓ · Score filtering ✓ · Restaurant-name filtering ✓ · Violation parsing ✓ · Severity classification ✓ · Max-result limits ✓ · Clear logs ✓ · Run statistics (OUTPUT) ✓ · Graceful missing-field handling ✓ · Source-link preservation ✓ · Error reporting (circuit breaker + warnings) ✓ · Data-quality rules ✓.
Monitoring & source-change detection
The Actor logs warnings (and records them in the OUTPUT summary) for: sudden zero-result runs, raw-rows-but-no-normalized (schema drift), missing required fields (>5%), unmapped inspection_result values, and pagination accounting mismatches.
Validation
A live validation run against NYC DOHMH (see docs/05-validation-report.md) validated 100 inspections field-by-field: 100% match on restaurant name, address, inspection date, result, score, grade, violation text, closure status, source URL, and record ID. A 1000-record smoke test passed (1,446 inspections processed without crashing).
Legal & ethical use
- Uses only public records from official open-data portals. No authentication, CAPTCHA, paywall, or access-control circumvention.
- Does not collect or republish inspector personal information or unnecessary personal data.
- Preserves inspection dates and result context; old inspections are not presented as current conditions.
- Distinguishes open / closed / reopened and corrected vs. historical violations.
- Derived scores are estimates and never label a business as dangerous. Users must verify with the original agency (NYC DOHMH / CDPH via the preserved
source_url). - Data is attributed to the issuing agency; every record carries
source_urlandsource_record_id.
See docs/03-source-recommendation.md for the full access & compliance assessment.
Known limitations (v1)
- v1 fully supports NYC; Chicago is supported as an expansion target (no numeric score, so Chicago risk uses result-rank decline and the violation-text blob is parsed heuristically).
chain_nameis intentionallynull— chain ownership is not inferred from name similarity without a documented rule.- The dataset reflects each source's rolling window (NYC ~3 years of active establishments).
- Socrata unauthenticated requests are rate-limited; provide a free
socrataAppTokenfor high-volume runs.
Changelog
- 1.0.0 — Initial release. NYC + Chicago Socrata adapters, normalized schema, intelligence layer, 122 tests, live 100-record validation (100% match).