InvoanceInvoance
Log inStart free
Developers
Search docs…⌘K
Getting started
OverviewConceptsAuthenticationCreate an API key
API reference
EndpointsErrors
Audit Logs
Quick startEvent schemaExporting eventsSDK reference
Resources
EventsDocumentsAI AttestationsTraces
SDKs
PythonNode.jscURL
Verification
How it works
Support
API FAQ

Audit Logs

A signed, independently verifiable activity log for your product. Send events over the API and each one is signed with your tenant's Ed25519 key and given a gap-free sequence number, so tampering or deletion shows up as a broken signature or a missing sequence. Hand your end customers a hosted viewer, stream events to a SIEM, export a range, and verify any event without trusting our answer.

Before you begin

The audit API uses its own scopes, distinct from the ledger scopes: a key needsaudit:write to send events and audit:readto read, verify, and export. Grant them on an API key in Settings. Every example below is a complete request, swap invoance_live_xxx for your key.

https://api.invoance.com/v1

Quick start

1Create an org

Each of your end customers is an org, addressed by your ownexternal_id. Create it once; events reference it by that id.

curl -X POST https://api.invoance.com/v1/audit/orgs \
  -H "Authorization: Bearer invoance_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "acme-prod", "name": "Acme Production" }'
2Send an event

Post an event with an action, an actor, and optional targets. An Idempotency-Key is required; the SDKs generate one for you. The response returns the minted event id.

curl -X POST https://api.invoance.com/v1/audit/events \
  -H "Authorization: Bearer invoance_live_xxx" \
  -H "Idempotency-Key: 7f4c1c9d-5b8a-4d42-9e0b-1f2a3b4c5d6e" \
  -H "Content-Type: application/json" \
  -d '{
    "org": "acme-prod",
    "action": "user.signed_in",
    "occurred_at": "2026-06-24T12:00:00.000Z",
    "actor": { "type": "user", "id": "user_123", "name": "Ada Lovelace" },
    "targets": [{ "type": "team", "id": "t_eng" }]
  }'
3Hand off a hosted viewer

Mint a one-time link to a read-only, org-scoped viewer your customer can open with no Invoance account. The link is single-use and the session is short-lived; setintent toaudit_logs for the event viewer or log_streams for the stream-config screen.

curl -X POST https://api.invoance.com/v1/audit/portal_sessions \
  -H "Authorization: Bearer invoance_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "org_id": "aorg_01J…", "intent": "audit_logs" }'
4Verify an event

Ask the API to re-check an event's signature against your tenant's pinned key, or verify it yourself offline with the SDK (below). Both reconstruct the exact signed bytes and check the Ed25519 signature.

curl https://api.invoance.com/v1/audit/events/aevt_01J…/verify \
  -H "Authorization: Bearer invoance_live_xxx"

Event schema (invoance.audit/1)

Every event uses the frozen invoance.audit/1 envelope: a fixed set of fields, fixed types, within the limits below. The envelope is strict and identical for every customer, so an unknown top-level field, a missing required field, or a wrong type is rejected 400 and never stored or signed. What is not constrained is the contents of metadata: you send whatever key/values you want within the size limits, with no pre-declaration.

A complete event

Exactly what you send to POST /v1/audit/events. The server adds id, org_id, seq, ingested_at and schema_id, then signs it.

{
  "action": "user.signed_in",
  "occurred_at": "2026-06-24T12:00:00.000Z",
  "actor": {
    "type": "user",
    "id": "user_42",
    "name": "Ada Lovelace"
  },
  "targets": [
    { "type": "team", "id": "team_eng" }
  ],
  "context": {
    "location": "203.0.113.10",
    "user_agent": "Chrome/124.0.0.0"
  },
  "metadata": {
    "plan": "growth"
  }
}
Schema id invoance.audit/1·version 1 (reserved; if you send a version it must equal 1)
FieldTypeRequiredNotes
actionstringYesDotted resource.verb, e.g. user.signed_in.
occurred_atstring (RFC3339 UTC)YesWhen it happened, on your clock. Bounded to now − 5y … now + 24h. SDKs default this to now.
actorobjectYesWho did it: type (user | api_key | system) and id required; name and metadata optional.
targetsarray of objectYesWhat it was done to. May be empty ([]) but the field must be present. Each: type, id, optional name and metadata.
contextobjectNolocation = actor IP, user_agent = client string.
metadataobjectNoFlat key to scalar map (see limits). Free-form contents.
versionintegerNoReserved. Must equal 1 if present.

Absent optional fields, or fields sent as null, are omitted from the signed bytes entirely, so the signature covers exactly the fields present. The server assigns id (aevt_<ULID>), org_id, ingested_at, seq and schema_id; client-sent values for those are ignored.

Limits and rules

  • metadata (and actor / target metadata): ≤ 50 keys, key ≤ 40 chars, value ≤ 500 chars.
  • metadata values are scalars only: string, boolean, or int64. Floats and nested objects/arrays are rejected 400 (send decimals as strings).
  • Idempotency-Key header is required on ingest; the SDKs derive one from the event content.
  • Ids: orgs are aorg_<ULID>, events are aevt_<ULID>.

What gets signed: the canonical bytes of the present fields are signed with your tenant's Ed25519 key. The /verify endpoint and the SDK offline verifier rebuild those exact bytes and check the signature against your pinned key. The signature attests ingested_at (our server clock), not the client-supplied occurred_at.

Using the SDKs

The Python and Node SDKs expose the same surface under client.audit. They defaultoccurred_at to now, generate the idempotency key, and ship an offline verifier that needs no network call.

Python
from invoance import InvoanceClient, verify_audit_event

async with InvoanceClient() as client:
    ev = await client.audit.events.ingest(
        org="acme-prod",
        action="user.signed_in",
        actor={"type": "user", "id": "user_123", "name": "Ada"},
    )
    stored = await client.audit.events.get(ev["event_id"])
    print(verify_audit_event(stored).valid)   # True — verified offline
Node
import { InvoanceClient, verifyAuditEvent } from "invoance";

const client = new InvoanceClient();
const ev = await client.audit.events.ingest({
  org: "acme-prod",
  action: "user.signed_in",
  actor: { type: "user", id: "user_123", name: "Ada" },
});
const stored = await client.audit.events.get(ev.event_id as string);
console.log(verifyAuditEvent(stored).valid);  // true — verified offline

Exporting events

Exports are asynchronous. Create a job with the same filters you would pass to the list endpoint, poll until its status is ready, then download from the short-lived presigned URL. Choose csv for spreadsheets or ndjson for line-delimited JSON. A single export spans both hot and cold-storage rows, so an old range comes back whole.

1Create the export
curl -X POST https://api.invoance.com/v1/audit/exports \
  -H "Authorization: Bearer invoance_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "org_id": "aorg_01J…",
    "format": "csv",
    "filters": { "actions": "user.signed_in", "occurred_after": "2026-06-01T00:00:00Z" }
  }'
2Poll until ready
curl https://api.invoance.com/v1/audit/exports/aexp_01J… \
  -H "Authorization: Bearer invoance_live_xxx"
3Download the file
curl -L "https://…r2.cloudflarestorage.com/…" -o audit-export.csv
With the Python SDK
import asyncio
from invoance import InvoanceClient

async with InvoanceClient() as client:
    job = await client.audit.exports.create(
        org_id="aorg_01J…",
        format="csv",
        filters={"actions": "user.signed_in"},
    )
    while True:
        status = await client.audit.exports.get(job["id"])
        if status["status"] in ("ready", "failed"):
            break
        await asyncio.sleep(2)
    print(status["status"], status.get("download_url"))

Beyond the basics

Hosted viewer

A read-only, org-scoped event viewer and stream-config screen your customers open from a one-time link, with no account.

SIEM streaming

Register a webhook destination per org and Invoance delivers each new event, HMAC-signed, in order, with retries and backoff.

Exports

Queue an async CSV or NDJSON export of any filtered range; the worker streams it to storage and returns a short-lived download URL.

Endpoint reference

Next steps

SDK reference How verification works
Invoance

Neutral digital proof infrastructure for business. Tamper-evident, independently verifiable records.

Subscribe to our newsletter

Products
Platform
How It Works
Developers
Verify
Resources
Help & Legal
Products
  • Event Ledger
  • Document Anchoring
  • AI Attestation
  • Traces
Platform
  • Why Invoance
  • For Compliance Teams
  • For Finance Teams
  • Pricing
How It Works
  • Overview
  • Event Ledger
  • Document Anchoring
  • AI Attestation
Developers
  • Overview
  • Endpoints
  • Authentication
  • Concepts
Verify
  • Verify Document
  • Verify AI Attestation
  • Verify Event
  • Verify Trace
Resources
  • All Resources
  • SOC 2 Guide
  • HIPAA Guide
  • ISO 27001 Guide
Help & Legal
  • Support
  • Status
  • Verification Help
  • FAQ

Invoance provides technical verification and proof infrastructure for digital records. Invoance does not issue legal, financial, or regulatory advice.

Records anchored through Invoance are cryptographically signed and tamper-evident by design. Invoance does not verify the accuracy, legality, or authenticity of document contents, only that a record existed in a specific form at a specific time. Verification links are publicly resolvable and do not require authentication. Invoance does not act as a custodian of funds, a legal authority, or a regulated financial entity. Use of Invoance does not constitute legal compliance. Consult qualified counsel for your specific obligations.

© 2025 – 2026 Invoance, Inc. All rights reserved.••