Skip to main content

Python SDK for Licensy — real-time U.S. medical-license verification across all 50 states + DC, sourced from state medical boards.

Project description

Licensy Python SDK

Real-time U.S. medical-license verification across all 50 states + DC, sourced directly from state medical boards. Free tier returns basic status / expiration; paid tiers (Pro / Enterprise) unlock board-source screenshots, bulk verification, disciplinary actions, point-in-time history, and webhook subscriptions.

Install

pip install licensy

Quickstart

from licensy import LicensyClient

client = LicensyClient(api_key="sk_live_...")

result = client.verify_license(npi="1700013174", state="CA")
if result.license_active:
    print(f"Active through {result.expiration_date}")
else:
    print(f"Inactive: {result.status}")

Async

from licensy import AsyncLicensyClient

async with AsyncLicensyClient(api_key="sk_live_...") as client:
    result = await client.verify_license(npi="1700013174", state="CA")

All tools

# Single state, single physician
client.verify_license(npi="...", state="CA")

# All states for one physician
result = client.list_physician_licenses(npi="...", include_inactive=False)
for license in result:
    print(license.state, license.status)

# Cheapest call — just the status word
client.get_license_status(npi="...", state="CA")  # → "active"

# Audit-grade screenshot of the state-board page (Paid only)
client.get_screenshot_url(npi="...", state="CA")

# Bulk verification (Paid only)
client.bulk_verify([
    {"npi": "1700013174", "state": "CA"},
    {"npi": "1700013174", "state": "NY"},
])

# Find disciplinary actions (Paid only)
client.search_disciplinary_actions(npi="...")

# Was this physician licensed on YYYY-MM-DD? (Paid only)
client.get_license_history(npi="...", state="CA", as_of_date="2024-08-12")

# Subscribe to status-change webhooks (Paid only)
sub = client.subscribe_to_changes(
    npis=["1700013174"],
    webhook_url="https://your-app.com/webhooks/licensy",
)
client.unsubscribe(subscription_id=sub.subscription_id)

Error handling

from licensy import (
    LicensyClient,
    AuthError,
    NotFoundError,
    RateLimitError,
    TierUpgradeRequired,
    ValidationError,
)

try:
    client.get_screenshot_url(npi="...", state="CA")
except TierUpgradeRequired as e:
    print(f"Need {e.required_tier} plan for screenshots")
except RateLimitError as e:
    print(f"Slow down — retry in {e.retry_after_seconds}s")
except NotFoundError:
    print("No license on file for that NPI/state")
except AuthError:
    print("Check your API key")
except ValidationError as e:
    print(f"Bad input: {e.details}")

Configuration

client = LicensyClient(
    api_key="sk_live_...",
    base_url="https://mcp.licensy.ai",  # default; override for staging
    timeout=30.0,
)

You can also bring your own httpx.Client for advanced proxying / retry behaviour:

import httpx
client = LicensyClient(api_key="...", http_client=httpx.Client(proxy="..."))

Real-world recipes

Daily roster check (cron-friendly)

import csv, datetime, os, sys
from licensy import LicensyClient, NotFoundError

client = LicensyClient(api_key=os.environ["LICENSY_API_KEY"])
today = datetime.date.today()
warn_in = datetime.timedelta(days=60)

with open("roster.csv") as f, open("alerts.csv", "w") as out:
    reader = csv.DictReader(f)
    writer = csv.writer(out)
    writer.writerow(["npi", "state", "issue"])
    for row in reader:
        try:
            r = client.verify_license(npi=row["npi"], state=row["state"])
        except NotFoundError:
            writer.writerow([row["npi"], row["state"], "no record"])
            continue
        if not r.license_active:
            writer.writerow([row["npi"], row["state"], f"inactive: {r.status}"])
        elif r.expiration_date and (r.expiration_date - today) < warn_in:
            writer.writerow([row["npi"], row["state"], f"expires {r.expiration_date}"])

print("Done — see alerts.csv", file=sys.stderr)

Webhook receiver (FastAPI) with signature verification

import hmac, hashlib, os
from fastapi import FastAPI, Header, HTTPException, Request

SECRET = os.environ["LICENSY_WEBHOOK_SECRET"].encode()
app = FastAPI()

@app.post("/webhooks/licensy")
async def receive(request: Request, x_licensy_signature: str = Header(...)):
    body = await request.body()
    expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, x_licensy_signature):
        raise HTTPException(401, "bad signature")
    event = await request.json()
    # event.type ∈ {"license.expired", "license.status_changed", ...}
    print(f"{event['type']} for NPI {event['data']['npi']}/{event['data']['state']}")
    return {"ok": True}

Signing-secret format and snippets in 5 languages at https://licensy.ai/docs#webhook-signing.

Bulk verification with retry

import time
from licensy import LicensyClient, RateLimitError

client = LicensyClient(api_key=os.environ["LICENSY_API_KEY"])
pairs = [{"npi": n, "state": s} for n, s in roster]

while True:
    try:
        results = client.bulk_verify(pairs)
        break
    except RateLimitError as e:
        time.sleep(e.retry_after_seconds)

inactive = [r for r in results if not r.license_active]
print(f"{len(inactive)} inactive of {len(results)}")

Versioning

licensy.__version__ is the package version. The wire protocol is versioned at /api/v1/...; minor SDK releases stay backward-compatible on the API.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

licensy-0.1.0.tar.gz (14.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

licensy-0.1.0-py3-none-any.whl (11.4 kB view details)

Uploaded Python 3

File details

Details for the file licensy-0.1.0.tar.gz.

File metadata

  • Download URL: licensy-0.1.0.tar.gz
  • Upload date:
  • Size: 14.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.11

File hashes

Hashes for licensy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ce634276855073991aec1b435dbcabc1642a7a787a2d83abffa2bd24502deb43
MD5 6488e04e9dbba0a2237ba44a5b7e1aa1
BLAKE2b-256 fcc0881b8c4fb4445140a4f4a4a23af0a27ad5fb0d1a462b9db5d07dcb200e32

See more details on using hashes here.

File details

Details for the file licensy-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: licensy-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 11.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.11

File hashes

Hashes for licensy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1973dffb9ad9de32f6ee66296f69b7c7e500e67912909b683abaf6fee19f5cb0
MD5 cc3110f18805e1cc51f42bab8b209b79
BLAKE2b-256 27ebbcd9df763bde872bb3b700a2b2cc98e54227571424fdd12d3f79fd91ee60

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page