Audkit
Reference

REST API

Every /api/v1 endpoint, with authentication, required scopes, parameters, and responses.

Everything the SDK does is a plain HTTP call against /api/v1, so any language can use Audkit. The default host is https://audkit.dev.

Authentication

Every endpoint takes a project API key as a bearer token:

Authorization: Bearer <project-api-key>

Keys are minted per project under Settings → API Keys and are scope-enforced per endpoint:

ScopeEndpoints
log:writePOST /api/v1/log
log:readGET /api/v1/events, GET /api/v1/legal-holds/:holdId/proof
log:exportGET /api/v1/export
log:verifyGET /api/v1/verify, /root, /proof/inclusion, /proof/consistency, /anchors

The key determines the project — there is no project id in any path. Common failures:

StatusBodyCause
401{ "error": "Missing API key" }No bearer token.
401{ "error": "Invalid API key" }Unknown, revoked, or expired key.
403{ "error": "API key missing <scope> scope" }Key lacks the endpoint's scope.

Endpoints that address a stream accept stream=events (default) or stream=project-audit; anything else is a 400.

POST /api/v1/log

Ingest one event. Scope: log:write.

Body

FieldTypeNotes
actionstringRequired, 1–256 chars.
actor{ type, id, display? }Required. type ≤ 128, id ≤ 512.
statusenumsuccess (default), failed, pending, approved, denied.
target{ type, id, display? }
riskenumlow, medium, high, critical.
context, metadataobjectArbitrary JSON records.
requestId, sessionId, ipAddress, userAgentstringRequest attribution.
agentId, model, toolName, toolCallId, approvalId, policyVersionstringAgent attribution.
inputHash, outputHashstringCommit to large payloads without storing them.
idstringSigned ingests only — the id the signature commits to.
noncestringSigned ingests only — 32-byte lowercase hex blinding nonce.
signingKeyId, signaturestringCustomer Ed25519 attestation; must be sent together.

Response

202 Accepted
{ "id": "evt_…", "status": "sealed" }

202 here is a promise about durable state, not about a queue. Before it is sent, the event has been sequenced under the project row lock, encrypted, committed as an RFC 6962 leaf, and written to Postgres. Only order-independent mirror work runs afterwards.

Errors

StatusCause
400Invalid JSON, schema failure (details carries the field errors), signingKeyId/signature sent apart, signed event missing id or nonce, unknown or revoked signing key, signature that does not verify, or an unsigned event on a project that requires signing.
429Per-key rate limit (500 requests / 10 seconds) — carries Retry-After — or a billing suspension, which also returns a reason.
500The seal failed; nothing was recorded.

GET /api/v1/events

Query stored events. Scope: log:read.

Filter mode

ParameterNotes
action, status, actorId, targetIdExact-match filters.
from, toTime bounds.
limitDefault 50, capped at 500.
cursorOpaque offset token; pass back the nextCursor from the previous page.
{ "events": [ /* … */ ], "nextCursor": "50" }

nextCursor is present only when the page came back full.

Entity mode

Pass entityType and entityId to get an entity-centred timeline instead:

ParameterNotes
entityType, entityIdBoth required to enter this mode.
directionoutgoing (what it did), incoming (what happened to it), or all (default).
limitSame default and cap.

Entity mode returns { "events": [ … ] } with no cursor.

GET /api/v1/export

Stream matching events as a downloadable file. Scope: log:export.

ParameterNotes
formatcsv (default) or json (newline-delimited JSON).
action, status, actorId, targetId, from, toSame filters as /events.
maxOptional hard cap on exported rows.

The body is streamed page-by-page from Postgres, so large exports never buffer in memory, and Content-Disposition: attachment marks it as a download. Feed the json form to npx audkit verify full for an offline rebuild.

GET /api/v1/verify

Rebuild the stream's tree from row content and return a service-signed receipt. Scope: log:verify.

ParameterNotes
streamevents (default) or project-audit.
{
  "receiptVersion": 2,
  "stream": "events",
  "projectId": "prj_…",
  "valid": true,
  "checkedCount": 18442,
  "treeSize": 18442,
  "rootHash": "a8d772…",
  "firstBreak": null,
  "verifiedAt": "2026-08-23T09:14:02.118Z",
  "servicePublicKey": "…",
  "signature": "…"
}

firstBreak is null when valid, otherwise { sequence, reason, detail } — see the reason list.

GET /api/v1/root

The current signed tree head. Scope: log:verify.

ParameterNotes
streamevents (default) or project-audit.
{
  "sth": {
    "v": 1,
    "projectId": "prj_…",
    "stream": "events",
    "treeSize": 18442,
    "rootHash": "a8d772…",
    "timestamp": "2026-08-23T09:14:02.118Z",
    "signature": "…"
  },
  "servicePublicKey": "…"
}

The head is Ed25519-signed by the service key. This is the reference point the SDK's auto-verification hangs off.

GET /api/v1/proof/inclusion

RFC 6962 inclusion proof: the audit path linking one leaf to the root at treeSize. Scope: log:verify.

ParameterNotes
eventIdAddress the leaf by event id.
leafIndexOr by 0-based index. Ignored when eventId is given.
treeSizeDefaults to the live tree size; must be in [1, liveSize].
streamevents (default) or project-audit.
{ "stream": "events", "leafIndex": 17, "treeSize": 18442, "leafHash": "…", "proof": ["…"] }

404 when the event id is unknown; 400 when treeSize or leafIndex is out of range. Verify the path locally — a forged one cannot reproduce a root you pinned.

GET /api/v1/proof/consistency

RFC 6962 consistency proof: that the tree at oldSize is a prefix of the tree at newSize. Scope: log:verify.

ParameterNotes
oldSizeRequired, non-negative integer.
newSizeDefaults to the live tree size; must be in [oldSize, liveSize].
streamevents (default) or project-audit.
{ "stream": "events", "oldSize": 12000, "newSize": 18442, "proof": ["…"] }

The trivial cases — oldSize of 0, or oldSize === newSize — return an empty proof, which verifies.

GET /api/v1/anchors

The project's attested tree heads, newest first. Scope: log:verify.

ParameterNotes
streamevents (default) or project-audit.
limitDefault 20, capped at 100.
{
  "stream": "events",
  "heads": [
    {
      "treeSize": 18442,
      "rootHash": "a8d772…",
      "timestamp": "2026-08-23T09:00:00.000Z",
      "signature": "…",
      "anchorType": "rekor",
      "anchorRef": "…",
      "anchorProof": { "…": "…" },
      "anchoredAt": "2026-08-23T09:00:11.402Z"
    }
  ]
}

Anchor fields are null until the head is published. Resolve anchorRef at the transparency log yourself — https://search.sigstore.dev/?uuid=<anchorRef> or rekor-cli get --uuid <anchorRef> — and compare the anchored root against your own rebuild.

GET /api/v1/legal-holds/:holdId/proof

Prove that one event was captured under a legal hold. Scope: log:read.

ParameterNotes
eventIdRequired query parameter.

Returns a Merkle inclusion proof against the hold's committed membership root, which is itself anchored in the project's project_audit stream. Verify it offline against that root.

StatusCause
400eventId missing.
404Unknown hold, or the event is not part of it.

On this page