validpay 0.1.0__tar.gz → 1.0.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.
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to the ValidPay Python SDK will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-05-03
9
+
10
+ ### Added
11
+
12
+ - **Core client** (`ValidPayClient`) — create, verify, revoke, and reinstate
13
+ document intents via the ValidPay API.
14
+ - **AES-256-GCM encryption** — client-side encryption/decryption with
15
+ commitment hash verification (Patent B).
16
+ - **Split-key verification** (Patent C) — XOR key splitting into Share A
17
+ (document) and Share B (server). Neither alone decrypts.
18
+ - **Time-locked verification** (Patent D) — optional `valid_from` /
19
+ `valid_until` windows with client-side enforcement.
20
+ - **Selective field disclosure** (Patent E) — per-field encryption with
21
+ role-based disclosure policies.
22
+ - **Physical medium binding** (Patent F) — perceptual hashing and binding
23
+ zone comparison for document-to-physical matching.
24
+ Requires optional `binding` extra (`pip install validpay[binding]`).
25
+ - **Chain-of-custody tracking** (Patent G) — verification event audit log
26
+ via the API.
27
+ - **Blind revocation** (Patent H) — revoke/reinstate intents without
28
+ decrypting the payload.
29
+ - **Offline verification** (`OfflineCache`) — encrypted local cache for
30
+ offline verification with staleness tracking and revocation sync.
31
+ - **Batch intent creation** — create up to 100 intents in a single API call.
32
+ - **115 automated tests** across 4 test modules.
33
+
34
+ [1.0.0]: https://github.com/ValidPay-io/validpay-python-sdk/releases/tag/v1.0.0
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 MiLu Technologies LLC
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ValidPay
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,7 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ recursive-include validpay *.py
5
+ prune tests
6
+ prune check21
7
+ prune .github
@@ -0,0 +1,281 @@
1
+ Metadata-Version: 2.4
2
+ Name: validpay
3
+ Version: 1.0.0
4
+ Summary: Official ValidPay Python SDK — client-side AES-256-GCM encryption + ValidPay API client
5
+ Author-email: ValidPay <dev@validpay.io>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 ValidPay
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://validpay.io
29
+ Project-URL: Documentation, https://validpay.io/docs/api
30
+ Project-URL: Repository, https://github.com/ValidPay-io/validpay-python-sdk
31
+ Project-URL: Issues, https://github.com/ValidPay-io/validpay-python-sdk/issues
32
+ Project-URL: Changelog, https://github.com/ValidPay-io/validpay-python-sdk/blob/main/CHANGELOG.md
33
+ Keywords: validpay,encryption,aes-256-gcm,document-verification,blind-escrow
34
+ Classifier: Development Status :: 5 - Production/Stable
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Topic :: Security :: Cryptography
44
+ Requires-Python: >=3.9
45
+ Description-Content-Type: text/markdown
46
+ License-File: LICENSE
47
+ Requires-Dist: cryptography>=41.0
48
+ Requires-Dist: requests>=2.28
49
+ Provides-Extra: dev
50
+ Requires-Dist: pytest>=7.0; extra == "dev"
51
+ Provides-Extra: binding
52
+ Requires-Dist: Pillow>=10.0; extra == "binding"
53
+ Requires-Dist: numpy>=1.24; extra == "binding"
54
+ Requires-Dist: scipy>=1.10; extra == "binding"
55
+ Dynamic: license-file
56
+
57
+ # ValidPay Python SDK
58
+
59
+ Official Python SDK for the [ValidPay](https://validpay.io) document
60
+ verification API. Provides client-side AES-256-GCM encryption and a thin
61
+ client around the ValidPay HTTP API.
62
+
63
+ The encryption format is wire-compatible with the
64
+ [Node.js SDK](https://github.com/ValidPay-io/validpay-node-sdk): a payload
65
+ encrypted by the Python SDK can be decrypted by the Node SDK and vice
66
+ versa.
67
+
68
+ ## Install
69
+
70
+ ```bash
71
+ pip install validpay
72
+ ```
73
+
74
+ For physical-binding support (Patent F — image-based binding zones), install
75
+ the optional extras:
76
+
77
+ ```bash
78
+ pip install validpay[binding]
79
+ ```
80
+
81
+ Requires Python 3.9+.
82
+
83
+ ## Quick start
84
+
85
+ ```python
86
+ from validpay import ValidPayClient
87
+
88
+ client = ValidPayClient(api_key="vp_live_xxx")
89
+
90
+ # Create a single intent — the payload is encrypted locally before
91
+ # anything leaves your process. Only the ciphertext is sent to ValidPay.
92
+ result = client.create_intent(
93
+ document_type="check",
94
+ payload={"payee": "John Doe", "amount": 1500.00, "check_number": "10042"},
95
+ )
96
+ print(result.retrieval_id) # vp_abc123def456
97
+ print(result.key) # base64 AES-256 key — deliver out-of-band
98
+
99
+ # Create up to 100 intents in one round trip.
100
+ results = client.create_intent_batch([
101
+ {"document_type": "check", "payload": {"payee": "Alice", "amount": 500}},
102
+ {"document_type": "check", "payload": {"payee": "Bob", "amount": 750}},
103
+ ])
104
+ for r in results:
105
+ print(r.retrieval_id, r.key)
106
+
107
+ # Verify (retrieve + decrypt). No API key required for this endpoint.
108
+ verification = client.verify_intent(
109
+ retrieval_id="vp_abc123def456",
110
+ key=result.key,
111
+ )
112
+ print(verification.payload) # decrypted dict
113
+ print(verification.issuer) # "Acme Corp"
114
+ print(verification.issuer_verified) # True
115
+ print(verification.status) # "active"
116
+ ```
117
+
118
+ ### Time-Locked Verification (Patent D)
119
+
120
+ Restrict when a document can be verified by specifying a validity window:
121
+
122
+ ```python
123
+ from datetime import datetime, timezone, timedelta
124
+
125
+ result = client.create_intent(
126
+ document_type="check",
127
+ payload={"payee": "Jane Doe", "amount": 1500.00},
128
+ valid_from=(datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
129
+ valid_until=(datetime.now(timezone.utc) + timedelta(days=30)).isoformat(),
130
+ )
131
+
132
+ # Later, when verifying:
133
+ verified = client.verify_intent(result.retrieval_id, result.key)
134
+ print(verified.time_lock_status) # "valid", "not_yet_valid", or "expired"
135
+ print(verified.valid_from) # ISO-8601 timestamp or None
136
+ print(verified.valid_until) # ISO-8601 timestamp or None
137
+ ```
138
+
139
+ Time-lock status is informational — the SDK always returns the decrypted
140
+ payload regardless of the time window. Your application decides how to
141
+ handle `not_yet_valid` or `expired` results. The server stores the
142
+ timestamps but never enforces them; this preserves the blind intermediary
143
+ model (the server never decides whether a document is "still good").
144
+
145
+ The same `valid_from` / `valid_until` keyword arguments are accepted by
146
+ `create_intent_batch` (per-item), `create_split_key_intent`, and
147
+ `create_selective_intent`.
148
+
149
+ ### Split-key intents (Patent C)
150
+
151
+ Splits the AES key into two XOR shares: Share A is returned to the caller
152
+ (typically embedded in the QR code), Share B is stored server-side. Neither
153
+ share alone can decrypt the payload.
154
+
155
+ ```python
156
+ result = client.create_split_key_intent(
157
+ document_type="ssn_card",
158
+ payload={"ssn": "123-45-6789"},
159
+ )
160
+ # result.key is Share A — pair it with Share B at verification time.
161
+
162
+ verified = client.verify_split_key_intent(result.retrieval_id, result.key)
163
+ print(verified.payload)
164
+ ```
165
+
166
+ ### Selective disclosure (Patent E)
167
+
168
+ Each field is encrypted with its own per-field key. A disclosure policy maps
169
+ role names to the fields that role may decrypt. A `full` role with access to
170
+ every field is added automatically.
171
+
172
+ ```python
173
+ result = client.create_selective_intent(
174
+ document_type="check",
175
+ payload={"payee": "Alice", "amount": 1500.00, "memo": "rent"},
176
+ disclosure_policy={"bank": ["amount"], "auditor": ["amount", "payee"]},
177
+ )
178
+
179
+ # Bank sees only 'amount'; other fields come back as REDACTED markers.
180
+ verified = client.verify_selective_intent(result.retrieval_id, result.key, role="bank")
181
+ print(verified.payload)
182
+ ```
183
+
184
+ `create_selective_intent` accepts `split_key=True` to combine Patents C + E.
185
+
186
+ ### Revocation (Patent H — Blind Revocation)
187
+
188
+ Issuers can revoke or reinstate an intent without decrypting it. Verifiers
189
+ of a revoked intent receive `status="revoked"` and no encrypted payload.
190
+
191
+ ```python
192
+ client.revoke_intent("vp_abc123def456", reason="Stop payment")
193
+ client.reinstate_intent("vp_abc123def456", reason="False alarm")
194
+
195
+ history = client.get_revocation_history("vp_abc123def456")
196
+ for event in history:
197
+ print(event["action"], event["reason"], event["performed_at"])
198
+ ```
199
+
200
+ ### Offline verification (Patent G)
201
+
202
+ `OfflineCache` lets verifiers cache intents locally and verify them without
203
+ network access. Cached entries are encrypted at rest with a caller-supplied
204
+ key.
205
+
206
+ ```python
207
+ from validpay.offline import OfflineCache
208
+
209
+ cache = OfflineCache("./offline.db", cache_key="optional-aes-key-base64")
210
+ cache.store(retrieval_id="vp_abc123def456", key=result.key,
211
+ encrypted_payload=..., issuer="Acme Bank")
212
+
213
+ verified = cache.verify_offline("vp_abc123def456", result.key)
214
+ print(verified.payload, verified.time_lock_status)
215
+ ```
216
+
217
+ `OfflineCache.list_entries()`, `mark_revoked()`, `update_online_check()`, and
218
+ `get_stale_entries()` round out the cache lifecycle for verifier devices that
219
+ periodically reconcile with the live API.
220
+
221
+ ## API
222
+
223
+ ### `ValidPayClient(api_key, *, base_url=..., timeout=30.0, session=None)`
224
+
225
+ - `api_key` — your ValidPay API key (required for create endpoints).
226
+ - `base_url` — defaults to `https://api.validpay.io`.
227
+ - `timeout` — per-request timeout in seconds.
228
+ - `session` — optionally provide a `requests.Session` for connection
229
+ pooling, custom adapters, or mocking in tests.
230
+
231
+ ### `client.create_intent(document_type, payload) -> CreateIntentResult`
232
+
233
+ Encrypts `payload` (any JSON-serializable value) under a freshly
234
+ generated AES-256 key and registers it with ValidPay. Returns the
235
+ retrieval id and the key. **The key is never sent to ValidPay** — you
236
+ must hand it off out-of-band to whoever needs to verify the intent.
237
+
238
+ ### `client.create_intent_batch(intents) -> list[CreateIntentResult]`
239
+
240
+ Bulk version. `intents` is an iterable of mappings shaped
241
+ `{"document_type": str, "payload": Any}`, with 1–100 items. Each intent
242
+ gets its own unique key. Result order matches input order.
243
+
244
+ ### `client.verify_intent(retrieval_id, key) -> VerifyIntentResult`
245
+
246
+ Fetches the intent (public endpoint, no API key required), decrypts the
247
+ payload locally, and returns issuer metadata + the decrypted payload.
248
+
249
+ ### Errors
250
+
251
+ All SDK and API errors are raised as `ValidPayError`, which exposes:
252
+
253
+ - `code` — machine-readable code (e.g. `"unauthorized"`, `"not_found"`,
254
+ `"decryption_failed"`, `"invalid_key"`, `"network_error"`).
255
+ - `status` — HTTP status when the error came from the API.
256
+ - `details` — raw error body / extra context when available.
257
+
258
+ ### Low-level crypto
259
+
260
+ For advanced use cases the encryption primitives are exported directly:
261
+
262
+ ```python
263
+ from validpay import generate_key, encrypt, decrypt
264
+
265
+ key = generate_key()
266
+ blob = encrypt('{"hello": "world"}', key)
267
+ assert decrypt(blob, key) == '{"hello": "world"}'
268
+ ```
269
+
270
+ Wire format: `base64(iv[12] || authTag[16] || ciphertext)`.
271
+
272
+ ## Development
273
+
274
+ ```bash
275
+ pip install -e ".[dev]"
276
+ pytest
277
+ ```
278
+
279
+ ## License
280
+
281
+ MIT — see `LICENSE`.
@@ -0,0 +1,225 @@
1
+ # ValidPay Python SDK
2
+
3
+ Official Python SDK for the [ValidPay](https://validpay.io) document
4
+ verification API. Provides client-side AES-256-GCM encryption and a thin
5
+ client around the ValidPay HTTP API.
6
+
7
+ The encryption format is wire-compatible with the
8
+ [Node.js SDK](https://github.com/ValidPay-io/validpay-node-sdk): a payload
9
+ encrypted by the Python SDK can be decrypted by the Node SDK and vice
10
+ versa.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pip install validpay
16
+ ```
17
+
18
+ For physical-binding support (Patent F — image-based binding zones), install
19
+ the optional extras:
20
+
21
+ ```bash
22
+ pip install validpay[binding]
23
+ ```
24
+
25
+ Requires Python 3.9+.
26
+
27
+ ## Quick start
28
+
29
+ ```python
30
+ from validpay import ValidPayClient
31
+
32
+ client = ValidPayClient(api_key="vp_live_xxx")
33
+
34
+ # Create a single intent — the payload is encrypted locally before
35
+ # anything leaves your process. Only the ciphertext is sent to ValidPay.
36
+ result = client.create_intent(
37
+ document_type="check",
38
+ payload={"payee": "John Doe", "amount": 1500.00, "check_number": "10042"},
39
+ )
40
+ print(result.retrieval_id) # vp_abc123def456
41
+ print(result.key) # base64 AES-256 key — deliver out-of-band
42
+
43
+ # Create up to 100 intents in one round trip.
44
+ results = client.create_intent_batch([
45
+ {"document_type": "check", "payload": {"payee": "Alice", "amount": 500}},
46
+ {"document_type": "check", "payload": {"payee": "Bob", "amount": 750}},
47
+ ])
48
+ for r in results:
49
+ print(r.retrieval_id, r.key)
50
+
51
+ # Verify (retrieve + decrypt). No API key required for this endpoint.
52
+ verification = client.verify_intent(
53
+ retrieval_id="vp_abc123def456",
54
+ key=result.key,
55
+ )
56
+ print(verification.payload) # decrypted dict
57
+ print(verification.issuer) # "Acme Corp"
58
+ print(verification.issuer_verified) # True
59
+ print(verification.status) # "active"
60
+ ```
61
+
62
+ ### Time-Locked Verification (Patent D)
63
+
64
+ Restrict when a document can be verified by specifying a validity window:
65
+
66
+ ```python
67
+ from datetime import datetime, timezone, timedelta
68
+
69
+ result = client.create_intent(
70
+ document_type="check",
71
+ payload={"payee": "Jane Doe", "amount": 1500.00},
72
+ valid_from=(datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
73
+ valid_until=(datetime.now(timezone.utc) + timedelta(days=30)).isoformat(),
74
+ )
75
+
76
+ # Later, when verifying:
77
+ verified = client.verify_intent(result.retrieval_id, result.key)
78
+ print(verified.time_lock_status) # "valid", "not_yet_valid", or "expired"
79
+ print(verified.valid_from) # ISO-8601 timestamp or None
80
+ print(verified.valid_until) # ISO-8601 timestamp or None
81
+ ```
82
+
83
+ Time-lock status is informational — the SDK always returns the decrypted
84
+ payload regardless of the time window. Your application decides how to
85
+ handle `not_yet_valid` or `expired` results. The server stores the
86
+ timestamps but never enforces them; this preserves the blind intermediary
87
+ model (the server never decides whether a document is "still good").
88
+
89
+ The same `valid_from` / `valid_until` keyword arguments are accepted by
90
+ `create_intent_batch` (per-item), `create_split_key_intent`, and
91
+ `create_selective_intent`.
92
+
93
+ ### Split-key intents (Patent C)
94
+
95
+ Splits the AES key into two XOR shares: Share A is returned to the caller
96
+ (typically embedded in the QR code), Share B is stored server-side. Neither
97
+ share alone can decrypt the payload.
98
+
99
+ ```python
100
+ result = client.create_split_key_intent(
101
+ document_type="ssn_card",
102
+ payload={"ssn": "123-45-6789"},
103
+ )
104
+ # result.key is Share A — pair it with Share B at verification time.
105
+
106
+ verified = client.verify_split_key_intent(result.retrieval_id, result.key)
107
+ print(verified.payload)
108
+ ```
109
+
110
+ ### Selective disclosure (Patent E)
111
+
112
+ Each field is encrypted with its own per-field key. A disclosure policy maps
113
+ role names to the fields that role may decrypt. A `full` role with access to
114
+ every field is added automatically.
115
+
116
+ ```python
117
+ result = client.create_selective_intent(
118
+ document_type="check",
119
+ payload={"payee": "Alice", "amount": 1500.00, "memo": "rent"},
120
+ disclosure_policy={"bank": ["amount"], "auditor": ["amount", "payee"]},
121
+ )
122
+
123
+ # Bank sees only 'amount'; other fields come back as REDACTED markers.
124
+ verified = client.verify_selective_intent(result.retrieval_id, result.key, role="bank")
125
+ print(verified.payload)
126
+ ```
127
+
128
+ `create_selective_intent` accepts `split_key=True` to combine Patents C + E.
129
+
130
+ ### Revocation (Patent H — Blind Revocation)
131
+
132
+ Issuers can revoke or reinstate an intent without decrypting it. Verifiers
133
+ of a revoked intent receive `status="revoked"` and no encrypted payload.
134
+
135
+ ```python
136
+ client.revoke_intent("vp_abc123def456", reason="Stop payment")
137
+ client.reinstate_intent("vp_abc123def456", reason="False alarm")
138
+
139
+ history = client.get_revocation_history("vp_abc123def456")
140
+ for event in history:
141
+ print(event["action"], event["reason"], event["performed_at"])
142
+ ```
143
+
144
+ ### Offline verification (Patent G)
145
+
146
+ `OfflineCache` lets verifiers cache intents locally and verify them without
147
+ network access. Cached entries are encrypted at rest with a caller-supplied
148
+ key.
149
+
150
+ ```python
151
+ from validpay.offline import OfflineCache
152
+
153
+ cache = OfflineCache("./offline.db", cache_key="optional-aes-key-base64")
154
+ cache.store(retrieval_id="vp_abc123def456", key=result.key,
155
+ encrypted_payload=..., issuer="Acme Bank")
156
+
157
+ verified = cache.verify_offline("vp_abc123def456", result.key)
158
+ print(verified.payload, verified.time_lock_status)
159
+ ```
160
+
161
+ `OfflineCache.list_entries()`, `mark_revoked()`, `update_online_check()`, and
162
+ `get_stale_entries()` round out the cache lifecycle for verifier devices that
163
+ periodically reconcile with the live API.
164
+
165
+ ## API
166
+
167
+ ### `ValidPayClient(api_key, *, base_url=..., timeout=30.0, session=None)`
168
+
169
+ - `api_key` — your ValidPay API key (required for create endpoints).
170
+ - `base_url` — defaults to `https://api.validpay.io`.
171
+ - `timeout` — per-request timeout in seconds.
172
+ - `session` — optionally provide a `requests.Session` for connection
173
+ pooling, custom adapters, or mocking in tests.
174
+
175
+ ### `client.create_intent(document_type, payload) -> CreateIntentResult`
176
+
177
+ Encrypts `payload` (any JSON-serializable value) under a freshly
178
+ generated AES-256 key and registers it with ValidPay. Returns the
179
+ retrieval id and the key. **The key is never sent to ValidPay** — you
180
+ must hand it off out-of-band to whoever needs to verify the intent.
181
+
182
+ ### `client.create_intent_batch(intents) -> list[CreateIntentResult]`
183
+
184
+ Bulk version. `intents` is an iterable of mappings shaped
185
+ `{"document_type": str, "payload": Any}`, with 1–100 items. Each intent
186
+ gets its own unique key. Result order matches input order.
187
+
188
+ ### `client.verify_intent(retrieval_id, key) -> VerifyIntentResult`
189
+
190
+ Fetches the intent (public endpoint, no API key required), decrypts the
191
+ payload locally, and returns issuer metadata + the decrypted payload.
192
+
193
+ ### Errors
194
+
195
+ All SDK and API errors are raised as `ValidPayError`, which exposes:
196
+
197
+ - `code` — machine-readable code (e.g. `"unauthorized"`, `"not_found"`,
198
+ `"decryption_failed"`, `"invalid_key"`, `"network_error"`).
199
+ - `status` — HTTP status when the error came from the API.
200
+ - `details` — raw error body / extra context when available.
201
+
202
+ ### Low-level crypto
203
+
204
+ For advanced use cases the encryption primitives are exported directly:
205
+
206
+ ```python
207
+ from validpay import generate_key, encrypt, decrypt
208
+
209
+ key = generate_key()
210
+ blob = encrypt('{"hello": "world"}', key)
211
+ assert decrypt(blob, key) == '{"hello": "world"}'
212
+ ```
213
+
214
+ Wire format: `base64(iv[12] || authTag[16] || ciphertext)`.
215
+
216
+ ## Development
217
+
218
+ ```bash
219
+ pip install -e ".[dev]"
220
+ pytest
221
+ ```
222
+
223
+ ## License
224
+
225
+ MIT — see `LICENSE`.
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "validpay"
7
+ version = "1.0.0"
8
+ description = "Official ValidPay Python SDK — client-side AES-256-GCM encryption + ValidPay API client"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [{ name = "ValidPay", email = "dev@validpay.io" }]
12
+ requires-python = ">=3.9"
13
+ keywords = [
14
+ "validpay",
15
+ "encryption",
16
+ "aes-256-gcm",
17
+ "document-verification",
18
+ "blind-escrow",
19
+ ]
20
+ classifiers = [
21
+ "Development Status :: 5 - Production/Stable",
22
+ "Intended Audience :: Developers",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.9",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Topic :: Security :: Cryptography",
31
+ ]
32
+ dependencies = [
33
+ "cryptography>=41.0",
34
+ "requests>=2.28",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=7.0",
40
+ ]
41
+ binding = ["Pillow>=10.0", "numpy>=1.24", "scipy>=1.10"]
42
+
43
+ [project.urls]
44
+ Homepage = "https://validpay.io"
45
+ Documentation = "https://validpay.io/docs/api"
46
+ Repository = "https://github.com/ValidPay-io/validpay-python-sdk"
47
+ Issues = "https://github.com/ValidPay-io/validpay-python-sdk/issues"
48
+ Changelog = "https://github.com/ValidPay-io/validpay-python-sdk/blob/main/CHANGELOG.md"
49
+
50
+ [tool.setuptools.packages.find]
51
+ include = ["validpay*"]
52
+ exclude = ["tests*", "check21*"]
53
+
54
+ [tool.setuptools.package-data]
55
+ validpay = ["py.typed"]
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+