Skip to content
AuditKit

Documentation

AuditKit is a REST API (https://api.auditkit.dev) with an MCP server in the same process. You send events; you get back signed receipts; the service chains, roots and anchors them; anyone can verify an export offline.

Quickstart

Create a project by signing in, create a test key with read and write scopes, then:

// Until @auditkit/sdk is on npm: git clone https://github.com/AuditKitDev/auditkit && pnpm install && pnpm -r build,
// then npm install <clone>/packages/sdk. ESM ("type": "module") and Node 22+.
import { AuditKit } from "@auditkit/sdk";
const audit = new AuditKit({ apiKey: process.env.AUDITKIT_KEY!, baseUrl: process.env.AUDITKIT_URL ?? "https://api.auditkit.dev" });
const receipt = await audit.log({ tenant: "acme", actor: "u_1", action: "invoice.delete", target: "inv_42" });
console.log(receipt.position, receipt.event_hash, receipt.server_sig);

Both SDKs are in the repo and tested; the npm and PyPI packages (auditkit-sdk) publish with v2.0.0. Until then install from the clone as the first line shows. Set AUDITKIT_URL to your own instance when self-hosting.

# Until auditkit-sdk is on PyPI: pip install <clone>/packages/sdk-python  (Python 3.10+)
import os
from auditkit_sdk import AuditKit
audit = AuditKit(api_key=os.environ["AUDITKIT_KEY"], base_url=os.environ.get("AUDITKIT_URL", "https://api.auditkit.dev"))
receipt = audit.log("acme", "u_1", "invoice.delete", target="inv_42")
print(receipt["position"], receipt["event_hash"], receipt["server_sig"])

Both SDKs are in the repo and tested; the npm and PyPI packages (auditkit-sdk) publish with v2.0.0. Until then install from the clone as the first line shows. Set AUDITKIT_URL to your own instance when self-hosting.

curl -X POST https://api.auditkit.dev/v1/events \
  -H "Authorization: Bearer $AUDITKIT_KEY" -H "Content-Type: application/json" \
  -d '{"tenant":"acme","actor":"u_1","action":"invoice.delete","target":"inv_42"}'
# → {"id":"01J…","position":0,"event_hash":"…","prev_hash":"000…","server_sig":"…"}

Neither SDK has third-party dependencies: TypeScript depends only on @auditkit/core (Node 22+ with global fetch); Python 3.10+ uses the standard library, with client signing and offline receipt checks behind pip install auditkit-sdk[sign]. Methods: log, logBulk/log_bulk, search, get, proof, verify, export (async iterable / iterator of NDJSON lines), tenants, erase. Retries: 3 attempts with exponential backoff on 429, 5xx and network errors; log always sends an idempotency key, so a retry replays the original receipt (duplicate: true) and never double-logs. erase is never retried. Failures throw AuditKitError { status, code, message }.

Authentication

Bearer API keys: ak_live_… or ak_test_…, both for your project. The prefix is a label for your own routing; the server treats them identically today. Every new account also gets read access to a shared Sample project with seeded events. Scopes: read (search, get, proof, verify, export, tenants), write (events), erase, admin (keys; implies all). We store a SHA-256 hash of the key and its prefix; the key is shown once. Errors are { error: { code, message } } with 4xx/5xx; over the monthly limit, writes return 429 plan_limit.

Receipts

Every accepted event returns id, tenant, position, event_hash, prev_hash, server_sig, duplicate. event_hash = sha256(JCS(header)) where the header is {id, project_id, tenant_id, position, occurred_at, actor, action, target, payload_commit, prev_hash} canonicalised per RFC 8785; payload_commit = sha256(salt ∥ JCS(payload)); server_sig is an Ed25519 signature over the raw hash bytes with the key published at /.well-known/auditkit.json. Keep receipts (the TS SDK has keepReceipts): a later verify shows the service never dropped or reordered an event after acknowledging it.

API reference

Generated at build time from the server's OpenAPI document; the live copy is at /openapi.json. All /v1 routes need a Bearer key.

Event (request body)

fieldtypenotes
tenant *stringYour tenant/customer identifier
actor *string
action *string e.g. invoice.delete
targetstring | null
occurred_atstring (date-time)
payloadany JSONAny JSON. Committed by hash; erasable.
idempotency_keystring
client_sigstringbase64 Ed25519 signature by the customer's key (optional)

Endpoints

methodpathsummaryquery / paramsresponses
POST/v1/eventsRecord an audit eventIdempotency-Key string200 Duplicate (idempotent replay)201 Receipt400 Invalid
GET/v1/eventsSearch eventstenant stringactor stringaction stringfrom string (date-time)to string (date-time)limit integer ≤500cursor string200 Page
POST/v1/events/bulkRecord up to 1000 events in one transaction201 Receipts
GET/v1/events/{id}Get one eventid* string200 Event404 Not found
GET/v1/events/{id}/proofMerkle path and anchor receipts for an eventid* string200 Proof
GET/v1/verifyServer-side chain check for a tenanttenant* stringfrom integerto integer200 Verdict
GET/v1/exportJSONL export for offline verificationtenant* stringfrom integerto integer200 application/x-ndjson
GET/v1/tenantsList tenants200 Tenants
POST/v1/tenantsCreate a tenant201 Tenant
POST/v1/erase/{id}Crypto-shred an event's payload (scope: erase)id* string200 Erased
POST/v1/keysCreate an API key (scope: admin)201 Key, shown once
DELETE/v1/keys/{id}Revoke an API key (scope: admin)id* string200 Revoked
GET/.well-known/auditkit.jsonServer public key and anchoring policy200 Policy
POST/mcpMCP Streamable HTTP endpoint (same Bearer key)200 JSON-RPC

Search semantics: action accepts a trailing * (prefix match); from inclusive, to exclusive, ISO 8601; results newest first, limit ≤ 500, next_cursor is the last event id. GET /v1/events/:id/proof returns {anchored: false} for events newer than the last anchor tick, otherwise the Merkle path to the tenant root, project root and global root plus the Rekor and OTS receipts.

Export and verify offline

An export is NDJSON: a manifest line (server, tenant, position range, server public key, export time), every event in position order, then the root lines with their anchor receipts. The requested range is widened to whole root batches so every root can be rebuilt. The verifier needs nothing else.

# export a tenant's evidence (SDK, REST or the dashboard)
curl -H "Authorization: Bearer $AUDITKIT_KEY" \
  "https://api.auditkit.dev/v1/export?tenant=acme" > acme.jsonl

# verify with no call to AuditKit; --online adds a live Rekor/Bitcoin cross-check.
# Until @auditkit/verify is published, run it from the repo (pnpm install && pnpm -r build first):
node packages/verify/dist/cli.js acme.jsonl --pin <server public key, base64 SPKI>
node packages/verify/dist/cli.js acme.jsonl --online --json
# eventual form, once published:
npx @auditkit/verify acme.jsonl --pin <server public key, base64 SPKI>

Until @auditkit/verify is published to npm, run it from the repo: node packages/verify/dist/cli.js <file> after pnpm install && pnpm -r build; npx @auditkit/verify is the eventual command. Checks, in order: manifest, chain, signatures, payloads, client_sigs, roots, anchors, coverage. Exit codes: 0 VALID, 2 VALID_UNANCHORED (chain and signatures hold but no root has a verified anchor yet), 1 INVALID with the first failing check and position. --pin takes the server key as base64 SPKI; without it the key from the manifest is used and the report says so. --online re-fetches the Rekor entry and the Bitcoin block header for a live cross-check; without it the receipts are verified from the proof bytes in the file.

auditkit-verifyexample output · exit 0
# until @auditkit/verify is published, run it from the repo; later: npx @auditkit/verify …
$ node packages/verify/dist/cli.js acme-2026-10.jsonl --pin MCowBQYDK2VwAyEA…
ok   manifest    version 1, tenant ten_01j9k4, positions 0..1283, exported 2026-10-05T09:12:44.102Z
ok   chain       1284 events, prev GENESIS → head 9f3c1a…e1a7
ok   signatures  1284 server signatures verify (pinned key)
ok   payloads    1281 payload commitments verify, 3 erased
?    client_sigs 1284 client signatures present, no client keys given
ok   roots       4 of 4 roots rebuilt and chained to their global root
ok   anchors     8 of 8 anchor receipts verify
     rekor 184201337/24296fb2… for 0..311: verified
     ots alice.btc.calendar.opentimestamps.org for 0..311: verified at 2026-10-04T18:41:08.000Z
     …
ok   coverage    anchored: 0..1283

VALID

Erasure (crypto-shredding)

POST /v1/erase/:id with an erase-scoped key deletes the payload row (payload and salt). The event row, its payload_commit, the chain, roots and anchors are untouched, so everything still verifies; the event reads back with payload: null, erased: true and the verifier reports it as erased. The response is { erased: true, audit: Receipt }: the erasure is appended to the same tenant chain as a payload.erased event (target = the erased id), so the log records who erased what and when. There is no erase tool over MCP. Plan retention applies the same operation automatically to payloads older than the window.

Client-side signing

Pass an Ed25519 private key (clientKey in TS, client_key in Python) and every event carries client_sig = base64(Ed25519.sign(sha256(JCS({tenant, actor, action, target, occurred_at})))) over the raw 32 digest bytes; target is null when absent, tenant is your external tenant id. Register the tenant's public key (dashboard → Tenants → Signing keys, or POST /v1/tenants/:tenant/keys) and the server verifies every signed event on ingest; set require_client_sig on the tenant and it refuses unsigned events. Signatures travel in the export and the verifier checks them again when you pass your public keys (clientKeys, keyed by tenant). Dual-signed events mean neither party can forge the other's entries. The payload is not covered by client_sig; the chain covers it through payload_commit.

MCP for Claude

Streamable HTTP at https://auditkit.dev/mcp, stateless, same process as the REST API. Two ways in: OAuth 2.1 (the server is its own authorization server, discovery at /.well-known/oauth-authorization-server, dynamic client registration at /oauth/register, PKCE; sign in with your email, pick a project, consent at /oauth/authorize, token from /oauth/token) or a Bearer API key for clients that read a JSON config.

claude mcp add --transport http auditkit https://auditkit.dev/mcp
# first use opens a browser: sign in with your email, pick the project, done
{
  "mcpServers": {
    "auditkit": {
      "type": "http",
      "url": "https://auditkit.dev/mcp",
      "headers": { "Authorization": "Bearer ak_live_…" }
    }
  }
}
  • search_eventsSearch audit eventsread-only
  • get_eventGet one audit eventread-only
  • get_proofGet tamper-evidence proof for an eventread-only
  • verify_rangeVerify chain integrity for a tenantread-only
  • list_tenantsList tenantsread-only
  • export_evidenceExport verifier-ready evidenceread-only
  • log_eventRecord an audit eventwrites · not destructive

Each tool declares title, readOnlyHint, destructiveHint, idempotentHint and openWorldHint. export_evidence truncates at 2000 lines over MCP; use REST for full files. Try: "who changed billing settings last week, and is the chain for tenant acme intact?"

Connect from Claude

Claude.ai: Settings → Connectors → Add custom connector, URL https://auditkit.dev/mcp, then sign in with your email (magic link), pick the project, done. The access token Claude receives is scoped to that project; revoke it from the project at any time.

Claude Code: claude mcp add --transport http auditkit https://auditkit.dev/mcp; the same browser sign-in opens on first use. MCP clients that cannot do OAuth: use an API key as a Bearer header (JSON tab above).

Viewer tokens

From the dashboard, create a read-only token scoped to one tenant with a TTL (default 7 days, max 90). The link /viewer?token=vt_… renders that tenant's events with proofs, a verify button and an export link; add &embed=1 to drop the site chrome for an iframe. The same token works as Authorization: Bearer vt_… against /api/viewer/me, /api/viewer/events, /api/viewer/events/:id, /api/viewer/events/:id/proof, /api/viewer/verify and /api/viewer/export. A tenant query parameter is ignored: the token decides.

Self-host

One Node process (22.5 or newer for node:sqlite; the Dockerfile uses 26) running Hono, SQLite in WAL mode, one database file per project, an in-process anchor timer. Backups are file copies. Environment: the Ed25519 private key (never in the database), the anchor interval, optional Stripe and email keys. The same @auditkit/verify verifies exports from your instance; pass --pin with your key. Source under AGPL-3.0: https://github.com/AuditKitDev/auditkit.