Invoance
Get a DemoLog InSign Up
Developers
Search docs…⌘K
Getting started
OverviewConceptsAuthenticationCreate an API key
API reference
EndpointsErrors
Audit Logs
Quick startIntegrationsEmbeddable viewerEvent schemaExporting eventsSDK reference
AI Attestations
Quick startAttestation schemaVerification & proofSDK reference
Events
OverviewSDK reference
Documents
OverviewSDK reference
Traces
OverviewSDK reference
SDKs
PythonNode.jsGoJavaRubyRust.NETPHPcURL
Verification
How it works
Support
API FAQ

AI Attestations SDK

The attestations namespace lives under client.attestations. Each attestation seals an AI input, output, and model identity into a signed, append-only record: three SHA-256 hashes plus a per-tenant Ed25519 signature that anyone can independently verify. A key needs the write scope to ingest and the read scope to list, get, and verify.

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

Python
pip install invoance

# The Ed25519 signature verifier (verify_signature) additionally needs PyNaCl:
pip install pynacl

Create a client

Both SDKs read INVOANCE_API_KEY from the environment automatically. You can also pass the key explicitly.

Python
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 an attestation

POST/ai/attestations

Seal one AI interaction. type is one of output, decision, or approval. input, output, and the three model context fields are required and non-empty. subject is an optional dict of user_id, session_id, and up to 20 extra keys (8 KB max); trace_id must reference one of your open traces. The whole body is capped at 1 MB. Persistence is asynchronous: a 201 means durably queued, so a get() immediately after may briefly 404.

Python
res = await client.attestations.ingest(
    attestation_type="output",
    input="Summarize this contract for a non-lawyer.",
    output="This agreement covers a 12-month engagement...",
    model_provider="openai",
    model_name="gpt-4o",
    model_version="2024-11-20",
    subject={"user_id": "user_123", "session_id": "sess_456"},
    idempotency_key="att-req-8f3d2c1a",   # optional
    trace_id="6c9d2f4e-...",              # optional, must be an open trace
)
print(res.attestation_id, res.status)      # "accepted" (or "duplicate")

Returns{ attestation_id, created_at, input_hash, output_hash, payload_hash, status }. status is "accepted" on first write; resubmitting a byte-identical body returns 200 with status "duplicate" and the original attestation_id. Reusing an idempotency key with a different body fails with 409 idempotency_key_reuse_mismatch.

List attestations

GET/ai/attestations

Page through your attestations, newest first. Filters: date_from / date_to (RFC 3339, from inclusive, to exclusive), attestation_type (output, decision, approval), and model_provider (exact match). limit defaults to 50 and clamps to 500. Responses are served from a 30-second cache, so a just-ingested record can take a moment to appear.

Python
page = await client.attestations.list(
    page=1,
    limit=100,
    date_from="2026-06-01T00:00:00Z",
    attestation_type="output",
    model_provider="openai",
)
for att in page.attestations:
    print(att.attestation_id, att.attestation_hash)
print(page.total, page.has_more)

Returns{ attestations, page, limit, total, has_more }. Each item carries attestation_id, attestation_type, attestation_hash, model_provider, model_name, retention_policy, and created_at. Records sealed past their retention window are omitted.

Get an attestation

GET/ai/attestations/{attestation_id}

Fetch the full signed record for one attestation, including the exact bytes that were signed and the signature itself.

Python
att = await client.attestations.get("a1b2c3d4-...")
print(att.attestation_type, att.attestation_hash)
print(att.signature_alg)   # "ed25519"

Returns{ attestation_id, tenant_id, attestation_type, attestation_hash, input_hash, output_hash, signed_payload (hex canonical signed bytes), signature (hex, 64-byte Ed25519), public_key (hex, 32-byte), signature_alg, model_provider, model_name, model_version, retention_policy, created_at, organization }. Records sealed past their retention window return 410 retention_expired; verification endpoints still work on sealed records.

Get the raw canonical payload

GET/ai/attestations/{attestation_id}/raw

Fetch the exact, byte-identical canonical JSON that was hashed at ingest, straight from object storage. Hashing these bytes with SHA-256 reproduces payload_hash, so this is the ground truth for any independent recomputation. The server caches the bytes for five minutes.

Python
raw = await client.attestations.get_raw("a1b2c3d4-...")
print(raw["payload"]["output"])
print(raw["context"]["model_name"])

ReturnsThe original canonical JSON object: { type, payload: { input, output }, context: { model_provider, model_name, model_version }, subject?, trace_id? }. Sealed records return 410 retention_expired.

Verify by hash

POST/ai/attestations/{attestation_id}/verify

Ask the API whether a SHA-256 you computed matches the anchored record. content_hash must be exactly 64 hex characters. The server compares it against attestation_hash, input_hash, output_hash, and payload_hash. Each call counts against your monthly verification quota.

Python
import hashlib

output_text = "This agreement covers a 12-month engagement..."
digest = hashlib.sha256(output_text.encode()).hexdigest()

result = await client.attestations.verify("a1b2c3d4-...", content_hash=digest)
print(result.match_result, result.matched_field)   # True "output_hash"

Returns{ attestation_id, match_result, matched_field, anchored_hash, submitted_hash, anchored_at, organization }. matched_field is "attestation_hash", "input_hash", "output_hash", "payload_hash", or null when nothing matched.

Verify by payload

SDK onlyPOST/ai/attestations/{attestation_id}/verify

Hand the SDK a payload you hold locally; it serializes it compactly, computes the SHA-256 client-side, and calls verify() for you. Field-order caveat: hashing is byte-exact, so an object payload must use the server's canonical key order — type, payload, context, subject — with compact separators. The safest input is the object you get back from get_raw(), or the exact string/bytes you originally sent.

Python
payload = await client.attestations.get_raw("a1b2c3d4-...")

result = await client.attestations.verify_payload(
    "a1b2c3d4-...", payload=payload
)
print(result.match_result, result.matched_field)   # True "payload_hash"

ReturnsThe same shape as verify(): { attestation_id, match_result, matched_field, anchored_hash, submitted_hash, anchored_at, organization }. A payload match reports matched_field 'payload_hash'.

Verify the Ed25519 signature

SDK onlyGET/ai/attestations/{attestation_id}

Fetch the attestation from the API and check the Ed25519 signature over signed_payload against the public key embedded in that same response. To be clear about what this proves: the record is internally consistent, but both the bytes and the key come from the server in one response — it is not an offline check. For verification against the issuer's pinned, domain-bound key served by GET /keys/{domain}, follow the pinned-key recipe in the verification guide. In Python this method needs PyNaCl (pip install pynacl).

Python
# Requires PyNaCl: pip install pynacl
result = await client.attestations.verify_signature("a1b2c3d4-...")
print(result.valid, result.reason)     # True None
print(result.signed_data["payload_hash"])

Returns{ valid, reason, attestation, signed_data }. valid is a boolean; reason explains a failure (or is null); signed_data is the parsed canonical statement that was signed — version, attestation id, tenant id, type, the three hashes, model context, and created_at.

Errors

Every error is a JSON body of { "error": code, "message": text }. The codes you will meet most often here: invalid_attestation_type, empty_payload, invalid_context (400), idempotency_key_reuse_mismatch (409), attestation_not_found (404), retention_expired (410), payload_too_large (413), and quota_exceeded (429). The full catalog, including quota and rate-limit behavior, lives in the error reference.

Next steps

AI Attestations quick startAttestation schemaVerification guide
01Audit logs02AI decisions03Documents04Business events05Whole workflows
Invoance

Neutral proof infrastructure for records that must survive scrutiny. Signed at creation. Verifiable outside your dashboard.

ALL SYSTEMS OPERATIONALEvidence infrastructure · Online

Build

  • Developer overview
  • API endpoints
  • Official SDKs
  • Authentication
  • Verification model

Use Invoance

  • Why Invoance
  • How it works
  • Compliance teams
  • Finance teams
  • Pricing

Verify

  • Audit log
  • AI attestation
  • Document
  • Ledger event
  • Sealed trace

Company

  • Help center
  • Resources
  • Security
  • Partners
  • Contact
  • System status
FIELD NOTES / 01Proof patterns for teams building trust.

Invoance provides cryptographic proof and verification infrastructure. It does not provide legal, financial, compliance, or regulatory advice.

Read proof disclaimer

Records anchored with Invoance are cryptographically signed and designed to reveal tampering. Invoance verifies that a specific record existed in a particular form at a particular time; it does not assess the record's accuracy, authenticity, legality, or underlying contents. Public verification links can be resolved without authentication. Invoance is not a custodian of funds, a legal authority, or a regulated financial institution. Using Invoance does not by itself satisfy any legal or regulatory requirement. Consult qualified legal or compliance professionals regarding your obligations.

© 2025 – 2026 Invoance, Inc. All rights reserved.
PrivacyLegalFAQ
PROOF, NOT PROMISES.