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/v1Quick start
Each of your end customers is an org, addressed by your ownorganization_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 '{ "organization_id": "org_01J8F3KQ2R7VWX9YB4ND6MCZAH", "name": "Acme Production" }'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 '{
"organization_id": "org_01J8F3KQ2R7VWX9YB4ND6MCZAH",
"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" }],
"context": { "location": "203.0.113.10", "user_agent": "Chrome/126.0.0.0" },
"metadata": { "method": "sso", "mfa": true }
}'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 '{ "organization_id": "org_01J8F3KQ2R7VWX9YB4ND6MCZAH", "intent": "audit_logs" }'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"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.
from invoance import InvoanceClient, verify_audit_event
async with InvoanceClient() as client:
ev = await client.audit.events.ingest(
organization_id="org_01J8F3KQ2R7VWX9YB4ND6MCZAH",
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 offlineimport { InvoanceClient, verifyAuditEvent } from "invoance";
const client = new InvoanceClient();
const ev = await client.audit.events.ingest({
organization_id: "org_01J8F3KQ2R7VWX9YB4ND6MCZAH",
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 offlineBeyond 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.
Zero-code integrations
Already on Clerk or Auth0? Skip instrumentation entirely: point your auth provider's webhooks at Invoance and every organization in your app starts accumulating signed audit events (sign-ins, membership changes, role updates) with orgs auto-created as their events arrive. The Integrations overview covers how it works and the shared delivery semantics; the Clerk and Auth0 guides have the setup steps and curated event maps.
Embed the viewer
The hosted viewer also ships as a React component: @invoance/audit-viewerrenders the same signed event table, filters, export, and in-browser Ed25519 tamper test inside your own product (admin.yourapp.com/audit). Native DOM, no iframe; the stylesheet is prefixed and themed by CSS variables, and verification still runs in your customer's browser, so the proof never depends on trusting the page that renders it.
npm install @invoance/audit-viewerYour backend mints a short-lived portal session for the signed-in customer. This runs server-side only: the API key must never reach the browser; only the one-time token does.
// A route in YOUR backend (server-side only; never expose the API key)
const session = await client.audit.portalSessions.create({
organization_id: currentCustomerOrgId, // your id for this customer
intent: "audit_logs",
session_duration_seconds: 3600,
});
return { token: session.token }; // one-time token, safe for the browserimport { AuditLogViewer } from "@invoance/audit-viewer";
import "@invoance/audit-viewer/styles.css";
<AuditLogViewer
getPortalToken={async () => {
const r = await fetch("/api/audit-portal-token", { method: "POST" });
return (await r.json()).token;
}}
/>The component exchanges the token for an org-scoped session and re-invokes your callback when it expires, so short sessions cost nothing. Sessions minted with intent: "log_streams" render the stream-destination screen instead. Props, theming, and session behavior are covered in the Embeddable viewer guide.