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.
Files changed (57) hide show
  1. invoance-0.1.0/.env.example +4 -0
  2. invoance-0.1.0/.github/workflows/ci.yml +32 -0
  3. invoance-0.1.0/.github/workflows/release.yml +101 -0
  4. invoance-0.1.0/.gitignore +32 -0
  5. invoance-0.1.0/CHANGELOG.md +67 -0
  6. invoance-0.1.0/LICENSE +21 -0
  7. invoance-0.1.0/PKG-INFO +200 -0
  8. invoance-0.1.0/README.md +160 -0
  9. invoance-0.1.0/examples/__init__.py +0 -0
  10. invoance-0.1.0/examples/ai_attestations/__init__.py +0 -0
  11. invoance-0.1.0/examples/ai_attestations/get_attestation.py +56 -0
  12. invoance-0.1.0/examples/ai_attestations/get_raw_attestation.py +36 -0
  13. invoance-0.1.0/examples/ai_attestations/ingest_attestation.py +48 -0
  14. invoance-0.1.0/examples/ai_attestations/list_attestations.py +59 -0
  15. invoance-0.1.0/examples/ai_attestations/verify_attestation.py +63 -0
  16. invoance-0.1.0/examples/ai_attestations/verify_signature.py +49 -0
  17. invoance-0.1.0/examples/documents/__init__.py +0 -0
  18. invoance-0.1.0/examples/documents/anchor_document.py +82 -0
  19. invoance-0.1.0/examples/documents/get_document.py +48 -0
  20. invoance-0.1.0/examples/documents/get_document_original.py +54 -0
  21. invoance-0.1.0/examples/documents/list_documents.py +54 -0
  22. invoance-0.1.0/examples/documents/verify_document.py +78 -0
  23. invoance-0.1.0/examples/events/__init__.py +0 -0
  24. invoance-0.1.0/examples/events/get_event.py +48 -0
  25. invoance-0.1.0/examples/events/ingest_event.py +37 -0
  26. invoance-0.1.0/examples/events/list_events.py +54 -0
  27. invoance-0.1.0/examples/events/verify_event.py +78 -0
  28. invoance-0.1.0/examples/integration.py +570 -0
  29. invoance-0.1.0/examples/quickstart.py +95 -0
  30. invoance-0.1.0/examples/traces/__init__.py +1 -0
  31. invoance-0.1.0/examples/traces/add_event_to_trace.py +53 -0
  32. invoance-0.1.0/examples/traces/create_trace.py +43 -0
  33. invoance-0.1.0/examples/traces/delete_trace.py +47 -0
  34. invoance-0.1.0/examples/traces/export_proof.py +57 -0
  35. invoance-0.1.0/examples/traces/export_proof_pdf.py +47 -0
  36. invoance-0.1.0/examples/traces/full_workflow.py +104 -0
  37. invoance-0.1.0/examples/traces/get_trace.py +55 -0
  38. invoance-0.1.0/examples/traces/list_traces.py +44 -0
  39. invoance-0.1.0/examples/traces/seal_trace.py +46 -0
  40. invoance-0.1.0/invoance/__init__.py +35 -0
  41. invoance-0.1.0/invoance/_internal/__init__.py +0 -0
  42. invoance-0.1.0/invoance/_internal/http.py +182 -0
  43. invoance-0.1.0/invoance/_internal/validate.py +31 -0
  44. invoance-0.1.0/invoance/_version.py +18 -0
  45. invoance-0.1.0/invoance/client.py +188 -0
  46. invoance-0.1.0/invoance/config.py +105 -0
  47. invoance-0.1.0/invoance/errors.py +167 -0
  48. invoance-0.1.0/invoance/models.py +457 -0
  49. invoance-0.1.0/invoance/py.typed +0 -0
  50. invoance-0.1.0/invoance/resources/__init__.py +6 -0
  51. invoance-0.1.0/invoance/resources/attestations.py +278 -0
  52. invoance-0.1.0/invoance/resources/documents.py +202 -0
  53. invoance-0.1.0/invoance/resources/events.py +125 -0
  54. invoance-0.1.0/invoance/resources/traces.py +130 -0
  55. invoance-0.1.0/pyproject.toml +75 -0
  56. invoance-0.1.0/tests/__init__.py +0 -0
  57. invoance-0.1.0/tests/test_smoke.py +81 -0
@@ -0,0 +1,4 @@
1
+ # Copy to .env and fill in your real values. Never commit .env.
2
+ # Get an API key from https://app.invoance.com/dashboard/api-keys
3
+
4
+ INVOANCE_API_KEY=invoance_live_REPLACE_WITH_YOUR_KEY
@@ -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.
@@ -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
@@ -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)