Official ValidPay Python SDK — client-side AES-256-GCM encryption + ValidPay API client
Project description
ValidPay Python SDK
Official Python SDK for the ValidPay document verification API. Provides client-side AES-256-GCM encryption and a thin client around the ValidPay HTTP API.
The encryption format is wire-compatible with the Node.js SDK: a payload encrypted by the Python SDK can be decrypted by the Node SDK and vice versa.
Install
pip install validpay
For physical-binding support (Patent F — image-based binding zones), install the optional extras:
pip install validpay[binding]
Requires Python 3.9+.
Quick start
from validpay import ValidPayClient
client = ValidPayClient(api_key="vp_live_xxx")
# Create a single intent — the payload is encrypted locally before
# anything leaves your process. Only the ciphertext is sent to ValidPay.
result = client.create_intent(
document_type="check",
payload={"payee": "John Doe", "amount": 1500.00, "check_number": "10042"},
)
print(result.retrieval_id) # vp_abc123def456
print(result.key) # base64 AES-256 key — deliver out-of-band
# Create up to 100 intents in one round trip.
results = client.create_intent_batch([
{"document_type": "check", "payload": {"payee": "Alice", "amount": 500}},
{"document_type": "check", "payload": {"payee": "Bob", "amount": 750}},
])
for r in results:
print(r.retrieval_id, r.key)
# Verify (retrieve + decrypt). No API key required for this endpoint.
verification = client.verify_intent(
retrieval_id="vp_abc123def456",
key=result.key,
)
print(verification.payload) # decrypted dict
print(verification.issuer) # "Acme Corp"
print(verification.issuer_verified) # True
print(verification.status) # "active"
Time-Locked Verification (Patent D)
Restrict when a document can be verified by specifying a validity window:
from datetime import datetime, timezone, timedelta
result = client.create_intent(
document_type="check",
payload={"payee": "Jane Doe", "amount": 1500.00},
valid_from=(datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
valid_until=(datetime.now(timezone.utc) + timedelta(days=30)).isoformat(),
)
# Later, when verifying:
verified = client.verify_intent(result.retrieval_id, result.key)
print(verified.time_lock_status) # "valid", "not_yet_valid", or "expired"
print(verified.valid_from) # ISO-8601 timestamp or None
print(verified.valid_until) # ISO-8601 timestamp or None
Time-lock status is informational — the SDK always returns the decrypted
payload regardless of the time window. Your application decides how to
handle not_yet_valid or expired results. The server stores the
timestamps but never enforces them; this preserves the blind intermediary
model (the server never decides whether a document is "still good").
The same valid_from / valid_until keyword arguments are accepted by
create_intent_batch (per-item), create_split_key_intent, and
create_selective_intent.
Split-key intents (Patent C)
Splits the AES key into two XOR shares: Share A is returned to the caller (typically embedded in the QR code), Share B is stored server-side. Neither share alone can decrypt the payload.
result = client.create_split_key_intent(
document_type="ssn_card",
payload={"ssn": "123-45-6789"},
)
# result.key is Share A — pair it with Share B at verification time.
verified = client.verify_split_key_intent(result.retrieval_id, result.key)
print(verified.payload)
Selective disclosure (Patent E)
Each field is encrypted with its own per-field key. A disclosure policy maps
role names to the fields that role may decrypt. A full role with access to
every field is added automatically.
result = client.create_selective_intent(
document_type="check",
payload={"payee": "Alice", "amount": 1500.00, "memo": "rent"},
disclosure_policy={"bank": ["amount"], "auditor": ["amount", "payee"]},
)
# Bank sees only 'amount'; other fields come back as REDACTED markers.
verified = client.verify_selective_intent(result.retrieval_id, result.key, role="bank")
print(verified.payload)
create_selective_intent accepts split_key=True to combine Patents C + E.
Revocation (Patent H — Blind Revocation)
Issuers can revoke or reinstate an intent without decrypting it. Verifiers
of a revoked intent receive status="revoked" and no encrypted payload.
client.revoke_intent("vp_abc123def456", reason="Stop payment")
client.reinstate_intent("vp_abc123def456", reason="False alarm")
history = client.get_revocation_history("vp_abc123def456")
for event in history:
print(event["action"], event["reason"], event["performed_at"])
Offline verification (Patent G)
OfflineCache lets verifiers cache intents locally and verify them without
network access. Cached entries are encrypted at rest with a caller-supplied
key.
from validpay.offline import OfflineCache
cache = OfflineCache("./offline.db", cache_key="optional-aes-key-base64")
cache.store(retrieval_id="vp_abc123def456", key=result.key,
encrypted_payload=..., issuer="Acme Bank")
verified = cache.verify_offline("vp_abc123def456", result.key)
print(verified.payload, verified.time_lock_status)
OfflineCache.list_entries(), mark_revoked(), update_online_check(), and
get_stale_entries() round out the cache lifecycle for verifier devices that
periodically reconcile with the live API.
API
ValidPayClient(api_key, *, base_url=..., timeout=30.0, session=None)
api_key— your ValidPay API key (required for create endpoints).base_url— defaults tohttps://api.validpay.io.timeout— per-request timeout in seconds.session— optionally provide arequests.Sessionfor connection pooling, custom adapters, or mocking in tests.
client.create_intent(document_type, payload) -> CreateIntentResult
Encrypts payload (any JSON-serializable value) under a freshly
generated AES-256 key and registers it with ValidPay. Returns the
retrieval id and the key. The key is never sent to ValidPay — you
must hand it off out-of-band to whoever needs to verify the intent.
client.create_intent_batch(intents) -> list[CreateIntentResult]
Bulk version. intents is an iterable of mappings shaped
{"document_type": str, "payload": Any}, with 1–100 items. Each intent
gets its own unique key. Result order matches input order.
client.verify_intent(retrieval_id, key) -> VerifyIntentResult
Fetches the intent (public endpoint, no API key required), decrypts the payload locally, and returns issuer metadata + the decrypted payload.
Errors
All SDK and API errors are raised as ValidPayError, which exposes:
code— machine-readable code (e.g."unauthorized","not_found","decryption_failed","invalid_key","network_error").status— HTTP status when the error came from the API.details— raw error body / extra context when available.
Low-level crypto
For advanced use cases the encryption primitives are exported directly:
from validpay import generate_key, encrypt, decrypt
key = generate_key()
blob = encrypt('{"hello": "world"}', key)
assert decrypt(blob, key) == '{"hello": "world"}'
Wire format: base64(iv[12] || authTag[16] || ciphertext).
Development
pip install -e ".[dev]"
pytest
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file validpay-1.0.0.tar.gz.
File metadata
- Download URL: validpay-1.0.0.tar.gz
- Upload date:
- Size: 25.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0a3aa91dbad7a9a8011a7acec94fd7982ef79e42d805ab904cb4330b88619f1
|
|
| MD5 |
ab0e25ac7f02f64206bf69ea29d2b391
|
|
| BLAKE2b-256 |
f4961e49169dc28360b97550ba3c72b55cbc812cd79683fcfc40159a6599df5a
|
File details
Details for the file validpay-1.0.0-py3-none-any.whl.
File metadata
- Download URL: validpay-1.0.0-py3-none-any.whl
- Upload date:
- Size: 24.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1832909fe629f105a678705c501f7575bfb68781d53871f85ff8b2fafa635b0
|
|
| MD5 |
62fbf2b91469001dbfc3302e4646ed41
|
|
| BLAKE2b-256 |
19879b8d4e1e471dde57f72ffcf8e6ab2691b8e066a60c740081320c92536943
|