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)
| field | type | notes |
|---|---|---|
tenant * | string | Your tenant/customer identifier |
actor * | string | |
action * | string | e.g. invoice.delete |
target | string | null | |
occurred_at | string (date-time) | |
payload | any JSON | Any JSON. Committed by hash; erasable. |
idempotency_key | string | |
client_sig | string | base64 Ed25519 signature by the customer's key (optional) |
Endpoints
| method | path | summary | query / params | responses |
|---|---|---|---|---|
POST | /v1/events | Record an audit event | Idempotency-Key string | 200 Duplicate (idempotent replay)201 Receipt400 Invalid |
GET | /v1/events | Search events | tenant stringactor stringaction stringfrom string (date-time)to string (date-time)limit integer ≤500cursor string | 200 Page |
POST | /v1/events/bulk | Record up to 1000 events in one transaction | 201 Receipts | |
GET | /v1/events/{id} | Get one event | id* string | 200 Event404 Not found |
GET | /v1/events/{id}/proof | Merkle path and anchor receipts for an event | id* string | 200 Proof |
GET | /v1/verify | Server-side chain check for a tenant | tenant* stringfrom integerto integer | 200 Verdict |
GET | /v1/export | JSONL export for offline verification | tenant* stringfrom integerto integer | 200 application/x-ndjson |
GET | /v1/tenants | List tenants | 200 Tenants | |
POST | /v1/tenants | Create a tenant | 201 Tenant | |
POST | /v1/erase/{id} | Crypto-shred an event's payload (scope: erase) | id* string | 200 Erased |
POST | /v1/keys | Create an API key (scope: admin) | 201 Key, shown once | |
DELETE | /v1/keys/{id} | Revoke an API key (scope: admin) | id* string | 200 Revoked |
GET | /.well-known/auditkit.json | Server public key and anchoring policy | 200 Policy | |
POST | /mcp | MCP 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.
# 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-onlyget_eventGet one audit eventread-onlyget_proofGet tamper-evidence proof for an eventread-onlyverify_rangeVerify chain integrity for a tenantread-onlylist_tenantsList tenantsread-onlyexport_evidenceExport verifier-ready evidenceread-onlylog_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.