# California Medical Facilities & Enforcement Actions (`j0401/ca-medical-facilities`) Actor

California medical facilities (public open data): the licensed-facility register (~10.9k hospitals/SNF/home-health/hospice/clinics) with category, status, beds and ER level; CDPH enforcement actions (~20.5k) with penalty class and appeals; and licensed/certified locations with CCN/NPI.

- **URL**: https://apify.com/j0401/ca-medical-facilities.md
- **Developed by:** [Wenhao Yang](https://apify.com/j0401) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.10 / 1,000 ca medical facility 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?

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

## California Medical Facilities & Enforcement Actions

California publishes its **licensed medical-facility register**, the **state enforcement actions** taken against those facilities, and the **licensed-and-certified facility locations** as open data. This actor turns all three into a **charged-per-record lookup, filter and aggregate tool**: pull the hospitals, skilled-nursing homes, home-health agencies, hospices and clinics in the state, read the penalty history behind a facility, or check a location's CCN/NPI and hospital designations.

**Built for:** hospital / SNF / clinic compliance and diligence teams, medical-facility suppliers and staffing firms building a target list, and enforcement / complaint researchers - anyone who needs the state-level facility record, not just the federal CMS layer.

### What it covers

Three corpora, one clean schema:

| Corpus | Records | What it is |
|---|---|---|
| **facilities** | 10,914 | The licensed-facility register: one row per licensed facility, with licensing category, level (parent / branch / consolidated), open-closed status + date, emergency-service level, bed count, address + county + coordinates. |
| **enforcement** | 20,550 | CDPH state enforcement actions, 1997-present: one row per penalty - Citation / Administrative Penalty / Failure-to-Report, the assessed class, initial vs final amount, collected and balance due, the violation window, disposition, whether it was appealed, and whether the violation was death-related. |
| **certified** | 15,097 | Licensed-and-certified locations - the Medicare/Medicaid side: CCN, NPI, deemed status, participation and approval dates, facility administrator, contact phone/email, capacity, license status and dates, plus hospital designations (trauma center, critical-access, birthing facility). |

The **facility register** spans the whole licensed spectrum - **General Acute Care Hospitals, Skilled Nursing Facilities, Home Health Agencies, Hospices, Community and Free Clinics, Chronic Dialysis Clinics, Acute Psychiatric Hospitals, Congregate Living Health Facilities** and more - each with its **status, county and bed license**.

The **enforcement** corpus is the part the federal-only sources don't carry: **20,550 state penalties**, each with the **assessed class (AA / A / B)**, the **amount assessed vs the amount due after appeal**, the **appeal trail** (was it appealed, appeal-due and appeal-received dates) and a **death-related flag**.

Each record carries location (**address, city, county, ZIP, latitude/longitude**), identity keys (**OSHPD id, license number, CCN, NPI, facility id**) and dates. The three corpora come from three different state systems and **do not share one common key** - the registered `facilities` and the certified `certified` file overlap only partially, and the enforcement `facID` matches the certified `FACID` only after the source's zero-padding is normalized. Treat each corpus as its own register; join them on a specific id (CCN / NPI / license number) where the two systems both carry it, and expect partial matches.

### Typical questions

- "Every **General Acute Care Hospital** in Los Angeles County with its bed count."
- "Which facilities have **penalties over $100,000** - and which were **appealed**?"
- "The **death-related citations** at skilled-nursing facilities in 2024."
- "Every **critical-access hospital** or **trauma center** in the state."
- "Look up a facility by **CCN** or **NPI** for its Medicare participation and capacity."
- "Aggregate the register by **category** or **county**; aggregate the penalties by **type**, **disposition** or **state fiscal year**."

### Inputs

| Input | What it does |
|---|---|
| `corpus` | `facilities` (default) / `enforcement` / `certified` |
| `mode` | `rows` (default) / `aggregate` |
| `licenseCategory` / `status` / `bedsMin` / `emergencyServicesOnly` | narrow the facility register |
| `penaltyType` / `disposition` / `facilityType` / `facility` / `classAssessed` / `amountMin/Max` / `deathRelated` / `appealed` / `issuedFrom/To` | narrow the enforcement actions |
| `licensedCertified` / `licenseStatus` / `deemedOnly` / `traumaCenter` / `criticalAccess` / `birthFacility` / `ccn` / `npi` | narrow the certified locations |
| `county` / `city` / `keywords` | shared location / name filters |
| `groupBy` | aggregate over a dimension of the selected corpus |
| `maxResults` | cap records (default 200; 0 = the whole result set, paged) |

**Default run = the first 200 facilities** - fast for the daily auto-test. For a targeted query add a filter; for a broad view use `aggregate` (a handful of summary rows, not a row dump).

### Low cost

**From $0.0001 per record** - billed only for the rows you use, at the platform floor. Cost scales with what you pull, not with the size of the register, and because each record is metered individually there's no per-run charge cap to hit on a big pull.

California's medical-facility data is published as three files that do not agree with each other, and the traps are all in the joins and the typed columns. The **bed count only exists on bed-bearing facility types** - hospitals, skilled-nursing, psychiatric and congregate-living carry it, while the home-health agencies, hospices and clinics that make up most of the register carry none by design, so a naive "beds > 0" filter silently drops two-thirds of the state. That bed column is **text with thousands separators** ("1,106"), so even the numeric range has to be unwound before it can be compared. The **enforcement amounts are text holding decimals and negative offsets**, and the citation **class only lives in the *initial* column** - the *final* column is a post-appeal outcome that is empty on four rows in five and, on the rows that carry it, is a free-form result ("UPHELD", "Dismissed by Court", "B Trebled") mixed with stray tab characters, exactly the field a thin scraper grabs and reports wrong. The **trauma designation is a bare "LEVEL I"-"LEVEL IV"** with blank meaning "not a trauma center", and critical-access is a single `Y`, not the `YES` a template would assume. Normalizing three update rhythms, five vocabularies and two broken number columns into one schema where an `amountMin` / `bedsMin` / `traumaCenter` query returns exactly the facilities you mean is the actual product. Every pull is integrity-checked against each feed's known shape, so a degraded source fails loudly instead of returning bad rows.

### Source

- [HCAI: Licensed Healthcare Facility Listing](https://data.ca.gov/dataset/licensed-healthcare-facility-listing) - the licensed-facility register (semi-annual snapshot).
- [CDPH: Health Facilities State Enforcement Actions](https://data.ca.gov/dataset/health-facilities-state-enforcement-actions) - the state penalty history.
- [CDPH: Licensed and Certified Healthcare Facility Listing](https://data.ca.gov/dataset/licensed-and-certified-healthcare-facility-listing) - the licensed-and-certified locations.

Public open data. Records are the state's own published fields; this is not an endorsement of any facility, and the register reflects the state's snapshot as of each pull.

# Actor input Schema

## `corpus` (type: `string`):

facilities = the HCAI licensed-facility register (~10.9k). enforcement = CDPH state enforcement actions / penalties (~20.5k, 1997-present). certified = licensed-and-certified facility locations with CCN/NPI (~15.1k).

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

rows = records matching your filters (default). aggregate = one count row per group (see groupBy).

## `groupBy` (type: `string`):

Dimension to aggregate over (only used when mode=aggregate). Blank = the corpus's primary dimension. Must belong to the selected corpus: facilities -> licenseCategory / facilityStatus / county / erServiceLevel / facilityLevel; enforcement -> penaltyType / disposition / facilityType / stateFiscalYear / districtOffice / appealed; certified -> licensedCertified / licenseStatus / county / entityType / deemed.

## `status` (type: `string`):

facilities corpus only. Open / Suspense / Closed. Blank = any.

## `licenseCategory` (type: `string`):

facilities corpus only. Matches the licensing category: Hospital, Skilled Nursing, Home Health, Hospice, Community Clinic, Dialysis, Psychiatric, ... Blank = any.

## `emergencyServicesOnly` (type: `boolean`):

facilities corpus only. When on, returns only facilities that provide an emergency service level.

## `bedsMin` (type: `integer`):

facilities corpus only. Only facilities licensed for at least this many beds. Bed counts are only present on bed-bearing types (hospitals, skilled nursing, psychiatric, congregate living). 0 = any.

## `penaltyType` (type: `string`):

enforcement corpus only. Citation / Administrative Penalty / Failure to Report Penalty. Blank = any.

## `disposition` (type: `string`):

enforcement corpus only. Open / Closed. Blank = any.

## `facilityType` (type: `string`):

enforcement corpus only. Exact source type code: SNF, GACH, ICFDDH, ICFDDN, CLHF, APH, HHA, HOSPICE, ... Blank = any.

## `facility` (type: `string`):

enforcement corpus only. Facility-name substring on the penalty record. Blank = any.

## `issuedFrom` (type: `string`):

enforcement corpus only. Only penalties issued on/after this date (YYYY-MM-DD).

## `issuedTo` (type: `string`):

enforcement corpus only. Only penalties issued before this date (YYYY-MM-DD).

## `classAssessed` (type: `string`):

enforcement corpus only. The class as initially assessed: AA, A, B (citation classes) or the penalty/failure-to-report routes (AP IJ, AP BR, AP NHPPD, AP NON-IJ, FTR AE, FTR BR, FTR RES, WF, NP, WO, RD). Blank = any.

## `amountMin` (type: `number`):

enforcement corpus only. Only penalties whose initial amount is at least this many dollars (numeric filter). 0 = any.

## `amountMax` (type: `number`):

enforcement corpus only. Only penalties whose initial amount is below this many dollars. 0 = any.

## `deathRelated` (type: `boolean`):

enforcement corpus only. When on, returns only penalties flagged as death-related.

## `appealed` (type: `boolean`):

enforcement corpus only. When on, returns only penalties that were appealed.

## `licensedCertified` (type: `string`):

certified corpus only. LICENSED AND CERTIFIED / LICENSED ONLY / CERTIFIED ONLY / NOT LICENSED/NOT CERTIFIED / EXEMPT FROM LICENSURE/NOT CERTIFIED. Blank = any.

## `licenseStatus` (type: `string`):

certified corpus only. License-status substring: ACTIVE, INACTIVE - CAPEN DECISION, ... Blank = any.

## `deemedOnly` (type: `boolean`):

certified corpus only. When on, returns only facilities with deemed (accreditation-based) Medicare status.

## `traumaCenter` (type: `boolean`):

certified corpus only. When on, returns only designated trauma centers (level I-IV).

## `criticalAccess` (type: `boolean`):

certified corpus only. When on, returns only critical-access hospitals.

## `birthFacility` (type: `boolean`):

certified corpus only. When on, returns only facilities flagged as birthing facilities.

## `ccn` (type: `string`):

certified corpus only. Exact Medicare/Medicaid provider number (CCN). Blank = any.

## `npi` (type: `string`):

certified corpus only. Exact National Provider Identifier. Blank = any.

## `county` (type: `string`):

County name substring, e.g. 'Los Angeles', 'Orange', 'San Diego'. Blank = any.

## `city` (type: `string`):

City substring (facilities / certified corpora). Blank = any.

## `keywords` (type: `string`):

Free-text match on facility name (and license number for facilities, administrator / business name for certified). Blank = any.

## `maxResults` (type: `integer`):

Cap the records pushed. 0 = the whole matching set (the request pages through the corpus, so a broad pull is complete, not truncated at a page). Each record is metered individually, so there is no per-run charge cap. An aggregate has at most a few hundred groups.

## Actor input object example

```json
{
  "corpus": "facilities",
  "mode": "rows",
  "groupBy": "",
  "status": "",
  "licenseCategory": "",
  "emergencyServicesOnly": false,
  "bedsMin": 0,
  "penaltyType": "",
  "disposition": "",
  "facilityType": "",
  "facility": "",
  "issuedFrom": "",
  "issuedTo": "",
  "classAssessed": "",
  "amountMin": 0,
  "amountMax": 0,
  "deathRelated": false,
  "appealed": false,
  "licensedCertified": "",
  "licenseStatus": "",
  "deemedOnly": false,
  "traumaCenter": false,
  "criticalAccess": false,
  "birthFacility": false,
  "ccn": "",
  "npi": "",
  "county": "",
  "city": "",
  "keywords": "",
  "maxResults": 50
}
```

# Actor output Schema

## `recordsUrl` (type: `string`):

California medical facility, enforcement-action or licensed-and-certified records - as JSON

## `datasetUrl` (type: `string`):

No description

## `runUrl` (type: `string`):

No description

# 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("j0401/ca-medical-facilities").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("j0401/ca-medical-facilities").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 j0401/ca-medical-facilities --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,j0401/ca-medical-facilities"
        }
    }
}
```

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/39d1u1ssQhko8mV9s/builds/foIgi3cc1sBCDtylA/openapi.json
