# TX Board of Nursing License Status Delta (`titan_coder/tx-nursing-board-license-status-delta`) Actor

Watches named Texas nursing (RN/VN) licenses in the official TX Board of Nursing registry, alerting only on a genuine change: license status (Revoked, Suspended, Vol.Surrender, Renewal Denied) or a board action flag. For hospitals, staffing agencies, HR, insurers, compliance. Free when unchanged.

- **URL**: https://apify.com/titan\_coder/tx-nursing-board-license-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 nursing license 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

## TX Board of Nursing License Status Delta

Durable monitor for the two official Texas Board of Nursing (TX BON) bulk registries — **RN-All**
(Registered Nurses) and **VN-All** (Vocational/Licensed Vocational Nurses) — published as open data on
`data.texas.gov`. Watch specific nursing licenses by license number and get notified only when the license
status or a board disciplinary-action flag genuinely changes. No API key needed, no login, no captcha.

### Source

Two Socrata Open Data API datasets, identical schema, disjoint license pools:

- **RN-All**: `https://data.texas.gov/resource/tgb2-j935.json` — 793,555 rows (confirmed live 13.09.2026)
- **VN-All**: `https://data.texas.gov/resource/yjie-tuwv.json` — 318,084 rows (confirmed live 13.09.2026)

`license_number` is a plain integer, unique **within each pool independently** (RN license 621981 and VN
license 621981 are two different people) — confirmed live via the Socrata schema (`dataTypeName: "number"`
for both datasets), which is why every watch names both a `licenseType` (`RN` or `VN`) and a
`licenseNumber`. Updated monthly (`Last-Modified` observed 01.09.2026 at check time — this is a property
of the source, not a limitation of this actor).

`license_status` observed live (13.09.2026) takes at least these 12 values (RN counts shown):
`CURRENT (C)` (435,951), `DELINQUENT (D)` (207,318), `INACTIVE (I)` (99,860), `DECEASED (E)` (32,081),
`RETIRED - INACTIVE (Z)` (9,320), `REVOKED (R)` (3,296), `VOL.SURRENDER (V)` (2,629),
`VOLUNTEER RETIRED (W)` (2,194), `NLC LICENSE - TX INVALID(Y)` (450), `SUSPENDED (S)` (402),
`Current RENEWAL DENIED (K)` (53), `NOT CURRENT - SEE ENF (X)` (1). VN-All uses the same value set with
different frequencies.

**A separate, independent disciplinary signal:** `current_board_action` (a real boolean) and
`date_of_board_action_imposed`. Confirmed live 13.09.2026: a license can carry `license_status=CURRENT (C)`
(fully current) **and** `current_board_action=true` at the same time — a board action (reprimand,
conditions, supervision order) does not always change the plain-text license status. This actor treats a
change in **either** `license_status` **or** `current_board_action`/`date_of_board_action_imposed` as
billable — watching `license_status` text alone would silently miss board actions imposed on an otherwise
"current" license.

### How it works

1. Each `watch` names exactly **one** nursing license by `licenseType` (`RN` or `VN`) + `licenseNumber` —
   look it up at
   [bon.texas.gov/licensure\_verification.asp](https://www.bon.texas.gov/licensure_verification.asp) if you
   only have a name. One watch = one point query (`$where=license_number=<N>` against the RN or VN dataset
   selected by `licenseType`) = one HTTP request per run.
2. The first check of a new watch establishes a baseline (no charge). Every later check compares the
   current combination of `license_status` + `current_board_action` + `date_of_board_action_imposed`
   against the durable record of what it was last time.
3. Billing is tied to that combination — not to name, address, employment, education, or the routine
   `date_of_last_renewal`/`lincense_expiration_date` fields that shift for every actively licensed holder
   as they renew. A status that changes and later reverts (e.g. suspended, then reinstated, then suspended
   again for a new violation) bills every genuine transition, not just the first one.
4. Genuinely new (first found) or changed records are pushed to the dataset and billed once each
   (`nursing-license-status-changed`); a check that finds nothing new costs nothing beyond the fixed
   platform run cost.

### Input

```json
{
  "monitorId": "my-nursing-watch",
  "watches": [
    { "watchId": "new-hire-jones", "licenseType": "RN", "licenseNumber": "621981" }
  ],
  "notifyOn": "new_alerts",
  "webhookUrl": "https://example.com/webhook"
}
```

Add more license holders later under the same `monitorId` — each watch keeps its own independent history.
A `watchId` is permanently bound to the `(licenseType, licenseNumber)` pair it first saw; pointing the same
`watchId` at a different license number OR a different license type later fails the run instead of
silently mixing histories.

`socrataAppToken` is optional — `data.texas.gov` does not require a key for these datasets, but a free
Socrata app token from your own account raises the anonymous request-rate ceiling if you run many watches
across many monitors.

### Output row (per change)

`watchId, licenseNumber, licenseType (RN|VN), changeType ("new"|"status_changed"), licenseStatus,
previousLicenseStatus, currentBoardAction, previousCurrentBoardAction, dateOfBoardActionImposed, lastName,
firstName, middleName, gender, ethnicity, licenseStatusDate, licenseExpirationDate,
texasLicenseIssuanceDate, countyOfResidence, employmentStatus, primaryPracticeSetting,
primaryPracticePositionType, primarySpecialty, highestDegree, basicNursingEducation,
currentAdvancedPracticeRecognition, stateOfOriginalLicensure, dateOfLastRenewal, dateOfLastUpdate,
nameOfBasicNursingSchool, graduationDateFromNursingSchool, placeOfEmployment, monitorId, runId,
discoveredAt, eventId, billed`

### Billing

Pay-per-event: `nursing-license-status-changed` — charged only for a watch's first found record (baseline
is free) or a genuine change in `license_status` and/or `current_board_action`/`date_of_board_action_imposed`
since the previous check. Failed/blocked checks are never charged.

#### Delivery/billing guarantee: **at-most-once**, not exactly-once

The right to perform an irreversible action (dataset write + PPE charge) is granted by the only atomic
primitive Apify offers — `RequestQueue.addRequest(uniqueKey)` → `wasAlreadyPresent` — in a separate
named queue that acts as a permanent journal (`<prefix>-<monitorId>-claims`). The Apify Key-Value Store
has no CAS, no conditional write and no ETag, so it can only ever be a diagnostic state machine, never
the source of at-most-once.

Concretely: for one computed event, delivery and charging each happen **no more than once**. If the run
dies *after* taking the claim, the event may be **lost** (it stays `dataset_unknown`/`charge_unknown`
and is never re-delivered) — but you will never be billed twice. That is a deliberate trade: "never
overcharge" beats "never lose a row".

Boundaries of the guarantee, stated honestly:

- Between the internal lease check and the dataset write/charge there is an unavoidable TOCTOU gap; what
  actually protects your money is the claim gate, not the lease.
- The guarantee holds for as long as the named claims queue exists. Anyone with account access can
  delete or recreate it via Console/API, which starts the journal from zero. This is a boundary of any
  durable storage, not a defect of the protocol.
- The guarantee applies from the build in which the claim gate was introduced onward. Older builds must
  not keep running against the same `monitorId`.

### Important — read before relying on this for any hiring/staffing/compliance decision

**This registry is a publication of license status, not a certification of good standing or a complete
disciplinary record.** The Texas Board of Nursing maintains its own authoritative enforcement files; the
public bulk dataset reflects what TX BON has published, which is updated only **monthly** (not daily) and
can lag real-world disciplinary events. **This actor is an informational monitor of CHANGES to that public
publication — it is NOT a current "license to practice" certification, NOT a disciplinary-history lookup,
and NOT legal/HR advice.** Always confirm directly at
[bon.texas.gov/licensure\_verification.asp](https://www.bon.texas.gov/licensure_verification.asp) (or with
TX BON directly) before acting on any single entry — especially for hiring, staffing placement,
credentialing, or compliance decisions.

### Honest limits

- **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 default-dataset mirror write fails (e.g. a
  transient Apify storage error interrupts the run between the two writes), the item is marked
  `dataset_unknown`, billing for it is permanently blocked (fail-closed — we never charge for a row we
  can't confirm was delivered), and the run does not retry into re-creating that exact row. The durable
  dataset can therefore end up with a small number of orphan rows that were never mirrored and never
  billed. 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: the
  row is written before `Actor.charge()` runs, so if charging then fails or comes back `charge_unknown`,
  the row is present but not confirmably paid. **`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.
- **One watch = one nursing license, one request.** There is no bulk/roster mode — to track a whole unit or
  staffing pool, add one watch per license (up to 30 per run). This keeps the network cost fixed and
  predictable regardless of the registry's total size (793k+ / 318k+ rows), and keeps each license's
  history independently auditable.
- **A "not found" result on the very first check of a watch is reported honestly, not as an error** —
  `coverage.watches[].matched: false`. Neither registry deletes records (confirmed live 13.09.2026: decades-
  old `DECEASED (E)` licenses issued in 1927/1951 are still returned by a direct point query, each with a
  current `date_of_last_update` from the monthly refresh), so a typo'd `licenseNumber` or wrong
  `licenseType` will simply never match; it costs nothing and is safe to correct and retry under the same
  `watchId`.
- **A license number that WAS found on a previous check but is NOT found on a later one** is treated as
  `source_access_limited` for that watch this run — no baseline/history update, no billing. Since neither
  registry deletes rows (see above), this should never happen from a genuine data change; it's the honest
  fallback if the source ever answers unexpectedly.
- **Billing tracks `license_status` + `current_board_action` + `date_of_board_action_imposed` only**,
  deliberately excluding `date_of_last_renewal`/`lincense_expiration_date`/`date_of_last_update` (which
  shift routinely on every renewal for every actively licensed holder) and employment/education/address
  fields (which can change administratively without any change to license standing). Those fields are
  still delivered in every row for context.
- **Update cadence is monthly, not daily** — this is a property of the source (TX BON), not a limitation of
  this actor. Running this monitor more than once a month against the same watches will typically find
  nothing new between refreshes.
- We don't invent data: if the API ever returns something other than a bare JSON array, more than one row
  for a single `license_number` (violating the dataset's own uniqueness contract), or a row missing
  `license_number`/`license_status`/`current_board_action`, the run reports it honestly
  (`source_access_limited`) instead of guessing.

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-30 objects: {"watchId": "new-hire-jones", "licenseType": "RN", "licenseNumber": "621981"}. licenseType is RN (Registered Nurse) or VN (Vocational/Licensed Vocational Nurse) — the same numeric licenseNumber exists independently in BOTH pools (different people), so licenseType picks the correct one. licenseNumber is the plain numeric TX Board of Nursing license number as published in the registry — look it up at https://www.bon.texas.gov/licensure\_verification.asp if you only have a name. One watch = one license holder. New licenses can be added later under the same monitorId.

## `socrataAppToken` (type: `string`):

Optional. data.texas.gov does not require a key for this dataset, but a free Socrata app token (from your own data.texas.gov account) raises the anonymous request-rate ceiling if you run many watches across many monitors. Leave empty for normal use — a handful of watches per run works fine without one.

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

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

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

Optional. Receives a digest of delivered (paid) status changes as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "my-nursing-watch",
  "watches": [
    {
      "watchId": "example-rn",
      "licenseType": "RN",
      "licenseNumber": "621981"
    }
  ],
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every row this run produced. Key fields: watchId, licenseNumber, licenseType (RN|VN), changeType (new|status\_changed), licenseStatus, previousLicenseStatus, currentBoardAction, previousCurrentBoardAction, dateOfBoardActionImposed, lastName, firstName, countyOfResidence. Informational only — not a legal certification of current standing.

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

What this run actually covered and what it charged for: per-watch status/reason/matched, records delivered and billed, requested/attempted/succeeded/failed watch 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": "my-nursing-watch",
    "watches": [
        {
            "watchId": "example-rn",
            "licenseType": "RN",
            "licenseNumber": "621981"
        }
    ],
    "notifyOn": "new_alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/tx-nursing-board-license-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": "my-nursing-watch",
    "watches": [{
            "watchId": "example-rn",
            "licenseType": "RN",
            "licenseNumber": "621981",
        }],
    "notifyOn": "new_alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/tx-nursing-board-license-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": "my-nursing-watch",
  "watches": [
    {
      "watchId": "example-rn",
      "licenseType": "RN",
      "licenseNumber": "621981"
    }
  ],
  "notifyOn": "new_alerts"
}' |
apify call titan_coder/tx-nursing-board-license-status-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/tx-nursing-board-license-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/OfhMAWLbPWLhQcfKd/builds/gU4qx6dBe6VA5LWbx/openapi.json
