Skip to main content

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.

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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

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