# City permitted events (NYC & San Francisco), change detection (`quietfetch/city-permitted-events`) Actor

Upcoming permitted public events — block parties, street fairs, farmers markets, parades, races, film shoots — from New York City's and San Francisco's own open-data APIs, in one schema with per-run change flags (new, updated, removed). Official open data only; no contact details are emitted.

- **URL**: https://apify.com/quietfetch/city-permitted-events.md
- **Developed by:** [Quietfetch](https://apify.com/quietfetch) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 event records

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

## City permitted events (NYC & San Francisco), change detection

Upcoming **permitted public events** — block parties, street fairs, farmers markets, parades, races, film shoots, plaza events — straight from **New York City's and San Francisco's own open-data APIs**, normalised to one schema, with a change flag on every row: what is new since your last run, what moved, and what quietly disappeared.

### Spec (10 lines)

1. **What:** upcoming permitted public events for NYC and San Francisco from each city's official open-data API, normalised to one schema, with per-run change flags.
2. **Buyer:** food trucks and vendors, local retailers, event-listing sites, logistics and rideshare planners, location scouts, local newsrooms — anyone who needs "which street is closed for what, and when" without polling two portals.
3. **Input:** `cities`, `daysAhead` (1–180), `eventTypes`, `includeSportsPermits`, `onlyChanged`, `includeRemoved`, `snapshotStoreName`, `maxRowsPerCity`.
4. **Output:** one row per event occurrence — `city, eventKey, eventId, name, category, sourceType, startLocal, endLocal, startUtc, endUtc, timezone, area, locations[], streetClosure, permitStatus, permittingAgency, communityBoards[], datasetUrl, fetchedAt, change, changedFields[]`.
5. **Pricing:** pay-per-event `event-record` at $0.001 ($1 per 1,000 rows). `onlyChanged` charges only for rows that moved.
6. **Legal floor:** official open-data APIs only, under licences that permit reuse (below); robots.txt checked and its crawl delay obeyed; no event website is scraped.
7. **How it works:** query the city API for events overlapping your window → normalise → merge multi-street permits into one event → diff against your own snapshot → write rows with change flags.
8. **Tests:** golden normalisation per city, time-zone and DST handling, query building, paging and polite-delay behaviour, change detection, and a full run against a local stand-in server. No network needed.
9. **Health:** a small nightly run reads two weeks of San Francisco events and checks the row shape.
10. **Not built:** scraping of ticketing or meetup sites. Cities join only when their data licence permits reuse and their feed is actually kept current.

### Sources and licences

| City | Dataset | Published by | Licence | Refresh |
|---|---|---|---|---|
| New York City | [NYC Permitted Event Information](https://data.cityofnewyork.us/City-Government/NYC-Permitted-Event-Information/tvpp-9vvx) | Office of Citywide Event Coordination and Management | NYC Admin. Code §23-502(d): public data sets are made available "without any registration requirement, license requirement or restrictions on their use" | daily |
| San Francisco | [Temporary Street Closures](https://data.sf.gov/Transportation/Temporary-Street-Closures/8x25-yybr), special events only | SFMTA | Open Data Commons PDDL 1.0 | daily |

Each run stores an attribution line per city in the `ATTRIBUTION` record. Neither city endorses this actor or its output, and neither guarantees the completeness or accuracy of its data; nor does this actor.

### What you get, and what you never get

- **Events, not people.** Rows carry the event name, when, where, what kind of closure, and the permitting agency. **Contact details (phone numbers, email addresses) and the names of individual applicants are never emitted** — the two sources used here do not publish them, and the parser drops any such field if one ever appears.
- **Times you can trust.** NYC publishes local wall-clock times; San Francisco publishes UTC. Every row carries both `startLocal` (city time) and `startUtc`, plus the `timezone`, correct across daylight-saving changes.
- **One row per event.** San Francisco lists one row per closed street segment and NYC sometimes one row per location; segments of the same permit at the same start time are merged, with every street in `locations`.
- **Ongoing permits included.** An event is in your window if it *overlaps* it, so a street closed since spring and still closed this weekend shows up.
- **NYC sports field permits are off by default** — tens of thousands of youth and adult field reservations a month. Turn on `includeSportsPermits` if you want them.

### Categories

`block-party`, `street-festival`, `farmers-market`, `parade`, `athletic-race`, `sidewalk-sale`, `plaza-event`, `open-street`, `religious-event`, `film-production`, `special-event`, `street-event`, `clean-up`, `press-conference`, `sports-permit`, `other`. NYC's own event type maps directly (`sourceType` keeps the original). San Francisco labels everything "Special Event", so its category is inferred from the event name and falls back to `special-event`.

### Change flags

| `change` | Meaning |
|---|---|
| `new` | Not seen on your previous run (every row on your first run) |
| `updated` | Same permit and start time, but the name, end time, locations, closure type, status or category changed — `changedFields` names which |
| `removed` | Was upcoming on your previous run, is still inside this run's window, and is no longer in the city feed: cancelled, denied or rescheduled (the city does not say which) |
| `unchanged` | Nothing tracked moved |

Honesty rules: a run that hit `maxRowsPerCity` never updates your snapshot and never reports removals — a partial read proves nothing about what is missing. A refused or unreadable feed produces one `outcome: "problem"` row for that city, is not charged, and leaves your snapshot untouched.

### Example output row

```json
{
  "city": "sf",
  "eventKey": "sf:1700002:2026-10-17T15:00:00.000Z",
  "name": "Bay Street Fair",
  "category": "street-festival",
  "startLocal": "2026-10-17T08:00:00",
  "startUtc": "2026-10-17T15:00:00.000Z",
  "timezone": "America/Los_Angeles",
  "locations": ["BAY ST between 01ST AVE and 02ND AVE", "BAY ST between 02ND AVE and 03RD AVE"],
  "streetClosure": "all-lanes-closed",
  "permitStatus": "Approved",
  "change": "new"
}
```

*(Illustrative values.)*

### Lawful and responsible use

This actor reads public open data through the cities' published APIs, identifies itself with a named user agent and a contact address, checks robots.txt, and never requests faster than the host's crawl delay (one request per second on both hosts). It does not log in, does not work around a refusal, and does not collect personal data. If you add an optional `SOCRATA_APP_TOKEN` environment variable (a free token from the portal), requests are attributed to your token instead of sharing an anonymous pool.

***

Built and maintained with AI assistance under human review.

# Actor input Schema

## `cities` (type: `array`):

Which cities to read. Leave empty for all. Each is the city's own open-data API.

## `daysAhead` (type: `integer`):

Events that overlap the window from now to this many days ahead. Multi-day permits already in progress are included.

## `eventTypes` (type: `array`):

Only output these normalised categories. Empty = all. Filtering never affects change tracking.

## `includeSportsPermits` (type: `boolean`):

NYC's feed lists tens of thousands of youth and adult sports field reservations a month. Off by default.

## `onlyChanged` (type: `boolean`):

Write (and charge for) only events that are new, changed or removed since your last run. The first run writes everything.

## `includeRemoved` (type: `boolean`):

Emit a row for an upcoming event that disappeared from the city feed since your last run (cancelled, denied or rescheduled).

## `snapshotStoreName` (type: `string`):

Named key-value store that remembers the previous run for change detection. Use different names for independent monitors.

## `maxRowsPerCity` (type: `integer`):

Safety cap. A capped read still outputs rows but does not update the snapshot or report removals.

## `requestDelaySecs` (type: `integer`):

Minimum 1. The hosts ask for a 1-second crawl delay; a longer robots.txt value always wins.

## Actor input object example

```json
{
  "cities": [
    "nyc",
    "sf"
  ],
  "daysAhead": 30,
  "eventTypes": [],
  "includeSportsPermits": false,
  "onlyChanged": false,
  "includeRemoved": true,
  "snapshotStoreName": "city-events-snapshots",
  "maxRowsPerCity": 20000,
  "requestDelaySecs": 1
}
```

# Actor output Schema

## `results` (type: `string`):

One row per event occurrence: city, cityName, eventKey, eventId, name, category, sourceType, startLocal, endLocal, startUtc, endUtc, timezone, area, locations\[], streetClosure, permitStatus, permittingAgency, communityBoards\[], datasetUrl, fetchedAt, change (new | updated | unchanged | removed), changedFields\[], outcome. No contact details or applicant names.

## `changesView` (type: `string`):

The same dataset, columns for monitoring: change, which fields moved, when, what, where.

## `problemsView` (type: `string`):

Cities whose feed could not be read this run, with the reason. Never charged for.

## `runSummary` (type: `string`):

RUN\_SUMMARY: per-city event counts, change tallies, window, requests made, robots.txt result and the attribution line.

# 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("quietfetch/city-permitted-events").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("quietfetch/city-permitted-events").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 quietfetch/city-permitted-events --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,quietfetch/city-permitted-events"
        }
    }
}
```

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/UKc5vE4vAwOvrkdtT/builds/FzUmawi6Kux7EFbFe/openapi.json
