# CMS Order & Referring Status Delta (`titan_coder/cms-order-referring-status-delta`) Actor

Monitors named NPIs in the official CMS Order and Referring registry and reports only real changes: gained/lost Medicare ordering/referring eligibility, or a Part B/DME/HHA/PMD/Hospice flag change. For billing companies, DME suppliers and compliance teams. Free when nothing changes.

- **URL**: https://apify.com/titan\_coder/cms-order-referring-status-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 order/referring 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 Order & Referring Status Delta

Durable, informational per-NPI monitor of the official CMS **Order and Referring** registry — the free,
public REST feed CMS publishes at `https://data.cms.gov/data-api/v1/dataset/{uuid}/data`. No API key,
no account, no proxy, no browser. Updated every **~3.5 days** (2M+ rows total).

**Order and Referring** lists every NPI (physician / non-physician practitioner) currently eligible
under Medicare to order or refer services covered by Part B, DME, HHA (home health), PMD (power mobility
devices) or Hospice. Losing this right is a hard compliance signal: a claim submitted for a patient whose
ordering/referring provider has lost eligibility gets **denied**. Most tools that touch this dataset are
one-shot lookups. This actor is a **recurring watch** — "tell me if any of the NPIs I track gains,
loses, or changes which of these rights it has" — built for billing companies, DME suppliers, home
health/hospice agencies and Medicare compliance teams.

**This is a mirror-with-diff of a public government registry, not a real-time eligibility check.** It is
not legal advice and not a substitute for verifying a provider's current status directly with CMS before
billing — the source itself updates only every ~3.5 days, so there is always a lag window.

### Why

CMS already publishes the full registry for free. What it doesn't give you is a durable, per-NPI "what
changed since I last looked" — you'd have to download and diff a 70+ MB CSV yourself, on every release,
against every prior release. This actor keeps that diff for you: a list of watches (one NPI each), a
durable memory of each NPI's flags between runs, and a bill only for NPIs that genuinely gained the
right, lost it, or changed which programs it covers.

### How it works

1. **Dataset UUID resolution (once per run, not per watch).** CMS does not publish a stable "latest"
   alias for this dataset — the UUID in the API URL changes with every release (confirmed live: two
   real releases two days apart used two different UUIDs). Every run fetches
   `https://data.cms.gov/data.json` (~3 MB) and resolves the freshest `API`-format distribution titled
   "Order and Referring" by its `modified` date (ties broken toward one explicitly marked
   `"description": "latest"` — both cases were observed live on the same day).
2. Each `watch` is **one NPI** (10-digit National Provider Identifier). One point query per watch per
   run: `?filter[NPI]=<npi>`. Confirmed live: this is an **exact string match**, not substring/LIKE — a
   leading zero, a stray space, or a wrong digit all return a legitimate `200 []`, indistinguishable at
   the HTTP level from "this NPI genuinely has no such record."
3. The **first** run for a watch establishes a baseline: whether the NPI is currently present and, if
   so, its five flags (`PARTB`, `DME`, `HHA`, `PMD`, `HOSPICE`). Nothing is billed or delivered.
4. Every later run compares the fresh point-query result against the stored state:
   - NPI now present, previously absent → **`added`** (billable) — the NPI gained ordering/referring
     eligibility (or regained it after a confirmed removal).
   - NPI present both times, flags differ → **`flag_changed`** (billable) — which program(s) it's
     eligible for changed.
   - NPI present both times, flags identical, only the name changed → **not billable** (administrative
     correction, not an eligibility change).
   - NPI now absent, previously present → **two-strike confirmation** (see below), because the source
     returns the identical `200 []` for "this NPI was removed" and for any transient hiccup on our end.
     The first consecutive absence produces an **unbilled** `removal_candidate` row (informational only,
     still atomically deduplicated so it is never re-delivered). Only the **second consecutive**
     accepted absence confirms **`removed`** (billable) — the NPI lost every ordering/referring right it
     had. If the NPI reappears before the second miss, the streak resets silently with no charge.

### Input

```json
{
  "monitorId": "my-referring-watch",
  "watches": [
    { "watchId": "dr-smith", "npi": "1295400745" }
  ],
  "notifyOn": "new_alerts",
  "webhookUrl": "https://example.com/webhook"
}
```

- `watches` — 1-25 objects, each `{watchId, npi}`. `npi` is a 10-digit National Provider Identifier
  (`^\d{10}$`, exact string as CMS stores it — no normalization). A `watchId` is bound to its `npi` on
  first use — reusing the same `watchId` with a different `npi` later fails the run honestly
  (`watch_config_mismatch`) instead of silently hiding history under a stale index.

### Output

One row per gained/changed/lost right: `watchId`, `npi`, `status`
(`added`/`flag_changed`/`removed`/`removal_candidate`), `lastName`, `firstName`, `partB`/`dme`/`hha`/
`pmd`/`hospice` (current or last-known flags), `previousPartB`/`previousDme`/`previousHha`/`previousPmd`/
`previousHospice`, `contentHash`, `sourceUrl`. `removal_candidate` rows carry `billed: false` — they are
an early, unpaid signal of a possible removal still awaiting second-run confirmation. A run that finds
nothing still writes an honest `run_summary` row to the default dataset (never silently empty).

### Billing

Pay-per-event, one named event: `order-referring-status-changed` — covers an NPI newly gaining a right
(`added`), an already-known NPI's flags changing (`flag_changed`), and a **confirmed** (two-strike) loss
of every right (`removed`). Deduplicated by NPI + a monotonically increasing change counter + a hash of
the resulting state, so the same NPI can be billed again later if its state genuinely changes again, but
never twice for the same transition. `removal_candidate` rows are never charged. The baseline run
establishes history without charging. Failed/blocked runs are never charged.

#### Delivery guarantee: at-most-once (not exactly-once)

The right to write a row and to charge for it is granted by a single atomic primitive — one
`addRequest(uniqueKey)` into a dedicated, named claim-journal Request Queue
(`<prefix>-<monitorId>-claims`). Exactly one run ever wins that key. Claim requests are never deleted and
never handled: the queue is a permanent journal of irreversible attempts, not a work list. The unbilled
`removal_candidate` row uses its own claim namespace (keyed without a run id, on a durable "missing
epoch" counter) so it is also delivered at most once, even though it is never charged.

What this buys you, stated honestly:

- **You will never be charged twice for the same event.** That is the guarantee.
- **It is not exactly-once.** If a run wins the claim and then dies before the row reaches the dataset
  (or before the charge completes), that event is *lost*: it closes as `dataset_unknown` /
  `charge_unknown` and is never re-delivered. We deliberately prefer losing a delivery over
  double-charging you.
- **Boundary of the guarantee:** it holds for as long as the named claim-journal queue exists. Anyone
  with account access can delete or re-create that queue through the Apify Console/API; a fresh journal
  starts empty, and previously delivered events could then be delivered and billed again. That is an
  inherent limit of any durable storage, not a defect of the protocol.
- **Migration boundary:** the guarantee applies from the build that introduced the claim gate onward.
  Older builds of this actor must not keep running against the same `monitorId`.
- `coverage.claimJournalSize` reports the journal's size each run (best-effort; `null` if the queue's
  metadata could not be read, and the value lags a few seconds because Apify's `totalRequestCount` is
  eventually consistent). Use it to watch growth, not to make decisions.

### Honest limits

- **This is not a real-time eligibility check.** CMS republishes the source dataset every ~3.5 days;
  between releases, a provider's true eligibility could have already changed. Before denying/paying a
  claim on an eligibility question, verify directly with CMS (`data.cms.gov`) or your MAC.
- **The durable dataset is a delivery-attempt log, not a guaranteed mirror of the default dataset.**
  Each row is written to the durable dataset first, then mirrored to the run's default dataset before
  billing proceeds for that row. If the durable write succeeds but the mirror fails, the item is marked
  `dataset_unknown`, billing for it is permanently blocked (fail-closed), and the row can end up an
  orphan in the durable dataset only. The **default dataset is the canonical log of rows successfully
  written to this run's output** (see its `run_summary` row) — but a default-dataset row does not by
  itself prove the row was billed. **`run_summary.eventsBilled` and Apify's own billing ledger are the
  source of truth for confirmed payment**, not the presence of a row in either dataset.
- **"Removed" needs two consecutive misses to bill, by design.** The source's point query returns the
  identical `200 []` for "this NPI has no such record" whether that's because the right was genuinely
  revoked or because of some transient issue on the CMS side that we cannot distinguish from the HTTP
  response alone. We would rather delay a `removed` charge by one run interval than bill a false
  positive caused by a source hiccup. `removal_candidate` rows tell you about the first miss for free.
- A name correction alone (no flag change) is not billed — only a change in at least one of the five
  eligibility flags (or the NPI appearing/disappearing entirely) is a billable signal.
- The dataset UUID changes with every CMS release and is re-resolved every run from `data.json`; if CMS
  changes the shape of `data.json` itself, or removes the "Order and Referring" title, this actor fails
  the run honestly (`source_access_limited` / `all_watches_failed`) instead of silently returning
  nothing.
- If the feed is temporarily unavailable or its response shape changes (missing field, non-Y/N flag,
  duplicate NPI in a point-query response, or the returned NPI not matching the one requested), the
  affected watch reports that honestly (`source_access_limited`) instead of silently accepting bad data.

See `ROADMAP.md` for the live-verification write-up and known source quirks.

Author: OmniCoder (https://t.me/OmniCoder)

# 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-25 objects: {"watchId": "dr-smith", "npi": "1295400745"}. npi is a 10-digit National Provider Identifier, matched as an exact string against CMS's own field (no normalization — a leading zero or stray space will not match). 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) NPI eligibility changes as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "example-monitor",
  "watches": [
    {
      "watchId": "dr-smith",
      "npi": "1295400745"
    }
  ],
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every row this run produced. Key fields: watchId, npi, status (added|flag\_changed|removed|removal\_candidate), lastName/firstName, partB/dme/hha/pmd/hospice (current or last-known), previousPartB/previousDme/previousHha/previousPmd/previousHospice. removal\_candidate rows carry billed:false (unpaid early signal). If nothing new was found, a single run\_summary row explains why the dataset is otherwise empty. Informational only — not a real-time eligibility check.

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

What this run actually covered and what it charged for: per-watch status/reason, records delivered and records billed, requested/attempted/succeeded/failed watch counts, removal-candidate counts. Enough to reconcile every charge against every row.

## `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": "example-monitor",
    "watches": [
        {
            "watchId": "dr-smith",
            "npi": "1295400745"
        }
    ],
    "notifyOn": "new_alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/cms-order-referring-status-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": "example-monitor",
    "watches": [{
            "watchId": "dr-smith",
            "npi": "1295400745",
        }],
    "notifyOn": "new_alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/cms-order-referring-status-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": "example-monitor",
  "watches": [
    {
      "watchId": "dr-smith",
      "npi": "1295400745"
    }
  ],
  "notifyOn": "new_alerts"
}' |
apify call titan_coder/cms-order-referring-status-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/cms-order-referring-status-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/FHQugLXkBYQDcmOem/builds/aFxnf02DG1tIUnCPV/openapi.json
