useyona-einvoice 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 (27) hide show
  1. useyona_einvoice-0.1.0/.gitignore +14 -0
  2. useyona_einvoice-0.1.0/CHANGELOG.md +28 -0
  3. useyona_einvoice-0.1.0/LICENSE +21 -0
  4. useyona_einvoice-0.1.0/PKG-INFO +352 -0
  5. useyona_einvoice-0.1.0/README.md +320 -0
  6. useyona_einvoice-0.1.0/pyproject.toml +82 -0
  7. useyona_einvoice-0.1.0/src/useyona/einvoice/__init__.py +97 -0
  8. useyona_einvoice-0.1.0/src/useyona/einvoice/_base.py +82 -0
  9. useyona_einvoice-0.1.0/src/useyona/einvoice/_transport.py +399 -0
  10. useyona_einvoice-0.1.0/src/useyona/einvoice/_version.py +3 -0
  11. useyona_einvoice-0.1.0/src/useyona/einvoice/client.py +152 -0
  12. useyona_einvoice-0.1.0/src/useyona/einvoice/errors.py +185 -0
  13. useyona_einvoice-0.1.0/src/useyona/einvoice/excluded_operations.py +30 -0
  14. useyona_einvoice-0.1.0/src/useyona/einvoice/py.typed +0 -0
  15. useyona_einvoice-0.1.0/src/useyona/einvoice/services/__init__.py +52 -0
  16. useyona_einvoice-0.1.0/src/useyona/einvoice/services/billing.py +136 -0
  17. useyona_einvoice-0.1.0/src/useyona/einvoice/services/catalogue.py +86 -0
  18. useyona_einvoice-0.1.0/src/useyona/einvoice/services/invoices.py +126 -0
  19. useyona_einvoice-0.1.0/src/useyona/einvoice/services/organization.py +36 -0
  20. useyona_einvoice-0.1.0/src/useyona/einvoice/services/output.py +44 -0
  21. useyona_einvoice-0.1.0/src/useyona/einvoice/services/parties.py +89 -0
  22. useyona_einvoice-0.1.0/src/useyona/einvoice/services/received.py +61 -0
  23. useyona_einvoice-0.1.0/src/useyona/einvoice/services/webhooks.py +81 -0
  24. useyona_einvoice-0.1.0/src/useyona/einvoice/types/__init__.py +130 -0
  25. useyona_einvoice-0.1.0/src/useyona/einvoice/types/generated.py +3825 -0
  26. useyona_einvoice-0.1.0/src/useyona/einvoice/types/webhook.py +174 -0
  27. useyona_einvoice-0.1.0/src/useyona/einvoice/webhooks.py +170 -0
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ .coverage
11
+ coverage.xml
12
+ htmlcov/
13
+ .env
14
+ .DS_Store
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## 0.1.0 (2026-10-07)
7
+
8
+ The first release of the Python SDK, at parity with `@useyona/einvoice-js` 0.8.1: 99 methods in 23
9
+ modules over the 106 operations an API key may call, 8 documented exclusions. The SDKs are versioned
10
+ independently; the einvoice-js commit pinned in `scripts/sync.sh` records which JS source this one tracks.
11
+
12
+ ### Added
13
+
14
+ - `EInvoice(api_key, ...)`: the mode follows the key prefix (`sk_test_` sandbox, `sk_live_` live) on
15
+ one permanent host; `assert_mode`, `timeout` (seconds), `retry=RetryConfig(...)`, `headers`,
16
+ `http_client` (an `httpx.Client`, e.g. with `httpx.MockTransport` in tests); context manager.
17
+ - Modules `invoices`, `submissions`, `output`, `share_links`, `items`, `reference`, `buyers`, `sellers`,
18
+ `inbound_invoices`, `issued_history`, `invoice_settings`, `tax_connection`, `organization`,
19
+ `billing.{accounts, payments, sandbox, statements, subscriptions, transactions}`,
20
+ `webhooks.{endpoints, deliveries, events, event_types}`; `paginate()`.
21
+ - The error hierarchy (`EInvoiceApiError` and its subclasses by status, `EInvoiceTimeoutError`,
22
+ `EInvoiceConnectionError`, `EInvoiceConfigError`, `EInvoiceWebhookError`).
23
+ - Retries with backoff and `Retry-After` for GET/PUT/DELETE and keyed writes; generated
24
+ `Idempotency-Key`s on the routes that accept one; one deadline per attempt covering headers and body.
25
+ - `useyona.einvoice.webhooks`: `verify_webhook`, `sign_webhook_payload`, `compute_webhook_signature`,
26
+ `parse_signature_header`, verified against the backend's test vectors.
27
+ - Generated `TypedDict` models for every request and response shape (`useyona.einvoice.types`).
28
+ - The five guide recipes in `examples/` and their export to `guides/guides.json`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Elyonar
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,352 @@
1
+ Metadata-Version: 2.5
2
+ Name: useyona-einvoice
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Yona e-invoicing API — what an API key may call: invoicing, submissions to the tax authority, buyers, items, received invoices, billing reads and webhooks
5
+ Project-URL: Homepage, https://useyona.com/docs/sdks/python/installation
6
+ Project-URL: Repository, https://github.com/Elyonar/einvoice-python
7
+ Project-URL: Issues, https://github.com/Elyonar/einvoice-python/issues
8
+ Project-URL: Changelog, https://github.com/Elyonar/einvoice-python/blob/main/CHANGELOG.md
9
+ Author-email: Yona <dev@useyona.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: api-client,billing,e-invoice,einvoice,electronic-invoice,invoice,nigeria,nrs,sdk,tax,tax-compliance,yona
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx<1,>=0.27
25
+ Requires-Dist: typing-extensions>=4.5
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.10; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
29
+ Requires-Dist: pytest>=8; extra == 'dev'
30
+ Requires-Dist: ruff>=0.5; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # useyona-einvoice
34
+
35
+ Official Python SDK for the Yona e-invoicing API. It covers exactly what an API key may call:
36
+ invoicing, submissions to the tax authority, output and share links, items, reference data, buyers,
37
+ the read-only seller, received invoices and issued history, invoice settings, the tax connection, the
38
+ organisation (read), billing reads and webhooks (read, test, redeliver).
39
+
40
+ The same surface as [`@useyona/einvoice-js`](https://github.com/Elyonar/einvoice-js), module for
41
+ module, with the same names in `snake_case`.
42
+
43
+ ## Features
44
+
45
+ - **One key, nothing else to configure.** `sk_test_…` is the sandbox, `sk_live_…` is live, on the same host.
46
+ - **Typed.** Every request and response shape is a `TypedDict` generated from the API's OpenAPI; `py.typed` ships.
47
+ - **Safe retries.** GET, PUT, DELETE and writes carrying an `Idempotency-Key` are retried on network
48
+ errors, timeouts, 408, 429 and 5xx, honouring `Retry-After`. The SDK generates the key on the
49
+ routes that accept one, so its own retry is never charged twice.
50
+ - **Typed errors.** `EInvoiceValidationError`, `EInvoiceNotFoundError`, `EInvoiceRateLimitError`… all
51
+ carry `status`, `error_code`, `errors`, `request_id` and `retry_after`.
52
+ - **Webhooks.** `verify_webhook` checks the `Yona-Signature` over the raw body in constant time.
53
+ - **Light.** `httpx` and `typing_extensions` are the only dependencies. Python 3.10+.
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install useyona-einvoice
59
+ ```
60
+
61
+ ## Quick start
62
+
63
+ ```python
64
+ import os
65
+ from useyona.einvoice import EInvoice
66
+
67
+ client = EInvoice(api_key=os.environ["YONA_API_KEY"])
68
+ client.mode # "sandbox" for sk_test_… keys, "live" for sk_live_… keys
69
+
70
+ # 1. A buyer
71
+ buyer = client.buyers.create(
72
+ {
73
+ "name": "Acme Nigeria Ltd",
74
+ "taxId": "12345678-0001",
75
+ "email": "accounts@acme.ng",
76
+ "partyType": "company",
77
+ "address": {"line1": "1 Marina", "city": "Lagos", "country": "NG"},
78
+ }
79
+ )
80
+
81
+ # 2. A saved item (optional: a line can also carry its own description, unit code and codes)
82
+ item = client.items.create(
83
+ {
84
+ "name": "Laptop",
85
+ "itemType": "goods",
86
+ "hsnCode": "8471.30",
87
+ "productCategory": "Machinery",
88
+ "unitCode": "EA",
89
+ "unitPriceMinor": "45000000",
90
+ "currency": "NGN",
91
+ "taxCategory": "STANDARD_VAT",
92
+ }
93
+ )
94
+
95
+ # 3. A draft invoice: invoiceKind and taxCategory, never taxPercent
96
+ invoice = client.invoices.create(
97
+ {
98
+ "invoiceKind": "B2B",
99
+ "invoiceDate": "2026-10-05",
100
+ "currency": "NGN",
101
+ "buyerId": buyer["id"],
102
+ "lineItems": [{"itemId": item["id"], "quantity": 2}],
103
+ }
104
+ )
105
+
106
+ # 4. Finalise and report it to the tax authority
107
+ client.invoices.finalise(invoice["id"])
108
+ client.submissions.submit(invoice["id"]) # 202: queued
109
+ status = client.submissions.get_status(invoice["id"])
110
+ ```
111
+
112
+ That is all the configuration an integration needs: the key. The SDK talks to the production
113
+ gateway for both modes; the key's prefix decides whether you are in the sandbox or live.
114
+
115
+ To fail fast when a deployment is given the wrong key:
116
+
117
+ ```python
118
+ EInvoice(api_key=key, assert_mode="live") # raises EInvoiceConfigError for an sk_test_ key
119
+ ```
120
+
121
+ A malformed key is refused at construction with `EInvoiceConfigError` (the key is never echoed).
122
+
123
+ The client holds a connection pool; close it with `client.close()` or use it as a context manager
124
+ (`with EInvoice(api_key=key) as client:`).
125
+
126
+ ## Guides
127
+
128
+ The recipes in [`examples/`](examples/) are the developer guides of the Yona portal (Developers,
129
+ Overview): your first invoice, webhooks, errors and retries, sandbox and live, received invoices.
130
+ Each runs end to end against a sandbox key (`YONA_API_KEY=sk_test_… make examples`).
131
+
132
+ ## Verifying your setup
133
+
134
+ ```bash
135
+ YONA_API_KEY=sk_test_… make smoke-remote
136
+ ```
137
+
138
+ runs free reads across every module and then the first-invoice example against the real API, with
139
+ your own sandbox key, and prints a table method → OK/FAIL with the error code and request id of any
140
+ failure. It refuses a live key and never prints the key. The example creates sandbox data in your
141
+ organisation: a buyer, an item and an invoice submitted to the tax authority's sandbox.
142
+
143
+ ## Responses and pagination
144
+
145
+ Methods return the API's `data` as a `TypedDict` with the wire names (`invoice["invoiceNumber"]`).
146
+ List methods return a `Page`:
147
+
148
+ ```python
149
+ page = client.buyers.list({"limit": 50})
150
+ page.data # list[BuyerViewDto]
151
+ page.pagination # {"total", "page", "pageSize", "totalPages", "hasNext", "hasPrevious"}
152
+ page.request_id
153
+ ```
154
+
155
+ `paginate` walks every page for you:
156
+
157
+ ```python
158
+ from useyona.einvoice import paginate
159
+
160
+ for buyer in paginate(client.buyers.list, {"limit": 100}):
161
+ ...
162
+ ```
163
+
164
+ Received invoices and issued history answer an object with `items`, so they return a `Paginated`:
165
+ `res.data["items"]` and `res.pagination`.
166
+
167
+ PDF downloads return a `BinaryResponse`: `data` (bytes), `content_type`, `file_name`, `request_id`.
168
+
169
+ ## Errors
170
+
171
+ Every refusal of the API raises a subclass of `EInvoiceApiError`, chosen by status:
172
+
173
+ | Status | Class | Typical `error_code` |
174
+ |---|---|---|
175
+ | 400, 422 | `EInvoiceValidationError` | `VAL…` |
176
+ | 401 | `EInvoiceAuthenticationError` | `AUTH…` (a revoked, expired or malformed key) |
177
+ | 402 | `EInvoiceInsufficientCreditsError` | `BIZ001` |
178
+ | 403 | `EInvoicePermissionError` | `AUTH019` (capability), `AUTH018` (user-only route) |
179
+ | 404 | `EInvoiceNotFoundError` | `RES001` |
180
+ | 409 | `EInvoiceConflictError` | `BIZ…` (state), `RES002` (duplicate) |
181
+ | 429 | `EInvoiceRateLimitError` | `SYS005`, with `retry_after` |
182
+ | 5xx | `EInvoiceServerError` | `SYS001` |
183
+
184
+ ```python
185
+ from useyona.einvoice import EInvoiceApiError, EInvoiceValidationError
186
+
187
+ try:
188
+ client.invoices.create(params)
189
+ except EInvoiceValidationError as err:
190
+ for e in err.errors:
191
+ print(e.field, e.message)
192
+ except EInvoiceApiError as err:
193
+ print(err.status, err.error_code, err.request_id) # quote request_id to support
194
+ ```
195
+
196
+ Branch on `error_code`, never on the message. Outside the API: `EInvoiceTimeoutError`,
197
+ `EInvoiceConnectionError`, `EInvoiceConfigError`, `EInvoiceWebhookError`; all extend `EInvoiceError`.
198
+
199
+ ## Retries and idempotency
200
+
201
+ ```python
202
+ from useyona.einvoice import EInvoice, RetryConfig
203
+
204
+ client = EInvoice(api_key=key, timeout=30, retry=RetryConfig(max_retries=3, base_delay=0.5, max_delay=8, max_retry_after=30))
205
+ ```
206
+
207
+ The SDK retries GET, PUT and DELETE, and writes that carry an `Idempotency-Key`, on network errors,
208
+ timeouts, 408, 429 and 5xx, with exponential backoff and jitter; a `Retry-After` on 429/503 is
209
+ waited out up to `max_retry_after` seconds (a longer one is raised at once, with `retry_after` set).
210
+ Other writes are never retried for you. `timeout` (seconds) bounds each attempt, headers and body.
211
+
212
+ On routes that accept an `Idempotency-Key` (`invoices.create`, `submissions.submit`, `output.send`,
213
+ the downloads…) the SDK generates one per call, so its own retries are never applied or charged
214
+ twice. Pass your own to make a retry across process restarts safe:
215
+
216
+ ```python
217
+ from useyona.einvoice import RequestOptions
218
+
219
+ client.invoices.create(params, RequestOptions(idempotency_key=f"order-{order_id}"))
220
+ ```
221
+
222
+ `RequestOptions` also takes `timeout`, `headers` and `max_retries` for one call.
223
+
224
+ ## Webhooks
225
+
226
+ ```python
227
+ from useyona.einvoice.webhooks import verify_webhook
228
+
229
+
230
+ @app.post("/webhooks/yona")
231
+ def yona_webhook():
232
+ event = verify_webhook(request.get_data(), request.headers, os.environ["YONA_WEBHOOK_SECRET"])
233
+ if event["type"] == "invoice.accepted":
234
+ ...
235
+ return "", 200
236
+ ```
237
+
238
+ Yona signs `t + "." + raw body` with HMAC-SHA256 and sends `Yona-Signature: t=…,v1=…` (a second
239
+ `v1=` while a rotated secret overlaps). `verify_webhook` accepts the delivery when a signature
240
+ matches in constant time and `t` is within ±300 s, and returns the parsed event. Pass the raw body
241
+ bytes, never a re-serialised object. Deduplicate on `event["id"]`. `sign_webhook_payload` signs a
242
+ payload exactly as Yona does, for testing your handler. Endpoints are registered in the dashboard;
243
+ the key can list and test them (`client.webhooks.endpoints`), read deliveries and redeliver.
244
+
245
+ ## What an API key cannot do
246
+
247
+ Users, invitations, roles, API keys, organisation management, collections, purchases and webhook
248
+ endpoint writes are done by a signed-in user in the dashboard; the API answers 403 `AUTH018` to a
249
+ key. The seller is the organisation itself (read-only; `sellers.create/update/delete` are 409
250
+ `BIZ205` and have no method). `EXCLUDED_OPERATIONS` lists the API-key operations the SDK deliberately
251
+ has no method for, with the reason.
252
+
253
+ ## API reference
254
+
255
+ Every method takes an optional trailing `options: RequestOptions`. Query and body parameters are
256
+ `TypedDict`s named after the operation (`ListInvoicesQuery`, `CreateInvoiceBody`), all in
257
+ `useyona.einvoice.types`.
258
+
259
+ #### `client.invoices`
260
+ `create(params)`, `list(query)`, `get(id)`, `update(id, params)`, `delete(id)`, `finalise(id)`, `reopen(id)`, `revise(id)`, `cancel(id, params)`, `issue_credit_note(id, params)`, `issue_debit_note(id, params)`, `get_overview(query)`, `get_statistics(query)`, `get_summary(query)`
261
+
262
+ #### `client.submissions`
263
+ `submit(id, query)`, `issue(id)`, `create_and_submit(params)`, `batch_submit(params)`, `retry(id)`, `renumber(id)`, `query_status(id)`, `get_status(id)`, `record_payment(id, params)`, `get_authority_copy(id)`
264
+
265
+ #### `client.output`
266
+ `get_download_link(id)`, `download_pdf(id)` → `BinaryResponse`, `send(id, params)`
267
+
268
+ #### `client.share_links`
269
+ `create(invoice_id, params)`, `list(invoice_id)`, `revoke(invoice_id, link_id)`
270
+
271
+ #### `client.items`
272
+ `list(query)`, `create(params)`, `get(id)`, `update(id, params)`, `delete(id)`, `archive(id)`, `unarchive(id)`, `list_used_codes(query)`
273
+
274
+ #### `client.reference`
275
+ `list_hs_codes(query)`, `list_hs_code_categories()`, `list_resources()`, `get_resource(type)`, `lookup_tax_id(value, query)`, `validate_invoice(params)`
276
+
277
+ #### `client.buyers`
278
+ `create(params)`, `list(query)`, `get(id)`, `update(id, params)`, `delete(id)`, `bulk_delete(params)`, `verify_tax_number(id)`, `get_verification_status(id)`, `search(query)`, `check_reachability(query)`, `check_with_tax_authority(params)`
279
+
280
+ #### `client.sellers`
281
+ `list(query)`, `get(id)`, `get_verification_status(id)`, `search(query)`
282
+
283
+ #### `client.inbound_invoices`
284
+ `list(query)` → `Paginated`, `get(id)`, `get_analytics(query)`, `list_history_runs(query)` → `Paginated`, `get_history_run(run_id)`, `load_older()` (deprecated)
285
+
286
+ #### `client.issued_history`
287
+ `list(query)` → `Paginated`, `get(id)`, `download_pdf(id)` → `BinaryResponse`
288
+
289
+ #### `client.invoice_settings` · `client.tax_connection`
290
+ `get()`
291
+
292
+ #### `client.organization`
293
+ `get(org_id)`, `get_readiness()`
294
+
295
+ #### `client.billing.accounts`
296
+ `get_mine(query)`, `get_stats(account_id="me", query)`, `check_balance(account_id, query)`
297
+
298
+ #### `client.billing.payments`
299
+ `list(query)`, `get(id)`
300
+
301
+ #### `client.billing.sandbox`
302
+ `list_transactions(query)`, `get_usage()`
303
+
304
+ #### `client.billing.statements`
305
+ `get(period, query)`
306
+
307
+ #### `client.billing.subscriptions`
308
+ `get_active()`, `list(query)`, `get(id)`, `list_renewals(query)`, `preview_plan_change(id, query)`
309
+
310
+ #### `client.billing.transactions`
311
+ `list(query)`, `get(id)`, `get_usage_analytics(query)`, `get_usage_by_cost_code(query)`
312
+
313
+ #### `client.webhooks.endpoints`
314
+ `list()`, `get(id)`, `test(id, params=None)`
315
+
316
+ #### `client.webhooks.deliveries`
317
+ `list(query)`, `get(id)`, `redeliver(id)`
318
+
319
+ #### `client.webhooks.events`
320
+ `list(query)`, `get(id)`, `redeliver(id, params)`
321
+
322
+ #### `client.webhooks.event_types`
323
+ `list()`
324
+
325
+ #### `useyona.einvoice.webhooks`
326
+ `verify_webhook(payload, headers, secret, *, tolerance_seconds=300, now=None)`, `sign_webhook_payload(payload, secrets, timestamp=None)`, `compute_webhook_signature(payload, secret, timestamp)`, `parse_signature_header(header)`
327
+
328
+ ## Advanced: `base_url`
329
+
330
+ `base_url` points the client at another gateway (a local one in development). It never changes
331
+ the mode: the key does. Never switch hosts by mode in your own code.
332
+
333
+ ## Development
334
+
335
+ ```bash
336
+ make install # a venv is recommended: python3 -m venv .venv && . .venv/bin/activate
337
+ make test # pytest with coverage, parity included
338
+ make lint # ruff + mypy --strict
339
+ make sync # copy the snapshot and vectors from einvoice-js and regenerate the models (needs Node 22)
340
+ make sync-check # CI: the three must match the pinned einvoice-js commit
341
+ make guides # examples/ → guides/guides.json (a test fails when it is stale)
342
+ make examples # run the examples (YONA_API_KEY; YONA_BASE_URL to point elsewhere)
343
+ make smoke-remote # verify your own sandbox key (see "Verifying your setup")
344
+ ```
345
+
346
+ The models (`useyona/einvoice/types/generated.py`), the API snapshot and the webhook test vectors come
347
+ from [`einvoice-js`](https://github.com/Elyonar/einvoice-js), the source of truth for every Yona SDK;
348
+ `tests/test_parity.py` fails when a method and the snapshot disagree.
349
+
350
+ ## License
351
+
352
+ MIT