salestax-python 0.1.0__tar.gz → 0.2.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 (72) hide show
  1. salestax_python-0.2.0/CHANGELOG.md +47 -0
  2. salestax_python-0.2.0/PKG-INFO +287 -0
  3. salestax_python-0.2.0/README.md +249 -0
  4. salestax_python-0.2.0/examples/async_usage.py +47 -0
  5. salestax_python-0.2.0/examples/basic_calculation.py +38 -0
  6. salestax_python-0.2.0/examples/batch_calculation.py +39 -0
  7. salestax_python-0.2.0/examples/check_coverage.py +17 -0
  8. salestax_python-0.2.0/examples/error_handling.py +27 -0
  9. salestax_python-0.2.0/examples/transaction_and_adjustment.py +41 -0
  10. {salestax_python-0.1.0 → salestax_python-0.2.0}/pyproject.toml +8 -21
  11. salestax_python-0.2.0/salestax/__init__.py +115 -0
  12. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_config.py +6 -12
  13. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_transport/http_client.py +34 -39
  14. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_transport/types.py +2 -22
  15. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_transport/urllib_transport.py +8 -10
  16. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_version.py +1 -1
  17. salestax_python-0.2.0/salestax/client.py +303 -0
  18. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/errors.py +43 -44
  19. salestax_python-0.2.0/salestax/models.py +326 -0
  20. salestax_python-0.2.0/salestax/resources/__init__.py +11 -0
  21. salestax_python-0.2.0/salestax/resources/_validation.py +51 -0
  22. salestax_python-0.2.0/salestax/resources/batches.py +27 -0
  23. salestax_python-0.2.0/salestax/resources/calculations.py +119 -0
  24. salestax_python-0.2.0/salestax/resources/coverage.py +47 -0
  25. salestax_python-0.2.0/salestax/resources/transactions.py +146 -0
  26. salestax_python-0.2.0/tests/unit/test_adjustments.py +100 -0
  27. salestax_python-0.2.0/tests/unit/test_async.py +152 -0
  28. salestax_python-0.2.0/tests/unit/test_batches.py +21 -0
  29. salestax_python-0.2.0/tests/unit/test_calculations.py +146 -0
  30. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/unit/test_client.py +15 -2
  31. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/unit/test_client_extras.py +39 -47
  32. salestax_python-0.2.0/tests/unit/test_coverage.py +54 -0
  33. salestax_python-0.2.0/tests/unit/test_errors.py +85 -0
  34. salestax_python-0.2.0/tests/unit/test_hooks.py +66 -0
  35. salestax_python-0.2.0/tests/unit/test_transactions.py +43 -0
  36. salestax_python-0.2.0/tests/unit/test_transport.py +117 -0
  37. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/unit/test_urllib_transport.py +3 -3
  38. salestax_python-0.1.0/CHANGELOG.md +0 -21
  39. salestax_python-0.1.0/PKG-INFO +0 -158
  40. salestax_python-0.1.0/README.md +0 -120
  41. salestax_python-0.1.0/examples/async_usage.py +0 -20
  42. salestax_python-0.1.0/examples/basic_calculation.py +0 -13
  43. salestax_python-0.1.0/examples/batch_calculation.py +0 -17
  44. salestax_python-0.1.0/examples/error_handling.py +0 -18
  45. salestax_python-0.1.0/salestax/__init__.py +0 -61
  46. salestax_python-0.1.0/salestax/client.py +0 -215
  47. salestax_python-0.1.0/salestax/models.py +0 -80
  48. salestax_python-0.1.0/salestax/resources/__init__.py +0 -5
  49. salestax_python-0.1.0/salestax/resources/jurisdictions.py +0 -27
  50. salestax_python-0.1.0/salestax/resources/rates.py +0 -25
  51. salestax_python-0.1.0/salestax/resources/tax.py +0 -146
  52. salestax_python-0.1.0/salestax/utils.py +0 -22
  53. salestax_python-0.1.0/tests/unit/test_async.py +0 -117
  54. salestax_python-0.1.0/tests/unit/test_errors.py +0 -50
  55. salestax_python-0.1.0/tests/unit/test_hooks.py +0 -52
  56. salestax_python-0.1.0/tests/unit/test_tax.py +0 -72
  57. salestax_python-0.1.0/tests/unit/test_transport.py +0 -110
  58. salestax_python-0.1.0/tests/unit/test_utils.py +0 -58
  59. {salestax_python-0.1.0 → salestax_python-0.2.0}/.gitignore +0 -0
  60. {salestax_python-0.1.0 → salestax_python-0.2.0}/LICENSE +0 -0
  61. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_transport/__init__.py +0 -0
  62. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_transport/httpx_transport.py +0 -0
  63. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_transport/retry.py +0 -0
  64. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/_transport/user_agent.py +0 -0
  65. {salestax_python-0.1.0 → salestax_python-0.2.0}/salestax/py.typed +0 -0
  66. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/__init__.py +0 -0
  67. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/conftest.py +0 -0
  68. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/integration/__init__.py +0 -0
  69. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/integration/test_live.py +0 -0
  70. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/unit/__init__.py +0 -0
  71. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/unit/test_httpx_transport.py +0 -0
  72. {salestax_python-0.1.0 → salestax_python-0.2.0}/tests/unit/test_retry.py +0 -0
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.2.0] - 2026-09-23
8
+
9
+ ### Changed
10
+
11
+ **BREAKING:** Redesigned the SDK to match the public OpenAPI contract.
12
+
13
+ - Replaced `client.tax`, `client.rates`, `client.jurisdictions` with
14
+ `client.calculations`, `client.transactions`, `client.transactions.adjustments`,
15
+ `client.batches`, and `client.coverage`.
16
+ - Request and response shapes now use snake_case matching the OpenAPI spec.
17
+ - Money and quantity are decimal strings, matching the wire format.
18
+ - Idempotency-Key is validated client-side against `[A-Za-z0-9][A-Za-z0-9._:-]{7,254}`.
19
+ - Error bodies are parsed as RFC 9457 problem+json. Field-level errors
20
+ populate `err.param` from `errors[0].pointer`.
21
+ - Base URL is now `https://api.salestaxcalculatorapi.com` (no `/v1` suffix).
22
+ - Added `expand="audit"` request option on all readable endpoints.
23
+
24
+ ### Added
25
+
26
+ - `client.transactions` and `client.transactions.adjustments` resources.
27
+ - `client.batches` resource for async batch polling.
28
+ - `client.coverage` resource for pre-calculation qualification checks.
29
+
30
+ ### Removed
31
+
32
+ - `client.tax.calculate`, `client.tax.calculate_batch`, `client.tax.calculate_batch_chunked`
33
+ - `client.rates.get`
34
+ - `client.jurisdictions.list`
35
+ - `salestax/utils.py` (chunk helper no longer needed)
36
+
37
+ ## [0.1.0] - 2026-09-22
38
+
39
+ ### Added
40
+
41
+ - Initial release.
42
+ - `SalesTaxClient` (sync) with zero runtime dependencies.
43
+ - `AsyncSalesTaxClient` (async, requires `[async]` extra).
44
+ - Automatic retries with exponential backoff, jitter, and `Retry-After` support.
45
+ - Full error hierarchy: `RateLimitError`, `ValidationError`, `TimeoutError`, and more.
46
+ - Lifecycle hooks: `on_request`, `on_response`, `on_retry`.
47
+ - PEP 561 `py.typed` marker; `mypy --strict` clean.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.5
2
+ Name: salestax-python
3
+ Version: 0.2.0
4
+ Summary: Official Python SDK for the Sales Tax Calculator API.
5
+ Project-URL: Homepage, https://salestaxcalculatorapi.com
6
+ Project-URL: Documentation, https://salestaxcalculatorapi.com/docs
7
+ Project-URL: Repository, https://github.com/InstaBlick-USA/salestax-python
8
+ Project-URL: Issues, https://github.com/InstaBlick-USA/salestax-python/issues
9
+ Project-URL: Changelog, https://github.com/InstaBlick-USA/salestax-python/blob/main/CHANGELOG.md
10
+ Author-email: InstaBlick USA <dev@salestaxcalculatorapi.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: api-client,async,checkout,ecommerce,gst,hst,python,sales-tax,tax-api,vat
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Provides-Extra: async
27
+ Requires-Dist: httpx>=0.27; extra == 'async'
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == 'dev'
30
+ Requires-Dist: mypy>=1.9; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
32
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0; extra == 'dev'
34
+ Requires-Dist: respx>=0.21; extra == 'dev'
35
+ Requires-Dist: ruff>=0.4; extra == 'dev'
36
+ Requires-Dist: twine>=5.0; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # salestax-python
40
+
41
+ [![PyPI](https://img.shields.io/pypi/v/salestax-python.svg?maxAge=3600)](https://pypi.org/project/salestax-python/)
42
+ [![Python versions](https://img.shields.io/pypi/pyversions/salestax-python.svg?maxAge=3600)](https://pypi.org/project/salestax-python/)
43
+ [![CI](https://github.com/InstaBlick-USA/salestax-python/actions/workflows/ci.yml/badge.svg)](https://github.com/InstaBlick-USA/salestax-python/actions)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
45
+
46
+ Official Python SDK for the **[Sales Tax Calculator API](https://salestaxcalculatorapi.com)** — immutable sales tax calculations, transactions, adjustments, batches, and coverage checks.
47
+
48
+ Zero runtime dependencies for the sync client. Full type hints.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install salestax-python
54
+ ```
55
+
56
+ Async support:
57
+
58
+ ```bash
59
+ pip install salestax-python[async]
60
+ ```
61
+
62
+ ## Quickstart
63
+
64
+ ```python
65
+ from salestax import SalesTaxClient
66
+
67
+ with SalesTaxClient() as client: # reads SALESTAX_API_KEY
68
+ calc = client.calculations.create(
69
+ currency="CAD",
70
+ tax_behavior="exclusive",
71
+ billing_event="subscription_start",
72
+ seller={
73
+ "country": "CA",
74
+ "channel_role": "direct_legal_supplier",
75
+ "registrations": [
76
+ {"country": "CA", "state": "ON", "type": "gst_hst", "effective_from": "2026-01-01"},
77
+ ],
78
+ },
79
+ customer={
80
+ "type": "consumer",
81
+ "address": {"country": "CA", "state": "ON", "postal_code": "M5V 2T6"},
82
+ },
83
+ lines=[
84
+ {"reference": "subscription", "amount": "100.00", "quantity": "1", "tax_code": "saas"},
85
+ ],
86
+ idempotency_key="order-1001-abc12345",
87
+ )
88
+
89
+ print(calc["outcome"]) # 'calculated'
90
+ print(calc["tax"]) # '13.00'
91
+ print(calc["total"]) # '113.00'
92
+ ```
93
+
94
+ Get your API key at **[salestaxcalculatorapi.com](https://salestaxcalculatorapi.com)**
95
+
96
+ ## Resources
97
+
98
+ | Resource | Methods |
99
+ |---|---|
100
+ | `client.calculations` | `create`, `get`, `create_batch`, `get_batch` |
101
+ | `client.transactions` | `create`, `get` |
102
+ | `client.transactions.adjustments` | `create`, `list`, `get` |
103
+ | `client.batches` | `get` |
104
+ | `client.coverage` | `check` |
105
+
106
+ ## Transactions
107
+
108
+ Finalize a calculation as an immutable commercial record:
109
+
110
+ ```python
111
+ txn = client.transactions.create(
112
+ calculation_id=calc["id"],
113
+ reference="order-1001",
114
+ idempotency_key="order-1001-txn-001",
115
+ )
116
+ print(txn["adjusted_amount"], txn["adjusted_tax"])
117
+ ```
118
+
119
+ ## Adjustments
120
+
121
+ Reverse part of a transaction by exact amount or proportional quantity:
122
+
123
+ ```python
124
+ adj = client.transactions.adjustments.create(
125
+ txn["id"],
126
+ reference="refund-order-1001",
127
+ reason="refund",
128
+ lines=[{"line_id": "line_01J6...", "amount": "25.00"}],
129
+ idempotency_key="refund-order-1001-001",
130
+ )
131
+ print(adj["tax"])
132
+ ```
133
+
134
+ ## Batches
135
+
136
+ Process up to 100 calculations asynchronously:
137
+
138
+ ```python
139
+ batch = client.calculations.create_batch(
140
+ [calc_a, calc_b, calc_c],
141
+ reference="batch-2026-09-23",
142
+ idempotency_key="batch-2026-09-23-xyz",
143
+ )
144
+
145
+ status = client.batches.get(batch["id"])
146
+ for item in status.get("items", []):
147
+ print(item["index"], item["status"])
148
+ ```
149
+
150
+ ## Coverage
151
+
152
+ Check qualification before you calculate:
153
+
154
+ ```python
155
+ coverage = client.coverage.check(
156
+ country="CA",
157
+ state="ON",
158
+ tax_code="saas",
159
+ transaction_type="sale",
160
+ )
161
+ print(coverage["qualification"]) # 'qualified' | 'review_required' | 'unsupported'
162
+ ```
163
+
164
+ ## Async
165
+
166
+ ```python
167
+ import asyncio
168
+ from salestax import AsyncSalesTaxClient
169
+
170
+ async def main():
171
+ async with AsyncSalesTaxClient() as client:
172
+ calc = await client.calculations.create(
173
+ currency="CAD",
174
+ tax_behavior="exclusive",
175
+ billing_event="subscription_start",
176
+ seller={
177
+ "country": "CA",
178
+ "channel_role": "direct_legal_supplier",
179
+ "registrations": [
180
+ {"country": "CA", "state": "ON", "type": "gst_hst",
181
+ "effective_from": "2026-01-01"},
182
+ ],
183
+ },
184
+ customer={
185
+ "type": "consumer",
186
+ "address": {"country": "CA", "state": "ON", "postal_code": "M5V 2T6"},
187
+ },
188
+ lines=[{"reference": "sub", "amount": "100.00", "tax_code": "saas"}],
189
+ idempotency_key="order-1001-abc12345",
190
+ )
191
+ print(calc["tax"])
192
+
193
+ asyncio.run(main())
194
+ ```
195
+
196
+ ## Audit expansion
197
+
198
+ Request the customer-safe audit expansion on any calculable resource:
199
+
200
+ ```python
201
+ calc = client.calculations.create(
202
+ # ...same params as above...
203
+ expand="audit",
204
+ )
205
+ print(calc.get("audit", {}).get("components"))
206
+ ```
207
+
208
+ ## Error Handling
209
+
210
+ Every status code maps to a typed error class. Field-level errors carry a JSON Pointer in `err.param`.
211
+
212
+ ```python
213
+ from salestax import RateLimitError, SalesTaxError, ValidationError
214
+
215
+ try:
216
+ client.calculations.create(...)
217
+ except RateLimitError as err:
218
+ # err.retry_after_ms - back off
219
+ # err.request_id
220
+ ...
221
+ except ValidationError as err:
222
+ # err.param - JSON Pointer to the offending field, e.g. '/lines/0/amount'
223
+ ...
224
+ except SalesTaxError as err:
225
+ # err.code, err.request_id, err.retryable, err.status_code
226
+ ...
227
+ ```
228
+
229
+ ## Idempotency
230
+
231
+ Every mutating request accepts an `Idempotency-Key`. Reuse the same key when retrying the same operation. The key must be 8-255 characters matching `[A-Za-z0-9][A-Za-z0-9._:-]{7,254}`.
232
+
233
+ ```python
234
+ client.calculations.create(..., idempotency_key="order-1001-abc12345")
235
+ ```
236
+
237
+ Reusing a key with a different request body returns `409 idempotency_conflict`.
238
+
239
+ ## Configuration
240
+
241
+ ```python
242
+ from salestax import RetryPolicy, SalesTaxClient
243
+
244
+ client = SalesTaxClient(
245
+ api_key="stca_...",
246
+ timeout_ms=20_000,
247
+ retry=RetryPolicy(
248
+ max_retries=4,
249
+ initial_delay_ms=250,
250
+ max_delay_ms=8_000,
251
+ backoff_factor=2.0,
252
+ jitter=True,
253
+ ),
254
+ )
255
+ ```
256
+
257
+ ### Defaults
258
+
259
+ | Option | Default |
260
+ |---|---|
261
+ | `base_url` | `https://api.salestaxcalculatorapi.com` |
262
+ | `timeout_ms` | `30000` |
263
+ | `retry.max_retries` | `2` |
264
+ | `retry.initial_delay_ms` | `250` |
265
+ | `retry.max_delay_ms` | `8000` |
266
+ | `retry.backoff_factor` | `2.0` |
267
+ | `retry.jitter` | `True` |
268
+
269
+ ## Behavior
270
+
271
+ - **Retries** — 5xx, network failures, and 429 responses are retried automatically with exponential backoff and full jitter. `Retry-After` and `retry_after_seconds` are honored when present. 4xx responses other than 429 are returned to the caller unchanged.
272
+ - **Timeouts** — each request is aborted after `timeout_ms` and surfaced as a `TimeoutError`.
273
+ - **Hooks** — throwing inside a hook never breaks the request flow. The exception is swallowed and the SDK continues.
274
+ - **Money and quantity** — always decimal strings matching the OpenAPI `Money` and `Quantity` patterns. `amount` is the total for the line, not a per-unit price.
275
+ - **Unknown fields** — the API rejects unknown JSON fields. The SDK passes the request body through unchanged so server-side validation is authoritative.
276
+
277
+ ## Also Available For
278
+
279
+ - [salestax-node](https://github.com/InstaBlick-USA/salestax-node) — Node.js / TypeScript
280
+
281
+ ## Documentation
282
+
283
+ Full API reference at **[salestaxcalculatorapi.com/docs](https://salestaxcalculatorapi.com/docs)**
284
+
285
+ ## License
286
+
287
+ MIT (c) [InstaBlick USA](https://github.com/InstaBlick-USA)
@@ -0,0 +1,249 @@
1
+ # salestax-python
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/salestax-python.svg?maxAge=3600)](https://pypi.org/project/salestax-python/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/salestax-python.svg?maxAge=3600)](https://pypi.org/project/salestax-python/)
5
+ [![CI](https://github.com/InstaBlick-USA/salestax-python/actions/workflows/ci.yml/badge.svg)](https://github.com/InstaBlick-USA/salestax-python/actions)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7
+
8
+ Official Python SDK for the **[Sales Tax Calculator API](https://salestaxcalculatorapi.com)** — immutable sales tax calculations, transactions, adjustments, batches, and coverage checks.
9
+
10
+ Zero runtime dependencies for the sync client. Full type hints.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pip install salestax-python
16
+ ```
17
+
18
+ Async support:
19
+
20
+ ```bash
21
+ pip install salestax-python[async]
22
+ ```
23
+
24
+ ## Quickstart
25
+
26
+ ```python
27
+ from salestax import SalesTaxClient
28
+
29
+ with SalesTaxClient() as client: # reads SALESTAX_API_KEY
30
+ calc = client.calculations.create(
31
+ currency="CAD",
32
+ tax_behavior="exclusive",
33
+ billing_event="subscription_start",
34
+ seller={
35
+ "country": "CA",
36
+ "channel_role": "direct_legal_supplier",
37
+ "registrations": [
38
+ {"country": "CA", "state": "ON", "type": "gst_hst", "effective_from": "2026-01-01"},
39
+ ],
40
+ },
41
+ customer={
42
+ "type": "consumer",
43
+ "address": {"country": "CA", "state": "ON", "postal_code": "M5V 2T6"},
44
+ },
45
+ lines=[
46
+ {"reference": "subscription", "amount": "100.00", "quantity": "1", "tax_code": "saas"},
47
+ ],
48
+ idempotency_key="order-1001-abc12345",
49
+ )
50
+
51
+ print(calc["outcome"]) # 'calculated'
52
+ print(calc["tax"]) # '13.00'
53
+ print(calc["total"]) # '113.00'
54
+ ```
55
+
56
+ Get your API key at **[salestaxcalculatorapi.com](https://salestaxcalculatorapi.com)**
57
+
58
+ ## Resources
59
+
60
+ | Resource | Methods |
61
+ |---|---|
62
+ | `client.calculations` | `create`, `get`, `create_batch`, `get_batch` |
63
+ | `client.transactions` | `create`, `get` |
64
+ | `client.transactions.adjustments` | `create`, `list`, `get` |
65
+ | `client.batches` | `get` |
66
+ | `client.coverage` | `check` |
67
+
68
+ ## Transactions
69
+
70
+ Finalize a calculation as an immutable commercial record:
71
+
72
+ ```python
73
+ txn = client.transactions.create(
74
+ calculation_id=calc["id"],
75
+ reference="order-1001",
76
+ idempotency_key="order-1001-txn-001",
77
+ )
78
+ print(txn["adjusted_amount"], txn["adjusted_tax"])
79
+ ```
80
+
81
+ ## Adjustments
82
+
83
+ Reverse part of a transaction by exact amount or proportional quantity:
84
+
85
+ ```python
86
+ adj = client.transactions.adjustments.create(
87
+ txn["id"],
88
+ reference="refund-order-1001",
89
+ reason="refund",
90
+ lines=[{"line_id": "line_01J6...", "amount": "25.00"}],
91
+ idempotency_key="refund-order-1001-001",
92
+ )
93
+ print(adj["tax"])
94
+ ```
95
+
96
+ ## Batches
97
+
98
+ Process up to 100 calculations asynchronously:
99
+
100
+ ```python
101
+ batch = client.calculations.create_batch(
102
+ [calc_a, calc_b, calc_c],
103
+ reference="batch-2026-09-23",
104
+ idempotency_key="batch-2026-09-23-xyz",
105
+ )
106
+
107
+ status = client.batches.get(batch["id"])
108
+ for item in status.get("items", []):
109
+ print(item["index"], item["status"])
110
+ ```
111
+
112
+ ## Coverage
113
+
114
+ Check qualification before you calculate:
115
+
116
+ ```python
117
+ coverage = client.coverage.check(
118
+ country="CA",
119
+ state="ON",
120
+ tax_code="saas",
121
+ transaction_type="sale",
122
+ )
123
+ print(coverage["qualification"]) # 'qualified' | 'review_required' | 'unsupported'
124
+ ```
125
+
126
+ ## Async
127
+
128
+ ```python
129
+ import asyncio
130
+ from salestax import AsyncSalesTaxClient
131
+
132
+ async def main():
133
+ async with AsyncSalesTaxClient() as client:
134
+ calc = await client.calculations.create(
135
+ currency="CAD",
136
+ tax_behavior="exclusive",
137
+ billing_event="subscription_start",
138
+ seller={
139
+ "country": "CA",
140
+ "channel_role": "direct_legal_supplier",
141
+ "registrations": [
142
+ {"country": "CA", "state": "ON", "type": "gst_hst",
143
+ "effective_from": "2026-01-01"},
144
+ ],
145
+ },
146
+ customer={
147
+ "type": "consumer",
148
+ "address": {"country": "CA", "state": "ON", "postal_code": "M5V 2T6"},
149
+ },
150
+ lines=[{"reference": "sub", "amount": "100.00", "tax_code": "saas"}],
151
+ idempotency_key="order-1001-abc12345",
152
+ )
153
+ print(calc["tax"])
154
+
155
+ asyncio.run(main())
156
+ ```
157
+
158
+ ## Audit expansion
159
+
160
+ Request the customer-safe audit expansion on any calculable resource:
161
+
162
+ ```python
163
+ calc = client.calculations.create(
164
+ # ...same params as above...
165
+ expand="audit",
166
+ )
167
+ print(calc.get("audit", {}).get("components"))
168
+ ```
169
+
170
+ ## Error Handling
171
+
172
+ Every status code maps to a typed error class. Field-level errors carry a JSON Pointer in `err.param`.
173
+
174
+ ```python
175
+ from salestax import RateLimitError, SalesTaxError, ValidationError
176
+
177
+ try:
178
+ client.calculations.create(...)
179
+ except RateLimitError as err:
180
+ # err.retry_after_ms - back off
181
+ # err.request_id
182
+ ...
183
+ except ValidationError as err:
184
+ # err.param - JSON Pointer to the offending field, e.g. '/lines/0/amount'
185
+ ...
186
+ except SalesTaxError as err:
187
+ # err.code, err.request_id, err.retryable, err.status_code
188
+ ...
189
+ ```
190
+
191
+ ## Idempotency
192
+
193
+ Every mutating request accepts an `Idempotency-Key`. Reuse the same key when retrying the same operation. The key must be 8-255 characters matching `[A-Za-z0-9][A-Za-z0-9._:-]{7,254}`.
194
+
195
+ ```python
196
+ client.calculations.create(..., idempotency_key="order-1001-abc12345")
197
+ ```
198
+
199
+ Reusing a key with a different request body returns `409 idempotency_conflict`.
200
+
201
+ ## Configuration
202
+
203
+ ```python
204
+ from salestax import RetryPolicy, SalesTaxClient
205
+
206
+ client = SalesTaxClient(
207
+ api_key="stca_...",
208
+ timeout_ms=20_000,
209
+ retry=RetryPolicy(
210
+ max_retries=4,
211
+ initial_delay_ms=250,
212
+ max_delay_ms=8_000,
213
+ backoff_factor=2.0,
214
+ jitter=True,
215
+ ),
216
+ )
217
+ ```
218
+
219
+ ### Defaults
220
+
221
+ | Option | Default |
222
+ |---|---|
223
+ | `base_url` | `https://api.salestaxcalculatorapi.com` |
224
+ | `timeout_ms` | `30000` |
225
+ | `retry.max_retries` | `2` |
226
+ | `retry.initial_delay_ms` | `250` |
227
+ | `retry.max_delay_ms` | `8000` |
228
+ | `retry.backoff_factor` | `2.0` |
229
+ | `retry.jitter` | `True` |
230
+
231
+ ## Behavior
232
+
233
+ - **Retries** — 5xx, network failures, and 429 responses are retried automatically with exponential backoff and full jitter. `Retry-After` and `retry_after_seconds` are honored when present. 4xx responses other than 429 are returned to the caller unchanged.
234
+ - **Timeouts** — each request is aborted after `timeout_ms` and surfaced as a `TimeoutError`.
235
+ - **Hooks** — throwing inside a hook never breaks the request flow. The exception is swallowed and the SDK continues.
236
+ - **Money and quantity** — always decimal strings matching the OpenAPI `Money` and `Quantity` patterns. `amount` is the total for the line, not a per-unit price.
237
+ - **Unknown fields** — the API rejects unknown JSON fields. The SDK passes the request body through unchanged so server-side validation is authoritative.
238
+
239
+ ## Also Available For
240
+
241
+ - [salestax-node](https://github.com/InstaBlick-USA/salestax-node) — Node.js / TypeScript
242
+
243
+ ## Documentation
244
+
245
+ Full API reference at **[salestaxcalculatorapi.com/docs](https://salestaxcalculatorapi.com/docs)**
246
+
247
+ ## License
248
+
249
+ MIT (c) [InstaBlick USA](https://github.com/InstaBlick-USA)
@@ -0,0 +1,47 @@
1
+ """Async client example. Requires: pip install salestax-python[async]"""
2
+
3
+ import asyncio
4
+
5
+ from salestax import AsyncSalesTaxClient
6
+
7
+
8
+ async def main() -> None:
9
+ async with AsyncSalesTaxClient() as client:
10
+ seller = {
11
+ "country": "CA",
12
+ "channel_role": "direct_legal_supplier",
13
+ "registrations": [
14
+ {"country": "CA", "state": "ON", "type": "gst_hst",
15
+ "effective_from": "2026-01-01"},
16
+ ],
17
+ }
18
+ customer = {
19
+ "type": "consumer",
20
+ "address": {"country": "CA", "state": "ON", "postal_code": "M5V 2T6"},
21
+ }
22
+
23
+ calc_a, calc_b, cov = await asyncio.gather(
24
+ client.calculations.create(
25
+ currency="CAD", tax_behavior="exclusive",
26
+ billing_event="subscription_start",
27
+ seller=seller, customer=customer,
28
+ lines=[{"reference": "a", "amount": "100.00", "tax_code": "saas"}],
29
+ idempotency_key="order-a-abc12345",
30
+ ),
31
+ client.calculations.create(
32
+ currency="CAD", tax_behavior="exclusive",
33
+ billing_event="subscription_start",
34
+ seller=seller, customer=customer,
35
+ lines=[{"reference": "b", "amount": "250.00", "tax_code": "saas"}],
36
+ idempotency_key="order-b-abc12345",
37
+ ),
38
+ client.coverage.check(
39
+ country="CA", state="ON", tax_code="saas", transaction_type="sale",
40
+ ),
41
+ )
42
+ print("Calc A:", calc_a["tax"], "Calc B:", calc_b["tax"])
43
+ print("Coverage:", cov["qualification"])
44
+
45
+
46
+ if __name__ == "__main__":
47
+ asyncio.run(main())
@@ -0,0 +1,38 @@
1
+ """Create a single sales tax calculation.
2
+
3
+ Run:
4
+ SALESTAX_API_KEY=stca_... python examples/basic_calculation.py
5
+ """
6
+
7
+ from salestax import SalesTaxClient
8
+
9
+ with SalesTaxClient() as client:
10
+ calc = client.calculations.create(
11
+ reference="order-1001",
12
+ transaction_date="2026-09-23",
13
+ currency="CAD",
14
+ tax_behavior="exclusive",
15
+ billing_event="subscription_start",
16
+ seller={
17
+ "country": "CA",
18
+ "channel_role": "direct_legal_supplier",
19
+ "registrations": [
20
+ {"country": "CA", "state": "ON", "type": "gst_hst",
21
+ "effective_from": "2026-01-01"},
22
+ ],
23
+ },
24
+ customer={
25
+ "type": "consumer",
26
+ "address": {"country": "CA", "state": "ON", "postal_code": "M5V 2T6"},
27
+ },
28
+ lines=[
29
+ {"reference": "subscription", "amount": "100.00",
30
+ "quantity": "1", "tax_code": "saas"},
31
+ ],
32
+ idempotency_key="order-1001-abc12345",
33
+ )
34
+
35
+ print("Outcome:", calc["outcome"])
36
+ print("Subtotal:", calc["subtotal"])
37
+ print("Tax:", calc["tax"])
38
+ print("Total:", calc["total"])