# CMS Revoked Medicare Provider Delta (`titan_coder/cms-revoked-medicare-provider-delta`) Actor

Watches the CMS Revoked Medicare Providers registry by state and reports only real changes: new revocations, updated reenrollment-bar dates, or confirmed reinstatements. Quarterly source cadence, not continuous. For billing, payer and credentialing teams. Free when nothing changes.

- **URL**: https://apify.com/titan\_coder/cms-revoked-medicare-provider-delta.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 revoked provider status changeds

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

## CMS Revoked Medicare Provider Delta

Watches the official CMS **Revoked Medicare Providers and Suppliers** registry (42 CFR 424.535) by
state and reports only genuine changes since your last check: a new revocation, an updated
reenrollment-bar date, or a confirmed reinstatement. Free when nothing changes — you pay only for
delivered events.

Built for billing companies, payers, compliance teams and HR/credentialing departments that need to
know when a provider in their book of business gets revoked from Medicare — or comes back.

### Honest limits — read this before you rely on the cadence

- **The source itself updates quarterly** (`accrualPeriodicity: R/P3M`). Historical volume measured
  directly from CMS extracts: roughly **180–230 revocations a month nationwide**, delivered in **4
  windows a year** of about 550 events each — not a steady daily/weekly drip. If you need a
  continuously-updated feed, this is not it; if you need to know "did anything change in my state
  since the last quarterly release," this is exactly it.
- This is an **informational monitor of a public federal registry**, not a real-time verification of a
  provider's current Medicare status. Always confirm directly with CMS/NPPES before making a business
  decision.
- `stateCd` is matched **exactly, server-side**, against CMS's own field (including `DC`). A typo gives
  zero matches, not an error.
- `providerTypeContains` is an **optional, case-insensitive substring** match against
  `PROVIDER_TYPE_DESC`, applied on the client on top of the state slice — it does not query the source
  a second time.
- Reinstatement (`removed`) detection needs **two consecutive complete state snapshots** without the
  `ENRLMT_ID` (two-strike, see below) — a single missing run only produces an unpaid
  `removal_candidate` row. Every successful fetch is a complete, verified snapshot of the state slice
  (one HTTP request, cross-checked against an independent row count taken immediately before it — see
  `ROADMAP.md`); a run where that cross-check fails is reported as `source_access_limited` rather than
  silently truncated, so reinstatement detection is never guessed from partial data.

### How it works

1. First run per watch (`monitorId` + `watchId`) records a private baseline of every currently-revoked
   `ENRLMT_ID` matching your `stateCd`/`providerTypeContains` — no rows, no charge.
2. Every later run re-fetches the state slice from CMS and classifies each `ENRLMT_ID`:
   - **`added`** — a new revocation appeared;
   - **`changed`** — a previously-seen `ENRLMT_ID`'s content changed (typically
     `REENROLLMENT_BAR_EXPRTN_DT` was extended or shortened);
   - **`removal_candidate`** — an `ENRLMT_ID` you'd seen before is missing from this run's snapshot for
     the first time in a row. Delivered as an informational row with `billed: false` — not charged, and
     not proof of anything (a single missing point is indistinguishable from a transient source hiccup);
   - **`removed`** — the same `ENRLMT_ID` is missing for the **second consecutive** complete run.
     Billed: the provider's registration was very likely reinstated.
3. Only `added`/`changed`/`removed` are billed, one PPE event each
   (`revoked-provider-status-changed`).

### Input

- `monitorId` — name of this monitor's durable history (`a-z0-9-`, up to 40 chars). Re-run with the
  same `monitorId` to continue the same history; a new value starts a fresh baseline.
- `watches` — 1-20 objects `{"watchId", "stateCd", "providerTypeContains"?}`. `stateCd` is the 2-letter
  code CMS publishes for the row (53 distinct values seen, including `DC`). Once a `watchId` is used, its
  `stateCd`/`providerTypeContains` are locked — reusing the id with a different filter fails the run
  instead of silently mixing histories. Add new `watchId`s any time under the same `monitorId`.
- `notifyOn` / `webhookUrl` — optional HTTPS webhook receiving a JSON digest of billed changes.

### Output

- **Dataset** — every row this run produced (`watchId`, `enrlmtId`, `status`, `subjectType`,
  name/org fields, `npi`, `stateCd`, `providerTypeDesc`, `revocationRsn`, `revocationEfctvDt`,
  `reenrollmentBarExprtnDt`, `previousContentHash`, `eventId`, `billed`). A durable named dataset holds
  the append-only history of everything ever delivered under this `monitorId`; the default (run) dataset
  mirrors what this run delivered plus a `run_summary` row.
  - `billed` on a `removal_candidate` row is always `false` — never charged, by design.
  - `billed` on an `added`/`changed`/`removed` row is written as `null`. The row is saved to the
    dataset *before* its charge is attempted (we never charge before delivery is confirmed — see
    "Guarantee" below), so the final charge outcome isn't known yet at write time. To find out whether
    one specific row was actually charged, match its `eventId` against this run's Pay-Per-Event
    charged-events ledger in the Apify Console. For the run-level total, use `recordsDelivered`/
    `recordsBilled` in `coverage` and the unbilled count in `digest`.
- **Key-value store** — `coverage` (per-watch status/reason/matchedCount/windowFull, requested/
  attempted/succeeded/failed, records delivered/billed; a non-fatal partial run — some watches failed,
  others succeeded — sets `partialReason: "partial_watch_failures"` and lists `failedWatchReasons`) and
  `digest` (a short human-readable summary, which also carries this partial-coverage warning even when
  no billable change occurred).

### Guarantee

**At-most-once delivery and billing, not exactly-once.** A crash right after a right-to-bill is granted
can lose that one event (it will not be redelivered), but you will never be charged twice for the same
underlying change. See `ROADMAP.md` for the full claim protocol and its boundaries.

### Durable dataset vs. default dataset

The **durable named dataset** is a journal of delivery attempts across every run of this `monitorId` —
it is not itself proof of payment (a row can in rare crash scenarios be written without a confirmed
charge). The **default (per-run) dataset** mirrors this run's rows plus one `run_summary` record and is
what you see in the Apify Console run view. Reconcile billing against `coverage`, not row count alone.

# Actor input Schema

## `monitorId` (type: `string`):

Name of this monitor's durable history (a-z, 0-9, dash; up to 40 chars).

## `watches` (type: `array`):

1-20 objects: {"watchId": "texas", "stateCd": "TX", "providerTypeContains": "cardio" (optional)}. stateCd is the 2-letter state code as CMS publishes it (matched server-side, exact — includes DC; 53 distinct values in the registry). providerTypeContains is an optional case-insensitive substring match against PROVIDER\_TYPE\_DESC, applied on top of the state slice (e.g. "cardio", "home health", "chiropractic") — leave empty to watch every revoked provider/supplier type in that state. New watches can be added later under the same monitorId.

## `notifyOn` (type: `string`):

new\_alerts — post the webhook only when new billed changes were delivered; always — post it every run; never — do not call webhookUrl at all.

## `webhookUrl` (type: `string`):

Optional. Receives a digest of delivered (billed) revocation-status changes as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "my-revoked-watch",
  "watches": [
    {
      "watchId": "texas",
      "stateCd": "TX"
    }
  ],
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every row this run produced. Key fields: watchId, enrlmtId (source-native id), status (added|changed|removed|removal\_candidate), subjectType (individual|business), npi/firstName/mdlName/lastName/orgName, stateCd, providerTypeDesc, revocationRsn, revocationEfctvDt, reenrollmentBarExprtnDt, previousContentHash, eventId. `billed` on removal\_candidate rows is always false (informational, never charged by design). On added/changed/removed rows `billed` is written as null: the row is saved before the charge is attempted (by design — we never charge before a row is confirmed delivered), so its final charge outcome isn't known yet at write time. Reconcile the actual charge for one of these rows via its `eventId` against this run's Pay-Per-Event charged-events ledger in the Apify Console, or read the run-level total in `coverage` (recordsDelivered/recordsBilled) and the digest's unbilled count. Informational monitor of a public registry — not a real-time provider-status verification.

## `coverage` (type: `string`):

What this run actually covered and what it charged for: per-watch status/reason/matchedCount/windowFull/candidateCount, records delivered and records billed, requested/attempted/succeeded/failed watch counts. A non-fatal partial run (some watches failed while others succeeded) sets partialReason to "partial\_watch\_failures" and lists failedWatchReasons. Use recordsDelivered/recordsBilled here (and the digest's unbilled count) for the run-level reconciliation total; for a single row's own charge outcome, join its eventId against this run's Pay-Per-Event charged-events ledger — added/changed/removed dataset rows are written before their charge is attempted and therefore carry billed:null, not the final outcome.

## `digest` (type: `string`):

A short human-readable summary of what this run found, written every run.

# 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 = {
    "monitorId": "my-revoked-watch",
    "watches": [
        {
            "watchId": "texas",
            "stateCd": "TX"
        }
    ],
    "notifyOn": "new_alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/cms-revoked-medicare-provider-delta").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 = {
    "monitorId": "my-revoked-watch",
    "watches": [{
            "watchId": "texas",
            "stateCd": "TX",
        }],
    "notifyOn": "new_alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/cms-revoked-medicare-provider-delta").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 '{
  "monitorId": "my-revoked-watch",
  "watches": [
    {
      "watchId": "texas",
      "stateCd": "TX"
    }
  ],
  "notifyOn": "new_alerts"
}' |
apify call titan_coder/cms-revoked-medicare-provider-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/cms-revoked-medicare-provider-delta"
        }
    }
}
```

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/JddiqG5iTQP4xzCE5/builds/4I067AMHbWq0V0skL/openapi.json
