Traces SDK
The traces namespace lives under client.traces. A trace groups events, documents, and AI attestations into a single verifiable process. When you seal it, Invoance computes a composite hash over every item and locks the trace so no further items can be added — the result is a self-contained proof bundle anyone can independently verify. A key needs the write scope to create, seal, and delete, and the read scope to list, get, and export proofs.
Base URL and authentication
All endpoints live under https://api.invoance.com/v1. Authenticate every request with your API key, either as Authorization: Bearer invoance_live_... or as X-API-Key: invoance_live_....
Install
pip install invoance
# The Ed25519 signature verifier additionally needs PyNaCl:
pip install pynaclInitialize the client
Both SDKs read INVOANCE_API_KEY from the environment automatically. You can also pass the key explicitly.
import asyncio
from invoance import InvoanceClient
async def main() -> None:
# Reads INVOANCE_API_KEY from the environment
async with InvoanceClient() as client:
...
# Or pass it explicitly:
# async with InvoanceClient(api_key="invoance_live_...") as client:
asyncio.run(main())Methods
Create a trace
POST/tracesCreate a new open trace to group events, documents, and AI attestations into a verifiable process proof.
result = await client.traces.create(
label="Vendor Onboarding, Acme Corp",
metadata={
"department": "procurement",
"initiated_by": "j.smith@acme.com"
}
)
print(result.trace_id)Add items to a trace
Items are not added through client.traces. Instead, pass the trace_id (traceId in the Node SDK) when you ingest an event, document, or AI attestation, and it is grouped into that open trace. The trace_id is a grouping reference, not part of the item's content hash. Add as many items as you need before sealing.
await client.events.ingest(
event_type="vendor.form.submitted",
payload={"vendor": "Acme Corp"},
trace_id="tr_abc123…", # groups this event into the trace
)List traces
GET/tracesPaginated listing of traces with optional status filter.
page = await client.traces.list(page=1, limit=50, status="open")
for t in page.traces:
print(t.trace_id, t.label, t.status)Get a trace
GET/traces/{trace_id}Retrieve a single trace with event summaries.
trace = await client.traces.get("tr_abc123…")
print(trace.trace_id, trace.event_count, trace.status)Seal a trace
POST/traces/{trace_id}/sealInitiate sealing to compute the composite hash and lock the trace from further items.
result = await client.traces.seal("tr_abc123…")
print(result.status) # "sealing"
# Poll for completion:
while True:
trace = await client.traces.get("tr_abc123…")
if trace.status == "sealed":
breakExport proof — JSON
GET/traces/{trace_id}/proofRetrieve the complete proof bundle for a sealed trace with all events and signatures.
bundle = await client.traces.proof("tr_abc123…")
print(bundle.composite_hash, bundle.event_count)
for event in bundle.events:
print(event.event_id, event.signature)Export proof — PDF
GET/traces/{trace_id}/proof.pdfRetrieve the same sealed proof bundle rendered as a signed, human-readable PDF certificate. The SDK returns the raw bytes; write them to disk.
data = await client.traces.proof_pdf("tr_abc123…")
with open("trace-proof.pdf", "wb") as f:
f.write(data)Delete a trace
DELETE/traces/{trace_id}Delete an open trace. Sealed traces are immutable and cannot be deleted; their items remain in the append-only ledger. Deleting an open trace only removes the grouping — any events, documents, or attestations already anchored to it stay anchored.
await client.traces.delete("tr_abc123…")Errors
Every error is a JSON body of { "error": code, "message": text }. The full catalog, including quota and rate-limit behavior, lives in the error reference.