snipget-client 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.
- snipget_client-0.1.0/.github/workflows/ci.yml +34 -0
- snipget_client-0.1.0/.github/workflows/publish.yml +62 -0
- snipget_client-0.1.0/.gitignore +38 -0
- snipget_client-0.1.0/LICENSE +21 -0
- snipget_client-0.1.0/PKG-INFO +170 -0
- snipget_client-0.1.0/README.md +142 -0
- snipget_client-0.1.0/pyproject.toml +59 -0
- snipget_client-0.1.0/src/snipget/__init__.py +41 -0
- snipget_client-0.1.0/src/snipget/_client.py +373 -0
- snipget_client-0.1.0/src/snipget/_exceptions.py +145 -0
- snipget_client-0.1.0/src/snipget/_response.py +101 -0
- snipget_client-0.1.0/src/snipget/_version.py +1 -0
- snipget_client-0.1.0/src/snipget/py.typed +0 -0
- snipget_client-0.1.0/tests/_helpers.py +80 -0
- snipget_client-0.1.0/tests/conftest.py +18 -0
- snipget_client-0.1.0/tests/test_async.py +190 -0
- snipget_client-0.1.0/tests/test_client.py +252 -0
- snipget_client-0.1.0/tests/test_errors.py +227 -0
- snipget_client-0.1.0/tests/test_retry.py +212 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install -e ".[dev]"
|
|
26
|
+
|
|
27
|
+
- name: Lint
|
|
28
|
+
run: ruff check src/ tests/
|
|
29
|
+
|
|
30
|
+
- name: Format check
|
|
31
|
+
run: ruff format --check src/ tests/
|
|
32
|
+
|
|
33
|
+
- name: Test
|
|
34
|
+
run: pytest -q
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Publish snipget-client to PyPI via Trusted Publishing (OIDC).
|
|
2
|
+
#
|
|
3
|
+
# No API tokens anywhere: PyPI's trusted-publisher config for the
|
|
4
|
+
# `snipget-client` project trusts exactly this repo + workflow file +
|
|
5
|
+
# the `pypi` environment. Releasing is one step: publish a GitHub
|
|
6
|
+
# Release tagged `vX.Y.Z` (matching pyproject.toml's version — the
|
|
7
|
+
# guard below fails the run on a mismatch).
|
|
8
|
+
|
|
9
|
+
name: Publish to PyPI
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
release:
|
|
13
|
+
types: [published]
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
name: Build distribution
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v5
|
|
21
|
+
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
|
|
26
|
+
- name: Verify release tag matches package version
|
|
27
|
+
run: |
|
|
28
|
+
PKG_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
29
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
30
|
+
if [ "$PKG_VERSION" != "$TAG" ]; then
|
|
31
|
+
echo "::error::Release tag v$TAG does not match pyproject.toml version $PKG_VERSION"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
- name: Build sdist + wheel
|
|
36
|
+
run: |
|
|
37
|
+
python -m pip install --upgrade build
|
|
38
|
+
python -m build
|
|
39
|
+
|
|
40
|
+
- uses: actions/upload-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: dist
|
|
43
|
+
path: dist/
|
|
44
|
+
|
|
45
|
+
publish:
|
|
46
|
+
name: Publish to PyPI
|
|
47
|
+
needs: build
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
environment:
|
|
50
|
+
name: pypi
|
|
51
|
+
url: https://pypi.org/project/snipget-client/
|
|
52
|
+
permissions:
|
|
53
|
+
# Required for PyPI Trusted Publishing — mints the OIDC token the
|
|
54
|
+
# pypa publish action exchanges for a short-lived PyPI credential.
|
|
55
|
+
id-token: write
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/download-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: dist
|
|
60
|
+
path: dist/
|
|
61
|
+
|
|
62
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Byte-compiled / optimized
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# Test / lint caches
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
htmlcov/
|
|
22
|
+
.tox/
|
|
23
|
+
|
|
24
|
+
# Type checker caches
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.pyre/
|
|
27
|
+
|
|
28
|
+
# Editors / IDE
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
|
|
33
|
+
# OS
|
|
34
|
+
.DS_Store
|
|
35
|
+
Thumbs.db
|
|
36
|
+
|
|
37
|
+
# Local env files
|
|
38
|
+
.env
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Snipget Inc.
|
|
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.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snipget-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python client for the Snipget API: data normalization, parsing, validation, and classification utilities for AI agents.
|
|
5
|
+
Project-URL: Homepage, https://snipget.ai
|
|
6
|
+
Project-URL: Documentation, https://api.snipget.ai/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/snipget/snipget-python
|
|
8
|
+
Author-email: "Snipget Inc." <hello@snipget.ai>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent-tools,ai-agents,api,data-normalization,data-validation,healthcare,llm,mcp,npi,parsing
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx<1,>=0.24.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# snipget-client
|
|
30
|
+
|
|
31
|
+
The official Python client for [Snipget](https://snipget.ai), the hosted utility API for AI agents: data normalization, parsing, validation, and classification over plain HTTPS.
|
|
32
|
+
|
|
33
|
+
## What is Snipget
|
|
34
|
+
|
|
35
|
+
Snipget is a hosted, pay-per-call utility API built for AI agents and the developers who build them. It serves 130+ programmatic endpoints for data normalization, parsing, validation, and classification, with particular depth in healthcare data: NPI validation and lookup, DEA numbers, provider taxonomy, credentials, and certifications. Every endpoint is deterministic (no LLM calls inside the API), returns a confidence score, and ships in single-record and batch variants.
|
|
36
|
+
|
|
37
|
+
Snipget is agent-native by design. Agents can discover and call it through the [OpenAPI spec](https://api.snipget.ai/openapi.json) or the MCP server, and every response uses one consistent JSON envelope so a single integration covers the whole catalog. This package is a thin HTTP wrapper around that hosted API; all the actual logic runs server-side, and the [interactive docs](https://api.snipget.ai/docs) are the per-endpoint contract.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install snipget-client
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python 3.10+. The only dependency is [httpx](https://www.python-httpx.org/).
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
You need an API key from [snipget.ai](https://snipget.ai). One generic `call()` method reaches every endpoint; pass the path and the JSON payload from the [API docs](https://api.snipget.ai/docs).
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from snipget import Client
|
|
53
|
+
|
|
54
|
+
client = Client(api_key="YOUR_API_KEY") # or set SNIPGET_API_KEY
|
|
55
|
+
|
|
56
|
+
resp = client.call("/healthcare/npi/validate", {"npi": "1234567893"})
|
|
57
|
+
|
|
58
|
+
print(resp.result)
|
|
59
|
+
# {'npi': 1234567893, 'is_valid': True, 'checksum_valid': True, 'input_was_clean': True}
|
|
60
|
+
print(resp.confidence) # 1.0
|
|
61
|
+
print(resp.meta.cost_units) # 1
|
|
62
|
+
print(resp.meta.request_id) # 'req_...'
|
|
63
|
+
|
|
64
|
+
# Batch variants exist for every utility:
|
|
65
|
+
resp = client.call(
|
|
66
|
+
"/healthcare/npi/validate/batch",
|
|
67
|
+
{"items": ["1234567893", "1234567890"]},
|
|
68
|
+
)
|
|
69
|
+
print(resp.result["summary"]) # {'total': 2, 'valid': 1, 'invalid': 1}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Async, same surface:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import asyncio
|
|
76
|
+
from snipget import AsyncClient
|
|
77
|
+
|
|
78
|
+
async def main():
|
|
79
|
+
async with AsyncClient() as client: # reads SNIPGET_API_KEY
|
|
80
|
+
resp = await client.call(
|
|
81
|
+
"/common/phone/validate",
|
|
82
|
+
{"value": "(415) 555-0132", "country_hint": "US"},
|
|
83
|
+
)
|
|
84
|
+
print(resp.result)
|
|
85
|
+
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`call()` defaults to `POST` when a payload is given and `GET` otherwise, which matches every endpoint in the spec; pass `method=` to override.
|
|
90
|
+
|
|
91
|
+
## Authentication
|
|
92
|
+
|
|
93
|
+
Get an API key at [snipget.ai](https://snipget.ai). The client resolves the key in this order:
|
|
94
|
+
|
|
95
|
+
1. `Client(api_key="...")`
|
|
96
|
+
2. The `SNIPGET_API_KEY` environment variable
|
|
97
|
+
|
|
98
|
+
By default the key is sent as `Authorization: Bearer <key>`. The API also accepts an `X-API-Key` header; opt in with `Client(auth_header="x-api-key")`.
|
|
99
|
+
|
|
100
|
+
## Error handling
|
|
101
|
+
|
|
102
|
+
Every API error is raised as a typed exception. All of them subclass `SnipgetError` and carry `error_code`, `message`, `request_id`, `http_status`, and the full parsed envelope as `body`.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import snipget
|
|
106
|
+
|
|
107
|
+
client = snipget.Client()
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
resp = client.call("/healthcare/npi/validate", {"npi": "1234567893"})
|
|
111
|
+
except snipget.AuthenticationError as e:
|
|
112
|
+
print("Check your API key:", e.error_code) # 401/403
|
|
113
|
+
except snipget.InvalidRequestError as e:
|
|
114
|
+
print("Bad request:", e.body.get("details")) # 400/422
|
|
115
|
+
except snipget.RateLimitError as e:
|
|
116
|
+
print("Throttled; retry in", e.retry_after, "seconds") # 429 RATE_LIMITED
|
|
117
|
+
except snipget.QuotaExceededError as e:
|
|
118
|
+
print("Out of monthly capacity:", e.body.get("limit_type"))
|
|
119
|
+
print("Allowance left (USD):", e.credit_remaining_usd) # 429 QUOTA_EXCEEDED
|
|
120
|
+
except snipget.MaintenanceError as e:
|
|
121
|
+
print("Maintenance window; retry in", e.retry_after) # 503 MAINTENANCE_MODE
|
|
122
|
+
except snipget.APIError as e:
|
|
123
|
+
print("Server error; quote this id to support:", e.request_id)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The two 429s mean different things: `RateLimitError` is a per-second throughput throttle and clears in seconds; `QuotaExceededError` means the monthly included calls or prepaid overage allowance are exhausted and will not clear until the monthly reset, a tier upgrade, or an allowance top-up. The client retries the first automatically and never retries the second.
|
|
127
|
+
|
|
128
|
+
## Retries and timeouts
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
client = Client(
|
|
132
|
+
api_key="...",
|
|
133
|
+
timeout=30.0, # per-request timeout in seconds
|
|
134
|
+
max_retries=2, # retries on top of the initial attempt
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The client automatically retries network errors, `RATE_LIMITED` 429s (honoring the server's `Retry-After`), and 5xx responses, using exponential backoff with jitter. Snipget utility calls are pure and idempotent, so retrying a POST is safe. It never retries `QUOTA_EXCEEDED` or any other 4xx. Maintenance 503s are retried on the short backoff only; if the window outlasts the retry budget you get a `MaintenanceError` with `retry_after` (typically 300 seconds) so you can schedule your own retry.
|
|
139
|
+
|
|
140
|
+
## The response envelope
|
|
141
|
+
|
|
142
|
+
Every Snipget endpoint, success or error, returns one envelope shape. `call()` returns a `SnipgetResponse`:
|
|
143
|
+
|
|
144
|
+
| Attribute | Type | Meaning |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| `result` | endpoint-specific | The payload, exactly as the API returned it |
|
|
147
|
+
| `confidence` | `float` | 0.0-1.0 confidence score (1.0 = deterministic match; batch responses always report 1.0 at the top level, with per-item confidences inside `result.items`) |
|
|
148
|
+
| `status` | `str` | `"ok"` on success |
|
|
149
|
+
| `meta.cost_units` | `int` | Billable units consumed by this call |
|
|
150
|
+
| `meta.request_id` | `str` | Server request id; quote it to support |
|
|
151
|
+
| `meta.elapsed_ms` | `int` | Server-side processing time |
|
|
152
|
+
| `meta.version` | `str` | API version |
|
|
153
|
+
| `meta.rate_limit_remaining` / `meta.rate_limit_reset` | `int` | Throughput headroom and bucket reset (unix time) |
|
|
154
|
+
| `meta.quota_remaining` / `meta.quota_reset` | `int` | Monthly included-call headroom and reset (unix time) |
|
|
155
|
+
| `meta.credit_remaining_usd` | `float` | Live prepaid-allowance balance, populated once a call starts burning allowance |
|
|
156
|
+
| `meta.trace` | `list[str]` | Reasoning trace, when the request set `include_trace: true` |
|
|
157
|
+
| `raw` | `dict` | The full unmodified envelope |
|
|
158
|
+
|
|
159
|
+
Meta fields the server didn't send are `None`; unknown future fields stay available via `meta.raw`.
|
|
160
|
+
|
|
161
|
+
## Links
|
|
162
|
+
|
|
163
|
+
- Website: [https://snipget.ai](https://snipget.ai)
|
|
164
|
+
- Interactive API docs: [https://api.snipget.ai/docs](https://api.snipget.ai/docs)
|
|
165
|
+
- OpenAPI spec: [https://api.snipget.ai/openapi.json](https://api.snipget.ai/openapi.json)
|
|
166
|
+
- npm sibling: a JavaScript/TypeScript client (`snipget` on npm) is planned but not yet published
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT. Copyright 2026 Snipget Inc.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# snipget-client
|
|
2
|
+
|
|
3
|
+
The official Python client for [Snipget](https://snipget.ai), the hosted utility API for AI agents: data normalization, parsing, validation, and classification over plain HTTPS.
|
|
4
|
+
|
|
5
|
+
## What is Snipget
|
|
6
|
+
|
|
7
|
+
Snipget is a hosted, pay-per-call utility API built for AI agents and the developers who build them. It serves 130+ programmatic endpoints for data normalization, parsing, validation, and classification, with particular depth in healthcare data: NPI validation and lookup, DEA numbers, provider taxonomy, credentials, and certifications. Every endpoint is deterministic (no LLM calls inside the API), returns a confidence score, and ships in single-record and batch variants.
|
|
8
|
+
|
|
9
|
+
Snipget is agent-native by design. Agents can discover and call it through the [OpenAPI spec](https://api.snipget.ai/openapi.json) or the MCP server, and every response uses one consistent JSON envelope so a single integration covers the whole catalog. This package is a thin HTTP wrapper around that hosted API; all the actual logic runs server-side, and the [interactive docs](https://api.snipget.ai/docs) are the per-endpoint contract.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install snipget-client
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Python 3.10+. The only dependency is [httpx](https://www.python-httpx.org/).
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
You need an API key from [snipget.ai](https://snipget.ai). One generic `call()` method reaches every endpoint; pass the path and the JSON payload from the [API docs](https://api.snipget.ai/docs).
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from snipget import Client
|
|
25
|
+
|
|
26
|
+
client = Client(api_key="YOUR_API_KEY") # or set SNIPGET_API_KEY
|
|
27
|
+
|
|
28
|
+
resp = client.call("/healthcare/npi/validate", {"npi": "1234567893"})
|
|
29
|
+
|
|
30
|
+
print(resp.result)
|
|
31
|
+
# {'npi': 1234567893, 'is_valid': True, 'checksum_valid': True, 'input_was_clean': True}
|
|
32
|
+
print(resp.confidence) # 1.0
|
|
33
|
+
print(resp.meta.cost_units) # 1
|
|
34
|
+
print(resp.meta.request_id) # 'req_...'
|
|
35
|
+
|
|
36
|
+
# Batch variants exist for every utility:
|
|
37
|
+
resp = client.call(
|
|
38
|
+
"/healthcare/npi/validate/batch",
|
|
39
|
+
{"items": ["1234567893", "1234567890"]},
|
|
40
|
+
)
|
|
41
|
+
print(resp.result["summary"]) # {'total': 2, 'valid': 1, 'invalid': 1}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Async, same surface:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import asyncio
|
|
48
|
+
from snipget import AsyncClient
|
|
49
|
+
|
|
50
|
+
async def main():
|
|
51
|
+
async with AsyncClient() as client: # reads SNIPGET_API_KEY
|
|
52
|
+
resp = await client.call(
|
|
53
|
+
"/common/phone/validate",
|
|
54
|
+
{"value": "(415) 555-0132", "country_hint": "US"},
|
|
55
|
+
)
|
|
56
|
+
print(resp.result)
|
|
57
|
+
|
|
58
|
+
asyncio.run(main())
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`call()` defaults to `POST` when a payload is given and `GET` otherwise, which matches every endpoint in the spec; pass `method=` to override.
|
|
62
|
+
|
|
63
|
+
## Authentication
|
|
64
|
+
|
|
65
|
+
Get an API key at [snipget.ai](https://snipget.ai). The client resolves the key in this order:
|
|
66
|
+
|
|
67
|
+
1. `Client(api_key="...")`
|
|
68
|
+
2. The `SNIPGET_API_KEY` environment variable
|
|
69
|
+
|
|
70
|
+
By default the key is sent as `Authorization: Bearer <key>`. The API also accepts an `X-API-Key` header; opt in with `Client(auth_header="x-api-key")`.
|
|
71
|
+
|
|
72
|
+
## Error handling
|
|
73
|
+
|
|
74
|
+
Every API error is raised as a typed exception. All of them subclass `SnipgetError` and carry `error_code`, `message`, `request_id`, `http_status`, and the full parsed envelope as `body`.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import snipget
|
|
78
|
+
|
|
79
|
+
client = snipget.Client()
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
resp = client.call("/healthcare/npi/validate", {"npi": "1234567893"})
|
|
83
|
+
except snipget.AuthenticationError as e:
|
|
84
|
+
print("Check your API key:", e.error_code) # 401/403
|
|
85
|
+
except snipget.InvalidRequestError as e:
|
|
86
|
+
print("Bad request:", e.body.get("details")) # 400/422
|
|
87
|
+
except snipget.RateLimitError as e:
|
|
88
|
+
print("Throttled; retry in", e.retry_after, "seconds") # 429 RATE_LIMITED
|
|
89
|
+
except snipget.QuotaExceededError as e:
|
|
90
|
+
print("Out of monthly capacity:", e.body.get("limit_type"))
|
|
91
|
+
print("Allowance left (USD):", e.credit_remaining_usd) # 429 QUOTA_EXCEEDED
|
|
92
|
+
except snipget.MaintenanceError as e:
|
|
93
|
+
print("Maintenance window; retry in", e.retry_after) # 503 MAINTENANCE_MODE
|
|
94
|
+
except snipget.APIError as e:
|
|
95
|
+
print("Server error; quote this id to support:", e.request_id)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The two 429s mean different things: `RateLimitError` is a per-second throughput throttle and clears in seconds; `QuotaExceededError` means the monthly included calls or prepaid overage allowance are exhausted and will not clear until the monthly reset, a tier upgrade, or an allowance top-up. The client retries the first automatically and never retries the second.
|
|
99
|
+
|
|
100
|
+
## Retries and timeouts
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
client = Client(
|
|
104
|
+
api_key="...",
|
|
105
|
+
timeout=30.0, # per-request timeout in seconds
|
|
106
|
+
max_retries=2, # retries on top of the initial attempt
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The client automatically retries network errors, `RATE_LIMITED` 429s (honoring the server's `Retry-After`), and 5xx responses, using exponential backoff with jitter. Snipget utility calls are pure and idempotent, so retrying a POST is safe. It never retries `QUOTA_EXCEEDED` or any other 4xx. Maintenance 503s are retried on the short backoff only; if the window outlasts the retry budget you get a `MaintenanceError` with `retry_after` (typically 300 seconds) so you can schedule your own retry.
|
|
111
|
+
|
|
112
|
+
## The response envelope
|
|
113
|
+
|
|
114
|
+
Every Snipget endpoint, success or error, returns one envelope shape. `call()` returns a `SnipgetResponse`:
|
|
115
|
+
|
|
116
|
+
| Attribute | Type | Meaning |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `result` | endpoint-specific | The payload, exactly as the API returned it |
|
|
119
|
+
| `confidence` | `float` | 0.0-1.0 confidence score (1.0 = deterministic match; batch responses always report 1.0 at the top level, with per-item confidences inside `result.items`) |
|
|
120
|
+
| `status` | `str` | `"ok"` on success |
|
|
121
|
+
| `meta.cost_units` | `int` | Billable units consumed by this call |
|
|
122
|
+
| `meta.request_id` | `str` | Server request id; quote it to support |
|
|
123
|
+
| `meta.elapsed_ms` | `int` | Server-side processing time |
|
|
124
|
+
| `meta.version` | `str` | API version |
|
|
125
|
+
| `meta.rate_limit_remaining` / `meta.rate_limit_reset` | `int` | Throughput headroom and bucket reset (unix time) |
|
|
126
|
+
| `meta.quota_remaining` / `meta.quota_reset` | `int` | Monthly included-call headroom and reset (unix time) |
|
|
127
|
+
| `meta.credit_remaining_usd` | `float` | Live prepaid-allowance balance, populated once a call starts burning allowance |
|
|
128
|
+
| `meta.trace` | `list[str]` | Reasoning trace, when the request set `include_trace: true` |
|
|
129
|
+
| `raw` | `dict` | The full unmodified envelope |
|
|
130
|
+
|
|
131
|
+
Meta fields the server didn't send are `None`; unknown future fields stay available via `meta.raw`.
|
|
132
|
+
|
|
133
|
+
## Links
|
|
134
|
+
|
|
135
|
+
- Website: [https://snipget.ai](https://snipget.ai)
|
|
136
|
+
- Interactive API docs: [https://api.snipget.ai/docs](https://api.snipget.ai/docs)
|
|
137
|
+
- OpenAPI spec: [https://api.snipget.ai/openapi.json](https://api.snipget.ai/openapi.json)
|
|
138
|
+
- npm sibling: a JavaScript/TypeScript client (`snipget` on npm) is planned but not yet published
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT. Copyright 2026 Snipget Inc.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "snipget-client"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python client for the Snipget API: data normalization, parsing, validation, and classification utilities for AI agents."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "Snipget Inc.", email = "hello@snipget.ai" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"api",
|
|
16
|
+
"ai-agents",
|
|
17
|
+
"llm",
|
|
18
|
+
"data-normalization",
|
|
19
|
+
"data-validation",
|
|
20
|
+
"parsing",
|
|
21
|
+
"healthcare",
|
|
22
|
+
"npi",
|
|
23
|
+
"mcp",
|
|
24
|
+
"agent-tools",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 4 - Beta",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Programming Language :: Python :: 3.13",
|
|
35
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
36
|
+
"Typing :: Typed",
|
|
37
|
+
]
|
|
38
|
+
dependencies = ["httpx>=0.24.0,<1"]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://snipget.ai"
|
|
42
|
+
Documentation = "https://api.snipget.ai/docs"
|
|
43
|
+
Repository = "https://github.com/snipget/snipget-python"
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
dev = ["pytest>=8.0", "ruff>=0.8"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/snipget"]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 100
|
|
56
|
+
target-version = "py310"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = ["E", "F", "W", "I", "UP", "B"]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Official Python client for the Snipget API.
|
|
2
|
+
|
|
3
|
+
Snipget is a hosted utility API for AI agents and developers: data
|
|
4
|
+
normalization, parsing, validation, and classification. This package is a
|
|
5
|
+
thin HTTP wrapper around it — the per-endpoint contract lives in the
|
|
6
|
+
OpenAPI spec at https://api.snipget.ai/openapi.json.
|
|
7
|
+
|
|
8
|
+
from snipget import Client
|
|
9
|
+
|
|
10
|
+
client = Client(api_key="...") # or set SNIPGET_API_KEY
|
|
11
|
+
resp = client.call("/healthcare/npi/validate", {"npi": "1234567893"})
|
|
12
|
+
print(resp.result, resp.confidence, resp.meta.request_id)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from snipget._client import AsyncClient, Client
|
|
16
|
+
from snipget._exceptions import (
|
|
17
|
+
APIError,
|
|
18
|
+
AuthenticationError,
|
|
19
|
+
InvalidRequestError,
|
|
20
|
+
MaintenanceError,
|
|
21
|
+
QuotaExceededError,
|
|
22
|
+
RateLimitError,
|
|
23
|
+
SnipgetError,
|
|
24
|
+
)
|
|
25
|
+
from snipget._response import ResponseMeta, SnipgetResponse
|
|
26
|
+
from snipget._version import __version__
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"APIError",
|
|
30
|
+
"AsyncClient",
|
|
31
|
+
"AuthenticationError",
|
|
32
|
+
"Client",
|
|
33
|
+
"InvalidRequestError",
|
|
34
|
+
"MaintenanceError",
|
|
35
|
+
"QuotaExceededError",
|
|
36
|
+
"RateLimitError",
|
|
37
|
+
"ResponseMeta",
|
|
38
|
+
"SnipgetError",
|
|
39
|
+
"SnipgetResponse",
|
|
40
|
+
"__version__",
|
|
41
|
+
]
|