Audkit
chain intact — head #184,217

Proof, not
promises.

Audkit is the append-only record of everything your product does. Every event is sequenced, hash-linked to the one before it, and signed — so the entire history can be re-verified on demand, down to the first record.

Start the recordnpm i audkit
live record — acme-prodappend-only · no update · no delete
0

mutation endpoints — the API cannot edit or delete

SHA-256

links every event to the one before it

Ed25519

customer-held keys sign events before they leave your infra

1 receipt

signed verdict for every verify()

01seal

prev 00000000

An event becomes permanent in six steps.

01queued

One FIFO lane per project. Order is enforced by the queue, not hoped for.

02sequenced

A monotonic counter assigns the event its place. The next number is never in doubt.

03linked

prevHash — SHA-256 of the previous event — welds it to the record so far.

04signed

HMAC-SHA256 over sequence, timestamp, and both hashes. The seal.

05mirrored

Safe aggregates flow to analytics. Payloads never leave Postgres.

06broadcast

The dashboard sees the event the moment it lands.

the sealed fields

sequencegaps and reorderings have nowhere to hide
createdAtserver-assigned; folded into the signature so time cannot drift
prevHashaltering any event breaks every link after it
payloadHasha fingerprint built to outlive the payload itself
signatureforging a field means forging the key

02verify

prev 9759cdf8

Don't trust it. Check it.

There is no edit endpoint to abuse — forging history means writing to the database directly. Even that shows: the altered row no longer matches the hash sealed into its successor. verify() re-walks the chain from sequence 1 and returns a signed receipt. When something is wrong, it names the exact sequence.

const receipt = await audit.verify();

receipt.valid;        // false — someone touched row 88,412
receipt.checkedCount; // 184,201 events re-walked, from sequence 1
receipt.firstBreak;   // { sequence: 88412, reason: "hash_mismatch" }
receipt.signature;    // the receipt itself is signed. check it.

what a forgery looks like

#88,4095ad41e5dseal verified
#88,41073ca62e2seal verified
#88,411247ea620seal verified
#88,41212b05d5fhash_mismatch — row edited in place
#88,413e39133ebunverifiable — downstream of the break
#88,414cc1da3a2unverifiable — downstream of the break
firstBreak

Not just “something is wrong” — the exact sequence where the chain no longer holds.

signed receipts

Every verdict is signed by the platform. The proof of integrity can't be forged either.

customer-held signing

Bring your own Ed25519 key. The SDK signs each event before it leaves your infrastructure, and only you hold the private key — a history not even the platform can forge.

out-of-band anchors

Email digests carry the signed chain head. Truncate the record and the last anchor disagrees.

a second stream

The platform's own control-plane actions land in project-audit — and verify the same way.

03ship

prev 37549f8e

First event in five minutes.

  1. 01create a project

    Personal or org-owned. The chain starts at sequence 1.

  2. 02mint a scoped key

    log:write · log:read · log:export · log:verify. Shown once, revocable always.

  3. 03send the first event

    One call. A 202 comes back before the seal work starts — your request path never waits.

import { Audkit } from "audkit";

const audit = new Audkit({ apiKey: process.env.AUDKIT_API_KEY! });

await audit.log({
  action: "role.changed",
  actor: { type: "user", id: "usr_9f27", display: "dana@acme.dev" },
  target: { type: "member", id: "mem_512", display: "kai@acme.dev" },
  metadata: { from: "member", to: "admin" },
});
// 202 — accepted. sequenced, linked, signed. permanent.

04govern

prev 5f7de154

Compliance without holes in your history.

Hard-delete retention

When events pass your retention window — 90 days by default, or never — Audkit deletes the payloads outright and the storage actually shrinks. Sequence, hashes, and signatures stay in the chain, so every old record still verifies end to end. Minimize data. Keep proof.

shred_expired_event_payloads()

Legal holds

Build a hold from a query — actor, action, time range. Matching events are committed to a Merkle root anchored into the platform's own audit chain, with inclusion proofs on demand. Deletion is blocked for as long as the hold stands.

merkle root · inclusion proofs

Agents on the record

AI tools act in production now. auditedTool() wraps any AI-SDK tool with risk labels, required reasons, and an authorize gate — every call enters the record as pending, then success, denied, or failed.

audkit/ai
const refund = auditedTool({
  client: audit,
  name: "refund_payment",
  risk: "high",
  requireReason: true,
  authorize: ({ input }) => input.amountCents <= 50_00,
  handler: ({ paymentId }) => payments.refund(paymentId),
});

05index

prev ef154ce6

The rest of the instrument.

Logs explorerSearch by actor, action, target, status — chain proof on every event.
Entity timelinesEverything an actor did. Everything done to a target.
Live activityEvents reach the dashboard the moment they seal.
AnalyticsVolume and breakdowns from a safe mirror. Aggregates only.
ExportsCSV or NDJSON — from the dashboard or an API key.
Route auditingwithAuditLogging() wraps a Next.js handler end to end.
Notification rulesA visual rule builder routing to email, webhook, Slack, Discord.
Email digestsDaily to monthly summaries, each carrying the signed chain head.

end of record — you've reached the head

Start the record.

Create a project, mint a key, send an event. What lands in the chain, stays in the chain.

#000001 · prev 00000000 · reserved for your first event