# SF Restaurant Inspection Scraper (`crawlerbros/sf-restaurant-inspection-scraper`) Actor

Scrape the official SF Dept. of Public Health restaurant/food-facility inspection dataset. Filter by neighborhood, district, rating status, permit/inspection type, violations, or date; lookup by permit number. Free Socrata API, no login.

- **URL**: https://apify.com/crawlerbros/sf-restaurant-inspection-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, Travel, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## SF Restaurant Inspection Scraper

Scrape the official **San Francisco Department of Public Health (DPH) Health Inspection** open dataset. Search or filter San Francisco restaurant/food-facility inspections by neighborhood, supervisor district, facility rating status, permit type, inspection type, violation count, or date range — or pull a facility's complete inspection history by its permit number. Powered by the public SF Open Data (Socrata) API. No login, no API key, no proxy required.

### What this actor does

- **Two modes:** `search` (filter/browse) and `byPermitNumber` (exact facility lookup)
- **Rich filters:** neighborhood, supervisor district, facility rating status, inspection type, inspection frequency type, permit type, violation count range, "has violations only", date range, facility-name keyword
- **Search near a location** — filter to facilities within a radius (in meters) of any latitude/longitude point
- **Full-text search** across facility name, address, and other text fields
- **Every inspection row** — one record per inspection event, so a single facility can appear multiple times across its inspection history
- **Geo data** — latitude/longitude, analysis neighborhood, supervisor district, inspection district/subdistrict/subsector
- **Empty fields are omitted** — an inspection with no cited violations simply has no `violationCount`/`violationCodes` field, never a placeholder

### Output per inspection record

- `permitNumber` — unique DPH facility permit number
- `dba` — facility name ("doing business as")
- `city`, `state` — always San Francisco, CA
- `streetAddress`
- `analysisNeighborhood` — SF planning analysis neighborhood
- `supervisorDistrict` — Board of Supervisors district (1-11)
- `district`, `subdistrict`, `subsector` — DPH internal inspection-area codes
- `census` — census tract code for the facility (when available)
- `inspectionDate`, `inspectionType`, `inspectionFrequencyType`, `inspector`
- `permitType` — DPH permit/facility classification
- `totalTimeMinutes` — time DPH spent on the inspection
- `facilityRatingStatus` — Pass / Conditional Pass / Closure
- `violationCount`, `violationCodes` — only present when violations were cited
- `latitude`, `longitude`
- `dataAsOf` — when SF Open Data last refreshed this row
- `rowId` — stable Socrata row identifier
- `sourceUrl` — direct link to this exact record on SF's open-data API
- `recordType: "inspection"`, `scrapedAt`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` (filter/browse) or `byPermitNumber` (exact lookup) |
| `searchQuery` | string | – | Full-text search across facility name/address text fields (mode=search) |
| `dbaKeyword` | string | – | Case-insensitive substring match on facility name |
| `permitNumbers` | array | – | Permit numbers to fetch inspection history for (mode=byPermitNumber) |
| `analysisNeighborhood` | string | any | One of 39 SF analysis neighborhoods |
| `supervisorDistrict` | string | any | Board of Supervisors district 1-11 |
| `facilityRatingStatus` | string | any | Pass / Conditional Pass / Closure |
| `inspectionType` | string | any | One of 14 DPH inspection types |
| `inspectionFrequencyType` | string | any | 1 (annual) / 2 (semi-annual) / High Hazard / N/A |
| `permitType` | string | any | One of 105 DPH permit/facility classifications |
| `inspectionDateFrom` / `inspectionDateTo` | string | – | ISO date range (YYYY-MM-DD) |
| `minViolationCount` / `maxViolationCount` | int | – | Violation-count bounds (0–50) |
| `hasViolationsOnly` | boolean | `false` | Only include inspections with at least one cited violation |
| `nearLatitude` / `nearLongitude` | number | – | Center point for a radius search. Both must be set together to activate the filter. |
| `nearRadiusMeters` | int | `500` | Radius (1–50,000 m) around `nearLatitude`/`nearLongitude`. Only applied when both coordinates are set. |
| `sortBy` | string | `inspectionDateDesc` | Sort order — inspection date, violation count, facility name, neighborhood, or supervisor district |
| `appToken` | string | – | Optional free Socrata app token for higher rate limits |
| `maxItems` | int | `50` | Hard cap on emitted records (1–10000) |

#### Example: browse the latest inspections in the Mission with a Conditional Pass or worse

```json
{
  "mode": "search",
  "analysisNeighborhood": "Mission",
  "facilityRatingStatus": "Conditional Pass",
  "maxItems": 50
}
```

#### Example: full inspection history for a specific facility

```json
{
  "mode": "byPermitNumber",
  "permitNumbers": ["93127"]
}
```

#### Example: routine inspections with violations in a date range

```json
{
  "mode": "search",
  "inspectionType": "Routine",
  "hasViolationsOnly": true,
  "inspectionDateFrom": "2025-01-01",
  "inspectionDateTo": "2025-12-31",
  "maxItems": 200
}
```

#### Example: keyword search for a restaurant chain

```json
{
  "mode": "search",
  "dbaKeyword": "pizza",
  "maxItems": 100
}
```

#### Example: facilities within 500m of SF Civic Center

```json
{
  "mode": "search",
  "nearLatitude": 37.7793,
  "nearLongitude": -122.4193,
  "nearRadiusMeters": 500,
  "maxItems": 100
}
```

### Use cases

- **Food safety research** — track violation trends by neighborhood, permit type, or time period
- **Consumer apps** — surface a restaurant's rating status and violation history before a visit
- **Real estate / business intelligence** — assess food-service density and compliance by neighborhood or supervisor district
- **Journalism** — investigate closures, repeat violators, or rating patterns
- **Academic research** — bulk-export inspection data for public health studies

### FAQ

**What is the data source?**
The San Francisco Department of Public Health's "Health Inspection Scores (2024-Present)" dataset, published on SF Open Data (Socrata, dataset ID `tvy3-wexg`) and updated monthly by the city.

**Why 2024-Present and not the full inspection history?**
SF DPH retired its older "LIVES" scoring standard at the end of 2023. The 2024-Present dataset uses the current facility-rating methodology and is the dataset the city actively maintains going forward; older LIVES-era datasets use a different, no-longer-updated schema (numeric 0-100 scores instead of Pass/Conditional Pass/Closure).

**Is this affiliated with the City and County of San Francisco or DPH?**
No. This is an independent, third-party actor built on SF's public open-data API.

**Why do some records have no `violationCount`/`violationCodes` field?**
Not every inspection results in a cited violation. When an inspection has zero violations, both fields are simply omitted rather than showing a placeholder or a zero that could be confused with "not yet processed".

**Why does one facility appear multiple times?**
Each row in the source dataset represents one inspection visit. A facility inspected several times over the years will have one record per visit.

**What does `facilityRatingStatus` mean?**
The outcome DPH assigned after the inspection: `Pass`, `Conditional Pass` (violations found that must be corrected on a timeline), or `Closure` (facility closed due to health/safety risk).

**How fresh is the data?**
SF Open Data refreshes this dataset monthly. Each record includes a `dataAsOf` field showing when it was last synced upstream from DPH's inspection system.

**Are there rate limits?**
The Socrata API allows unauthenticated access with reasonable limits. Supplying a free Socrata app token (optional) raises those limits, but the actor works without one.

**How does the "search near a location" filter work?**
Set both `nearLatitude` and `nearLongitude` (and optionally `nearRadiusMeters`, default 500m) to only return inspections within that radius of a point. Setting only one of the two coordinates disables the filter — both are required together. This is combined with all other filters (neighborhood, rating status, date range, etc.) using AND.

**What fields are NOT included in the output?**
Two documented columns (`suspension_notes`, `inspection_notes`) are 100% empty across the entire live dataset, so they're excluded entirely.

# Actor input Schema

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

What to fetch.

## `searchQuery` (type: `string`):

Free-text search across facility name, address, and other text fields (mode=search). Leave blank to browse without a text query.

## `dbaKeyword` (type: `string`):

Case-insensitive substring match on the facility's DBA ("doing business as") name (mode=search). Example: `pizza`.

## `permitNumbers` (type: `array`):

Facility permit numbers to fetch full inspection history for. Example: `93127`.

## `analysisNeighborhood` (type: `string`):

Filter to a single SF analysis neighborhood.

## `supervisorDistrict` (type: `string`):

Filter to a single SF Board of Supervisors district (1-11).

## `facilityRatingStatus` (type: `string`):

Filter by the outcome/status assigned after the inspection.

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

Filter by the type of inspection performed.

## `inspectionFrequencyType` (type: `string`):

Filter by the DPH inspection frequency code assigned to the facility.

## `permitType` (type: `string`):

Filter to a single DPH permit/facility type.

## `inspectionDateFrom` (type: `string`):

Drop inspections before this date.

## `inspectionDateTo` (type: `string`):

Drop inspections after this date.

## `minViolationCount` (type: `integer`):

Drop inspections with fewer cited violations than this.

## `maxViolationCount` (type: `integer`):

Drop inspections with more cited violations than this.

## `hasViolationsOnly` (type: `boolean`):

Only include inspections that had at least one cited violation.

## `nearLatitude` (type: `number`):

Latitude of a point to search near (mode=search). Must be combined with `nearLongitude` — both are required together to activate the radius filter. Example: `37.7749` (SF Civic Center).

## `nearLongitude` (type: `number`):

Longitude of a point to search near (mode=search). Must be combined with `nearLatitude`. Example: `-122.4194` (SF Civic Center).

## `nearRadiusMeters` (type: `integer`):

Radius in meters around the `nearLatitude`/`nearLongitude` point. Only applied when both coordinates are set.

## `sortBy` (type: `string`):

Sort order for results.

## `appToken` (type: `string`):

Optional free Socrata app token to raise API rate limits. Get one at https://data.sfgov.org/profile/app\_tokens. Not required — the actor works without it.

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

Hard cap on emitted records.

## Actor input object example

```json
{
  "mode": "search",
  "permitNumbers": [],
  "analysisNeighborhood": "",
  "supervisorDistrict": "",
  "facilityRatingStatus": "",
  "inspectionType": "",
  "inspectionFrequencyType": "",
  "permitType": "",
  "hasViolationsOnly": false,
  "nearRadiusMeters": 500,
  "sortBy": "inspectionDateDesc",
  "maxItems": 50
}
```

# Actor output Schema

## `inspections` (type: `string`):

Dataset containing all scraped SF restaurant inspection records.

# 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 = {
    "mode": "search",
    "permitNumbers": [],
    "analysisNeighborhood": "",
    "supervisorDistrict": "",
    "facilityRatingStatus": "",
    "inspectionType": "",
    "inspectionFrequencyType": "",
    "permitType": "",
    "hasViolationsOnly": false,
    "nearRadiusMeters": 500,
    "sortBy": "inspectionDateDesc",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/sf-restaurant-inspection-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 = {
    "mode": "search",
    "permitNumbers": [],
    "analysisNeighborhood": "",
    "supervisorDistrict": "",
    "facilityRatingStatus": "",
    "inspectionType": "",
    "inspectionFrequencyType": "",
    "permitType": "",
    "hasViolationsOnly": False,
    "nearRadiusMeters": 500,
    "sortBy": "inspectionDateDesc",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/sf-restaurant-inspection-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 '{
  "mode": "search",
  "permitNumbers": [],
  "analysisNeighborhood": "",
  "supervisorDistrict": "",
  "facilityRatingStatus": "",
  "inspectionType": "",
  "inspectionFrequencyType": "",
  "permitType": "",
  "hasViolationsOnly": false,
  "nearRadiusMeters": 500,
  "sortBy": "inspectionDateDesc",
  "maxItems": 50
}' |
apify call crawlerbros/sf-restaurant-inspection-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/sf-restaurant-inspection-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/c8honinKbfglax1Gq/builds/uCh2bNH6ScDWu8Zcv/openapi.json
