TX Medical Board License Status Delta avatar

TX Medical Board License Status Delta

Pricing

$10.00 / 1,000 physician license status change delivereds

Go to Apify Store
TX Medical Board License Status Delta

TX Medical Board License Status Delta

Watches named Texas Medical Board physician licenses in the official registry, alerting only on a genuine change: registration status (revoked, suspended, cancelled), disciplinary status, or board action. For hospitals, staffing agencies, insurers, malpractice due-diligence, HR. Free when unchanged.

Pricing

$10.00 / 1,000 physician license status change delivereds

Rating

0.0

(0)

Developer

Radu Furtuna

Radu Furtuna

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Durable monitor for the official Texas Medical Board (TMB) bulk registry — DataSet-01-All Licenses — published as open data on data.texas.gov. Watch specific Physician License (MD/DO) numbers and get notified only when the license's registration status, disciplinary status, license status, or a board action genuinely changes. No API key needed, no login, no captcha.

Source and scope

One Socrata Open Data API dataset: https://data.texas.gov/resource/tm3v-pfq9.json — 511,438 rows total (confirmed live 13.09.2026), covering 37 different TMB-regulated license types (physicians, physician assistants, radiologic technologists, respiratory care practitioners, acupuncturists, perfusionists, and more).

This actor covers only the Physician License pool (185,801 rows) — a deliberate scope decision, not an oversight. Live verification 13.09.2026 found that license_number is a text field that is not unique across the whole dataset: the value "ACTEMP" alone repeats 1,458 times (a placeholder code for Acupuncturist Temporary License, not a personal number), and even the pair

(license_number, license_type)
is not always unique for other license types (e.g. Acudetox Certificate). Within Physician License specifically, license_number is confirmed globally unique (count(*) == count(distinct license_number) == 185,801, live query). Extending this actor to the other 36 license types would require a separate live uniqueness audit for each one — not done, and not assumed. Every server-side request explicitly filters license_type='Physician License' in addition to license_number, and the client independently rejects any response with more than one matching row as a structural anomaly.

license_number format observed live within this pool: either purely numeric (4-7 digits, e.g. 10031, 8085) or one uppercase letter followed by 4 digits (e.g. H0001, B2944, Z4634). A leading zero right after the letter (H0001) is a legitimate part of the identifier and is preserved exactly, not stripped.

Updated live (rowsUpdatedAt observed 2026-09-08, 5 days before this actor was built) — cadence is a property of the source, not guaranteed daily.

Fields tracked for billing

Four fields confirmed live (13.09.2026) to be required and non-blank for 100% of the 185,801 Physician License rows (0 null/empty each): license_number, registration_status (the live "is this license active/cancelled/suspended right now" signal — ACTIVE 107,690, CANC NON-RENEWAL 30,978,

CANCELLED BY REQUEST
12,773, DECEASED 11,914, SUSPENDED, ACTIVE 38, etc.), disciplinary_status (NONE 179,639, SEE PREVIOUS ORDER 3,890, CANCELLED BY BOARD 1,231, UNDER BOARD ORDER 277, SUSPENDED BY BOARD 94, etc.), and license_status (99.93% literal "NONE", with rare non-NONE values also disciplinary in nature, e.g. SUSPENDED BY ATTORNEY GENERAL).

A separate, independent disciplinary signal: current_board_action_date and current_board_action_description — confirmed live to be legitimately null for ~95.8% of Physician License rows, but a live example (license M5333) shows disciplinary_status: NONE and current_board_action_date populated at the same time — a board action can exist independently of the plain-text disciplinary status. This actor treats a change in any of the five fields (registration_status, disciplinary_status, license_status, current_board_action_date, current_board_action_description) as billable.

How it works

  1. Each watch names exactly one Physician License by licenseNumber — look it up at profile.tmb.state.tx.us if you only have a name. One watch = one point query ($where=license_number='<N>' AND license_type='Physician License') = 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 the five tracked fields against the durable record of what it was last time.
  3. Billing deliberately excludes administrative/date fields that shift routinely (license_issue_date, license_expiration_date, registration_status_date, license_status_date, disciplinary_status_date) and non-status fields (practice address, degree, specialties). A status that changes and later reverts 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 (tmb-physician-license-status-changed); a check that finds nothing new costs nothing beyond the fixed platform run cost.

Input

{
"monitorId": "my-tmb-watch",
"watches": [
{ "watchId": "new-hire-robbins", "licenseNumber": "C2582" }
],
"notifyOn": "new_alerts",
"webhookUrl": "https://example.com/webhook"
}

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

socrataAppToken is optional — data.texas.gov does not require a key for this dataset, 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, changeType ("new"|"status_changed"), registrationStatus, previousRegistrationStatus, disciplinaryStatus, previousDisciplinaryStatus, licenseStatus, previousLicenseStatus, disciplinaryStatusDate, currentBoardActionDate, previousCurrentBoardActionDate, currentBoardActionDescription, previousCurrentBoardActionDescription, firstName, lastName, suffix, yearOfBirth, licenseIssueDate, licenseExpirationDate, registrationStatusDate, licenseStatusDate, degree, specialties, practiceAddress, practiceCity, practiceState, practiceZip, currentlyLicensed, monitorId, runId, discoveredAt, eventId, billed

Billing

Pay-per-event: tmb-physician-license-status-changed — charged only for a watch's first found record (baseline is free) or a genuine change in registrationStatus/disciplinaryStatus/licenseStatus/ currentBoardActionDate/currentBoardActionDescription since the previous check. Failed/blocked checks are never charged.

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

This registry is a publication of license status, not a certification of good standing or a complete disciplinary record. The Texas Medical Board maintains its own authoritative enforcement files; the public bulk dataset reflects what TMB has published, which 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/malpractice due-diligence advice. Always confirm directly at profile.tmb.state.tx.us (or with TMB directly) before acting on any single entry — especially for hiring, staffing placement, credentialing, insurance underwriting, or compliance decisions.

Delivery guarantee: at-most-once (we would rather lose an alert than bill you twice)

Each computed change is delivered to the dataset and charged at most once, for as long as the monitor's claim log exists (see the boundary below). Before any irreversible step (writing the row, charging the event) the run takes an atomic claim on that exact change, using the only atomic primitive the Apify platform offers: a request queue's unique-key insert. Exactly one run can win that claim for a given change. The claim log is never consumed, deleted or rotated by this actor; it is a permanent record of what was already attempted, and coverage.claimJournalSize reports its size each run so you can watch it grow (the platform's counter is eventually consistent, so treat it as a lagging estimate, not an exact count).

Where that guarantee ends — the honest boundary. The claim log lives in a named request queue (<prefix>-<monitorId>-claims) in your own account. The at-most-once guarantee holds as long as that queue keeps existing. If you — or any process holding your account credentials — delete, rename or re-create it from the Console or API, the log starts empty and previously delivered changes can be delivered and charged again. That is the unavoidable boundary of any durable storage, not a loophole in the protocol. For the same reason, the actor's storage prefix and internal claim namespace are frozen after release: changing either would create a fresh, empty log with exactly the same effect.

The response the platform returns for each claim is interpreted strictly: only a real boolean false grants the right to write and charge, only a real boolean true denies it, and anything else — a missing field, null, 0, an empty string, a changed SDK response shape — aborts the run's delivery for that item with claim_protocol_error before any row or charge. An answer we do not fully understand is never read as "you may charge".

One thing we deliberately do not claim: the monitor's lease makes overlapping runs a fail-closed exception rather than a fact of life, but between the moment a run verifies it still holds the lease and the moment the dataset write or charge actually lands there is an unavoidable time gap (the platform offers no fencing token for datasets or billing). So "a run that lost the lease can never write another row" would be an overstatement. What actually protects your money is the claim above: the key is already taken, so even a ghost run cannot charge for the same change twice.

The honest consequence, stated plainly: if a run dies after taking the claim but before finishing, that one change is lost. It is recorded as dataset_unknown or charge_unknown and it is not re-delivered on the next run — the next run moves on to the physician's next status change. We deliberately chose possible loss of one alert over the possibility of charging you twice for the same event. This is at-most-once delivery, not exactly-once; any actor that claims exactly-once over a store without compare-and-swap is overstating what the platform can do.

Practically this only happens if the Apify run is killed mid-delivery (platform abort, timeout, migration). Every such case is visible: the run's coverage and run_summary report it, and run_summary.eventsBilled plus Apify's own billing ledger remain the source of truth for what you actually paid for.

Honest limits

  • This actor covers the Physician License pool only — not physician assistants, radiologic technologists, or any of the other 36 license types in the same source dataset. See "Source and scope" above for the live evidence (duplicate/non-unique license_number values in other pools) behind this decision.
  • 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 physician license, one request. There is no bulk/roster mode — to track a whole unit or medical staff, 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 (511k+ rows / 185k+ in the Physician License pool), 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 errorcoverage.watches[].matched: false. TMB does not delete records (confirmed live 13.09.2026: a 1915-issued, 1980-deceased physician license is still returned by a direct point query), so a typo'd licenseNumber, or a license number that belongs to a different license type in the source dataset, 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 TMB does not delete 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 registrationStatus + disciplinaryStatus + licenseStatus + currentBoardActionDate + currentBoardActionDescription only, deliberately excluding licenseIssueDate/licenseExpirationDate/registrationStatusDate/licenseStatusDate/ disciplinaryStatusDate (administrative dates that shift routinely) and practice-address/degree/ specialty fields (which can change administratively without any change to license standing). Those fields are still delivered in every row for context.
  • All four required fields (license_number, registration_status, disciplinary_status, license_status) are confirmed live to be non-blank for 100% of Physician License rows. If the source ever returns a blank/whitespace-only value for one of them, or omits current_board_action_date/ current_board_action_description as a whitespace-only string rather than a true absence, the run treats it as a structural anomaly (source_access_limited), not a silent guess.
  • 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 within the Physician License pool (violating the dataset's own uniqueness contract), or a row whose returned license_number doesn't exactly match the one requested (including case and whitespace), the run reports it honestly (source_access_limited) instead of guessing.

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