Docs · SDKs
Python SDK
Install the Python SDK, build a client, see every method with a Python sample, and verify records offline.
Install
Python
invoance on PyPI.
Python 3.9 or later. Every method is a coroutine; PyNaCl ships with the package for Ed25519.
Terminal
pip install invoance
Client
api_key | The API key. Falls back to INVOANCE_API_KEY; ValueError when neither is set. |
|---|---|
base_url | API host. Falls back to INVOANCE_BASE_URL, then https://api.invoance.com. |
api_version | Path prefix put before every request path. Default v1. |
| timeout | Per-request timeout in seconds. Default 30.0, must be positive; past it the call raises TimeoutError. |
idempotency_key | Default Idempotency-Key header for every mutating request; a per-call key wins. |
extra_headers | Headers merged into every request. |
| config | A full ClientConfig instead of the options above; ClientConfig.load() reads the environment. Not combinable with api_key, base_url or timeout. |
| Retries | None. Each request is sent once; on TimeoutError or NetworkError, retry it yourself with the same idempotency_key. |
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY and INVOANCE_BASE_URL from the environment.
async with InvoanceClient() as client:
# GET /v1/me checks no scope, so any live key passes. Never raises.
result = await client.validate()
print(result.valid, result.reason)
# Or pass options.
async with InvoanceClient(
api_key="invoance_live_...",
base_url="https://api.invoance.com",
timeout=60.0,
) as configured:
print(configured.base_url)
asyncio.run(main())
Methods
Every endpoint with a Python sample, by resource. Open a row for the sample; the link opens the endpoint card with its fields, response and errors.
EventsReference
POST/v1/events
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.events.ingest(
event_type="policy.approval",
event_time="2026-09-22T08:14:07Z",
payload={
"policy_id": "pol_8472",
"approved_by": "risk_committee",
"decision": "approved",
},
idempotency_key="policy-approval-pol_8472",
)
print(result.event_id, result.ingested_at)
asyncio.run(main())
GET/v1/events
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
page = await client.events.list(
page=1,
limit=50,
event_type="policy.approval",
)
print(page.total, page.has_more)
for event in page.events:
print(event.event_id, event.ingested_at, event.payload_hash)
asyncio.run(main())
GET/v1/events/{event_id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
event = await client.events.get("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
print(event.event_type, event.ingested_at)
print(event.payload_hash, event.event_hash, event.request_hash)
print(event.payload)
asyncio.run(main())
POST/v1/events/{event_id}/verify
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.events.verify(
"7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4",
payload={
"policy_id": "pol_8472",
"approved_by": "risk_committee",
"decision": "approved",
},
)
print(result.match_result, result.matched_field)
print(result.anchored_hash, result.submitted_hash, result.anchored_at)
asyncio.run(main())
DocumentsReference
POST/v1/document/anchor
Python
import asyncio
import hashlib
from invoance import InvoanceClient
async def main():
with open("./INV-2026-0917.pdf", "rb") as f:
document_hash = hashlib.sha256(f.read()).hexdigest()
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.documents.anchor(
document_hash=document_hash,
document_ref="INV-2026-0917.pdf",
event_type="invoice.issued",
metadata={"invoice_number": "INV-2026-0917", "amount": 5230, "currency": "USD"},
idempotency_key="anchor-" + document_hash,
)
print(result.event_id, result.status)
asyncio.run(main())
GET/v1/document
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
page = await client.documents.list(
limit=25,
date_from="2026-09-01T00:00:00Z",
)
print(page.total, page.has_more)
for d in page.documents:
print(d.event_id, d.document_ref, d.has_original)
asyncio.run(main())
GET/v1/document/{event_id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
doc = await client.documents.get("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
print(doc.document_hash, doc.has_original, doc.created_at)
if doc.organization:
print(doc.organization.issuer_name, doc.organization.domain_verified)
asyncio.run(main())
GET/v1/document/{event_id}/original
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
data = await client.documents.get_original("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
with open("./INV-2026-0917.pdf", "wb") as f:
f.write(data)
print(len(data))
asyncio.run(main())
POST/v1/document/{event_id}/verify
Python
import asyncio
import hashlib
from invoance import InvoanceClient
async def main():
with open("./INV-2026-0917.pdf", "rb") as f:
document_hash = hashlib.sha256(f.read()).hexdigest()
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.documents.verify(
"7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4",
document_hash=document_hash,
)
print(result.match_result, result.anchored_hash, result.anchored_at)
asyncio.run(main())
AI AttestationsReference
POST/v1/ai/attestations
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.attestations.ingest(
attestation_type="output",
input="Summarize the termination clause in contract CT-8472.",
output="Either party may terminate with 30 days written notice. Early termination fees do not apply after month 12.",
model_provider="openai",
model_name="gpt-4.1",
model_version="2026-04-14",
subject={"user_id": "u_4821", "session_id": "sess_9f3a", "department": "legal"},
idempotency_key="ct-8472-summary-1",
)
print(result.attestation_id, result.payload_hash)
asyncio.run(main())
GET/v1/ai/attestations
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
page = await client.attestations.list(
limit=50,
attestation_type="output",
model_provider="openai",
)
print(page.total, page.has_more, len(page.attestations))
asyncio.run(main())
GET/v1/ai/attestations/{attestation_id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
att = await client.attestations.get("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
print(att.attestation_hash, att.signature_alg, att.public_key)
asyncio.run(main())
GET/v1/ai/attestations/{attestation_id}/raw
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
raw = await client.attestations.get_raw("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
print(raw["type"], raw["context"]["model_name"])
asyncio.run(main())
POST/v1/ai/attestations/{attestation_id}/verify
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.attestations.verify(
"7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4",
content_hash="c4efe15781214a84046ad7e0592977c634a5cf45f4c1e06160daf760a295a8df",
)
print(result.match_result, result.matched_field)
asyncio.run(main())
TracesReference
POST/v1/traces
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
trace = await client.traces.create(
label="Invoice batch 2026-09",
metadata={"batch_id": "b_4471", "region": "eu-west"},
)
print(trace.trace_id, trace.status)
asyncio.run(main())
GET/v1/traces
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
page = await client.traces.list(status="open", page=1, limit=25)
for trace in page.traces:
print(trace.trace_id, trace.label, trace.status)
print(page.total, page.has_more)
asyncio.run(main())
GET/v1/traces/{trace_id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
trace = await client.traces.get(
"7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4",
event_page=1,
event_limit=50,
)
print(trace.status, trace.composite_hash)
for event in trace.events:
print(event.event_id, event.event_type, event.payload_hash)
asyncio.run(main())
DELETE/v1/traces/{trace_id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.traces.delete("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
print(result.trace_id, result.deleted)
asyncio.run(main())
POST/v1/traces/{trace_id}/seal
Python
import asyncio
from invoance import InvoanceClient
TRACE_ID = "7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4"
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
seal = await client.traces.seal(TRACE_ID)
print(seal.status) # "sealing"
# The seal runs in the background. Poll until the status changes.
trace = await client.traces.get(TRACE_ID)
while trace.status == "sealing":
await asyncio.sleep(1)
trace = await client.traces.get(TRACE_ID)
print(trace.status, trace.composite_hash)
asyncio.run(main())
GET/v1/traces/{trace_id}/proof
Python
import asyncio
import hashlib
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
bundle = await client.traces.proof("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
# Recompute the composite hash: SHA-256 over the raw item hashes in
# timestamp order across events, documents and attestations.
raw = bundle.model_dump()
items = (
[(e["timestamp"], e["content_hash"]) for e in raw["events"]]
+ [(d["timestamp"], d["document_hash"]) for d in raw.get("documents", [])]
+ [(a["timestamp"], a["payload_hash"]) for a in raw.get("attestations", [])]
)
hasher = hashlib.sha256()
for _, item_hash in sorted(items):
hasher.update(bytes.fromhex(item_hash))
recomputed = hasher.hexdigest()
print(bundle.composite_hash)
print("composite hash matches" if recomputed == bundle.composite_hash else "mismatch")
asyncio.run(main())
GET/v1/traces/{trace_id}/proof/pdf
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
pdf = await client.traces.proof_pdf("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
with open("trace-proof.pdf", "wb") as f:
f.write(pdf)
print("wrote trace-proof.pdf", len(pdf), "bytes")
asyncio.run(main())
GET/v1/proof/trace/{trace_id}
Python
import json
import urllib.request
# No API key: the public proof endpoint is unauthenticated.
TRACE_ID = "7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4"
with urllib.request.urlopen(f"https://api.invoance.com/v1/proof/trace/{TRACE_ID}") as res:
proof = json.load(res)
print(proof["issuer_name"], proof["composite_hash"])
print(len(proof["events"]), "events", len(proof["documents"]), "documents", len(proof["attestations"]), "attestations")
Audit LogsReference
POST/v1/audit/events
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.audit.events.ingest(
organization_id="org_8472",
action="user.signed_in",
occurred_at="2026-09-22T08:14:07Z",
actor={"type": "user", "id": "u_4821", "name": "Ada Lovelace"},
targets=[{"type": "workspace", "id": "ws_17"}],
context={"location": "203.0.113.10", "user_agent": "Mozilla/5.0"},
metadata={"method": "sso", "mfa": True},
idempotency_key="signin-u_4821-2026-09-22T08:14:07Z",
)
print(result["event_id"], result["ingested_at"])
asyncio.run(main())
GET/v1/audit/events
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
page = await client.audit.events.list(
organization_id="org_8472",
actions="user.signed_in,user.signed_out",
range_start="2026-09-01T00:00:00Z",
limit=50,
)
for event in page["events"]:
print(event["seq"], event["action"], event["actor"]["id"])
print(page["next_cursor"])
asyncio.run(main())
GET/v1/audit/events/{id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
event = await client.audit.events.get("aevt_01J0Y1Z2A3B4C5D6E7F8G9H0JK")
print(event["seq"], event["action"], event["payload_hash"])
asyncio.run(main())
GET/v1/audit/events/{id}/verify
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.audit.events.verify("aevt_01J0Y1Z2A3B4C5D6E7F8G9H0JK")
print(result["valid"], result["reason"], result["payload_hash"])
asyncio.run(main())
POST/v1/audit/orgs
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
org = await client.audit.orgs.create(
organization_id="org_8472",
name="Acme Robotics",
)
print(org["id"], org["retention_days"])
asyncio.run(main())
GET/v1/audit/orgs
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.audit.orgs.list(include_archived=True)
for org in result["orgs"]:
print(org["id"], org["organization_id"], org["archived_at"])
asyncio.run(main())
PATCH/v1/audit/orgs/{id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
org = await client.audit.orgs.update("org_8472", name="Acme Robotics Ltd")
print(org["name"])
# Pass None to clear the name.
await client.audit.orgs.update("org_8472", name=None)
asyncio.run(main())
DELETE/v1/audit/orgs/{id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.audit.orgs.delete("org_8472")
print(result["deleted"], result["id"])
asyncio.run(main())
POST/v1/audit/orgs/{id}/archive
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
org = await client.audit.orgs.archive("org_8472")
print(org["archived_at"])
asyncio.run(main())
POST/v1/audit/orgs/{id}/unarchive
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
org = await client.audit.orgs.unarchive("org_8472")
print(org["archived_at"])
asyncio.run(main())
GET/v1/audit/orgs/{id}/integrity
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
report = await client.audit.orgs.integrity("org_8472")
print(report["contiguous"], report["count"], report["expected"], report["gaps"])
asyncio.run(main())
PUT/v1/audit/orgs/{id}/retention
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.audit.orgs.set_retention("org_8472", days=365)
print(result["retention_days"], result["clamped"], result["plan_cap_days"])
asyncio.run(main())
POST/v1/audit/orgs/{id}/streams
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
stream = await client.audit.streams.create(
"org_8472",
url="https://siem.example.com/hooks/invoance",
)
# Store signing_secret now; it is not returned again.
print(stream["id"], stream["signing_secret"])
asyncio.run(main())
GET/v1/audit/orgs/{id}/streams
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.audit.streams.list("org_8472")
for stream in result["streams"]:
print(stream["id"], stream["state"], stream["cursor_seq"], stream["last_error"])
asyncio.run(main())
DELETE/v1/audit/orgs/{id}/streams/{stream_id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.audit.streams.delete("org_8472", "astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG")
print(result["deleted"], result["id"])
asyncio.run(main())
POST/v1/audit/orgs/{id}/streams/{stream_id}/test
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
result = await client.audit.streams.test("org_8472", "astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG")
print(result["delivered"], result["http_status"], result["error"])
asyncio.run(main())
POST/v1/audit/portal_sessions
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
session = await client.audit.portal_sessions.create(
organization_id="org_8472",
intent="audit_logs",
session_duration_seconds=3600,
)
print(session["url"], session["link_expires_in"], session["session_expires_in"])
asyncio.run(main())
POST/v1/audit/exports
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
job = await client.audit.exports.create(
organization_id="org_8472",
format="ndjson",
filters={
"actions": "user.signed_in,user.signed_out",
"occurred_after": "2026-09-01T00:00:00Z",
},
)
print(job["id"], job["status"])
asyncio.run(main())
GET/v1/audit/exports/{id}
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
job = await client.audit.exports.get("aexp_01J0Y4Q6S8V0X2Z4B6D8F0H2JK")
while job["status"] in ("pending", "running"):
await asyncio.sleep(5)
job = await client.audit.exports.get("aexp_01J0Y4Q6S8V0X2Z4B6D8F0H2JK")
print(job["status"], job["row_count"], job["download_url"] or job["error"])
asyncio.run(main())
POST/v1/audit/portal/exchange
Python
import json
import os
import urllib.request
# No API key: the exchange is public and the link token is the credential.
link_token = os.environ["PORTAL_LINK_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/exchange",
data=json.dumps({"token": link_token}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as res:
session = json.load(res)
print(session["intent"], session["expires_in"])
# session["token"] is the Bearer token for the /v1/audit/portal/* routes.
GET/v1/audit/portal/events
Python
import json
import os
import urllib.request
# Portal reads use the short-lived JWT from POST /v1/audit/portal/exchange,
# not an API key. PORTAL_TOKEN holds that JWT.
portal_token = os.environ["PORTAL_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/events?actions=user.signed_in&limit=50",
headers={"Authorization": f"Bearer {portal_token}"},
)
with urllib.request.urlopen(req) as res:
page = json.load(res)
for event in page["events"]:
print(event["seq"], event["action"], event["actor"]["id"])
print(page["next_cursor"])
GET/v1/audit/portal/events/{id}
Python
import json
import os
import urllib.request
# Portal reads use the short-lived JWT from POST /v1/audit/portal/exchange,
# not an API key. PORTAL_TOKEN holds that JWT.
portal_token = os.environ["PORTAL_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/events/aevt_01J0Y1Z2A3B4C5D6E7F8G9H0JK",
headers={"Authorization": f"Bearer {portal_token}"},
)
with urllib.request.urlopen(req) as res:
event = json.load(res)
print(event["seq"], event["action"], event["payload_hash"])
GET/v1/audit/portal/events/{id}/verify
Python
import json
import os
import urllib.request
# Portal reads use the short-lived JWT from POST /v1/audit/portal/exchange,
# not an API key. PORTAL_TOKEN holds that JWT.
portal_token = os.environ["PORTAL_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/events/aevt_01J0Y1Z2A3B4C5D6E7F8G9H0JK/verify",
headers={"Authorization": f"Bearer {portal_token}"},
)
with urllib.request.urlopen(req) as res:
result = json.load(res)
print(result["valid"], result["reason"], result["key_source"])
GET/v1/audit/portal/org
Python
import json
import os
import urllib.request
# Portal reads use the short-lived JWT from POST /v1/audit/portal/exchange,
# not an API key. PORTAL_TOKEN holds that JWT.
portal_token = os.environ["PORTAL_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/org",
headers={"Authorization": f"Bearer {portal_token}"},
)
with urllib.request.urlopen(req) as res:
info = json.load(res)
print(info["issuer"]["name"], info["org"]["name"], info["intent"])
GET/v1/audit/portal/streams
Python
import json
import os
import urllib.request
# Portal reads use the short-lived JWT from POST /v1/audit/portal/exchange,
# not an API key. PORTAL_TOKEN holds that JWT.
portal_token = os.environ["PORTAL_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/streams",
headers={"Authorization": f"Bearer {portal_token}"},
)
with urllib.request.urlopen(req) as res:
streams = json.load(res)["streams"]
for stream in streams:
print(stream["id"], stream["state"], stream["endpoint"])
POST/v1/audit/portal/streams
Python
import json
import os
import urllib.request
# Portal reads use the short-lived JWT from POST /v1/audit/portal/exchange,
# not an API key. PORTAL_TOKEN holds that JWT.
portal_token = os.environ["PORTAL_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/streams",
data=json.dumps({"type": "webhook", "url": "https://siem.example.com/hooks/invoance"}).encode(),
headers={
"Authorization": f"Bearer {portal_token}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req) as res:
stream = json.load(res)
# Store signing_secret now; it is not returned again.
print(stream["id"], stream["signing_secret"])
DELETE/v1/audit/portal/streams/{id}
Python
import json
import os
import urllib.request
# Portal reads use the short-lived JWT from POST /v1/audit/portal/exchange,
# not an API key. PORTAL_TOKEN holds that JWT.
portal_token = os.environ["PORTAL_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/streams/astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG",
headers={"Authorization": f"Bearer {portal_token}"},
method="DELETE",
)
with urllib.request.urlopen(req) as res:
result = json.load(res)
print(result["deleted"], result["id"])
POST/v1/audit/portal/streams/{id}/test
Python
import json
import os
import urllib.request
# Portal reads use the short-lived JWT from POST /v1/audit/portal/exchange,
# not an API key. PORTAL_TOKEN holds that JWT.
portal_token = os.environ["PORTAL_TOKEN"]
req = urllib.request.Request(
"https://api.invoance.com/v1/audit/portal/streams/astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG/test",
headers={"Authorization": f"Bearer {portal_token}"},
method="POST",
)
with urllib.request.urlopen(req) as res:
result = json.load(res)
print(result["delivered"], result["http_status"], result["error"])
GET/v1/proof/audit/{event_id}
Python
import json
import urllib.request
# No API key: the public proof endpoint is unauthenticated.
EVENT_ID = "aevt_01J0Y1Z2A3B4C5D6E7F8G9H0JK"
with urllib.request.urlopen(f"https://api.invoance.com/v1/proof/audit/{EVENT_ID}") as res:
proof = json.load(res)
print(proof["organization"]["issuer_name"], proof["event"]["action"], proof["event"]["seq"])
print(proof["verification"]["valid"], proof["verification"]["reason"])
POST/v1/proof/audit/{event_id}/verify
Python
import json
import urllib.request
# No API key: the public verify endpoint is unauthenticated.
# event.json holds the event exactly as get, an export or a stream delivered it.
with open("event.json") as f:
event = json.load(f)
req = urllib.request.Request(
f"https://api.invoance.com/v1/proof/audit/{event['id']}/verify",
data=json.dumps({"event": event}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as res:
result = json.load(res)
print(result["match_result"], result["signature_valid"], result["reason"])
PlatformReference
GET/v1/me
Python
import asyncio
from invoance import InvoanceClient
async def main():
# Reads INVOANCE_API_KEY from the environment.
async with InvoanceClient() as client:
me = await client.me()
print(me["organization"]["primary_domain"], me["organization"]["plan_tier"])
print(me["api_key"]["scopes"], me["limits"]["rate_limit_per_sec"])
asyncio.run(main())
GET/keys/{domain}
Python
import base64
import json
import urllib.request
# pip install cryptography
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
domain = "acme.com"
event_id = "7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4"
# 1. The key for the issuer's verified domain (no API key needed).
with urllib.request.urlopen(f"https://api.invoance.com/keys/{domain}") as res:
key = json.load(res)
# 2. The signed record, from the public event proof endpoint.
with urllib.request.urlopen(f"https://api.invoance.com/v1/proof/event/{event_id}") as res:
event = json.load(res)["event"]
# 3. base64url without padding to the raw 32 bytes.
b64 = key["public_key"]
raw = base64.urlsafe_b64decode(b64 + "=" * (-len(b64) % 4))
pinned = Ed25519PublicKey.from_public_bytes(raw)
# 4. The record must name the same key, and the signature must verify with it.
same_key = bytes.fromhex(event["public_key"]) == raw
try:
pinned.verify(bytes.fromhex(event["signature"]), bytes.fromhex(event["signed_payload"]))
signature_valid = True
except InvalidSignature:
signature_valid = False
print(key["key_id"], same_key, signature_valid)
GET/v1/proof/event/{event_id}
Python
import json
import urllib.request
event_id = "7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4"
with urllib.request.urlopen(f"https://api.invoance.com/v1/proof/event/{event_id}") as res:
proof = json.load(res)
print(proof["organization"]["primary_domain"], proof["organization"]["domain_verified"])
print(proof["event"]["event_type"], proof["event"]["payload_hash"])
POST/v1/proof/event/{event_id}/verify
Python
import json
import urllib.request
event_id = "7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4"
body = json.dumps({
"payload": {
"policy_id": "pol_8472",
"approved_by": "risk_committee",
"decision": "approved",
}
}).encode()
req = urllib.request.Request(
f"https://api.invoance.com/v1/proof/event/{event_id}/verify",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as res:
result = json.load(res)
print(result["match_result"], result["signature_valid"], result["method"])
GET/v1/proof/{event_id}/organization
Python
import json
import urllib.request
event_id = "3f9d2a71-5c6e-4b8a-9d1f-8e2c47b0a5d3"
with urllib.request.urlopen(f"https://api.invoance.com/v1/proof/{event_id}/organization") as res:
proof = json.load(res)
print(proof["organization"]["issuer_name"], proof["organization"]["domain_verified"])
print(proof["event"]["event_id"], proof["event"]["created_at"])
POST/v1/proof/{event_id}/verify
Python
import hashlib
import json
import urllib.request
event_id = "3f9d2a71-5c6e-4b8a-9d1f-8e2c47b0a5d3"
with open("contract.pdf", "rb") as f:
document_hash = hashlib.sha256(f.read()).hexdigest()
req = urllib.request.Request(
f"https://api.invoance.com/v1/proof/{event_id}/verify",
data=json.dumps({"document_hash": document_hash}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as res:
result = json.load(res)
print(result["match_result"], result["signature_valid"], result["anchored_at"])
GET/v1/proof/ai/{attestation_id}
Python
import json
import urllib.request
attestation_id = "a1d4f8c2-7b3e-4e9a-b5c6-0d2e8f4a7c19"
with urllib.request.urlopen(f"https://api.invoance.com/v1/proof/ai/{attestation_id}") as res:
proof = json.load(res)
print(proof["organization"]["primary_domain"], proof["organization"]["domain_verified"])
print(proof["attestation"]["attestation_type"], proof["attestation"]["model_name"])
print(proof["attestation"]["output_hash"])
POST/v1/proof/ai/{attestation_id}/verify
Python
import hashlib
import json
import urllib.request
attestation_id = "a1d4f8c2-7b3e-4e9a-b5c6-0d2e8f4a7c19"
output = "Either party may terminate with 30 days written notice. Early termination fees do not apply after month 12."
content_hash = hashlib.sha256(output.encode()).hexdigest()
req = urllib.request.Request(
f"https://api.invoance.com/v1/proof/ai/{attestation_id}/verify",
data=json.dumps({"content_hash": content_hash}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as res:
result = json.load(res)
print(result["match_result"], result["matched_field"], result["signature_valid"])
Verify offlineHow verification works
Two checks run without trusting the server: attestations.verify_signature fetches the record and checks its Ed25519 signature over signed_payload; verify_audit_event rebuilds the invoance.audit/1 bytes of an audit event and checks its signature. Pass public_key to pin the key from GET /keys/{domain} instead of the key on the row.
Python
import asyncio
from invoance import InvoanceClient, verify_audit_event
async def main():
async with InvoanceClient() as client:
# AI attestation: Ed25519 over signed_payload, checked locally.
sig = await client.attestations.verify_signature("a1d4f8c2-7b3e-4e9a-b5c6-0d2e8f4a7c19")
print(sig.valid, sig.reason)
# Audit event: canonical bytes rebuilt locally, signature checked
# against a pinned key (the public_key from GET /keys/{domain}, base64url decoded to hex).
pinned_hex_key = "d4443bd9d30ef4c2e0e5db03467e3c5c740358482c1a1e30cdb27a2e02a38176"
event = await client.audit.events.get("aevt_01J8F3KQ2R7VWX9YB4ND6MCZAH")
result = verify_audit_event(event, public_key=pinned_hex_key)
print(result.valid, result.reason, result.key_source) # key_source: "pinned"
asyncio.run(main())