licensy 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
licensy-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Licensy AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
licensy-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.4
2
+ Name: licensy
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Licensy — real-time U.S. medical-license verification across all 50 states + DC, sourced from state medical boards.
5
+ Author-email: Licensy <engineering@licensy.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://licensy.ai
8
+ Project-URL: Documentation, https://licensy.ai/docs
9
+ Project-URL: API Reference, https://mcp.licensy.ai/.well-known/oauth-protected-resource
10
+ Project-URL: Source, https://github.com/licensyai/licensy-sdk-python
11
+ Keywords: healthcare,credentialing,medical-license,verification,physician,NPI
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Healthcare Industry
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: httpx>=0.25
25
+ Requires-Dist: typing-extensions>=4.0; python_version < "3.11"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
29
+ Requires-Dist: respx>=0.21; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # Licensy Python SDK
33
+
34
+ Real-time U.S. medical-license verification across all 50 states + DC,
35
+ sourced directly from state medical boards. Free tier returns basic
36
+ status / expiration; paid tiers (Pro / Enterprise) unlock board-source
37
+ screenshots, bulk verification, disciplinary actions, point-in-time
38
+ history, and webhook subscriptions.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install licensy
44
+ ```
45
+
46
+ ## Quickstart
47
+
48
+ ```python
49
+ from licensy import LicensyClient
50
+
51
+ client = LicensyClient(api_key="sk_live_...")
52
+
53
+ result = client.verify_license(npi="1700013174", state="CA")
54
+ if result.license_active:
55
+ print(f"Active through {result.expiration_date}")
56
+ else:
57
+ print(f"Inactive: {result.status}")
58
+ ```
59
+
60
+ ## Async
61
+
62
+ ```python
63
+ from licensy import AsyncLicensyClient
64
+
65
+ async with AsyncLicensyClient(api_key="sk_live_...") as client:
66
+ result = await client.verify_license(npi="1700013174", state="CA")
67
+ ```
68
+
69
+ ## All tools
70
+
71
+ ```python
72
+ # Single state, single physician
73
+ client.verify_license(npi="...", state="CA")
74
+
75
+ # All states for one physician
76
+ result = client.list_physician_licenses(npi="...", include_inactive=False)
77
+ for license in result:
78
+ print(license.state, license.status)
79
+
80
+ # Cheapest call — just the status word
81
+ client.get_license_status(npi="...", state="CA") # → "active"
82
+
83
+ # Audit-grade screenshot of the state-board page (Paid only)
84
+ client.get_screenshot_url(npi="...", state="CA")
85
+
86
+ # Bulk verification (Paid only)
87
+ client.bulk_verify([
88
+ {"npi": "1700013174", "state": "CA"},
89
+ {"npi": "1700013174", "state": "NY"},
90
+ ])
91
+
92
+ # Find disciplinary actions (Paid only)
93
+ client.search_disciplinary_actions(npi="...")
94
+
95
+ # Was this physician licensed on YYYY-MM-DD? (Paid only)
96
+ client.get_license_history(npi="...", state="CA", as_of_date="2024-08-12")
97
+
98
+ # Subscribe to status-change webhooks (Paid only)
99
+ sub = client.subscribe_to_changes(
100
+ npis=["1700013174"],
101
+ webhook_url="https://your-app.com/webhooks/licensy",
102
+ )
103
+ client.unsubscribe(subscription_id=sub.subscription_id)
104
+ ```
105
+
106
+ ## Error handling
107
+
108
+ ```python
109
+ from licensy import (
110
+ LicensyClient,
111
+ AuthError,
112
+ NotFoundError,
113
+ RateLimitError,
114
+ TierUpgradeRequired,
115
+ ValidationError,
116
+ )
117
+
118
+ try:
119
+ client.get_screenshot_url(npi="...", state="CA")
120
+ except TierUpgradeRequired as e:
121
+ print(f"Need {e.required_tier} plan for screenshots")
122
+ except RateLimitError as e:
123
+ print(f"Slow down — retry in {e.retry_after_seconds}s")
124
+ except NotFoundError:
125
+ print("No license on file for that NPI/state")
126
+ except AuthError:
127
+ print("Check your API key")
128
+ except ValidationError as e:
129
+ print(f"Bad input: {e.details}")
130
+ ```
131
+
132
+ ## Configuration
133
+
134
+ ```python
135
+ client = LicensyClient(
136
+ api_key="sk_live_...",
137
+ base_url="https://mcp.licensy.ai", # default; override for staging
138
+ timeout=30.0,
139
+ )
140
+ ```
141
+
142
+ You can also bring your own `httpx.Client` for advanced proxying / retry
143
+ behaviour:
144
+
145
+ ```python
146
+ import httpx
147
+ client = LicensyClient(api_key="...", http_client=httpx.Client(proxy="..."))
148
+ ```
149
+
150
+ ## Real-world recipes
151
+
152
+ ### Daily roster check (cron-friendly)
153
+
154
+ ```python
155
+ import csv, datetime, os, sys
156
+ from licensy import LicensyClient, NotFoundError
157
+
158
+ client = LicensyClient(api_key=os.environ["LICENSY_API_KEY"])
159
+ today = datetime.date.today()
160
+ warn_in = datetime.timedelta(days=60)
161
+
162
+ with open("roster.csv") as f, open("alerts.csv", "w") as out:
163
+ reader = csv.DictReader(f)
164
+ writer = csv.writer(out)
165
+ writer.writerow(["npi", "state", "issue"])
166
+ for row in reader:
167
+ try:
168
+ r = client.verify_license(npi=row["npi"], state=row["state"])
169
+ except NotFoundError:
170
+ writer.writerow([row["npi"], row["state"], "no record"])
171
+ continue
172
+ if not r.license_active:
173
+ writer.writerow([row["npi"], row["state"], f"inactive: {r.status}"])
174
+ elif r.expiration_date and (r.expiration_date - today) < warn_in:
175
+ writer.writerow([row["npi"], row["state"], f"expires {r.expiration_date}"])
176
+
177
+ print("Done — see alerts.csv", file=sys.stderr)
178
+ ```
179
+
180
+ ### Webhook receiver (FastAPI) with signature verification
181
+
182
+ ```python
183
+ import hmac, hashlib, os
184
+ from fastapi import FastAPI, Header, HTTPException, Request
185
+
186
+ SECRET = os.environ["LICENSY_WEBHOOK_SECRET"].encode()
187
+ app = FastAPI()
188
+
189
+ @app.post("/webhooks/licensy")
190
+ async def receive(request: Request, x_licensy_signature: str = Header(...)):
191
+ body = await request.body()
192
+ expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
193
+ if not hmac.compare_digest(expected, x_licensy_signature):
194
+ raise HTTPException(401, "bad signature")
195
+ event = await request.json()
196
+ # event.type ∈ {"license.expired", "license.status_changed", ...}
197
+ print(f"{event['type']} for NPI {event['data']['npi']}/{event['data']['state']}")
198
+ return {"ok": True}
199
+ ```
200
+
201
+ Signing-secret format and snippets in 5 languages at <https://licensy.ai/docs#webhook-signing>.
202
+
203
+ ### Bulk verification with retry
204
+
205
+ ```python
206
+ import time
207
+ from licensy import LicensyClient, RateLimitError
208
+
209
+ client = LicensyClient(api_key=os.environ["LICENSY_API_KEY"])
210
+ pairs = [{"npi": n, "state": s} for n, s in roster]
211
+
212
+ while True:
213
+ try:
214
+ results = client.bulk_verify(pairs)
215
+ break
216
+ except RateLimitError as e:
217
+ time.sleep(e.retry_after_seconds)
218
+
219
+ inactive = [r for r in results if not r.license_active]
220
+ print(f"{len(inactive)} inactive of {len(results)}")
221
+ ```
222
+
223
+ ## Versioning
224
+
225
+ `licensy.__version__` is the package version. The wire protocol is
226
+ versioned at `/api/v1/...`; minor SDK releases stay backward-compatible
227
+ on the API.
228
+
229
+ ## License
230
+
231
+ MIT.
@@ -0,0 +1,200 @@
1
+ # Licensy Python SDK
2
+
3
+ Real-time U.S. medical-license verification across all 50 states + DC,
4
+ sourced directly from state medical boards. Free tier returns basic
5
+ status / expiration; paid tiers (Pro / Enterprise) unlock board-source
6
+ screenshots, bulk verification, disciplinary actions, point-in-time
7
+ history, and webhook subscriptions.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install licensy
13
+ ```
14
+
15
+ ## Quickstart
16
+
17
+ ```python
18
+ from licensy import LicensyClient
19
+
20
+ client = LicensyClient(api_key="sk_live_...")
21
+
22
+ result = client.verify_license(npi="1700013174", state="CA")
23
+ if result.license_active:
24
+ print(f"Active through {result.expiration_date}")
25
+ else:
26
+ print(f"Inactive: {result.status}")
27
+ ```
28
+
29
+ ## Async
30
+
31
+ ```python
32
+ from licensy import AsyncLicensyClient
33
+
34
+ async with AsyncLicensyClient(api_key="sk_live_...") as client:
35
+ result = await client.verify_license(npi="1700013174", state="CA")
36
+ ```
37
+
38
+ ## All tools
39
+
40
+ ```python
41
+ # Single state, single physician
42
+ client.verify_license(npi="...", state="CA")
43
+
44
+ # All states for one physician
45
+ result = client.list_physician_licenses(npi="...", include_inactive=False)
46
+ for license in result:
47
+ print(license.state, license.status)
48
+
49
+ # Cheapest call — just the status word
50
+ client.get_license_status(npi="...", state="CA") # → "active"
51
+
52
+ # Audit-grade screenshot of the state-board page (Paid only)
53
+ client.get_screenshot_url(npi="...", state="CA")
54
+
55
+ # Bulk verification (Paid only)
56
+ client.bulk_verify([
57
+ {"npi": "1700013174", "state": "CA"},
58
+ {"npi": "1700013174", "state": "NY"},
59
+ ])
60
+
61
+ # Find disciplinary actions (Paid only)
62
+ client.search_disciplinary_actions(npi="...")
63
+
64
+ # Was this physician licensed on YYYY-MM-DD? (Paid only)
65
+ client.get_license_history(npi="...", state="CA", as_of_date="2024-08-12")
66
+
67
+ # Subscribe to status-change webhooks (Paid only)
68
+ sub = client.subscribe_to_changes(
69
+ npis=["1700013174"],
70
+ webhook_url="https://your-app.com/webhooks/licensy",
71
+ )
72
+ client.unsubscribe(subscription_id=sub.subscription_id)
73
+ ```
74
+
75
+ ## Error handling
76
+
77
+ ```python
78
+ from licensy import (
79
+ LicensyClient,
80
+ AuthError,
81
+ NotFoundError,
82
+ RateLimitError,
83
+ TierUpgradeRequired,
84
+ ValidationError,
85
+ )
86
+
87
+ try:
88
+ client.get_screenshot_url(npi="...", state="CA")
89
+ except TierUpgradeRequired as e:
90
+ print(f"Need {e.required_tier} plan for screenshots")
91
+ except RateLimitError as e:
92
+ print(f"Slow down — retry in {e.retry_after_seconds}s")
93
+ except NotFoundError:
94
+ print("No license on file for that NPI/state")
95
+ except AuthError:
96
+ print("Check your API key")
97
+ except ValidationError as e:
98
+ print(f"Bad input: {e.details}")
99
+ ```
100
+
101
+ ## Configuration
102
+
103
+ ```python
104
+ client = LicensyClient(
105
+ api_key="sk_live_...",
106
+ base_url="https://mcp.licensy.ai", # default; override for staging
107
+ timeout=30.0,
108
+ )
109
+ ```
110
+
111
+ You can also bring your own `httpx.Client` for advanced proxying / retry
112
+ behaviour:
113
+
114
+ ```python
115
+ import httpx
116
+ client = LicensyClient(api_key="...", http_client=httpx.Client(proxy="..."))
117
+ ```
118
+
119
+ ## Real-world recipes
120
+
121
+ ### Daily roster check (cron-friendly)
122
+
123
+ ```python
124
+ import csv, datetime, os, sys
125
+ from licensy import LicensyClient, NotFoundError
126
+
127
+ client = LicensyClient(api_key=os.environ["LICENSY_API_KEY"])
128
+ today = datetime.date.today()
129
+ warn_in = datetime.timedelta(days=60)
130
+
131
+ with open("roster.csv") as f, open("alerts.csv", "w") as out:
132
+ reader = csv.DictReader(f)
133
+ writer = csv.writer(out)
134
+ writer.writerow(["npi", "state", "issue"])
135
+ for row in reader:
136
+ try:
137
+ r = client.verify_license(npi=row["npi"], state=row["state"])
138
+ except NotFoundError:
139
+ writer.writerow([row["npi"], row["state"], "no record"])
140
+ continue
141
+ if not r.license_active:
142
+ writer.writerow([row["npi"], row["state"], f"inactive: {r.status}"])
143
+ elif r.expiration_date and (r.expiration_date - today) < warn_in:
144
+ writer.writerow([row["npi"], row["state"], f"expires {r.expiration_date}"])
145
+
146
+ print("Done — see alerts.csv", file=sys.stderr)
147
+ ```
148
+
149
+ ### Webhook receiver (FastAPI) with signature verification
150
+
151
+ ```python
152
+ import hmac, hashlib, os
153
+ from fastapi import FastAPI, Header, HTTPException, Request
154
+
155
+ SECRET = os.environ["LICENSY_WEBHOOK_SECRET"].encode()
156
+ app = FastAPI()
157
+
158
+ @app.post("/webhooks/licensy")
159
+ async def receive(request: Request, x_licensy_signature: str = Header(...)):
160
+ body = await request.body()
161
+ expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
162
+ if not hmac.compare_digest(expected, x_licensy_signature):
163
+ raise HTTPException(401, "bad signature")
164
+ event = await request.json()
165
+ # event.type ∈ {"license.expired", "license.status_changed", ...}
166
+ print(f"{event['type']} for NPI {event['data']['npi']}/{event['data']['state']}")
167
+ return {"ok": True}
168
+ ```
169
+
170
+ Signing-secret format and snippets in 5 languages at <https://licensy.ai/docs#webhook-signing>.
171
+
172
+ ### Bulk verification with retry
173
+
174
+ ```python
175
+ import time
176
+ from licensy import LicensyClient, RateLimitError
177
+
178
+ client = LicensyClient(api_key=os.environ["LICENSY_API_KEY"])
179
+ pairs = [{"npi": n, "state": s} for n, s in roster]
180
+
181
+ while True:
182
+ try:
183
+ results = client.bulk_verify(pairs)
184
+ break
185
+ except RateLimitError as e:
186
+ time.sleep(e.retry_after_seconds)
187
+
188
+ inactive = [r for r in results if not r.license_active]
189
+ print(f"{len(inactive)} inactive of {len(results)}")
190
+ ```
191
+
192
+ ## Versioning
193
+
194
+ `licensy.__version__` is the package version. The wire protocol is
195
+ versioned at `/api/v1/...`; minor SDK releases stay backward-compatible
196
+ on the API.
197
+
198
+ ## License
199
+
200
+ MIT.
@@ -0,0 +1,75 @@
1
+ """
2
+ Licensy — real-time U.S. medical-license verification.
3
+
4
+ Quickstart
5
+ ==========
6
+
7
+ ::
8
+
9
+ from licensy import LicensyClient
10
+
11
+ client = LicensyClient(api_key="sk_live_...")
12
+
13
+ # Verify one physician in one state
14
+ result = client.verify_license(npi="1700013174", state="CA")
15
+ print(result.status, result.expiration_date)
16
+
17
+ # List all licenses for an NPI
18
+ for lic in client.list_physician_licenses(npi="1700013174"):
19
+ print(lic.state, lic.status)
20
+
21
+ Async usage
22
+ ===========
23
+
24
+ ::
25
+
26
+ from licensy import AsyncLicensyClient
27
+
28
+ async with AsyncLicensyClient(api_key="sk_live_...") as client:
29
+ result = await client.verify_license(npi="1700013174", state="CA")
30
+
31
+ Tier
32
+ ====
33
+
34
+ The same client surface works for free and paid tiers. Paid-only fields
35
+ (screenshot URL, license number, board source, etc.) are populated when
36
+ the API key is on a Pro/Enterprise plan and ``None`` otherwise.
37
+ """
38
+
39
+ from licensy.client import LicensyClient, AsyncLicensyClient
40
+ from licensy.errors import (
41
+ LicensyError,
42
+ AuthError,
43
+ RateLimitError,
44
+ NotFoundError,
45
+ TierUpgradeRequired,
46
+ ValidationError,
47
+ )
48
+ from licensy.models import (
49
+ LicenseResult,
50
+ LicenseListResult,
51
+ LicenseStatus,
52
+ DisciplinaryAction,
53
+ HistoryResult,
54
+ SubscriptionResult,
55
+ )
56
+
57
+ __version__ = "0.1.0"
58
+
59
+ __all__ = [
60
+ "LicensyClient",
61
+ "AsyncLicensyClient",
62
+ "LicensyError",
63
+ "AuthError",
64
+ "RateLimitError",
65
+ "NotFoundError",
66
+ "TierUpgradeRequired",
67
+ "ValidationError",
68
+ "LicenseResult",
69
+ "LicenseListResult",
70
+ "LicenseStatus",
71
+ "DisciplinaryAction",
72
+ "HistoryResult",
73
+ "SubscriptionResult",
74
+ "__version__",
75
+ ]