Home
Home/Developers/SDKs/python
Ed25519 signaturesSHA-256 hashes8 SDKs
Status
Sign inStart free
Home
Start here
Overview
Authentication
Errors
FAQ
API
Events
Canonical JSON and hashes
Documents
Anchoring a file
AI attestations
Attestation schema
Verifying attestations
Traces
Sealing a trace
Audit logs
Organizations
Streams
Portal
Public proof
Event schema
Exporting events
Integrations
Clerk
Auth0
Embeddable viewer
All endpoints
Reference
SDKs
Node.js
Python
Go
Java
Ruby
Rust
.NET
PHP
REST
Verification
Docs · SDKs

Python SDK

Install the Python SDK, build a client, see every method with a Python sample, and verify records offline.

sha-256 · 3a352297…2592
sha-256 · 971e439d…e24c
RESTNode.jsPythonGoJavaRubyRust.NETPHP
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_keyThe API key. Falls back to INVOANCE_API_KEY; ValueError when neither is set.
base_urlAPI host. Falls back to INVOANCE_BASE_URL, then https://api.invoance.com.
api_versionPath prefix put before every request path. Default v1.
timeoutPer-request timeout in seconds. Default 30.0, must be positive; past it the call raises TimeoutError.
idempotency_keyDefault Idempotency-Key header for every mutating request; a per-call key wins.
extra_headersHeaders merged into every request.
configA full ClientConfig instead of the options above; ClientConfig.load() reads the environment. Not combinable with api_key, base_url or timeout.
RetriesNone. 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/eventsIngest an event
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())
Ingest an event: fields, response and errors
GET/v1/eventsList 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())
List events: fields, response and errors
GET/v1/events/{event_id}Get an event
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())
Get an event: fields, response and errors
POST/v1/events/{event_id}/verifyVerify an event
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())
Verify an event: fields, response and errors
DocumentsReference
POST/v1/document/anchorAnchor a document
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())
Anchor a document: fields, response and errors
GET/v1/documentList documents
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())
List documents: fields, response and errors
GET/v1/document/{event_id}Get a document
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 a document: fields, response and errors
GET/v1/document/{event_id}/originalDownload the 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())
Download the original: fields, response and errors
POST/v1/document/{event_id}/verifyVerify a document hash
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())
Verify a document hash: fields, response and errors
AI AttestationsReference
POST/v1/ai/attestationsIngest an attestation
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())
Ingest an attestation: fields, response and errors
GET/v1/ai/attestationsList 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())
List attestations: fields, response and errors
GET/v1/ai/attestations/{attestation_id}Get an attestation
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 an attestation: fields, response and errors
GET/v1/ai/attestations/{attestation_id}/rawGet the raw payload
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())
Get the raw payload: fields, response and errors
POST/v1/ai/attestations/{attestation_id}/verifyVerify a hash
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())
Verify a hash: fields, response and errors
TracesReference
POST/v1/tracesCreate a trace
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())
Create a trace: fields, response and errors
GET/v1/tracesList 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())
List traces: fields, response and errors
GET/v1/traces/{trace_id}Get a trace
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())
Get a trace: fields, response and errors
DELETE/v1/traces/{trace_id}Delete an empty trace
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())
Delete an empty trace: fields, response and errors
POST/v1/traces/{trace_id}/sealSeal a trace
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())
Seal a trace: fields, response and errors
GET/v1/traces/{trace_id}/proofGet the proof bundle
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 the proof bundle: fields, response and errors
GET/v1/traces/{trace_id}/proof/pdfDownload the proof bundle as 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())
Download the proof bundle as PDF: fields, response and errors
GET/v1/proof/trace/{trace_id}Get the public proof
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")
Get the public proof: fields, response and errors
Audit LogsReference
POST/v1/audit/eventsIngest an audit event
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())
Ingest an audit event: fields, response and errors
GET/v1/audit/eventsList 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())
List audit events: fields, response and errors
GET/v1/audit/events/{id}Get an audit event
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 an audit event: fields, response and errors
GET/v1/audit/events/{id}/verifyVerify an audit event
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())
Verify an audit event: fields, response and errors
POST/v1/audit/orgsCreate an audit org
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())
Create an audit org: fields, response and errors
GET/v1/audit/orgsList 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())
List audit orgs: fields, response and errors
PATCH/v1/audit/orgs/{id}Rename an audit org
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())
Rename an audit org: fields, response and errors
DELETE/v1/audit/orgs/{id}Delete an audit org
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())
Delete an audit org: fields, response and errors
POST/v1/audit/orgs/{id}/archiveArchive an audit org
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())
Archive an audit org: fields, response and errors
POST/v1/audit/orgs/{id}/unarchiveUnarchive an audit org
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())
Unarchive an audit org: fields, response and errors
GET/v1/audit/orgs/{id}/integrityCheck an org's sequence 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())
Check an org's sequence integrity: fields, response and errors
PUT/v1/audit/orgs/{id}/retentionSet an org's 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())
Set an org's retention: fields, response and errors
POST/v1/audit/orgs/{id}/streamsCreate a webhook stream
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())
Create a webhook stream: fields, response and errors
GET/v1/audit/orgs/{id}/streamsList an org's 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())
List an org's streams: fields, response and errors
DELETE/v1/audit/orgs/{id}/streams/{stream_id}Delete a stream
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())
Delete a stream: fields, response and errors
POST/v1/audit/orgs/{id}/streams/{stream_id}/testSend a test delivery
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())
Send a test delivery: fields, response and errors
POST/v1/audit/portal_sessionsCreate a portal session
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())
Create a portal session: fields, response and errors
POST/v1/audit/exportsCreate an export
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())
Create an export: fields, response and errors
GET/v1/audit/exports/{id}Get an export
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())
Get an export: fields, response and errors
POST/v1/audit/portal/exchangeExchange a portal link token
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.
Exchange a portal link token: fields, response and errors
GET/v1/audit/portal/eventsList events through the portal
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"])
List events through the portal: fields, response and errors
GET/v1/audit/portal/events/{id}Get an event through the portal
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 an event through the portal: fields, response and errors
GET/v1/audit/portal/events/{id}/verifyVerify an event through the portal
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"])
Verify an event through the portal: fields, response and errors
GET/v1/audit/portal/orgGet the portal's org and issuer
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 the portal's org and issuer: fields, response and errors
GET/v1/audit/portal/streamsList streams through the portal
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"])
List streams through the portal: fields, response and errors
POST/v1/audit/portal/streamsCreate a stream through the portal
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"])
Create a stream through the portal: fields, response and errors
DELETE/v1/audit/portal/streams/{id}Delete a stream through the portal
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"])
Delete a stream through the portal: fields, response and errors
POST/v1/audit/portal/streams/{id}/testTest a stream through the portal
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"])
Test a stream through the portal: fields, response and errors
GET/v1/proof/audit/{event_id}Get the public proof of an audit event
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"])
Get the public proof of an audit event: fields, response and errors
POST/v1/proof/audit/{event_id}/verifyVerify a copy of an audit event
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"])
Verify a copy of an audit event: fields, response and errors
PlatformReference
GET/v1/meIntrospect the API key
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())
Introspect the API key: fields, response and errors
GET/keys/{domain}Fetch an organization's public key
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)
Fetch an organization's public key: fields, response and errors
GET/v1/proof/event/{event_id}Read an event's public proof
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"])
Read an event's public proof: fields, response and errors
POST/v1/proof/event/{event_id}/verifyVerify an event hash publicly
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"])
Verify an event hash publicly: fields, response and errors
GET/v1/proof/{event_id}/organizationRead a document anchor's public proof
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"])
Read a document anchor's public proof: fields, response and errors
POST/v1/proof/{event_id}/verifyVerify a document hash publicly
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"])
Verify a document hash publicly: fields, response and errors
GET/v1/proof/ai/{attestation_id}Read an AI attestation's public proof
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"])
Read an AI attestation's public proof: fields, response and errors
POST/v1/proof/ai/{attestation_id}/verifyVerify an AI content hash publicly
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 an AI content hash publicly: fields, response and errors
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())

Proof infrastructure. Records are hashed, signed with your organization's Ed25519 key, and stored append-only, so anyone can check them later.

Products

  • Audit Logs
  • Event Ledger
  • AI Attestation
  • Document Anchoring
  • Traces

Developers

  • Documentation
  • API reference
  • SDKs
  • How it works
  • How traces seal
  • System status

Verify

  • Audit Log
  • Event
  • AI Attestation
  • Document
  • Trace

Company

  • Why Invoance
  • Pricing
  • Security
  • Compliance teams
  • Finance teams
  • Partners
  • Resources
  • Help center
  • Contact
© 2026 Invoance
PrivacyLegal noticeLegal FAQGitHubLinkedInX