Documentation

VerifiedTrail reads standard OpenTelemetry. There is nothing to install in your agent: point its exporter at the endpoint with an API key from Settings, and sessions appear within a minute.

Connect Claude Code

Set these in the shell that starts Claude Code, then use it as usual.

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf        # http/json works too
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://verifiedtrail.com/api/v1/otel/logs
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $VERIFIEDTRAIL_API_KEY"
export OTEL_LOG_TOOL_DETAILS=1                          # optional, see below

The last variable makes Claude Code send tool arguments so they can be hashed. They are dropped before anything is stored. Leave it out and tool calls are still recorded, without an argument hash.

Connect through the OpenTelemetry Collector

The standard otlphttp exporter works with its defaults, protobuf and gzip included.

exporters:
  otlphttp/verifiedtrail:
    traces_endpoint: https://verifiedtrail.com/api/v1/otel/traces
    logs_endpoint: https://verifiedtrail.com/api/v1/otel/logs
    headers:
      Authorization: "Bearer ${env:VERIFIEDTRAIL_API_KEY}"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/verifiedtrail]

Connect any OTLP exporter

Send OTLP over HTTP, as protobuf or JSON, to the traces endpoint with the header Authorization: Bearer and your key. gRPC is not supported. These attributes decide what an entry says:

AttributeWhat it becomes
gen_ai.agent.id, gen_ai.agent.name, service.nameWhich agent this is. The first one present names the agent; an agent appears in the dashboard with its first event.
enduser.id, user.idWho the agent was acting for. Shown as the principal on every entry and counted for AU-3 and AU-12.
gen_ai.operation.name, mcp.method.nameWhat kind of event it is: a model call, a tool call, an agent invocation, an MCP call.
gen_ai.tool.name, mcp.tool.nameThe tool that was called. The daily review compares it with the authorization record.
gen_ai.request.model, gen_ai.usage.*Model and token counts.
span statusOutcome: success, failure or unknown.

Then confirm a session arrived and its chain recomputes:

curl -s -H "Authorization: Bearer $VERIFIEDTRAIL_API_KEY" \
  https://verifiedtrail.com/api/v1/sessions/$SESSION_ID/verify

What is stored, and what never is

Stored per event: its type, time, tool, model, token counts, the principal, the outcome, trace and span ids, and non-content attributes up to 8 KB.

Never stored: prompts, model output, file contents, tool arguments and tool results. Arguments are hashed with SHA-256 so two calls can be shown to be the same without keeping what they said.

Each entry is hashed over every stored field plus the hash before it. The database rejects updates and deletes on audit rows. Once a day the head of each idle session is written to Sigstore Rekor and timestamped by DigiCert. How to check it yourself

Authorization records and the daily review

On an agent's page, an owner or admin records what the agent is for, who authorized it, which tools it may use, which it must not, and which need a person's approval. The record is hashed, signed and anchored like a session. Every night the last 24 hours of activity are reviewed:

CheckWhat is reported
IntegrityEvery session touched in the last 24 hours is re-hashed. A chain that does not recompute is a critical finding.
ScopeTool calls are compared with the authorization record in force: tools outside the allowed list, tools on the denied list, and more calls than the daily limit.
ApprovalsFor tools that need a person's approval, each call must carry an accept decision made by a user. Automatic approvals do not count.
Failure rate and volumeAt least five failures making up a fifth of the day's events, or three times the recent daily average, are reported.
No authorizationAn agent that is active with no authorization record in force is reported once, until a record exists.

The rules are fixed and involve no model. Each review is recorded; that record is the evidence for AU-6.

API

RouteAuthenticationPurpose
POST /api/v1/otel/logsAPI keyClaude Code log events. Other log records are ignored.
POST /api/v1/otel/tracesAPI keySpans from any OTLP exporter. Up to 500 spans per request.
GET /api/v1/sessions/{id}/verifyAPI key or signed inRecomputes the hash chain from the stored rows.
GET /api/v1/sessions/{id}/anchorsAPI key or signed inThe Rekor entry and RFC 3161 timestamp token for the chain head.
GET /api/v1/sessions/{id}/evidence.pdfAPI key or signed inEvidence package for one session.
GET /api/v1/orgs/{id}/evidence.pdf?from=&to=API key or signed inEvidence package for a period; the last 30 days if no dates are given.

Re-sending the same spans is safe: entries already recorded are skipped.

StatusMeaning
401The API key is missing, wrong or revoked.
400The body is not valid OTLP JSON, protobuf or gzip.
413More than 500 spans in one request, or a body larger than about 4 MB as sent. Split the export.
415A Content-Encoding other than gzip.

Ask a question or request a pilot