Metadata-Version: 2.5
Name: vaquill
Version: 0.1.0a1
Summary: Python SDK for the Vaquill US primary law Data API.
Project-URL: Homepage, https://www.vaquill.ai
Project-URL: Documentation, https://www.vaquill.ai/docs/api-guide/sdks/overview
Project-URL: Source, https://github.com/Vaquill-AI/vaquill-sdks/tree/main/python
Project-URL: Issues, https://github.com/Vaquill-AI/vaquill-sdks/issues
Author: Vaquill
License-Expression: MIT
License-File: LICENSE
Keywords: api,cli,law,legal,legal-data,regulations,sdk,statutes,vaquill
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2
Description-Content-Type: text/markdown

# vaquill

Python SDK for the Vaquill US primary law Data API: statutes, regulations, court rules, constitutions, session laws and law-change alerts.

Server-side use only: an API key (`vq_key_...`) must never ship to a browser.
Python 3.11 or newer. Sync and async clients with the same methods.

```python
from vaquill import Vaquill

client = Vaquill()  # reads VAQUILL_API_KEY; VAQUILL_BASE_URL overrides the host

# 2 credits. Even an unresolved citation is charged: the answer is what you pay for.
result = client.statutes.resolve("42 U.S.C. 1983")
if result.resolved and result.section:
    body = client.statutes.get_section_body(result.section.act_id)  # 6 credits
    print(body.plain)

hits = client.statutes.search(
    "statute of limitations for medical malpractice", state="ca"
)  # 4 credits
for hit in hits.results:
    print(hit.citation, hit.section_title)
print(hits.credits_consumed, client.credits_spent)
```

Async is the same with `AsyncVaquill` and `await`:

```python
import asyncio
from vaquill import AsyncVaquill


async def main() -> None:
    async with AsyncVaquill() as client:
        balance = await client.credits.balance()
        print(balance.credits_remaining)


asyncio.run(main())
```

## Namespaces

| Namespace | What it does |
| --- | --- |
| `client.statutes` | search, resolve, sections and their text, browse, changes, enactments, rulemaking, cross-references, citation extraction |
| `client.session_laws` | acts as enacted: search, list, record, text, versions, changes |
| `client.watches` | law-change alerts by webhook or email |
| `client.boards` | the sources a watch can follow |
| `client.credits` | `balance()` |
| `client.pricing` | `get()` the credit price of every endpoint |

All 36 operations, their method names and which ones cost credits are in [docs/OPERATIONS.md](docs/OPERATIONS.md).
Required arguments come first and may be positional; optional ones are keyword-only `snake_case` (sent to the API as camelCase).

## Errors

Every failure is a `VaquillError`. Status errors carry `status_code`, `request_id` and the parsed body.

```python
import vaquill

try:
    client.statutes.resolve("42 U.S.C. 1983")
except vaquill.InsufficientCreditsError:
    ...  # never retried; top up credits
except vaquill.RateLimitError as err:
    print(err.retry_after_seconds)  # seconds; the client already retried with backoff
except vaquill.APIStatusError as err:
    print(err.status_code, err.request_id)
```

## Pagination and the credit warning

`iter_*` helpers hide the offset, cursor and `sinceId`/`beforeId` styles behind one iterator.
**On a charged endpoint every page spends credits.** Iterating a whole result set can cost far more than one call, so always set `max_pages` or `max_items`.

```python
pager = client.statutes.iter_search(
    "negligence", state="ny", limit=10, max_pages=3
)  # at most 12 credits
for hit in pager:
    print(hit.citation)
print(pager.truncated)  # True when the guard stopped it with more available

for page in client.statutes.iter_divisions("STATE", state="wa", max_items=100).pages():
    ...  # whole pages, to control cost yourself
```

## Spend guard

```python
client = Vaquill(
    max_credits=500
)  # charged calls raise SpendGuardExceeded once 500 credits are used
client.credits_spent  # running total across this client and any with_options() copy
client.pricing.get()  # free: the live price list
```

Charged calls send an `Idempotency-Key` automatically and are only retried when that is safe.
Pass `idempotency_key="..."` to any call to supply your own.

## Raw responses

```python
raw = client.with_raw_response.request(
    "GET",
    "/api/v1/us/statutes/resolve",
    cast_to=dict,
    params={"cite": "42 U.S.C. 1983"},
    charged=True,
)
raw.request_id, raw.rate_limit.remaining, raw.credits_charged_this_call, raw.parse()
```

`client.raw_request(method, path, ...)` reaches endpoints newer than this SDK and returns plain JSON.

## Webhooks

Verify the raw request body, never re-serialised JSON.

```python
from vaquill import verify_webhook, WebhookVerificationError

try:
    event = verify_webhook(raw_body, request_headers, WEBHOOK_SECRET, allow_legacy=False)
except WebhookVerificationError:
    ...  # respond 400
```

A watch is created with `client.watches.create("state", state="wa", webhook_url=..., webhook_secret=...)`.
See [examples/](examples/) for runnable versions (`resolve_citation.py`, `search_and_paginate.py`, `watch_and_verify_webhook.py`); the test suite runs them against a fake server.

## Command line

Installing the package also installs a `vaquill` command (standard library only, no extra dependencies).

```bash
export VAQUILL_API_KEY=vq_key_...
vaquill resolve "42 U.S.C. 1983"                  # 2 credits
vaquill search "medical malpractice limitations" --state ca --limit 5 --json
vaquill section USC_42_1983 --body                # 6 credits
vaquill credits                                   # free
vaquill webhook verify --secret "$SECRET" --id "$ID" --timestamp "$TS" --signature "$SIG" < body.json
```

Charged commands print `credits used: N` on stderr, so stdout stays pipeable.
Exit codes: 0 ok, 1 error, 2 usage, 3 auth, 4 no credits, 5 rate limited, 6 not found, 7 network.
Every command, flag and the JSON shape are in [docs/CLI.md](docs/CLI.md).

## Types

Responses are Pydantic v2 models in `vaquill.types`, generated from `openapi/vaquill-us.json` by `scripts/gen_models.py`.
Unknown fields are kept and unknown enum values never raise: a field typed `Status | str` holds the enum member for values this SDK knows and the plain string for new ones.
Every top-level response has `credits_consumed`, `request_id` and `idempotent_replayed`.

## Development

```bash
.venv/bin/python scripts/gen_models.py   # regenerate src/vaquill/types after changing the spec
.venv/bin/python scripts/gen_async.py    # regenerate src/vaquill/resources/_async after editing a sync resource
.venv/bin/python -m pytest -q
```

The sync resources in `src/vaquill/resources/` are the one hand-written definition; the async ones are derived from them mechanically and a test fails on drift.
