NYC BID 311 Explorer

About & Methodology
← Back to Map
← Back to Explorer

About & Methodology

How this tool works, what we discovered building it, and why it matters.

1. Why This Tool Exists

NYC's 76 Business Improvement Districts (BIDs) manage streetscape, sanitation, and public safety in commercial corridors across all five boroughs. To understand quality-of-life issues in their district, BID managers need to see 311 complaint patterns — what types of complaints are filed, where they cluster, how quickly they're resolved, and how trends change over time.

The existing NYC 311 portal at portal.311.nyc.gov/check-status/ offers filters for borough, address radius, problem type, and date range — but not by BID. There is no way to draw a BID boundary and see all complaints within it. This tool fills that gap.

Existing Portal Filter Parameters

ParameterTypeLimitation
boroughDropdownToo broad — a borough contains dozens of BIDs
minlatitude / maxlatitude / minlongitude / maxlongitudeBounding boxRectangular only, can't follow BID boundaries
fromdate / todateDate rangeWorks well, no issues
problemarea / problemCRM option set IDsInternal Dynamics 365 values, not documented
communitydistrictTextBIDs don't align with community districts

None of these parameters support polygon-based spatial queries. A BID manager would need to manually adjust a bounding box, visually filter results, and repeat for each date range — an impractical workflow for ongoing monitoring.

2. Data Sources

NYC Open Data — 311 Service Requests

The primary data source is the NYC Open Data 311 Service Requests dataset, containing over 20 million records since 2010. It is updated daily, provides rich metadata per record, and offers a fully public SODA API with spatial query support.

NYC 311 Portal — Internal API

The 311 portal at portal.311.nyc.gov runs on Microsoft Dynamics 365 Power Apps Portals. It has an internal pin-fetching endpoint used by its map interface.

Field Comparison

FieldOpen DataPortal
Unique Identifierunique_key (e.g., 64365278)srnumber (e.g., 311-22260438)
Complaint Typecomplaint_typedata.problem
Descriptor/DetailsdescriptorDetail page only
Statusstatusdata.status
Agencyagency, agency_nameNot available
Created Datecreated_date (UTC)submitteddate (Eastern)
Closed Dateclosed_dateDetail page only
Resolutionresolution_descriptionDetail page only
Coordinateslat/lng + state planelat/lng
AddressStructured fieldsCombined string
BBL (Parcel ID)bblNot available
Community Boardcommunity_boardNot available
Channelopen_data_channel_typeNot available
Portal Detail LinkNot availableCRM GUID for /sr-details/

3. How We Discovered the Portal API

The 311 portal's internal API is not documented. We discovered it by reverse-engineering the JavaScript on the /check-status/ page. The portal uses an Esri ArcGIS map with clustered markers, and a FilterManager class that aggregates multiple filter types before sending requests to the backend.

The key endpoint is /entity-pin-fetch-service-requests/, which accepts GET parameters for borough, date range, bounding box coordinates, and CRM-internal option set IDs for problem area and problem type. Filter option values are loaded dynamically from:

Each pin in the response contains:

{
  "id": "70f3d629-59e0-ef11-95f5-7c1e52e4043a",  // Dynamics 365 CRM GUID
  "label": "Noise - Street/Sidewalk",
  "sublabel": "616 WEST 184 STREET, MANHATTAN (NEW YORK), NY, 10033",
  "latitude": "40.8513685798",
  "longitude": "-73.9334866503",
  "data": {
    "address": "616 WEST 184 STREET, MANHATTAN (NEW YORK), NY, 10033",
    "problem": "Noise - Street/Sidewalk",
    "submitteddate": "2/1/2025 4:59:48 AM",
    "status": "Closed",
    "srnumber": "311-21789902"
  }
}

Portal Limitations

CORS Restriction: The portal returns Access-Control-Allow-Origin: https://www1.nyc.gov, meaning browser-side JavaScript from any other domain is blocked. This tool uses a server-side proxy to relay requests.
100-Record Cap: The endpoint returns at most 100 pins per request, sorted by most recent. There are no pagination parameters — page, offset, skip, and $skip were all tested and ignored. For a BID with thousands of complaints in a date range, the portal returns only the 100 most recent.
HTML Response: Despite the AJAX-style calling pattern, the endpoint returns a full HTML page (Content-Type: text/html, ~50KB) with JSON data embedded. The proxy must parse this to extract usable data.
No Datetime Filtering: The fromdate and todate parameters accept only dates (YYYY-MM-DD), not datetimes. You cannot request "6am-12pm on Feb 15th" — only "all of Feb 15th." This means time-based subdivision within a single day is impossible through the API.

How We Bypass the 100-Record Cap

A single portal request for a BID over a 30-day range might contain 500+ complaints, but the API only returns the 100 most recent. Pagination parameters don't exist. To capture all records, our server uses an adaptive multi-phase strategy:

Phase 1: Daily Time Windows

Instead of one request for the full date range, the server splits it into one request per day. Most BIDs receive 20-50 complaints per day — well under the 100 cap. For a 30-day query, this means 30 parallel requests (running 8 at a time) instead of 1, capturing nearly 100% of records.

Real-world result: Times Square Alliance, 28-day range — 936 portal records captured from 28 calls, vs. 100 records from the old single-call approach. A 9x improvement.

Phase 2: Spatial Subdivision (Fallback)

If any single day returns exactly 100 records (meaning it hit the cap), that day is automatically re-fetched using spatial tiling. The BID's bounding box is divided into a 2×2 grid of smaller sub-boxes, and each tile is queried separately for that day. This works because the portal's date filter only accepts whole dates — you can't split by time-of-day, so space is the only remaining axis to subdivide.

In practice, Phase 2 rarely triggers. Even Times Square — one of NYC's busiest BIDs — stayed under 100 complaints per day across a full month of testing. Phase 2 exists as a safety net for extreme cases (e.g., a major event or outage generating a surge of complaints in one district on one day).

Interactive Demo: How Spatial Splitting Works

Use the slider below to simulate increasing complaint volume for a single day in the Hudson Square BID (bounding box: 819m × 530m). Watch what happens when the 100-record portal cap is reached.

Hudson Square BID — Single Day Simulation 1 API call
Captured by single request Truncated (lost) Recovered by spatial split

Phase 3: Deduplication & Caching

After spatial tiling, records near tile boundaries may appear in multiple tiles. The server deduplicates by SR number before returning results. The complete result set is then cached in memory for 10 minutes, so repeat queries are instant.

PhaseWhat It DoesWhen It RunsCalls
1Split date range into daily windows, fetch in parallel (8 concurrent)Always1 per day
2Re-split capped days into 2×2 spatial tilesOnly if a day hits 1004 per capped day
3Deduplicate by SR number + cache 10 minAlways0
ScenarioTotal CallsEstimated Time
Quiet BID, 7 days7<1 second
Busy BID, 30 days (no caps)30~3 seconds
Busy BID, 30 days (2 capped days)30 + 8 = 38~4 seconds

4. Record Matching: No Primary Key

The two data sources use completely different identifier systems with no crosswalk:

Initial hypothesis: strip "311-" from the SR number to get the unique_key. This is wrong. The numbers are in entirely different ranges and represent independent ID systems.

Composite Key Matching

Through systematic testing of 200+ records across Manhattan and Brooklyn, we developed a composite key matching strategy:

  1. Timestamp alignment: The two sources store timestamps with a consistent offset equal to the Eastern Time UTC offset (4-5 hours depending on DST). Empirically verified: subtracting the ET offset from the portal's numeric timestamp produces the Open Data timestamp exactly. We apply this correction and allow a ±3 minute window to handle timestamp rounding in certain complaint types.
  2. Complaint type matching: Case-insensitive comparison. Portal may use "Heat/Hot Water" while Open Data uses "HEAT/HOT WATER".
  3. Address verification: Extract street name (remove house number and borough suffix) and check for substring match.

Verified Match Rates

TestMethodMatch Rate
Manhattan, exact timestampTimestamp + type + address80/100 (80%)
Manhattan, 3-min windowFuzzy timestamp + case-insensitive91/100 (91%)
Brooklyn, 3-min windowFuzzy timestamp + case-insensitive91/100 (91%)
Reverse (OD → Portal)Same composite key96/102 (94%)

Why ~9% Don't Match

5. Why Open Data Is the Source of Truth

Open Data is the primary data source for this tool. Here's why:

The portal's value is its CRM GUID which enables deep-linking to the /sr-details/ page for full agency response details. When records match (~91%), we include this link in the map popup.

6. The BID Polygon Challenge

The BID boundary dataset from NYC Open Data (data.cityofnewyork.us/resource/7jdm-inj8) does not contain district boundaries. Instead, it contains tax lot parcel polygons — the individual building footprints within each BID. Streets, sidewalks, and public rights-of-way are excluded.

The Problem

BIDPolygon RingsArea CoveredStreet Gaps
Times Square Alliance4536%64%
Alliance for Downtown NY11937%63%
Village Alliance4012%88%
Union Square Partnership2013%87%
Diamond District331%69%

If we used the raw polygons for point-in-polygon filtering, we would miss 60-88% of 311 requests — especially street-level complaints (noise, potholes, sidewalk conditions, encampments) which are geocoded to street centerlines.

The Solution: Polygon Buffering

We buffer each BID's parcel polygons by 30 meters (approximately half the width of a typical NYC street) using Turf.js. The buffered shapes overlap across streets, creating a continuous boundary that covers the full district area.

Buffer DistanceGap CoverageNotes
10m55%Too narrow for wide avenues
15m70%Covers most side streets
20m76%Good balance
30m84%Used in this tool — covers avenues
50m93%Risk of including adjacent non-BID areas

On the map, you can see both layers: the blue dashed outlines are the raw tax lot parcels, and the yellow solid outline is the 30m-buffered boundary used for filtering.

7. Coordinate Accuracy

The two data sources geocode addresses independently, producing slightly different coordinates for the same complaint:

MetricValue
Mean distance between sources37.68m
Max distance89m
Within 50m85% of records
Within 100m100% of records

Open Data provides parcel-level geocoding: 95% of records have a BBL (Borough-Block-Lot) identifier, and 100% have state plane coordinates. This means Open Data coordinates point to the actual tax lot associated with the address.

The portal appears to use a different geocoding service that produces rooftop or street-centerline coordinates. For map display, we use Open Data coordinates as primary (matched and OD-only records) and portal coordinates only for portal-only records.

8. How the Map Works

Data Flow

  1. User selects a BID from the dropdown
  2. The raw tax lot parcels are buffered by 30m and merged into a single boundary
  3. A bounding box (with 50m padding) is computed around the buffered boundary
  4. Two parallel API calls are made:
    • Open Data (direct from browser): SODA within_box() query with date range, paginated
    • Portal (via server proxy): Pin-fetch with bounding box coordinates, max 100 results
  5. Returned points are filtered client-side using Turf.js booleanPointInPolygon() against the buffered boundary
  6. Records are matched between sources using the composite key (timestamp + type + address)
  7. Results are classified as Matched (both), Open Data only, or Portal only
  8. Pins are plotted with color-coding by complaint type (fill) and source (border)

Pin Types

Pin BorderSourceExpected %What You Get
GreenMatched (both)~85%Full Open Data fields + portal detail link
BlueOpen Data only~9%Rich fields, no portal link
OrangePortal only~6%Basic fields + portal detail link

9. Future Expansion

This tool's architecture — BID polygons + geospatial data overlay — can be extended to incorporate other NYC datasets for a comprehensive district dashboard:

NYPD Crime Data

CompStat crime statistics and 911 call data, available on NYC Open Data. Would show crime patterns alongside quality-of-life complaints, enabling BIDs to correlate public safety with 311 activity.

DOB Building Permits

Department of Buildings permit filings and violations. Useful for BIDs tracking construction activity, scaffolding complaints, and development patterns in their district.

HPD Housing Violations

Housing Preservation & Development violation data. Relevant for BIDs with residential components to understand building condition and habitability issues.

DEP Environmental Data

Water quality, noise monitoring, and air quality data from the Department of Environmental Protection. Would add an environmental health layer to the BID dashboard.

BID-to-BID Comparison

Side-by-side comparison mode showing two BIDs with normalized metrics (complaints per acre, time to resolution, complaint type distribution) for benchmarking.

Time-Series Analysis

Historical trend analysis showing how complaint patterns change over months and years. Seasonal analysis, year-over-year comparisons, and anomaly detection.

Real-Time Streaming

Integration with the Socrata streaming API for near-real-time complaint monitoring. Push notifications when new complaints are filed within a BID.

Exportable Reports

PDF and PowerPoint report generation for BID board meetings, with auto-generated charts, maps, and executive summaries of complaint activity.

10. Technical Stack

ComponentTechnologyWhy
ServerNode.js + ExpressProxies portal API (CORS bypass), serves static files, easy deployment
MapLeaflet + MarkerClusterFree, lightweight, handles thousands of pins with clustering
Spatial ProcessingTurf.jsClient-side polygon buffering, point-in-polygon, bounding box computation
ChartsChart.jsLightweight charting with no build step
Date PickerFlatpickrTiny, range-mode support, dark theme
HeatmapLeaflet.heatDensity visualization layer
Primary APINYC Open Data SODACORS open, unlimited records, spatial queries
Secondary API311 Portal (proxied)Portal detail links, supplementary data

Built with Claude · Data sourced from NYC Open Data and the NYC 311 Portal