Documentation
Logs in. Signed evidence out. Every endpoint, documented.
kolm audits your AI application: the agent and every tool, endpoint, identity and data flow around it, from the logs you already have. These docs cover the whole API surface: send telemetry to /v1/audit, read the signed envelope, and let your reviewer verify it offline against your key.
Send the logs
One curl to POST /v1/audit/scan with the telemetry your application already emits. Go to quickstart
Read the envelope
One canonical JSON object: findings, evidence digest, passport, and the Ed25519 signature over the exact bytes. Go to the envelope
Verify it offline
Your reviewer checks the signature in the browser, in a script, or against the API. Go to verify
App telemetry
The observability your application already emits, imported or captured at the gateway.
importor captureAudit run
Permissions, audit trail, egress and injection graded against scope.
ASR-01.. ASR-08Signed report
One canonical object, sealed over the exact bytes: same logs in, same signature out.
Ed25519envelopeReviewer checks
Offline against the key inside the report. The run is reproducible end to end.
VALIDofflineRun the free scan in three steps.
The scan reads your application's existing logs and returns a watermarked, signed preview of the full report. It is the same engine and the same signature as the paid deliverable; only the tier differs.
Step 1
Get a key
Create an account at /signup. Every request authenticates with Authorization: Bearer ks_... The scan is free.
Step 2
Post the logs
POST /v1/audit/scan accepts JSONL text, a JSON array, or an array of records in the logs field, up to 20,000 records.
Step 3
Read the result
The response carries a summary, the evidence grade, and the full signed envelope. Paste the envelope into /verify to see the check pass.
POST your logs
One call to /v1/audit/scan with the telemetry you already emit.
1 curl, free tierControls run
ASR-01 through ASR-08 grade against scope. Unassessed controls are named, not guessed.
8 controls gradedEnvelope sealed
The report is canonicalized and signed with Ed25519 over the exact bytes.
Ed25519 canonical bytesReviewer checks
Offline in the browser, in a script, or against the public API.
VALID no account neededThe run is reproducible (same input, same output)A run is reproducible when the same logs always produce the same report and the same signature, so a reviewer can re-run the check and get the identical result.: the same logs in produce the same signature out.
curl -s https://kolm.ai/v1/audit/scan \
-H "Authorization: Bearer ks_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"subject": "checkout-assistant",
"logs": [
{ "model": "openai/gpt-4o",
"user": "agent-checkout",
"timestamp": "2026-06-11T09:14:02Z",
"request": {
"messages": [{ "role": "user",
"content": "refund order 1042" }],
"tools": [{ "type": "function",
"function": { "name": "issue_refund" } }]
},
"response": { "choices": [{ "message": {
"role": "assistant",
"tool_calls": [{ "function": {
"name": "issue_refund",
"arguments": "{\"order\":1042}" } }]
} }] }
}
]
}'{
"ok": true,
"id": "audses_9c81b2f0e6a44d1c77aa",
"report_id": "asrr_mbqv3k",
"signed": true,
"key_fingerprint": "...",
"summary": {
"readiness_pct": 64,
"total_findings": 3,
"by_severity": { "high": 1, "medium": 1, "low": 1 },
"blocking_count": 1,
"tamper_evident": false
},
"ingest": { "records": 1, "events": 2 },
"evidence_tier": { "grade": "B" },
"report": { "schema": "kolm-audit-report-1", "...": "..." },
"verify_url": "https://kolm.ai/verify"
}No hash chain in your logs yet? Drop in the evidence-grade logging shim, a zero-dependency logger for Node or Python that emits the canonical event shape with per-agent keys, declared grants and a SHA-256 hash / prev_hash chain. Feed its toJSONL() / to_jsonl() output to the scan and the run grades the evidence tier B (hash-verified). No network call leaves your runtime; see the shim readme.
Optional body fields: subject (what the report names, 200 chars), source (a label for where the logs came from, 64 chars), retention_days (your declared log-retention window), allowed_hosts (your sanctioned egress destinations, up to 200 hostnames; see ASR-3), coverage_declaration (what window and systems the export covers; bound inside the signed envelope, see the envelope), sign and persist (both default true). The Signed Readiness Report is $750 one-time: POST /v1/audit/report/checkout with {"audit_id":"audses_..."} re-signs the same audit without the watermark.
The telemetry kolm reads.
The engine ingests the observability your application already produces. Connector detection sniffs the export shape, most-specific markers first; you can also tag the source yourself. Unparseable lines are skipped at the boundary and reported, never fatal.
| Source | What the detector keys on |
|---|---|
| OpenTelemetry | OTLP exports: resourceSpans / scopeSpans, span attributes under gen_ai.*, http.*, url.*. |
| OpenInference | OTLP spans carrying openinference.span.kind, llm.model_name, tool.parameters, retrieval.documents. Checked before plain OTel. |
| Datadog | LLM Observability spans: meta.kind of llm / tool / agent / workflow / task / embedding / retrieval, ml_app, start_ns. |
| Trace observation exports | Traces with observations[] of type GENERATION / SPAN / EVENT, or bare observations with traceId / modelParameters / usage. |
| Run tree exports | Runs with run_type, child_runs, dotted_order, or provider metadata such as model name and token usage. |
| MCP server logs | JSON-RPC 2.0 rows: tools/call requests paired with results by id, tools/list declared tool surfaces, initialize serverInfo, and kolm mcp-gateway receipts (mcp-tool-call-1). |
| OpenAI Agents SDK | Trace exports: object of trace / trace.span with span_data.type of agent, generation, function, handoff or guardrail. Handoffs land as explicit delegation edges. |
| Gateway logs | LiteLLM, Helicone, Portkey and OpenRouter rows read natively: request / request_body, response / response_body, api_base, provider-prefixed model names. |
| Plain JSONL | One JSON object per line. Also accepted: a JSON array, a wrapper object (data / rows / events / generations), or a single record. OpenAI chat, Responses and Assistants envelopes and Anthropic tool_use blocks are all recognized. |
Running LangGraph, CrewAI or the Vercel AI SDK? Each ships an OpenTelemetry exporter (LangGraph and CrewAI also via OpenInference instrumentation; the Vercel AI SDK via its built-in experimental_telemetry OTel support). Point the exporter at a file or collector and the export arrives through the OpenTelemetry or OpenInference connector above. No kolm-specific code in your application.
The fields the analyzers use.
Seven analyzers (permission, audit trail, agent identity, delegation, model provenance, retrieval and memory, data egress) plus the injection battery read the same normalized events. The dimensions that matter, per record: tools granted (request.tools / functions), tools invoked (assistant tool_calls / function_call), the egress host (api_base, the provider prefix on model, URL-bearing tool arguments like url / webhook / to), the identity behind the call (user, metadata, key id), timestamps (ISO or unix epoch), tamper evidence (hash / prev_hash chains), and message and tool-argument content for the sensitivity scan: PII classes plus secret-shaped tokens, so an API key in a tool-call body flips has_sensitive the same way an SSN does. A record can be minimal; absent dimensions are reported as not assessed, never guessed.
terms are defined in the glossary
{"model": "openai/gpt-4o",
"user": "agent-checkout",
"timestamp": 1781082842,
"api_base": "https://api.openai.com/v1",
"request": { "messages": [...],
"tools": [{ "type": "function",
"function": { "name": "send_email" } }] },
"response": { "choices": [{ "message": {
"tool_calls": [{ "function": {
"name": "send_email",
"arguments": "{\"to\":\"ops@acme.com\"}"
} }] } }] },
"hash": "f3ab...", "prev_hash": "09cd..."}Sessions, for exports bigger than one request.
A single request body caps at 4 MB. Sessions accumulate logs across calls, up to 24 MiB and 20,000 records per session, then run the audit once over the whole export. Each session belongs to your tenant and never crosses it.
| Endpoint | What it does |
|---|---|
| POST /v1/audit/sessions | Open a session. Body: { subject?, source?, retention_days? }. Returns 201 with an audses_* id. |
| POST /v1/audit/sessions/:id/ingest | Append logs. Body: { logs }. Repeat per chunk; returns accepted and the running record_count. |
| POST /v1/audit/sessions/:id/run | Run the audit and sign. Body: { subject?, source?, retention_days?, allowed_hosts?, coverage_declaration?, sign? }. Closes the session. |
| GET /v1/audit/sessions/:id | Status and summary. Raw logs are never echoed back. |
| GET /v1/audit/sessions/:id/report | The deliverable: ?format=json|html|pdf. JSON is the bare signed envelope, downloadable and offline-verifiable. |
| GET /v1/audit/sessions/:id/export | Procurement artifacts over the same signed envelope: ?format=csv|xlsx|drata|vanta|exec|crosswalk|sarif|oscal|aibom|scorecard|modelcard. |
| POST /v1/audit/sessions/:id/delta | Diff two reports you own: ?against=<audses_* or asrr_*>. Readiness movement, control transitions, finding counts. |
| GET /v1/audit/sessions/:id/questionnaire | A standard security questionnaire pre-filled from the signed report: ?template=generic-ai-vendor&format=csv. Unassessed controls answer n/a, never an unsupported yes. |
| GET /v1/audit/reports | Every audit and report your tenant owns; powers the dashboard. |
| POST /v1/audit/import | Pull logs from a URL you control: { source:"url", url, headers? }, same caps, same optional allowed_hosts and coverage_declaration, same signed result. Built for a scheduled sidecar. |
The source tag kolm-capture is reserved. Sending {"source":"kolm-capture"} with no logs audits the gateway captures stored for your tenant and grades the evidence A (first-party capture); supplying it with vendor logs is a clean 400, so imported logs can never claim capture-grade evidence.
What the audit assesses, ASR-1 to ASR-8.
Every finding in a report traces to one of these controls, and each control maps to the frameworks your reviewer cites: SOC 2, ISO 42001, NIST AI RMF, the EU AI Act, OWASP and MITRE ATLAS. kolm maps findings to those standards; it does not certify against them.
| Control | What it requires |
|---|---|
| ASR-1 Least privilege | Scopes held match scopes used; no shared keys across isolation boundaries. |
| ASR-2 Audit trail | Append-only, tamper-evident activity log with a stated retention policy. |
| ASR-3 Data egress | Egress destinations enumerated; sensitive fields redacted before they leave. |
| ASR-4 Injection | Direct and indirect injection and jailbreaks tested and reported with reproductions. |
| ASR-5 Provenance | Model and dependency provenance; MCP and vendor surface enumerated. |
| ASR-6 Evidence | Findings signed, logged, and offline-verifiable. |
| ASR-7 Memory and retrieval integrity | Retrieval sources enumerated and trusted; memory writes carry an integrity link and a recorded author. |
| ASR-8 Multi-agent delegation | Each handoff is attributable and attenuates the sub-agent to a subset of the delegating agent's authority. |
ASR-3 has a dedicated egress analyzer. Every destination host is inventoried with call counts, tools and sensitivity flags, then graded against the allowed_hosts you declare (acme.com admits its subdomains). A destination outside the allowlist is a high finding; a secret-shaped token leaving the boundary is critical; with no allowlist declared, the enumerated-but-unvetted surface is a medium finding. A window with no observed egress marks the control untested and drops it from the readiness denominator, never a silent pass.
ASR-4 results carry an evidence_source per probe. Passive outcomes are scored from observed traffic. The consented active battery behind the Deep Red-Team add-on (+$10,000) sends the same probe corpus, from a fixed seed, against a staging endpoint you name in a written consent record, and merges live outcomes into the same red_team block. Worst outcome wins on merge: an exposed result is never erased.
A control the run could not assess is listed under summary.not_assessed with a reason, and the readiness number covers assessed controls only. The full crosswalk, pillar by pillar, lives at what we test.
One object. The signature covers it.
A report is one canonical JSON object, schema kolm-audit-report-1. The Ed25519 signature covers the recursive, key-sorted, whitespace-free serialization of every field except four: the signature block itself, and the detached evidence attached after signing.
Signed, or detached. Nothing in between.
Signed and tamper-evident: tier, watermark, evidence_tier, the subject, the summary, every finding, the remediation roadmap, the caveats, the evidence digest and the passport. The free scan signs tier:"scan" with the watermark on; the $750 purchase re-signs the same audit as tier:"report". Because both fields sit inside the signed payload, stripping the watermark breaks the signature.
The caveats are bounded claims, signed with everything else. They name the exact PII classes and secret shapes the sensitivity scan covered, so a clean egress posture is never read wider than the detectors. A coverage_declaration (window, systems, attestor, optional vendor Ed25519 signature) binds the vendor's statement of what the export covers next to evidence_tier; when the observed event span disagrees with the declared window, or vendor-supplied logs arrive with no declaration at all, the signed caveats say so.
Excluded from the signed bytes: signature_ed25519 (a signature cannot cover itself) and the detached evidence (log_checkpoint, timestamp_evidence, co_signatures), each of which references the sha256 of the signed canonical payload. The checkpoint embeds the report's RFC 9162 Merkle audit path (log_checkpoint.inclusion: leaf index, tree size, path hashes) at signing time, so transparency-log inclusion verifies offline along with the signature. The key fingerprint is the SHA-256 of the SPKI DER public key, truncated to 128 bits. The full byte-level rules are in the spec; the field-by-field walk is at anatomy of a report.
Scope is contractual. Permission posture, redaction and audit-trail integrity are assessed. Injection is tested and reported, not warranted.
Three ways to check the same signature.
The first two run offline. No account, no upload, no kolm server in the trust path: the report embeds the public key it was signed with, and the canonicalization is byte-identical everywhere, so any edit is self-evident.
Browser
Paste it at /verify
Drop the report JSON on the page. The Ed25519 check runs with WebCrypto, in front of the reviewer, against the published issuer keyring.
Script
kolm-audit-verify.js
A dependency-free module that runs in browsers and Node 20+. It returns a structured result and never throws; exit on a falsy ok to gate a pipeline.
API
POST the envelope
POST /v1/audit/report/verify is public and adds the issuer and revocation checks. It consults a kolm server, so treat it as the convenience path, not the trust path.
120 requests/min per address
// node 20+ (global WebCrypto) or any modern browser
import { verifyAuditReport }
from './kolm-audit-verify.js';
const report = JSON.parse(
fs.readFileSync('asrr_mbqv3k.json', 'utf8'));
const res = await verifyAuditReport(report);
// res = { ok, reason?, key_fingerprint,
// checks: [{ name, ok, detail }] }curl -s https://kolm.ai/v1/audit/report/verify \
-H "Content-Type: application/json" \
-d @asrr_mbqv3k.json
{ "ok": true,
"trusted": true,
"verify": { "ok": true },
"issuer": { "recognized": true, "kid": "live" },
"revocation": { "status": "live" } }A consumer that checks only the signature would accept a forgery re-signed with a rogue key: check trusted, which requires the signature to verify, the embedded key to be a published kolm issuer, and that key to be unrevoked. To pin the issuer yourself, fetch GET /v1/audit/issuer-key and confirm a key's lifecycle at GET /v1/audit/issuer-key/:fp/status (live, rotated or revoked).
Inclusion verifies offline too: verifyInclusionOffline in the same module walks the Merkle audit path embedded at log_checkpoint.inclusion with WebCrypto SHA-256 and confirms the report's leaf sits under the checkpointed root. A checkpoint that predates embedded proofs returns no_embedded_proof; fall back to GET /v1/transparency-log/proof/:seq.
The Trust link that stays current.
Continuous is $299/$999 per month and re-signs your report on a schedule and on every deploy, behind one stable, unguessable URL your buyer keeps. The link states its own freshness: a current report shows when it last re-attested, a report older than 8 days carries a stale banner, and a lapsed subscription says so while staying verifiable.
| Endpoint | What it does |
|---|---|
| POST /v1/audit/continuous/checkout | Subscribe: { "plan": "starter" | "growth" }. |
| POST /v1/audit/continuous/deploy-hook | Force an immediate re-attestation; call it from CI after a deploy (your own API key authenticates it). |
| GET /v1/trust/:slug | The shareable report, public to whoever holds the slug: ?format=html|json|pdf. |
| GET /v1/trust/:slug/export | The same procurement formats as the session export, straight into a buyer's GRC tool, no account. |
| GET /v1/trust/:slug/badge.svg | An embeddable readiness pill for a README or status page. It goes grey after 30 days without a refresh, and on issuer-key revocation. See the badge page. |
| GET /v1/trust/:slug/questionnaire | The pre-filled questionnaire, derived from the signed report behind the link. |
| GET /v1/trust/:slug/delta | The drift between the report the link serves now and the prior signed report. A first attestation returns delta:null with a note. |
To gate pull requests on the delta instead, the kolm-agent-audit GitHub Action scans on every PR, diffs against your prior signed report, and upserts one PR comment; the reference lives in docs/continuous-ci.md.
Every failure is a JSON envelope.
Nothing on this surface throws an HTML error page. A failed call returns { "ok": false, "error": "...", "detail": "..." } with the status codes below, and an unmatched path under /v1/audit or /v1/trust returns a JSON 404.
| Status · error | Meaning |
|---|---|
| 401 auth_required | Missing or invalid key. Send Authorization: Bearer ks_... |
| 400 logs_required · no_records | The body had no logs field, or nothing in it parsed as a record. |
| 400 source_reserved | The kolm-capture source tag is reserved for the gateway-capture bridge. |
| 400 invalid_allowed_hosts · invalid_coverage_declaration | allowed_hosts takes at most 200 hostname strings, each 253 chars or fewer; a malformed coverage declaration is rejected with the failing field named in detail. A declaration carrying an invalid vendor signature is a hard reject. |
| 404 session_not_found · not_found | No such session, report or Trust link for this tenant. A foreign id reads the same as an absent one. |
| 409 session_closed · report_not_ready | A session that already produced its report stays closed; a report endpoint called before run says so. |
| 413 too_many_records · session_too_large | Caps: 20,000 records and 24 MiB of JSONL per session or scan; 4 MB per request body. Split the export across ingest calls or sessions. |
| 422 audit_failed | The records parsed but could not be analyzed. |
| 429 rate_limited | The public verify endpoint caps at 120 requests per minute per address. Verify offline instead. |
| 503 no_signer_configured · pdf_unavailable | The server cannot sign or render right now; the audit itself is unaffected. Retry or mail dev@kolm.ai. |
That is the whole surface: logs in at /v1/audit, a signed envelope out, and a check your reviewer can run without us. The byte-level format lives in the spec; questions reach dev@kolm.ai.
Verify the sample, then send your own logs.
Open the verifier on a signed sample, then run the free scan on your application's telemetry and hand your buyer a report they can check in their own browser.
Caveats: Scope is contractual. Permission posture, redaction and audit-trail integrity are assessed. Injection is tested and reported, not warranted.