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.

Metadata

Release files for licensy 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for licensy 0.1.0
File Size Uploaded
licensy-0.1.0.tar.gz 14.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for licensy 0.1.0
File Interpreter ABI Platform
licensy-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 25.4 kB

Release files / licensy-0.1.0.tar.gz

Download URL licensy-0.1.0.tar.gz
Size 14.0 kB
Tags Source
SHA-256 checksum
How to use checksums
ce634276855073991aec1b435dbcabc1642a7a787a2d83abffa2bd24502deb43
BLAKE2b-256 checksum
How to use checksums
fcc0881b8c4fb4445140a4f4a4a23af0a27ad5fb0d1a462b9db5d07dcb200e32
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.11

Release files / licensy-0.1.0-py3-none-any.whl

Download URL licensy-0.1.0-py3-none-any.whl
Size 11.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1973dffb9ad9de32f6ee66296f69b7c7e500e67912909b683abaf6fee19f5cb0
BLAKE2b-256 checksum
How to use checksums
27ebbcd9df763bde872bb3b700a2b2cc98e54227571424fdd12d3f79fd91ee60
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.11

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page