vaquill 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.
Files changed (42) hide show
  1. vaquill-0.1.0/.gitignore +17 -0
  2. vaquill-0.1.0/LICENSE +21 -0
  3. vaquill-0.1.0/PKG-INFO +195 -0
  4. vaquill-0.1.0/README.md +166 -0
  5. vaquill-0.1.0/docs/CLI.md +103 -0
  6. vaquill-0.1.0/docs/OPERATIONS.md +89 -0
  7. vaquill-0.1.0/examples/resolve_citation.py +26 -0
  8. vaquill-0.1.0/examples/search_and_paginate.py +29 -0
  9. vaquill-0.1.0/examples/watch_and_verify_webhook.py +42 -0
  10. vaquill-0.1.0/pyproject.toml +90 -0
  11. vaquill-0.1.0/src/vaquill/__init__.py +81 -0
  12. vaquill-0.1.0/src/vaquill/_base_client.py +578 -0
  13. vaquill-0.1.0/src/vaquill/_client.py +94 -0
  14. vaquill-0.1.0/src/vaquill/_config.py +22 -0
  15. vaquill-0.1.0/src/vaquill/_errors.py +207 -0
  16. vaquill-0.1.0/src/vaquill/_idempotency.py +23 -0
  17. vaquill-0.1.0/src/vaquill/_pagination.py +182 -0
  18. vaquill-0.1.0/src/vaquill/_response.py +154 -0
  19. vaquill-0.1.0/src/vaquill/_retry.py +74 -0
  20. vaquill-0.1.0/src/vaquill/_version.py +1 -0
  21. vaquill-0.1.0/src/vaquill/_webhooks.py +109 -0
  22. vaquill-0.1.0/src/vaquill/cli/__init__.py +5 -0
  23. vaquill-0.1.0/src/vaquill/cli/_main.py +628 -0
  24. vaquill-0.1.0/src/vaquill/cli/_render.py +61 -0
  25. vaquill-0.1.0/src/vaquill/py.typed +0 -0
  26. vaquill-0.1.0/src/vaquill/resources/__init__.py +29 -0
  27. vaquill-0.1.0/src/vaquill/resources/_async/__init__.py +1 -0
  28. vaquill-0.1.0/src/vaquill/resources/_async/boards.py +61 -0
  29. vaquill-0.1.0/src/vaquill/resources/_async/credits.py +19 -0
  30. vaquill-0.1.0/src/vaquill/resources/_async/pricing.py +29 -0
  31. vaquill-0.1.0/src/vaquill/resources/_async/session_laws.py +378 -0
  32. vaquill-0.1.0/src/vaquill/resources/_async/statutes.py +702 -0
  33. vaquill-0.1.0/src/vaquill/resources/_async/watches.py +252 -0
  34. vaquill-0.1.0/src/vaquill/resources/_base.py +97 -0
  35. vaquill-0.1.0/src/vaquill/resources/boards.py +60 -0
  36. vaquill-0.1.0/src/vaquill/resources/credits.py +18 -0
  37. vaquill-0.1.0/src/vaquill/resources/pricing.py +28 -0
  38. vaquill-0.1.0/src/vaquill/resources/session_laws.py +377 -0
  39. vaquill-0.1.0/src/vaquill/resources/statutes.py +691 -0
  40. vaquill-0.1.0/src/vaquill/resources/watches.py +251 -0
  41. vaquill-0.1.0/src/vaquill/types/__init__.py +434 -0
  42. vaquill-0.1.0/src/vaquill/types/_models.py +10219 -0
@@ -0,0 +1,17 @@
1
+ # Python
2
+ .venv/
3
+ __pycache__/
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ python/dist/
8
+
9
+ # Node
10
+ node_modules/
11
+ js/dist/
12
+ *.tgz
13
+ js/test/live/.out/
14
+
15
+ # Local
16
+ .DS_Store
17
+ .env
vaquill-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vaquill
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.
vaquill-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.5
2
+ Name: vaquill
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Vaquill US primary law Data API.
5
+ Project-URL: Homepage, https://www.vaquill.ai
6
+ Project-URL: Documentation, https://www.vaquill.ai/docs/api-guide/sdks/overview
7
+ Project-URL: Source, https://github.com/Vaquill-AI/vaquill-sdks/tree/main/python
8
+ Project-URL: Issues, https://github.com/Vaquill-AI/vaquill-sdks/issues
9
+ Author: Vaquill
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: api,cli,law,legal,legal-data,regulations,sdk,statutes,vaquill
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: httpx<1,>=0.27
27
+ Requires-Dist: pydantic<3,>=2
28
+ Description-Content-Type: text/markdown
29
+
30
+ # vaquill
31
+
32
+ Python SDK for the Vaquill US primary law Data API: statutes, regulations, court rules, constitutions, session laws and law-change alerts.
33
+
34
+ Server-side use only: an API key (`vq_key_...`) must never ship to a browser.
35
+ Python 3.11 or newer. Sync and async clients with the same methods.
36
+
37
+ ```python
38
+ from vaquill import Vaquill
39
+
40
+ client = Vaquill() # reads VAQUILL_API_KEY; VAQUILL_BASE_URL overrides the host
41
+
42
+ # 2 credits. Even an unresolved citation is charged: the answer is what you pay for.
43
+ result = client.statutes.resolve("42 U.S.C. 1983")
44
+ if result.resolved and result.section:
45
+ body = client.statutes.get_section_body(result.section.act_id) # 6 credits
46
+ print(body.plain)
47
+
48
+ hits = client.statutes.search(
49
+ "statute of limitations for medical malpractice", state="ca"
50
+ ) # 4 credits
51
+ for hit in hits.results:
52
+ print(hit.citation, hit.section_title)
53
+ print(hits.credits_consumed, client.credits_spent)
54
+ ```
55
+
56
+ Async is the same with `AsyncVaquill` and `await`:
57
+
58
+ ```python
59
+ import asyncio
60
+ from vaquill import AsyncVaquill
61
+
62
+
63
+ async def main() -> None:
64
+ async with AsyncVaquill() as client:
65
+ balance = await client.credits.balance()
66
+ print(balance.credits_remaining)
67
+
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ ## Namespaces
73
+
74
+ | Namespace | What it does |
75
+ | --- | --- |
76
+ | `client.statutes` | search, resolve, sections and their text, browse, changes, enactments, rulemaking, cross-references, citation extraction |
77
+ | `client.session_laws` | acts as enacted: search, list, record, text, versions, changes |
78
+ | `client.watches` | law-change alerts by webhook or email |
79
+ | `client.boards` | the sources a watch can follow |
80
+ | `client.credits` | `balance()` |
81
+ | `client.pricing` | `get()` the credit price of every endpoint |
82
+
83
+ All 36 operations, their method names and which ones cost credits are in [docs/OPERATIONS.md](docs/OPERATIONS.md).
84
+ Required arguments come first and may be positional; optional ones are keyword-only `snake_case` (sent to the API as camelCase).
85
+
86
+ ## Errors
87
+
88
+ Every failure is a `VaquillError`. Status errors carry `status_code`, `request_id` and the parsed body.
89
+
90
+ ```python
91
+ import vaquill
92
+
93
+ try:
94
+ client.statutes.resolve("42 U.S.C. 1983")
95
+ except vaquill.InsufficientCreditsError:
96
+ ... # never retried; top up credits
97
+ except vaquill.RateLimitError as err:
98
+ print(err.retry_after_seconds) # seconds; the client already retried with backoff
99
+ except vaquill.APIStatusError as err:
100
+ print(err.status_code, err.request_id)
101
+ ```
102
+
103
+ ## Pagination and the credit warning
104
+
105
+ `iter_*` helpers hide the offset, cursor and `sinceId`/`beforeId` styles behind one iterator.
106
+ **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`.
107
+
108
+ ```python
109
+ pager = client.statutes.iter_search(
110
+ "negligence", state="ny", limit=10, max_pages=3
111
+ ) # at most 12 credits
112
+ for hit in pager:
113
+ print(hit.citation)
114
+ print(pager.truncated) # True when the guard stopped it with more available
115
+
116
+ for page in client.statutes.iter_divisions("STATE", state="wa", max_items=100).pages():
117
+ ... # whole pages, to control cost yourself
118
+ ```
119
+
120
+ ## Spend guard
121
+
122
+ ```python
123
+ client = Vaquill(
124
+ max_credits=500
125
+ ) # charged calls raise SpendGuardExceeded once 500 credits are used
126
+ client.credits_spent # running total across this client and any with_options() copy
127
+ client.pricing.get() # free: the live price list
128
+ ```
129
+
130
+ Charged calls send an `Idempotency-Key` automatically and are only retried when that is safe.
131
+ Pass `idempotency_key="..."` to any call to supply your own.
132
+
133
+ ## Raw responses
134
+
135
+ ```python
136
+ raw = client.with_raw_response.request(
137
+ "GET",
138
+ "/api/v1/us/statutes/resolve",
139
+ cast_to=dict,
140
+ params={"cite": "42 U.S.C. 1983"},
141
+ charged=True,
142
+ )
143
+ raw.request_id, raw.rate_limit.remaining, raw.credits_charged_this_call, raw.parse()
144
+ ```
145
+
146
+ `client.raw_request(method, path, ...)` reaches endpoints newer than this SDK and returns plain JSON.
147
+
148
+ ## Webhooks
149
+
150
+ Verify the raw request body, never re-serialised JSON.
151
+
152
+ ```python
153
+ from vaquill import verify_webhook, WebhookVerificationError
154
+
155
+ try:
156
+ event = verify_webhook(raw_body, request_headers, WEBHOOK_SECRET, allow_legacy=False)
157
+ except WebhookVerificationError:
158
+ ... # respond 400
159
+ ```
160
+
161
+ A watch is created with `client.watches.create("state", state="wa", webhook_url=..., webhook_secret=...)`.
162
+ 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.
163
+
164
+ ## Command line
165
+
166
+ Installing the package also installs a `vaquill` command (standard library only, no extra dependencies).
167
+
168
+ ```bash
169
+ export VAQUILL_API_KEY=vq_key_...
170
+ vaquill resolve "42 U.S.C. 1983" # 2 credits
171
+ vaquill search "medical malpractice limitations" --state ca --limit 5 --json
172
+ vaquill section USC_42_1983 --body # 6 credits
173
+ vaquill credits # free
174
+ vaquill webhook verify --secret "$SECRET" --id "$ID" --timestamp "$TS" --signature "$SIG" < body.json
175
+ ```
176
+
177
+ Charged commands print `credits used: N` on stderr, so stdout stays pipeable.
178
+ Exit codes: 0 ok, 1 error, 2 usage, 3 auth, 4 no credits, 5 rate limited, 6 not found, 7 network.
179
+ Every command, flag and the JSON shape are in [docs/CLI.md](docs/CLI.md).
180
+
181
+ ## Types
182
+
183
+ Responses are Pydantic v2 models in `vaquill.types`, generated from `openapi/vaquill-us.json` by `scripts/gen_models.py`.
184
+ 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.
185
+ Every top-level response has `credits_consumed`, `request_id` and `idempotent_replayed`.
186
+
187
+ ## Development
188
+
189
+ ```bash
190
+ .venv/bin/python scripts/gen_models.py # regenerate src/vaquill/types after changing the spec
191
+ .venv/bin/python scripts/gen_async.py # regenerate src/vaquill/resources/_async after editing a sync resource
192
+ .venv/bin/python -m pytest -q
193
+ ```
194
+
195
+ 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.
@@ -0,0 +1,166 @@
1
+ # vaquill
2
+
3
+ Python SDK for the Vaquill US primary law Data API: statutes, regulations, court rules, constitutions, session laws and law-change alerts.
4
+
5
+ Server-side use only: an API key (`vq_key_...`) must never ship to a browser.
6
+ Python 3.11 or newer. Sync and async clients with the same methods.
7
+
8
+ ```python
9
+ from vaquill import Vaquill
10
+
11
+ client = Vaquill() # reads VAQUILL_API_KEY; VAQUILL_BASE_URL overrides the host
12
+
13
+ # 2 credits. Even an unresolved citation is charged: the answer is what you pay for.
14
+ result = client.statutes.resolve("42 U.S.C. 1983")
15
+ if result.resolved and result.section:
16
+ body = client.statutes.get_section_body(result.section.act_id) # 6 credits
17
+ print(body.plain)
18
+
19
+ hits = client.statutes.search(
20
+ "statute of limitations for medical malpractice", state="ca"
21
+ ) # 4 credits
22
+ for hit in hits.results:
23
+ print(hit.citation, hit.section_title)
24
+ print(hits.credits_consumed, client.credits_spent)
25
+ ```
26
+
27
+ Async is the same with `AsyncVaquill` and `await`:
28
+
29
+ ```python
30
+ import asyncio
31
+ from vaquill import AsyncVaquill
32
+
33
+
34
+ async def main() -> None:
35
+ async with AsyncVaquill() as client:
36
+ balance = await client.credits.balance()
37
+ print(balance.credits_remaining)
38
+
39
+
40
+ asyncio.run(main())
41
+ ```
42
+
43
+ ## Namespaces
44
+
45
+ | Namespace | What it does |
46
+ | --- | --- |
47
+ | `client.statutes` | search, resolve, sections and their text, browse, changes, enactments, rulemaking, cross-references, citation extraction |
48
+ | `client.session_laws` | acts as enacted: search, list, record, text, versions, changes |
49
+ | `client.watches` | law-change alerts by webhook or email |
50
+ | `client.boards` | the sources a watch can follow |
51
+ | `client.credits` | `balance()` |
52
+ | `client.pricing` | `get()` the credit price of every endpoint |
53
+
54
+ All 36 operations, their method names and which ones cost credits are in [docs/OPERATIONS.md](docs/OPERATIONS.md).
55
+ Required arguments come first and may be positional; optional ones are keyword-only `snake_case` (sent to the API as camelCase).
56
+
57
+ ## Errors
58
+
59
+ Every failure is a `VaquillError`. Status errors carry `status_code`, `request_id` and the parsed body.
60
+
61
+ ```python
62
+ import vaquill
63
+
64
+ try:
65
+ client.statutes.resolve("42 U.S.C. 1983")
66
+ except vaquill.InsufficientCreditsError:
67
+ ... # never retried; top up credits
68
+ except vaquill.RateLimitError as err:
69
+ print(err.retry_after_seconds) # seconds; the client already retried with backoff
70
+ except vaquill.APIStatusError as err:
71
+ print(err.status_code, err.request_id)
72
+ ```
73
+
74
+ ## Pagination and the credit warning
75
+
76
+ `iter_*` helpers hide the offset, cursor and `sinceId`/`beforeId` styles behind one iterator.
77
+ **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`.
78
+
79
+ ```python
80
+ pager = client.statutes.iter_search(
81
+ "negligence", state="ny", limit=10, max_pages=3
82
+ ) # at most 12 credits
83
+ for hit in pager:
84
+ print(hit.citation)
85
+ print(pager.truncated) # True when the guard stopped it with more available
86
+
87
+ for page in client.statutes.iter_divisions("STATE", state="wa", max_items=100).pages():
88
+ ... # whole pages, to control cost yourself
89
+ ```
90
+
91
+ ## Spend guard
92
+
93
+ ```python
94
+ client = Vaquill(
95
+ max_credits=500
96
+ ) # charged calls raise SpendGuardExceeded once 500 credits are used
97
+ client.credits_spent # running total across this client and any with_options() copy
98
+ client.pricing.get() # free: the live price list
99
+ ```
100
+
101
+ Charged calls send an `Idempotency-Key` automatically and are only retried when that is safe.
102
+ Pass `idempotency_key="..."` to any call to supply your own.
103
+
104
+ ## Raw responses
105
+
106
+ ```python
107
+ raw = client.with_raw_response.request(
108
+ "GET",
109
+ "/api/v1/us/statutes/resolve",
110
+ cast_to=dict,
111
+ params={"cite": "42 U.S.C. 1983"},
112
+ charged=True,
113
+ )
114
+ raw.request_id, raw.rate_limit.remaining, raw.credits_charged_this_call, raw.parse()
115
+ ```
116
+
117
+ `client.raw_request(method, path, ...)` reaches endpoints newer than this SDK and returns plain JSON.
118
+
119
+ ## Webhooks
120
+
121
+ Verify the raw request body, never re-serialised JSON.
122
+
123
+ ```python
124
+ from vaquill import verify_webhook, WebhookVerificationError
125
+
126
+ try:
127
+ event = verify_webhook(raw_body, request_headers, WEBHOOK_SECRET, allow_legacy=False)
128
+ except WebhookVerificationError:
129
+ ... # respond 400
130
+ ```
131
+
132
+ A watch is created with `client.watches.create("state", state="wa", webhook_url=..., webhook_secret=...)`.
133
+ 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.
134
+
135
+ ## Command line
136
+
137
+ Installing the package also installs a `vaquill` command (standard library only, no extra dependencies).
138
+
139
+ ```bash
140
+ export VAQUILL_API_KEY=vq_key_...
141
+ vaquill resolve "42 U.S.C. 1983" # 2 credits
142
+ vaquill search "medical malpractice limitations" --state ca --limit 5 --json
143
+ vaquill section USC_42_1983 --body # 6 credits
144
+ vaquill credits # free
145
+ vaquill webhook verify --secret "$SECRET" --id "$ID" --timestamp "$TS" --signature "$SIG" < body.json
146
+ ```
147
+
148
+ Charged commands print `credits used: N` on stderr, so stdout stays pipeable.
149
+ Exit codes: 0 ok, 1 error, 2 usage, 3 auth, 4 no credits, 5 rate limited, 6 not found, 7 network.
150
+ Every command, flag and the JSON shape are in [docs/CLI.md](docs/CLI.md).
151
+
152
+ ## Types
153
+
154
+ Responses are Pydantic v2 models in `vaquill.types`, generated from `openapi/vaquill-us.json` by `scripts/gen_models.py`.
155
+ 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.
156
+ Every top-level response has `credits_consumed`, `request_id` and `idempotent_replayed`.
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ .venv/bin/python scripts/gen_models.py # regenerate src/vaquill/types after changing the spec
162
+ .venv/bin/python scripts/gen_async.py # regenerate src/vaquill/resources/_async after editing a sync resource
163
+ .venv/bin/python -m pytest -q
164
+ ```
165
+
166
+ 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.
@@ -0,0 +1,103 @@
1
+ # Command line
2
+
3
+ `vaquill` is installed with the package.
4
+ It uses only the standard library and the SDK client, so it adds no dependencies.
5
+ Run `vaquill COMMAND --help` for the flags of any command.
6
+
7
+ ## Authentication and global options
8
+
9
+ The key comes from `--api-key` or the `VAQUILL_API_KEY` variable.
10
+ It is never stored on disk and never printed: error text and `--debug` logs have any `vq_key_` value replaced with `vq_key_[redacted]`.
11
+
12
+ | Option | Meaning |
13
+ | --- | --- |
14
+ | `--api-key KEY` | API key. Prefer the variable, because a flag is visible in the process list. |
15
+ | `--base-url URL` | API host. Defaults to `VAQUILL_BASE_URL`, then `https://api.vaquill.ai`. |
16
+ | `--timeout SECONDS` | Per-request timeout. |
17
+ | `--max-retries N` | Retries after the first attempt (default 2). |
18
+ | `--max-credits N` | Spend guard: a charged call is refused once N credits have been used in this run. |
19
+ | `--json` | Print the response as JSON on stdout. |
20
+ | `--no-color` | Never use colour. |
21
+ | `--debug` | Log requests to stderr and show a traceback on failure. |
22
+
23
+ Global options work before or after the command name.
24
+
25
+ ## Commands
26
+
27
+ | Command | What it does | Cost |
28
+ | --- | --- | --- |
29
+ | `search QUERY [--corpus-type T ...] [--state S] [--limit N] [--offset N]` | Search statutes, regulations and rules | charged |
30
+ | `resolve CITATION` | Resolve a citation to its section | charged |
31
+ | `section ACT_ID [--body]` | A section's metadata, or its text with `--body` | charged |
32
+ | `coverage` | What the corpus holds per jurisdiction | free |
33
+ | `credits` | Your credit balance | free |
34
+ | `pricing` | The credit price of every endpoint | free |
35
+ | `watches list [--limit N] [--offset N]` | List watches | free |
36
+ | `watches create CORPUS_TYPE [--state S] [--channel C] [--webhook-url U] [--webhook-secret S] [--email E]` | Create a watch | free |
37
+ | `watches delete WATCH_ID` | Delete a watch | free |
38
+ | `watches changes WATCH_ID [--limit N] [--since-id N] [--before-id N] [--order asc\|desc]` | Changes a watch recorded | free |
39
+ | `watches deliveries WATCH_ID [--limit N]` | Recent deliveries and delivery health | free |
40
+ | `webhook verify --secret S --id I --timestamp T --signature SIG [--file F] [--tolerance N]` | Verify a Standard Webhooks signature offline | none |
41
+ | `version` | Print the SDK and Python versions | none |
42
+ | `api METHOD PATH [--query K=V ...] [--data JSON] [--limit N] [--charged]` | Call any endpoint and print the JSON | see below |
43
+
44
+ Prices are not repeated here: run `vaquill pricing` for the live table.
45
+
46
+ ## Output
47
+
48
+ Human output is a compact table or a list of `key value` lines, clipped to the terminal width.
49
+ Colour is used only when stdout (or stderr, for messages) is a terminal and `NO_COLOR` is unset and `--no-color` is absent.
50
+
51
+ `--json` prints the response model dumped by alias, so the keys are the API's camelCase names (`creditsConsumed`, `actId`).
52
+ The shape is the same as the SDK's `model_dump(mode="json", by_alias=True)`.
53
+ `watches delete` prints `{"deleted": true, "id": "..."}`, `version` prints `{"version": ..., "python": ...}` and `webhook verify` prints `{"valid": true, "event": ...}`.
54
+
55
+ Messages never go to stdout.
56
+ Charged commands print `credits used: N` on stderr after the result, so `vaquill search ... --json | jq` stays clean.
57
+
58
+ ## Pages and cost
59
+
60
+ The CLI never paginates or loops by itself.
61
+ `search`, `watches list` and `watches changes` make exactly one request.
62
+ When the server reports more results, a notice on stderr names the flag that continues (`--offset N`, `--before-id N`, `--since-id N`).
63
+ Each continued search call is charged again.
64
+
65
+ ## The `api` command
66
+
67
+ `vaquill api GET /api/v1/watches --query limit=5` sends one request and prints the JSON body.
68
+ `--query` can repeat, and a repeated key becomes a repeated query parameter.
69
+ `--data` takes a JSON string, or `-` to read it from stdin.
70
+ `--limit N` sets the `limit` query parameter.
71
+ The CLI cannot know which paths are charged, so pass `--charged` for one that is.
72
+ That adds an idempotency key, applies `--max-credits` and counts the credits in `credits used`.
73
+ When the body says `hasMore`, a notice names the next page value if the body has one (`nextCursor` or `nextOffset`).
74
+
75
+ ## Webhook verification
76
+
77
+ ```bash
78
+ vaquill webhook verify --secret "$WEBHOOK_SECRET" \
79
+ --id "$WEBHOOK_ID" --timestamp "$WEBHOOK_TIMESTAMP" --signature "$WEBHOOK_SIGNATURE" \
80
+ --file body.json
81
+ ```
82
+
83
+ It makes no network call and needs no API key.
84
+ The body must be the exact bytes received: a trailing newline added by `echo` makes the signature fail, so use `--file`, a redirect (`< body.json`) or `printf %s`.
85
+ `--secret` falls back to the `VAQUILL_WEBHOOK_SECRET` variable.
86
+ Timestamps more than `--tolerance` seconds (default 300) from the current time are rejected.
87
+ Only Standard Webhooks headers are accepted; the legacy header has no replay protection.
88
+ The signature is checked before the body is parsed, so a body that is not a Vaquill event still passes with `"event": null`.
89
+
90
+ ## Exit codes
91
+
92
+ | Code | Meaning |
93
+ | --- | --- |
94
+ | 0 | Success |
95
+ | 1 | Any other error, including a failed webhook verification |
96
+ | 2 | Usage error (bad flag, unreadable file, invalid JSON) |
97
+ | 3 | Authentication: missing or malformed key, HTTP 401 or 403 |
98
+ | 4 | Insufficient credits (HTTP 402) or the `--max-credits` guard |
99
+ | 5 | Rate limited, including the daily cap (HTTP 429) |
100
+ | 6 | Not found (HTTP 404) |
101
+ | 7 | Network failure or timeout |
102
+
103
+ A failure prints one line to stderr, `error: MESSAGE (status N, code C, request id R)`, and no traceback unless `--debug` is set.
@@ -0,0 +1,89 @@
1
+ # Operations
2
+
3
+ All 36 operations of the frozen contract (`openapi/vaquill-us.json`, `info.version` 1.0.0), the SDK method for each, and which ones spend credits.
4
+ Each method exists on `Vaquill` and, with `await`, on `AsyncVaquill`.
5
+ `tests/test_operations.py` fails if this table and the spec disagree.
6
+
7
+ ## Charged rule
8
+
9
+ An operation is **charged** when the spec declares the `Idempotency-Key` header parameter on it.
10
+ That is 25 operations.
11
+ The SDK passes `charged=True` for exactly those: it sends an auto-generated `Idempotency-Key`, retries ambiguous failures safely, checks the `max_credits` spend guard before the call and adds the response's `creditsConsumed` to `client.credits_spent` after it.
12
+ The TypeScript SDK applies the same rule.
13
+
14
+ Free operations: `list_statutes_coverage`, `list_boards`, the watches operations except `get_watch_change_diff`, `get_credit_balance` and `get_pricing`.
15
+ Prices are not hard-coded here. Call `client.pricing.get()` (free) for the live matrix.
16
+ Some charged calls return `creditsConsumed: 0` (for example a body that is not held); read the figure from the response.
17
+
18
+ ## Naming
19
+
20
+ The method name is the `operationId` with the namespace noun removed (`search_statutes` is `statutes.search`, `get_session_law_body` is `session_laws.get_body`).
21
+ Where the verb carries meaning it is kept (`get_section`, `get_sections_batch`).
22
+ `get_section_neighbors` is `statutes.neighbors` (the path is `/related`).
23
+
24
+ ## Call shape
25
+
26
+ * Required parameters (path parameters first, then required query or body members) come first and may be passed by position.
27
+ * Optional parameters are keyword-only, in `snake_case`. The wire name is the API's camelCase (`title_number` is sent as `titleNumber`).
28
+ * Unset optional parameters are not sent, so the server default applies.
29
+ * Every method also accepts `timeout`, `max_retries`, `idempotency_key` and `headers` as keyword arguments.
30
+ * Array parameters take a list. Query arrays are sent as repeated keys.
31
+
32
+ ## Methods
33
+
34
+ | operationId | SDK method | Route | Cost |
35
+ | --- | --- | --- | --- |
36
+ | `get_section_cited_by` | `client.statutes.cited_by` | GET `/us/statutes/section/{act_id}/cited-by` | charged |
37
+ | `count_statute_sections` | `client.statutes.count` | POST `/us/statutes/count` | charged |
38
+ | `list_statutes_coverage` | `client.statutes.coverage` | GET `/us/statutes/coverage` | free |
39
+ | `get_section_cross_state` | `client.statutes.cross_state` | GET `/us/statutes/section/{act_id}/cross-state` | charged |
40
+ | `get_section_definitions` | `client.statutes.definitions` | GET `/us/statutes/section/{act_id}/definitions` | charged |
41
+ | `list_statute_divisions` | `client.statutes.divisions` | GET `/us/statutes/divisions` | charged |
42
+ | `get_section_enactments` | `client.statutes.enactments` | GET `/us/statutes/section/{act_id}/enactments` | charged |
43
+ | `get_section_rulemaking` | `client.statutes.section_rulemaking` | GET `/us/statutes/section/{act_id}/rulemaking` | charged |
44
+ | `get_part_rulemaking` | `client.statutes.part_rulemaking` | GET `/us/statutes/part/{title}/{part}/rulemaking` | charged |
45
+ | `extract_statute_citations` | `client.statutes.extract_citations` | POST `/us/statutes/extract-citations` | charged |
46
+ | `get_section` | `client.statutes.get_section` | GET `/us/statutes/section/{act_id}` | charged |
47
+ | `get_section_body` | `client.statutes.get_section_body` | GET `/us/statutes/section/{act_id}/body` | charged |
48
+ | `get_sections_batch` | `client.statutes.get_sections_batch` | POST `/us/statutes/sections` | charged |
49
+ | `get_section_neighbors` | `client.statutes.neighbors` | GET `/us/statutes/section/{act_id}/related` | charged |
50
+ | `resolve_statute_citation` | `client.statutes.resolve` | GET `/us/statutes/resolve` | charged |
51
+ | `resolve_statute_citations_batch` | `client.statutes.resolve_batch` | POST `/us/statutes/resolve` | charged |
52
+ | `search_statutes` | `client.statutes.search` | POST `/us/statutes/search` | charged |
53
+ | `get_section_changes` | `client.statutes.section_changes` | GET `/us/statutes/section/{act_id}/changes` | charged |
54
+ | `get_session_law_changes` | `client.session_laws.changes` | GET `/us/session-laws/changes` | charged |
55
+ | `get_session_law` | `client.session_laws.get` | GET `/us/session-laws/{session_law_id}` | charged |
56
+ | `get_session_laws_batch` | `client.session_laws.get_batch` | POST `/us/session-laws/batch` | charged |
57
+ | `get_session_law_body` | `client.session_laws.get_body` | GET `/us/session-laws/{session_law_id}/body` | charged |
58
+ | `list_session_laws` | `client.session_laws.list` | GET `/us/session-laws/list` | charged |
59
+ | `search_session_laws` | `client.session_laws.search` | POST `/us/session-laws/search` | charged |
60
+ | `get_session_law_versions` | `client.session_laws.versions` | GET `/us/session-laws/{session_law_id}/versions` | charged |
61
+ | `get_watch_change_diff` | `client.watches.change_diff` | GET `/watches/{watch_id}/changes/{change_id}/diff` | charged |
62
+ | `list_watch_changes` | `client.watches.changes` | GET `/watches/{watch_id}/changes` | free |
63
+ | `create_watch` | `client.watches.create` | POST `/watches` | free |
64
+ | `delete_watch` | `client.watches.delete` | DELETE `/watches/{watch_id}` | free |
65
+ | `list_watch_deliveries` | `client.watches.deliveries` | GET `/watches/{watch_id}/deliveries` | free |
66
+ | `list_watches` | `client.watches.list` | GET `/watches` | free |
67
+ | `test_watch` | `client.watches.test` | POST `/watches/{watch_id}/test` | free |
68
+ | `update_watch` | `client.watches.update` | PATCH `/watches/{watch_id}` | free |
69
+ | `list_boards` | `client.boards.list` | GET `/boards` | free |
70
+ | `get_credit_balance` | `client.credits.balance` | GET `/credits/balance` | free |
71
+ | `get_pricing` | `client.pricing.get` | GET `/api-credits/pricing` | free |
72
+
73
+ ## Auto-pagination
74
+
75
+ Each helper returns a `SyncPaginator` (or `AsyncPaginator`).
76
+ Iterate it for items, call `.pages()` for whole pages, read `.truncated` after iterating.
77
+ All accept `max_pages` and `max_items`. On a charged helper every page spends credits, so always set one of them.
78
+
79
+ | Helper | Wraps | Paging | Cost |
80
+ | --- | --- | --- | --- |
81
+ | `client.statutes.iter_search` | `search_statutes` | offset (`nextOffset`) | charged |
82
+ | `client.statutes.iter_divisions` | `list_statute_divisions` | cursor (`nextCursor`) | charged |
83
+ | `client.statutes.iter_section_changes` | `get_section_changes` | `beforeId` (newest first) or `sinceId` (`order="asc"`) | charged |
84
+ | `client.session_laws.iter_search` | `search_session_laws` | offset (`hasMore`) | charged |
85
+ | `client.session_laws.iter_list` | `list_session_laws` | cursor (`nextCursor`) | charged |
86
+ | `client.session_laws.iter_changes` | `get_session_law_changes` | `sinceId` | charged |
87
+ | `client.watches.iter_list` | `list_watches` | offset | free |
88
+ | `client.watches.iter_changes` | `list_watch_changes` | `beforeId` or `sinceId` (`order="asc"`) | free |
89
+ | `client.boards.iter_list` | `list_boards` | offset (`hasMore`) | free |
@@ -0,0 +1,26 @@
1
+ """Resolve a citation to its section, then read the section text.
2
+
3
+ VAQUILL_API_KEY=vq_key_... python examples/resolve_citation.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from vaquill import InsufficientCreditsError, Vaquill
9
+ from vaquill.types import StatuteResolveResponse
10
+
11
+
12
+ def main(client: Vaquill, cite: str = "42 U.S.C. 1983") -> StatuteResolveResponse | None:
13
+ try:
14
+ result = client.statutes.resolve(cite) # 2 credits, even when it does not resolve
15
+ except InsufficientCreditsError:
16
+ print("Out of credits. Top up in the developer dashboard.")
17
+ return None
18
+ print(f"{cite}: resolved={result.resolved} charged={result.credits_consumed} credits")
19
+ if result.resolved and result.section is not None and result.section.act_id:
20
+ body = client.statutes.get_section_body(result.section.act_id) # 6 credits
21
+ print((body.plain or body.content or "")[:200])
22
+ return result
23
+
24
+
25
+ if __name__ == "__main__":
26
+ main(Vaquill(max_credits=20))
@@ -0,0 +1,29 @@
1
+ """Search, then page through results with a cost guard.
2
+
3
+ Every page of a charged endpoint spends credits, so always bound the iteration.
4
+
5
+ VAQUILL_API_KEY=vq_key_... python examples/search_and_paginate.py
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from vaquill import Vaquill
11
+ from vaquill.types import StatuteResult
12
+
13
+
14
+ def main(client: Vaquill) -> list[StatuteResult]:
15
+ first = client.statutes.search("statute of limitations for medical malpractice", state="ca")
16
+ rows_on_page = len(first.results or [])
17
+ print(f"{rows_on_page} rows on page one, cost {first.credits_consumed} credits")
18
+
19
+ # 2 pages x 4 credits at most. `truncated` tells you more existed.
20
+ pager = client.statutes.iter_search(
21
+ "statute of limitations for medical malpractice", state="ca", limit=10, max_pages=2
22
+ )
23
+ rows = list(pager)
24
+ print(f"read {len(rows)} rows, truncated={pager.truncated}, spent={client.credits_spent}")
25
+ return rows
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main(Vaquill(max_credits=50))