invoance 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.
- invoance-0.1.0/.env.example +4 -0
- invoance-0.1.0/.github/workflows/ci.yml +32 -0
- invoance-0.1.0/.github/workflows/release.yml +101 -0
- invoance-0.1.0/.gitignore +32 -0
- invoance-0.1.0/CHANGELOG.md +67 -0
- invoance-0.1.0/LICENSE +21 -0
- invoance-0.1.0/PKG-INFO +200 -0
- invoance-0.1.0/README.md +160 -0
- invoance-0.1.0/examples/__init__.py +0 -0
- invoance-0.1.0/examples/ai_attestations/__init__.py +0 -0
- invoance-0.1.0/examples/ai_attestations/get_attestation.py +56 -0
- invoance-0.1.0/examples/ai_attestations/get_raw_attestation.py +36 -0
- invoance-0.1.0/examples/ai_attestations/ingest_attestation.py +48 -0
- invoance-0.1.0/examples/ai_attestations/list_attestations.py +59 -0
- invoance-0.1.0/examples/ai_attestations/verify_attestation.py +63 -0
- invoance-0.1.0/examples/ai_attestations/verify_signature.py +49 -0
- invoance-0.1.0/examples/documents/__init__.py +0 -0
- invoance-0.1.0/examples/documents/anchor_document.py +82 -0
- invoance-0.1.0/examples/documents/get_document.py +48 -0
- invoance-0.1.0/examples/documents/get_document_original.py +54 -0
- invoance-0.1.0/examples/documents/list_documents.py +54 -0
- invoance-0.1.0/examples/documents/verify_document.py +78 -0
- invoance-0.1.0/examples/events/__init__.py +0 -0
- invoance-0.1.0/examples/events/get_event.py +48 -0
- invoance-0.1.0/examples/events/ingest_event.py +37 -0
- invoance-0.1.0/examples/events/list_events.py +54 -0
- invoance-0.1.0/examples/events/verify_event.py +78 -0
- invoance-0.1.0/examples/integration.py +570 -0
- invoance-0.1.0/examples/quickstart.py +95 -0
- invoance-0.1.0/examples/traces/__init__.py +1 -0
- invoance-0.1.0/examples/traces/add_event_to_trace.py +53 -0
- invoance-0.1.0/examples/traces/create_trace.py +43 -0
- invoance-0.1.0/examples/traces/delete_trace.py +47 -0
- invoance-0.1.0/examples/traces/export_proof.py +57 -0
- invoance-0.1.0/examples/traces/export_proof_pdf.py +47 -0
- invoance-0.1.0/examples/traces/full_workflow.py +104 -0
- invoance-0.1.0/examples/traces/get_trace.py +55 -0
- invoance-0.1.0/examples/traces/list_traces.py +44 -0
- invoance-0.1.0/examples/traces/seal_trace.py +46 -0
- invoance-0.1.0/invoance/__init__.py +35 -0
- invoance-0.1.0/invoance/_internal/__init__.py +0 -0
- invoance-0.1.0/invoance/_internal/http.py +182 -0
- invoance-0.1.0/invoance/_internal/validate.py +31 -0
- invoance-0.1.0/invoance/_version.py +18 -0
- invoance-0.1.0/invoance/client.py +188 -0
- invoance-0.1.0/invoance/config.py +105 -0
- invoance-0.1.0/invoance/errors.py +167 -0
- invoance-0.1.0/invoance/models.py +457 -0
- invoance-0.1.0/invoance/py.typed +0 -0
- invoance-0.1.0/invoance/resources/__init__.py +6 -0
- invoance-0.1.0/invoance/resources/attestations.py +278 -0
- invoance-0.1.0/invoance/resources/documents.py +202 -0
- invoance-0.1.0/invoance/resources/events.py +125 -0
- invoance-0.1.0/invoance/resources/traces.py +130 -0
- invoance-0.1.0/pyproject.toml +75 -0
- invoance-0.1.0/tests/__init__.py +0 -0
- invoance-0.1.0/tests/test_smoke.py +81 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Run pytest + smoke build on every push and PR.
|
|
2
|
+
# Matrixed across the Python versions we support.
|
|
3
|
+
|
|
4
|
+
name: CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
pull_request:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
name: Python ${{ matrix.python }}
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
strategy:
|
|
19
|
+
fail-fast: false
|
|
20
|
+
matrix:
|
|
21
|
+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python }}
|
|
27
|
+
cache: pip
|
|
28
|
+
- run: python -m pip install --upgrade pip build
|
|
29
|
+
- run: pip install -e ".[dev]"
|
|
30
|
+
- run: pytest -v
|
|
31
|
+
- name: Build sdist + wheel (smoke)
|
|
32
|
+
run: python -m build --sdist --wheel
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Auto-publish to PyPI via Trusted Publishing when a tag matching `v*`
|
|
2
|
+
# is pushed. No API token required — OIDC handshake against the PyPI
|
|
3
|
+
# Trusted Publisher you configure once.
|
|
4
|
+
#
|
|
5
|
+
# To release:
|
|
6
|
+
# 1. Bump version in pyproject.toml AND invoance/_version.py
|
|
7
|
+
# 2. Update CHANGELOG.md
|
|
8
|
+
# 3. Commit, push to main
|
|
9
|
+
# 4. Tag and push:
|
|
10
|
+
# git tag v0.1.1 -m "0.1.1"
|
|
11
|
+
# git push origin v0.1.1
|
|
12
|
+
# 5. This workflow runs, publishes to PyPI, creates a GitHub Release.
|
|
13
|
+
#
|
|
14
|
+
# One-time PyPI Trusted Publisher setup:
|
|
15
|
+
# 1. Go to https://pypi.org/manage/account/publishing/
|
|
16
|
+
# 2. Add a new pending publisher:
|
|
17
|
+
# PyPI Project Name: invoance
|
|
18
|
+
# Owner: Invoance
|
|
19
|
+
# Repository name: invoance-python
|
|
20
|
+
# Workflow name: release.yml
|
|
21
|
+
# Environment name: pypi
|
|
22
|
+
# 3. Create the GitHub environment named `pypi` in the repo's
|
|
23
|
+
# Settings → Environments.
|
|
24
|
+
# 4. After the first successful publish, the project is live and
|
|
25
|
+
# future tags publish automatically.
|
|
26
|
+
|
|
27
|
+
name: Release to PyPI
|
|
28
|
+
|
|
29
|
+
on:
|
|
30
|
+
push:
|
|
31
|
+
tags:
|
|
32
|
+
- "v*"
|
|
33
|
+
|
|
34
|
+
permissions:
|
|
35
|
+
contents: write # to create the GitHub Release
|
|
36
|
+
id-token: write # required for PyPI Trusted Publishing OIDC
|
|
37
|
+
|
|
38
|
+
jobs:
|
|
39
|
+
build:
|
|
40
|
+
name: Build sdist + wheel
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v4
|
|
44
|
+
- uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.12"
|
|
47
|
+
|
|
48
|
+
- name: Verify tag matches pyproject.toml version
|
|
49
|
+
run: |
|
|
50
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
51
|
+
PKG=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
52
|
+
if [ "$TAG" != "$PKG" ]; then
|
|
53
|
+
echo "ERROR: tag $GITHUB_REF_NAME does not match pyproject.toml version $PKG" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
echo "Tag $GITHUB_REF_NAME matches pyproject.toml version $PKG"
|
|
57
|
+
|
|
58
|
+
- run: python -m pip install --upgrade pip build
|
|
59
|
+
- run: pip install -e ".[dev]"
|
|
60
|
+
- run: pytest -v
|
|
61
|
+
- run: python -m build --sdist --wheel
|
|
62
|
+
|
|
63
|
+
- uses: actions/upload-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: dist
|
|
66
|
+
path: dist/
|
|
67
|
+
|
|
68
|
+
publish:
|
|
69
|
+
name: Publish to PyPI (Trusted Publishing)
|
|
70
|
+
needs: build
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
environment:
|
|
73
|
+
name: pypi
|
|
74
|
+
url: https://pypi.org/p/invoance
|
|
75
|
+
permissions:
|
|
76
|
+
id-token: write
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/download-artifact@v4
|
|
79
|
+
with:
|
|
80
|
+
name: dist
|
|
81
|
+
path: dist/
|
|
82
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
83
|
+
|
|
84
|
+
github-release:
|
|
85
|
+
name: Create GitHub Release
|
|
86
|
+
needs: publish
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
permissions:
|
|
89
|
+
contents: write
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@v4
|
|
92
|
+
- uses: softprops/action-gh-release@v2
|
|
93
|
+
with:
|
|
94
|
+
name: ${{ github.ref_name }}
|
|
95
|
+
generate_release_notes: true
|
|
96
|
+
body: |
|
|
97
|
+
See [CHANGELOG](CHANGELOG.md) for details.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install invoance==${{ github.ref_name }}
|
|
101
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Bytecode / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
|
|
9
|
+
# Distribution
|
|
10
|
+
build/
|
|
11
|
+
dist/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
*.egg
|
|
14
|
+
|
|
15
|
+
# Virtual env
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
|
|
19
|
+
# Environment
|
|
20
|
+
.env
|
|
21
|
+
.env.local
|
|
22
|
+
.env.*.local
|
|
23
|
+
# Keep .env.example committed — it's the template
|
|
24
|
+
|
|
25
|
+
# IDE / OS
|
|
26
|
+
.vscode/
|
|
27
|
+
.idea/
|
|
28
|
+
.DS_Store
|
|
29
|
+
|
|
30
|
+
# Coverage
|
|
31
|
+
.coverage
|
|
32
|
+
htmlcov/
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the Invoance Python SDK are 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
|
+
While the SDK is pre-1.0, breaking changes are only introduced in MINOR releases
|
|
9
|
+
(0.x → 0.x+1) and always documented here. Once 1.0.0 ships, the standard SemVer
|
|
10
|
+
contract applies.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [Unreleased]
|
|
15
|
+
|
|
16
|
+
_Nothing yet._
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [0.1.0] — 2026-04-26
|
|
21
|
+
|
|
22
|
+
Initial public release.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Events** — `client.events.ingest()`, `get()`, `list()`, `verify()` for
|
|
27
|
+
signing-and-anchoring arbitrary compliance events with hex-SHA-256 payload
|
|
28
|
+
hashes.
|
|
29
|
+
- **Documents** — `client.documents.anchor()` (hash-only) and `anchor_file()`
|
|
30
|
+
(hashes + uploads in one call), plus `get()`, `list()`, `verify()`, and
|
|
31
|
+
`get_document_original()` for retrieving stored payloads.
|
|
32
|
+
- **AI attestations** — `client.attestations.ingest()`, `get()`, `list()`,
|
|
33
|
+
`verify()`, `verify_signature()` for cryptographically attesting model
|
|
34
|
+
inputs/outputs/decisions.
|
|
35
|
+
- **Traces** — full lifecycle: `create()`, `add_event()`, `seal()`,
|
|
36
|
+
`get_proof()`, `export_proof_pdf()` for grouping items into sealed bundles
|
|
37
|
+
with composite hashes.
|
|
38
|
+
- **`client.validate()`** — fast credential probe that never raises; returns
|
|
39
|
+
`ValidationResult(valid, reason, base_url)`. Use in health checks and CI
|
|
40
|
+
guards.
|
|
41
|
+
- **Typed error hierarchy** — `InvoanceError` base with `AuthenticationError`,
|
|
42
|
+
`ForbiddenError`, `NotFoundError`, `ValidationError`, `ConflictError`,
|
|
43
|
+
`QuotaExceededError`, `ServerError`, `NetworkError`, `TimeoutError`. Every
|
|
44
|
+
raised exception inherits from `InvoanceError` so consumers can catch the
|
|
45
|
+
base type.
|
|
46
|
+
- **Client-side validation** — `document_hash`, `payload_hash`, `content_hash`
|
|
47
|
+
must be valid 64-char hex SHA-256 before a request leaves the client.
|
|
48
|
+
- **Env-var configuration** — `INVOANCE_API_KEY` (required) and
|
|
49
|
+
`INVOANCE_BASE_URL` (default: `https://api.invoance.com`) auto-loaded by
|
|
50
|
+
`InvoanceClient()`. Explicit constructor args or a `ClientConfig` override.
|
|
51
|
+
- **`ClientConfig.load(...)` factory** — explicit env-var resolution for
|
|
52
|
+
callers that want to construct a `ClientConfig` programmatically with
|
|
53
|
+
fallback to environment variables.
|
|
54
|
+
- **Async-first** — built on `httpx.AsyncClient`, used as
|
|
55
|
+
`async with InvoanceClient() as client: ...`.
|
|
56
|
+
- **PEP 561 typed package** — `py.typed` marker shipped so `mypy` and
|
|
57
|
+
`pyright` pick up the bundled type hints with no extra stub install.
|
|
58
|
+
- **Examples** — full working scripts under `examples/` for events,
|
|
59
|
+
documents, attestations, and the full trace workflow.
|
|
60
|
+
|
|
61
|
+
### Notes
|
|
62
|
+
|
|
63
|
+
- Requires Python 3.9+.
|
|
64
|
+
- Runtime deps: `httpx>=0.27,<1`, `pydantic>=2.0,<3`, `PyNaCl>=1.5,<2`.
|
|
65
|
+
|
|
66
|
+
[Unreleased]: https://github.com/Invoance/invoance-python/compare/v0.1.0...HEAD
|
|
67
|
+
[0.1.0]: https://github.com/Invoance/invoance-python/releases/tag/v0.1.0
|
invoance-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Invoance, Inc.
|
|
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.
|
invoance-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: invoance
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Invoance compliance API
|
|
5
|
+
Project-URL: Homepage, https://invoance.com
|
|
6
|
+
Project-URL: Documentation, https://invoance.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/Invoance/invoance-python
|
|
8
|
+
Project-URL: Issues, https://github.com/Invoance/invoance-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Invoance/invoance-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: "Invoance, Inc." <sdk@invoance.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: attestation,audit,audit-trail,compliance,cryptographic-proof,ed25519,hipaa,invoance,soc2,tamper-evident
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Security :: Cryptography
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
|
+
Requires-Dist: httpx<1,>=0.27
|
|
30
|
+
Requires-Dist: pydantic<3,>=2.0
|
|
31
|
+
Requires-Dist: pynacl<2,>=1.5
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
37
|
+
Provides-Extra: examples
|
|
38
|
+
Requires-Dist: python-dotenv>=1.0; extra == 'examples'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# Invoance Python SDK
|
|
42
|
+
|
|
43
|
+
Official Python SDK for the [Invoance](https://invoance.com) compliance API — cryptographic proof, document anchoring, and AI attestation.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install invoance
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Requires Python 3.9+.
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
Set your API key:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export INVOANCE_API_KEY=invoance_live_...
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import asyncio
|
|
63
|
+
import hashlib
|
|
64
|
+
from invoance import InvoanceClient
|
|
65
|
+
|
|
66
|
+
async def main():
|
|
67
|
+
async with InvoanceClient() as client:
|
|
68
|
+
|
|
69
|
+
# Ingest a compliance event
|
|
70
|
+
event = await client.events.ingest(
|
|
71
|
+
event_type="policy.approval",
|
|
72
|
+
payload={"policy_id": "pol_001", "decision": "approved"},
|
|
73
|
+
)
|
|
74
|
+
print(event.event_id)
|
|
75
|
+
|
|
76
|
+
# Anchor a document by hash
|
|
77
|
+
doc_bytes = b"...your document bytes..."
|
|
78
|
+
doc = await client.documents.anchor(
|
|
79
|
+
document_hash=hashlib.sha256(doc_bytes).hexdigest(),
|
|
80
|
+
document_ref="Invoice #1042",
|
|
81
|
+
)
|
|
82
|
+
print(doc.event_id)
|
|
83
|
+
|
|
84
|
+
# Or use the file helper (hashes + uploads in one call)
|
|
85
|
+
doc = await client.documents.anchor_file(
|
|
86
|
+
file="./invoice.pdf",
|
|
87
|
+
document_ref="Invoice #1042",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Ingest an AI attestation
|
|
91
|
+
att = await client.attestations.ingest(
|
|
92
|
+
attestation_type="output",
|
|
93
|
+
input="Summarize this contract",
|
|
94
|
+
output="The contract states...",
|
|
95
|
+
model_provider="openai",
|
|
96
|
+
model_name="gpt-4o",
|
|
97
|
+
model_version="2025-01-01",
|
|
98
|
+
subject={"user_id": "u_42", "session_id": "sess_4f9a"},
|
|
99
|
+
)
|
|
100
|
+
print(att.attestation_id)
|
|
101
|
+
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Quick validation
|
|
106
|
+
|
|
107
|
+
Sanity-check that your API key works before wiring the SDK into a larger app:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
async with InvoanceClient() as client:
|
|
111
|
+
result = await client.validate()
|
|
112
|
+
if not result.valid:
|
|
113
|
+
raise RuntimeError(f"Invoance: {result.reason} (base: {result.base_url})")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`validate()` probes `GET /v1/events?limit=1`, never raises, and returns a `ValidationResult(valid, reason, base_url)` — use it in health checks, startup scripts, or CI guards.
|
|
117
|
+
|
|
118
|
+
One-liner for a terminal sanity check, no SDK install required:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
curl -sS -o /dev/null -w "%{http_code}\n" \
|
|
122
|
+
-H "Authorization: Bearer $INVOANCE_API_KEY" \
|
|
123
|
+
"${INVOANCE_BASE_URL:-https://api.invoance.com}/v1/events?limit=1"
|
|
124
|
+
# 200 = key valid · 401 = bad key · anything else = investigate
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Configuration
|
|
128
|
+
|
|
129
|
+
The client reads from environment variables automatically:
|
|
130
|
+
|
|
131
|
+
| Variable | Required | Default |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `INVOANCE_API_KEY` | Yes | — |
|
|
134
|
+
| `INVOANCE_BASE_URL` | No | `https://api.invoance.com` |
|
|
135
|
+
|
|
136
|
+
You can also pass them explicitly:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
client = InvoanceClient(
|
|
140
|
+
api_key="invoance_live_...",
|
|
141
|
+
timeout=60.0,
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`api_key`, `base_url`, and `timeout` are mutually exclusive with the `config=` argument — pass either individual overrides or a full `ClientConfig`, not both.
|
|
146
|
+
|
|
147
|
+
For env-var fallback when constructing a config manually, use the factory:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from invoance import ClientConfig
|
|
151
|
+
|
|
152
|
+
config = ClientConfig.load(timeout=60.0) # reads INVOANCE_API_KEY / INVOANCE_BASE_URL from env
|
|
153
|
+
client = InvoanceClient(config=config)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Error handling
|
|
157
|
+
|
|
158
|
+
Every error the SDK raises — API responses, network failures, client-side validation — inherits from `InvoanceError`:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from invoance import (
|
|
162
|
+
InvoanceClient,
|
|
163
|
+
InvoanceError,
|
|
164
|
+
AuthenticationError,
|
|
165
|
+
QuotaExceededError,
|
|
166
|
+
ValidationError,
|
|
167
|
+
TimeoutError,
|
|
168
|
+
NetworkError,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
await client.events.ingest(event_type="user.login", payload={...})
|
|
173
|
+
except AuthenticationError:
|
|
174
|
+
... # 401 — bad API key
|
|
175
|
+
except QuotaExceededError as e:
|
|
176
|
+
print(f"rate limited, retry in {e.retry_after_seconds}s")
|
|
177
|
+
except ValidationError:
|
|
178
|
+
... # 400 from server, or client-side input validation failure
|
|
179
|
+
except TimeoutError:
|
|
180
|
+
... # request exceeded configured timeout
|
|
181
|
+
except NetworkError:
|
|
182
|
+
... # DNS/connection/TLS failure before a response
|
|
183
|
+
except InvoanceError:
|
|
184
|
+
... # any other API or transport failure
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Common hex-SHA-256 fields (`document_hash`, `payload_hash`, `content_hash`) are validated client-side — passing a malformed hash raises `ValidationError` before a request is sent.
|
|
188
|
+
|
|
189
|
+
## Examples
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
pip install invoance[examples]
|
|
193
|
+
python examples/quickstart.py
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
See the `examples/` directory for complete working examples covering events, documents, AI attestations, and traces.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT
|
invoance-0.1.0/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Invoance Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [Invoance](https://invoance.com) compliance API — cryptographic proof, document anchoring, and AI attestation.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install invoance
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Python 3.9+.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Set your API key:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
export INVOANCE_API_KEY=invoance_live_...
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import asyncio
|
|
23
|
+
import hashlib
|
|
24
|
+
from invoance import InvoanceClient
|
|
25
|
+
|
|
26
|
+
async def main():
|
|
27
|
+
async with InvoanceClient() as client:
|
|
28
|
+
|
|
29
|
+
# Ingest a compliance event
|
|
30
|
+
event = await client.events.ingest(
|
|
31
|
+
event_type="policy.approval",
|
|
32
|
+
payload={"policy_id": "pol_001", "decision": "approved"},
|
|
33
|
+
)
|
|
34
|
+
print(event.event_id)
|
|
35
|
+
|
|
36
|
+
# Anchor a document by hash
|
|
37
|
+
doc_bytes = b"...your document bytes..."
|
|
38
|
+
doc = await client.documents.anchor(
|
|
39
|
+
document_hash=hashlib.sha256(doc_bytes).hexdigest(),
|
|
40
|
+
document_ref="Invoice #1042",
|
|
41
|
+
)
|
|
42
|
+
print(doc.event_id)
|
|
43
|
+
|
|
44
|
+
# Or use the file helper (hashes + uploads in one call)
|
|
45
|
+
doc = await client.documents.anchor_file(
|
|
46
|
+
file="./invoice.pdf",
|
|
47
|
+
document_ref="Invoice #1042",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Ingest an AI attestation
|
|
51
|
+
att = await client.attestations.ingest(
|
|
52
|
+
attestation_type="output",
|
|
53
|
+
input="Summarize this contract",
|
|
54
|
+
output="The contract states...",
|
|
55
|
+
model_provider="openai",
|
|
56
|
+
model_name="gpt-4o",
|
|
57
|
+
model_version="2025-01-01",
|
|
58
|
+
subject={"user_id": "u_42", "session_id": "sess_4f9a"},
|
|
59
|
+
)
|
|
60
|
+
print(att.attestation_id)
|
|
61
|
+
|
|
62
|
+
asyncio.run(main())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quick validation
|
|
66
|
+
|
|
67
|
+
Sanity-check that your API key works before wiring the SDK into a larger app:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
async with InvoanceClient() as client:
|
|
71
|
+
result = await client.validate()
|
|
72
|
+
if not result.valid:
|
|
73
|
+
raise RuntimeError(f"Invoance: {result.reason} (base: {result.base_url})")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`validate()` probes `GET /v1/events?limit=1`, never raises, and returns a `ValidationResult(valid, reason, base_url)` — use it in health checks, startup scripts, or CI guards.
|
|
77
|
+
|
|
78
|
+
One-liner for a terminal sanity check, no SDK install required:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
curl -sS -o /dev/null -w "%{http_code}\n" \
|
|
82
|
+
-H "Authorization: Bearer $INVOANCE_API_KEY" \
|
|
83
|
+
"${INVOANCE_BASE_URL:-https://api.invoance.com}/v1/events?limit=1"
|
|
84
|
+
# 200 = key valid · 401 = bad key · anything else = investigate
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
The client reads from environment variables automatically:
|
|
90
|
+
|
|
91
|
+
| Variable | Required | Default |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `INVOANCE_API_KEY` | Yes | — |
|
|
94
|
+
| `INVOANCE_BASE_URL` | No | `https://api.invoance.com` |
|
|
95
|
+
|
|
96
|
+
You can also pass them explicitly:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
client = InvoanceClient(
|
|
100
|
+
api_key="invoance_live_...",
|
|
101
|
+
timeout=60.0,
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`api_key`, `base_url`, and `timeout` are mutually exclusive with the `config=` argument — pass either individual overrides or a full `ClientConfig`, not both.
|
|
106
|
+
|
|
107
|
+
For env-var fallback when constructing a config manually, use the factory:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from invoance import ClientConfig
|
|
111
|
+
|
|
112
|
+
config = ClientConfig.load(timeout=60.0) # reads INVOANCE_API_KEY / INVOANCE_BASE_URL from env
|
|
113
|
+
client = InvoanceClient(config=config)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Error handling
|
|
117
|
+
|
|
118
|
+
Every error the SDK raises — API responses, network failures, client-side validation — inherits from `InvoanceError`:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from invoance import (
|
|
122
|
+
InvoanceClient,
|
|
123
|
+
InvoanceError,
|
|
124
|
+
AuthenticationError,
|
|
125
|
+
QuotaExceededError,
|
|
126
|
+
ValidationError,
|
|
127
|
+
TimeoutError,
|
|
128
|
+
NetworkError,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
await client.events.ingest(event_type="user.login", payload={...})
|
|
133
|
+
except AuthenticationError:
|
|
134
|
+
... # 401 — bad API key
|
|
135
|
+
except QuotaExceededError as e:
|
|
136
|
+
print(f"rate limited, retry in {e.retry_after_seconds}s")
|
|
137
|
+
except ValidationError:
|
|
138
|
+
... # 400 from server, or client-side input validation failure
|
|
139
|
+
except TimeoutError:
|
|
140
|
+
... # request exceeded configured timeout
|
|
141
|
+
except NetworkError:
|
|
142
|
+
... # DNS/connection/TLS failure before a response
|
|
143
|
+
except InvoanceError:
|
|
144
|
+
... # any other API or transport failure
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Common hex-SHA-256 fields (`document_hash`, `payload_hash`, `content_hash`) are validated client-side — passing a malformed hash raises `ValidationError` before a request is sent.
|
|
148
|
+
|
|
149
|
+
## Examples
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install invoance[examples]
|
|
153
|
+
python examples/quickstart.py
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
See the `examples/` directory for complete working examples covering events, documents, AI attestations, and traces.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Fetch a single AI attestation by ID and print it.
|
|
2
|
+
|
|
3
|
+
Usage
|
|
4
|
+
-----
|
|
5
|
+
python examples/ai_attestations/get_attestation.py <attestation_id>
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from dotenv import load_dotenv
|
|
12
|
+
from invoance import InvoanceClient
|
|
13
|
+
from invoance.errors import InvoanceError
|
|
14
|
+
|
|
15
|
+
load_dotenv()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def main():
|
|
19
|
+
if len(sys.argv) < 2:
|
|
20
|
+
print("Usage: python get_attestation.py <attestation_id>")
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
attestation_id = sys.argv[1]
|
|
24
|
+
|
|
25
|
+
async with InvoanceClient() as client:
|
|
26
|
+
att = await client.attestations.get(attestation_id)
|
|
27
|
+
print(f"attestation_id: {att.attestation_id}")
|
|
28
|
+
print(f"tenant_id: {att.tenant_id}")
|
|
29
|
+
print(f"type: {att.attestation_type}")
|
|
30
|
+
print(f"attestation_hash: {att.attestation_hash}")
|
|
31
|
+
print(f"input_hash: {att.input_hash}")
|
|
32
|
+
print(f"output_hash: {att.output_hash}")
|
|
33
|
+
print(f"signature: {att.signature[:40]}...")
|
|
34
|
+
print(f"public_key: {att.public_key[:40]}...")
|
|
35
|
+
print(f"signature_alg: {att.signature_alg}")
|
|
36
|
+
print(f"model_provider: {att.model_provider}")
|
|
37
|
+
print(f"model_name: {att.model_name}")
|
|
38
|
+
print(f"model_version: {att.model_version}")
|
|
39
|
+
print(f"retention_policy: {att.retention_policy}")
|
|
40
|
+
print(f"created_at: {att.created_at}")
|
|
41
|
+
|
|
42
|
+
if att.organization:
|
|
43
|
+
org = att.organization
|
|
44
|
+
print(f"\n── Issuer ──")
|
|
45
|
+
print(f" name: {org.name}")
|
|
46
|
+
print(f" issuer_name: {org.issuer_name}")
|
|
47
|
+
print(f" domain: {org.primary_domain}")
|
|
48
|
+
print(f" domain_verified: {org.domain_verified}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
try:
|
|
53
|
+
asyncio.run(main())
|
|
54
|
+
except InvoanceError as e:
|
|
55
|
+
print(f"\n✗ {type(e).__name__}: {e}")
|
|
56
|
+
sys.exit(1)
|