Documentation
A reference manual covering the facility dataset, roster collection, and how to use the registry.
Facility Registry
How the baseline facility dataset is built from federal data sources.
Overview
The Facility Registry combines two independent federal datasets — supplemented by manually verified additions — to create the most comprehensive public directory of jail facilities in the United States. Each facility is tagged with a BJS, HIFLD, or Manual source badge throughout the site.
- 2,936 facilities from the BJS Census of Jails (2019)
- 616+ additional facilities from the DHS HIFLD dataset that are not present in the BJS data
- Manual additions for facilities not covered by either federal dataset (e.g. newly opened jails, tribal facilities)
Source: BJS Census of Jails
The primary data source is the 2019 Bureau of Justice Statistics Census of Jails (ICPSR 38323), the most recent comprehensive federal survey of jail facilities. BJS collects this data approximately every five years.
Two files from the BJS dataset are used:
- DS0001 — 2,924 local jail facility records with addresses, rated capacity, jurisdiction identifiers, and county FIPS codes
- DS0004 — 12 Federal Bureau of Prisons (BOP) facilities. These have no addresses in the BJS data; coordinates are sourced from verified BOP facility locations
BJS hierarchy
BJS organizes jails into a three-level hierarchy using numeric identifiers:
- Jail Jurisdiction (
jurisid) — the governing authority. The third digit encodes the type: 1 = county, 2 = city, 6 = regional, 8 = other - Reporting Unit (
ru_id) — the agency that operates the jail (e.g. a sheriff's office or police department). One jurisdiction may oversee multiple reporting units - Facility (
facid) — an individual jail building or complex. One reporting unit may operate multiple facilities
Federal BOP facilities sit outside this hierarchy and have no jurisdiction or reporting-unit parent.
Multi-county corrections
BJS assigns a single county FIPS code to all facilities in a jurisdiction, even when buildings sit in different counties. The pipeline corrects this for known multi-county jurisdictions:
- NYC Department of Correction — 12 facilities across 4 boroughs, each assigned their correct county FIPS. ISREGIONAL is set to "1" so population metrics sum all five boroughs (~8.3M) instead of Queens alone
- Blue Ridge Regional Jail (VA) — 5 facilities across 5 counties
- Southwest Virginia Regional Jail — 4 facilities across 4 counties
- Meherrin River Regional Jail (VA) — 2 facilities across 2 counties
Source: HIFLD Prison Boundaries
The secondary data source is the Homeland Infrastructure Foundation-Level Data (HIFLD) prison/jail dataset, maintained by DHS/FEMA and served via ArcGIS. This dataset covers a broader set of facilities but has less structured metadata than BJS.
From the full HIFLD dataset (~6,700 features), the pipeline filters to jail-type facilities only:
- Only
COUNTY,LOCAL, andMULTIfacility types are kept (state prisons, federal prisons excluded) - Facilities with
CLOSEDstatus are excluded - Juvenile-focused facilities (names containing "Juvenile", "Youth", etc.) are excluded
- This produces approximately 3,472 jail-type candidates for matching
HIFLD provides geographic coordinates (lat/lng), facility name, address, county FIPS, facility type, and capacity for some facilities. However, it has no jurisdiction or reporting-unit hierarchy, and capacity data is missing for roughly half of the facilities.
Important caveat: scope differences
The BJS Census of Jails specifically surveys pretrial detention and short-term incarceration facilities — the core focus of the Jail Data Initiative. The HIFLD dataset uses a broader definition of "jail" and may include facilities that do not primarily serve a pretrial detention function. Examples include community corrections centers, social service facilities (e.g. "Catholic Guardian Society"), residential re-entry programs, courthouse holding areas, and similar facilities that fall outside the strict definition of a jail.
Because HIFLD does not classify facilities by function in a way that allows automated filtering to match the BJS scope, all HIFLD_NEW facilities are included in the registry but should be interpreted with this caveat in mind. The HIFLD source badge is shown throughout the site so users can distinguish these records from the BJS-verified facilities.
Matching Algorithm
Each HIFLD facility is compared against all BJS facilities in the same state using a composite similarity score. The score combines three signals:
- Name similarity (40%) — SequenceMatcher ratio between normalized facility names. Common institutional words (Jail, County, Sheriff, Detention, etc.) are stripped before comparison so the score focuses on the distinguishing parts of the name
- Address similarity (30%) — SequenceMatcher ratio between normalized addresses. Street abbreviations are standardized (Street→St, Avenue→Ave, etc.) and non-alphanumeric characters removed
- Geographic proximity (30%) — linear decay from 1.0 (same location) to 0.0 over 50 km, computed via the haversine formula
Classification
Each HIFLD facility is classified into one of three categories:
- MATCHED (2,849) — composite score ≥ 0.60 against a BJS facility. These HIFLD facilities are already represented in the BJS data and are not added to the registry
- HIFLD_SUBFACILITY (5) — not matched by name/location, but shares a street address (similarity ≥ 0.85) with a BJS facility in the same county. These are likely annexes or wings of an existing facility, and are not added separately
- HIFLD_NEW (618) — no match found. These represent facilities that exist in the HIFLD dataset but are absent from the BJS Census of Jails, and are added to the registry
Data Pipeline
The registry is built through a four-step pipeline. Each step produces an intermediate output file:
2. fetch_hifld → hifld_facilities.json (3,472 candidates)
3. match_hifld → bjs_hifld_comparison.csv (3,472 match results)
4. build → facilities.json (merged records + manual + rosters)
Steps 1 and 2 are independent and can run in parallel. Step 3 (matching) requires both. Step 4 (merge) requires steps 1 and 3, and also pulls in manual facilities and confirmed roster URLs from the live registry.
Step 1: Fetch BJS data
Reads the BJS TSV data files, applies multi-county corrections, geocodes all facility addresses, computes population metrics, and produces a clean JSON of BJS facility records.
Step 2: Fetch HIFLD data
Pages through the HIFLD ArcGIS REST API, filters to jail-type facilities, cleans fields, and produces a JSON of HIFLD facility candidates.
Step 3: Match HIFLD against BJS
Loads both datasets, runs the composite matching algorithm (described above), and writes a CSV with every HIFLD facility classified as MATCHED, HIFLD_NEW, or HIFLD_SUBFACILITY.
Step 4: Merge
Loads the BJS facility records, reads the match CSV to find HIFLD_NEW facilities, maps HIFLD fields to the registry schema, and computes capacity-per-100k for HIFLD facilities using Census county populations. The merge step also loads manually added facilities and confirmed roster URLs from the live registry, filters out any facilities marked as excluded, and writes the final combined facilities.json.
Geocoding
BJS facility coordinates are determined through a three-tier geocoding process:
- Census Bureau Batch Geocoder — all BJS facilities with street addresses are submitted to the U.S. Census Bureau geocoder. This matches approximately 87% of addresses
- Nominatim (OpenStreetMap) — unmatched addresses are retried against Nominatim, which resolves an additional ~225 facilities
- County centroid fallback — remaining facilities without a geocoded address are placed at the geographic center of their county, using the Census Bureau Gazetteer. These coordinates are less precise but ensure every facility with a county FIPS code appears on the map
Federal BOP facilities use pre-verified coordinates since the BJS DS0004 file contains no addresses.
A small number of facilities have verified coordinate overrides that take priority over all geocoding results. Currently this covers five Rikers Island facilities whose "Hazen Street, East Elmhurst" addresses are consistently misplaced by geocoders.
HIFLD facilities come with coordinates from the source dataset and are not re-geocoded.
Capacity per 100k
The "Per 100k" metric measures jail bed capacity relative to population: the total rated beds in a jurisdiction divided by the jurisdiction's resident population, multiplied by 100,000. It is computed at the jurisdiction level, so all facilities in the same jurisdiction share the same figure.
Population data comes from the 2019 Census Bureau county population estimates.
Population assignment rules
- Regional jails — the COUNTY field may list multiple counties (e.g. "Cassia County, Minidoka County"). Populations for all listed counties are summed
- County and city jails — population is taken from the facility's county FIPS code. For city jails, this means the enclosing county population is used as an approximation
- Federal BOP facilities — excluded from this metric (no jurisdiction)
- HIFLD-only facilities — capacity per 100k is computed individually using the facility's county FIPS and its own capacity, since these facilities have no jurisdiction grouping
Name Normalization
Facility names, addresses, and other text fields from ALL CAPS sources undergo several transformations:
- CamelCase splitting — some BJS names concatenate institutional words without spaces (e.g. "MercedCountyJail"). A regex iteratively inserts spaces before recognized words (County, Jail, Sheriff, Detention, etc.)
- Title casing — ALL CAPS text is converted to title case ("DOUGLAS COUNTY JAIL" → "Douglas County Jail") while correctly handling possessives ("SHERIFF'S" → "Sheriff's")
- Abbreviation preservation — known abbreviations are kept in ALL CAPS after title casing: MDC, MCC, FDC, FTC, CTR, JR, SR, ED, HQ, DC, DWI, DUI, IPD, GEO, NSD, ICE, RCC, LAPD, NYPD, SFPD, NMJC, JDC, and cardinal directions (NE, NW, SE, SW)
- Mc prefix — "MCDOWELL" → "McDowell" (not "Mcdowell"). The letter after "Mc" is always capitalized
- Ordinal suffixes — "132ND" → "132nd" (not "132Nd"). Ordinal suffixes (st, nd, rd, th) after digits are lowercased
- Special-case words — a small set of words with non-standard capitalization are handled individually: DeKalb, DeSoto, McRae
These rules apply to HIFLD facility names, addresses, and city names. BJS names that are already in mixed case pass through CamelCase splitting only, but known abbreviations are still corrected (e.g. "Dwi" → "DWI").
HIFLD Field Mapping
When HIFLD-only facilities are added to the registry, their fields are mapped to the registry schema as follows:
| Registry field | HIFLD source | Notes |
|---|---|---|
| facid | FACILITYID | Prefixed with "H" (e.g. "H10006714") to distinguish from BJS numeric IDs |
| facname | NAME | Title-cased and CamelCase-split (see Name Normalization) |
| county | COUNTY | Looked up from Census county FIPS data to get proper name with suffix (e.g. "Garfield County", "St. Tammany Parish"). Falls back to title-cased HIFLD value if no FIPS match |
| address | ADDRESS | Title-cased from ALL CAPS (see Name Normalization) |
| city | CITY | Title-cased from ALL CAPS |
| juris_level | TYPE | COUNTY→county, LOCAL→city, MULTI→regional |
| capacity | CAPACITY | Null or ≤0 values treated as missing. -999 sentinel removed |
| lat, lng | geometry.y, geometry.x | From ArcGIS point geometry |
| cntyfips | COUNTYFIPS | Zero-padded to 5 digits |
| jurisid | — | Always null (HIFLD has no jurisdiction hierarchy) |
| ru_id | — | Always null |
| source | — | Set to "hifld" |
Output Schema
Every facility record in facilities.json (and in downloaded CSV/JSON/GeoJSON files) contains these fields:
| Field | Type | Description |
|---|---|---|
| facid | string | Facility identifier. BJS numeric ID, "H" + HIFLD ID, or "M" + sequential number for manually added facilities |
| jurisid | string | null | BJS jurisdiction identifier (9-digit). Null for HIFLD and federal facilities |
| ru_id | string | null | BJS reporting unit identifier. Null for HIFLD and federal facilities |
| facname | string | Facility name (normalized) |
| runame | string | null | Reporting unit (agency) name. Null for HIFLD facilities |
| county | string | null | County name(s). May contain multiple comma-separated counties for regional jails |
| address | string | null | Street address |
| city | string | null | City |
| state | string | null | Two-letter state abbreviation |
| zip | string | null | ZIP code |
| statefips | string | null | Two-digit state FIPS code |
| cntyfips | string | null | Five-digit county FIPS code |
| status | string | "1" = open, "2" = closed |
| juris_level | string | county, city, regional, federal, or other |
| isregional | string | null | "1" if regional jail |
| isprivate | string | null | "1" if privately operated |
| urban | string | null | BJS urban/rural classification. Null for HIFLD |
| capacity | string | null | Rated bed capacity. Null if not reported or ≤0 |
| lat | number | null | Latitude (geocoded or county centroid) |
| lng | number | null | Longitude (geocoded or county centroid) |
| juris_pop | integer | null | Jurisdiction population (2019 Census estimate) |
| juris_capacity | integer | null | Total rated beds across all facilities in the jurisdiction |
| capacity_per_100k | number | null | Beds per 100,000 residents (jurisdiction level) |
| roster_url | string | null | URL of the facility's public jail roster or searchable custody database, if confirmed |
| source | string | "bjs", "hifld", or "manual" |
Roster Data Collection
Ongoing collection of jail roster data from public sources.
Roster URLs
Many jails publish online rosters or searchable databases listing the people currently in their custody. The Facility Registry links to these public roster pages where available. Roster URLs are identified through a combination of automated web searches and manual review.
Facilities with a confirmed roster link display a green "View roster" link in the popup and a Roster badge in the details panel. The roster_url field is included in all data downloads.
Use the Roster filter in the sidebar to show only facilities with (or without) roster links, or click the Roster toggle in the map legend to visually highlight them on the map.
Roster coverage is an ongoing effort. If you know of a public jail roster page that is not listed in the registry, please contact us.
Roster Data Collection
Beyond the static facility registry, we collect jail roster data from public sources on an ongoing basis. Each collection run follows a five-step pipeline. Every step is a hard gate — if it fails, the run aborts and no bad data reaches the archive.
2. Validate files → Check files are structurally sound
3. Spot-check IDs → Confirm person/booking ID fields exist in raw data
4. Stash → Archive raw files to long-term storage
5. Parse → Extract structured records and normalize names
Step 1: Fetch
Downloads raw content from the roster source. Depending on the platform, this may be one or more JSON API responses or a set of HTML pages (list pages and individual detail pages). The step fails on HTTP errors, timeouts, or authentication failures.
Step 2: Validate files
Checks that fetched files are structurally viable before archiving:
- At least one file was returned and no files are empty (0 bytes)
- JSON files must be valid JSON, and must not be empty arrays or objects
- HTML files must not contain error markers (403 Forbidden, 503, CAPTCHA pages, etc.) and must exceed a minimum size threshold
Step 3: Spot-check IDs
A lightweight pre-archive check confirming that the raw data contains person and booking identification fields. Every record must satisfy at least one identification tier for both person and booking (see Person & Booking Identification below). This runs on the raw field names before any normalization, ensuring we never archive data that cannot be parsed into identifiable people and bookings downstream.
Step 4: Stash
Archives all fetched files to long-term storage before any parsing occurs. If a parsing bug is discovered later, the raw data is still intact and can be re-parsed at any time. This separation ensures that collection and interpretation are independent — we never lose source data due to a code error.
Step 5: Parse
Extracts structured records from the raw files. Each record preserves all original fields from the source. The parse step also normalizes names into a consistent format (see Person & Booking Identification) and tags records with facility and jurisdiction identifiers for downstream matching.
Person & Booking Identification
Different jail platforms provide different fields for identifying people and bookings. We use a tiered identification system that prefers more reliable identifiers when they are available:
| Category | Tier | Fields | Reliability |
|---|---|---|---|
| Person | 1 | Unique system ID | High — unique per individual |
| Person | 2 | Full name + date of birth | Medium — rare collisions |
| Person | 3 | Full name alone | Lower — possible collisions |
| Booking | 1 | Unique booking ID | High — unique per booking |
| Booking | 2 | Booking date | Medium — combined with person ID |
Every record must satisfy at least one tier for both person and booking. If a platform does not provide enough fields to meet any tier, its data cannot enter the archive.
Name normalization
Names are normalized across platforms into a consistent format regardless of how the source system stores them. Some platforms provide a single full name field, others split names into first, middle, and last (sometimes with prefix and suffix). The pipeline combines whatever components are available into a single canonical name for each person, enabling consistent identification even when comparing records across different platforms.
Data Quality Gates
For platforms where collecting roster data requires fetching individual detail pages (one per person) after an initial list page, the success threshold depends on where identification fields live:
- 95% threshold — if the list page alone has sufficient identification fields to meet at least one person and booking tier, a 95% detail page success rate is accepted. Missing detail pages lose enrichment data (charges, bond amounts, etc.) but not identity
- 100% threshold — if identification fields are only available on detail pages, 100% success is required. Each missing detail page would be an unidentifiable person, so the run fails rather than producing incomplete data
These thresholds are enforced during validation (Step 2 of the pipeline) and prevent partial or unusable data from reaching the archive.
Booking Records
Roster snapshots tell us who is in custody right now, but each snapshot is a point in time. By comparing consecutive snapshots, we build longitudinal booking records that track each person’s stay from admission to release.
How it works
After each successful scrape, the system compares the new roster against all currently open bookings for that facility:
- New person appears — a booking record is created. If the platform reports an admission date, that date is used as the start time; otherwise the scrape timestamp is used.
- Person still present — the existing booking is continued. Any fields that changed since the last snapshot (charges, bond amounts, housing unit, etc.) are recorded in the booking’s change history.
- Person disappears — after two consecutive absences, the booking is closed. The last time the person was seen is recorded as the end time. If the platform reports an explicit release date, that date is used instead.
Change tracking
Every field on every record is compared between snapshots. Scalar fields (name, status, bond amount) record the old and new value. Array fields like charges use a set-based comparison that is insensitive to ordering — if a charge is added, modified, or dropped, the specific elements that changed are captured without being affected by the order the source system returns them in.
Coverage quality
Because roster snapshots are periodic (not real-time), there is inherent uncertainty about exactly when someone was released. Each booking tracks the gap between consecutive scrapes, and when a booking closes, the system records both a lower bound (last time definitely present) and an upper bound (first scrape where absent) for the release time.
If the scraper experiences an outage — a gap significantly longer than the normal scraping interval — any bookings that close during that period are flagged as having unreliable end times. This allows downstream analysis to distinguish between precise release times and estimates affected by data gaps.
Standardization
Each platform uses different field names and value formats for the same concepts. For example, one platform uses firstName/lastName while another uses first_name/last_name; height may be separate heightFeet + heightInches fields or a single string like 5'10".
The standardization layer converts raw platform-specific fields into a unified schema stored in bookings_standard. This uses per-platform field maps stored in MongoDB that define:
- Which raw field maps to which standard field name
- What transform to apply (rename, value map, currency parsing, height conversion, etc.)
- How to normalize charges (object arrays or parseable strings → common structure)
The Schema page shows all standard field definitions, per-platform coverage, and the specific mappings used.
Standardization runs automatically after each ingest and can also be backfilled for historical data. When a value map encounters an unknown value, it passes the raw value through unchanged and flags it for review.
Scraper System
How roster scrapers are scheduled, tuned, and recovered from failures.
Scheduling
A scheduler daemon continuously monitors all registered scrapers and dispatches them when they become due. Each scraper has a target interval (e.g. every 4 hours, every 24 hours) that determines how often it runs. The scheduler picks the most overdue scraper, runs it in an isolated container, records the result, and computes when it should next run.
Multiple scrapers can run concurrently. Browser-based scrapers (for sites that require JavaScript rendering) have a separate, lower concurrency limit due to their higher resource usage.
Platforms
Many jails use the same commercial software to publish their rosters. Rather than writing a unique scraper for each county, we build platform-level implementations that handle the common fetch and parse patterns for a given software product. Adding a new county on an already-supported platform typically requires only a few lines of configuration (facility IDs, URL, jurisdiction name).
Current platforms include Citizen RIMS and Inmate Roster PHP, with more added as coverage expands.
Auto-tuning
Rather than running every scraper at a fixed frequency, the scheduler automatically adjusts intervals based on whether the data actually changes between runs:
- Slowing down — after 6 consecutive runs where the data has not changed, the interval doubles (up to a 7-day cap). This avoids hammering roster websites that update infrequently.
- Speeding up — when a change is detected, the interval halves (down to a 1-hour floor). This ensures frequently-updated rosters are captured promptly.
Change detection works by computing a hash of the parsed records after each successful run and comparing it to the previous run. Volatile metadata fields (timestamps, session IDs) that change between fetches but don't represent real data changes are excluded from the hash.
Setting a scraper's interval manually resets auto-tuning back to the new base frequency.
Retries & failure handling
When a scraper run fails (network errors, site downtime, unexpected page structure), the scheduler uses exponential backoff for retries: 30 minutes after the first failure, then 1 hour, 2 hours, 4 hours, capping at half the normal interval. This avoids overwhelming a temporarily-down site with rapid retries while still recovering quickly once the site comes back.
After 10 consecutive failures, a scraper is marked broken and switches to a slower retry cadence (twice the normal interval). The consecutive failure counter resets to zero after any successful run.
Using the Registry
How to explore and download data from the registry website.
Map & Visualization
The interactive map renders facility locations as colored dots: blue for BJS facilities, teal for HIFLD facilities, and gray for closed facilities. Clicking a facility zooms to its county and opens a popup with details. If the facility belongs to a BJS jurisdiction, sibling facilities in the same reporting unit and jurisdiction are highlighted with colored rings.
The source legend (BJS / HIFLD / Closed / Roster) is clickable — click any category to toggle its visibility on both the map and the facility list panel. The Roster toggle highlights facilities that have a confirmed public roster link: roster facilities remain at full color and size while all other facilities are dimmed and shrunk.
The choropleth view (toggle via the heatmap button) shades counties by jail capacity per 100,000 residents, using quintile breaks computed from all counties that have data. Clicking a county shows a popup with population, capacity, and a list of facilities.
The state filter dropdown uses full state names. County popup headers also display the full state name.
County boundaries are from U.S. Atlas TopoJSON. Base map tiles are from OpenFreeMap.
Downloads
You can download facility data directly from the registry. In the full-screen table view, click the download button and choose a format:
- CSV — comma-separated values, compatible with Excel, Google Sheets, and most data tools
- JSON — structured data, useful for programmatic access
- GeoJSON — geographic features with point coordinates, for use with GIS tools and mapping libraries
Downloads reflect the currently active filters. To download all facilities, clear all filters first.
Limitations & Caveats
- The BJS Census of Jails was last conducted in 2019. Facilities opened, closed, or renamed since then will not be reflected in the BJS portion of the data
- The HIFLD dataset is updated on an irregular schedule. Matching is re-run against the latest HIFLD snapshot at build time
- City jail populations use the enclosing county population as an approximation. The actual catchment area may be smaller
- Facilities geocoded to county centroids (rather than street addresses) appear at the geographic center of the county, which may be far from the actual facility
- Capacity data is missing for approximately half of HIFLD-only facilities (287 of 618). Where capacity is unavailable, it is displayed as "Not reported"
- HIFLD uses a broader definition of "jail" than BJS. Some HIFLD-only facilities may not be pretrial detention facilities — they could be community corrections centers, social service facilities, residential re-entry programs, or courthouse holding areas. The HIFLD source badge helps users identify these records
- The matching algorithm uses a composite threshold of 0.60. Some edge cases may result in false negatives (HIFLD facilities incorrectly classified as new) or false positives (incorrectly classified as matched). The subfacility check (address similarity ≥ 0.85 in the same county) catches some but not all annexes
- Roster URL coverage is partial and ongoing. Not all facilities have a publicly accessible online roster, and some roster pages may become unavailable or change URLs over time
Source Code & Contact
The Jail Data Initiative is a project of the NYU Public Safety Lab. For questions about the data or to report issues, contact ojt212@nyu.edu.