1. Facility Registry 2. Rosters 3. Using the Registry Limitations Contact

Documentation

A reference manual covering the facility dataset, roster collection, and how to use the registry.

1

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.

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:

BJS hierarchy

BJS organizes jails into a three-level hierarchy using numeric identifiers:

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:

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:

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:

Classification

Each HIFLD facility is classified into one of three categories:

Data Pipeline

The registry is built through a four-step pipeline. Each step produces an intermediate output file:

1. fetch_bjs    → bjs_facilities.json          (2,936 records)
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:

  1. 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
  2. Nominatim (OpenStreetMap) — unmatched addresses are retried against Nominatim, which resolves an additional ~225 facilities
  3. 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

Name Normalization

Facility names, addresses, and other text fields from ALL CAPS sources undergo several transformations:

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
facidFACILITYIDPrefixed with "H" (e.g. "H10006714") to distinguish from BJS numeric IDs
facnameNAMETitle-cased and CamelCase-split (see Name Normalization)
countyCOUNTYLooked 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
addressADDRESSTitle-cased from ALL CAPS (see Name Normalization)
cityCITYTitle-cased from ALL CAPS
juris_levelTYPECOUNTY→county, LOCAL→city, MULTI→regional
capacityCAPACITYNull or ≤0 values treated as missing. -999 sentinel removed
lat, lnggeometry.y, geometry.xFrom ArcGIS point geometry
cntyfipsCOUNTYFIPSZero-padded to 5 digits
jurisidAlways null (HIFLD has no jurisdiction hierarchy)
ru_idAlways null
sourceSet to "hifld"

Output Schema

Every facility record in facilities.json (and in downloaded CSV/JSON/GeoJSON files) contains these fields:

Field Type Description
facidstringFacility identifier. BJS numeric ID, "H" + HIFLD ID, or "M" + sequential number for manually added facilities
jurisidstring | nullBJS jurisdiction identifier (9-digit). Null for HIFLD and federal facilities
ru_idstring | nullBJS reporting unit identifier. Null for HIFLD and federal facilities
facnamestringFacility name (normalized)
runamestring | nullReporting unit (agency) name. Null for HIFLD facilities
countystring | nullCounty name(s). May contain multiple comma-separated counties for regional jails
addressstring | nullStreet address
citystring | nullCity
statestring | nullTwo-letter state abbreviation
zipstring | nullZIP code
statefipsstring | nullTwo-digit state FIPS code
cntyfipsstring | nullFive-digit county FIPS code
statusstring"1" = open, "2" = closed
juris_levelstringcounty, city, regional, federal, or other
isregionalstring | null"1" if regional jail
isprivatestring | null"1" if privately operated
urbanstring | nullBJS urban/rural classification. Null for HIFLD
capacitystring | nullRated bed capacity. Null if not reported or ≤0
latnumber | nullLatitude (geocoded or county centroid)
lngnumber | nullLongitude (geocoded or county centroid)
juris_popinteger | nullJurisdiction population (2019 Census estimate)
juris_capacityinteger | nullTotal rated beds across all facilities in the jurisdiction
capacity_per_100knumber | nullBeds per 100,000 residents (jurisdiction level)
roster_urlstring | nullURL of the facility's public jail roster or searchable custody database, if confirmed
sourcestring"bjs", "hifld", or "manual"
2

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.

1. Fetch           → Download raw data from roster source
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:

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
Person1Unique system IDHigh — unique per individual
Person2Full name + date of birthMedium — rare collisions
Person3Full name aloneLower — possible collisions
Booking1Unique booking IDHigh — unique per booking
Booking2Booking dateMedium — 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:

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:

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:

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.

3

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:

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.

4

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:

Downloads reflect the currently active filters. To download all facilities, clear all filters first.

Limitations & Caveats

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.