Documentation

OpenTelemetry integration

AuditTrail ingests OTLP/JSON natively: any app instrumented with OpenTelemetry GenAI semantic conventions (OpenLLMetry, Langfuse's OTel-based SDKs, hand-rolled OTel) can send spans with zero AuditTrail SDK installed — traces, the DAG, cost accounting and constitutional rule evaluation all work from gen_ai.* attributes alone.

Endpoint

POST /api/v1/ingest/otlp
Content-Type: application/json
Authorization: Bearer sk-at-...        (an AuditTrail API key)

The body is a standard OTLP ExportTraceServiceRequest in JSON encoding. Protobuf is not accepted — configure your exporter for http/json (a protobuf body gets a clear 415 telling you the same). Batches are capped at 2,000 spans per request.

Exporter configuration

Whether you can point an SDK straight at AuditTrail depends on whether it can emit OTLP/JSON:

JS / Node — direct. The JS SDK implements the http/json protocol natively, so env vars are the whole setup:

bash
# Signal-specific endpoint — used verbatim (the generic
# OTEL_EXPORTER_OTLP_ENDPOINT would append /v1/traces per the OTel spec).
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://your-audittrail-host/api/v1/ingest/otlp"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/json"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer sk-at-..."

Python — bridge through an OpenTelemetry Collector. The Python SDK implements only grpc and http/protobuf — setting OTEL_EXPORTER_OTLP_PROTOCOL=http/json fails exporter selection (the upstream JSON exporter package is still unreleased). Ship the default protobuf to a local Collector and let it re-encode:

yaml
# otel-collector.yaml
receivers:
  otlp:
    protocols:
      http:                      # listens on :4318
exporters:
  otlphttp/audittrail:
    traces_endpoint: https://your-audittrail-host/api/v1/ingest/otlp
    encoding: json               # re-encode protobuf -> OTLP/JSON
    headers:
      Authorization: Bearer sk-at-...
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [otlphttp/audittrail]
bash
# App side: default http/protobuf, pointed at the Collector.
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

(For Python apps you control, the first-party Python SDK is the native, Collector-free path.)

With OpenLLMetry (Python) the app side is then just:

bash
pip install traceloop-sdk
python
from traceloop.sdk import Traceloop
 
Traceloop.init(app_name="support-bot")  # exports to the Collector above
# ... run your LangChain / LlamaIndex / raw-OpenAI app unchanged

Attribute mapping

otlp_mapper.py translates GenAI semantic conventions to native span fields; anything unmapped is preserved verbatim under span.attributes.

OTel attributeAuditTrail field
gen_ai.operation.name (else OTel span kind)span_type (llm / tool / agent / …)
gen_ai.usage.input_tokens or …prompt_tokenstokens_in
gen_ai.usage.output_tokens or …completion_tokenstokens_out
gen_ai.response.model or gen_ai.request.modelmodel (drives cost accounting)
gen_ai.content.prompt / gen_ai.content.completionspan input / output
resource attrs (service.name, gen_ai.agent.*)merged into every span

Spans arriving through OTLP are first-class: the governor evaluates your YAML rules on them at ingest, they stream to the live dashboard over WebSocket, and they feed the causal-attribution pipeline like SDK spans.

Gotchas

  • JSON only. A protobuf body 415s. JS/Node: set OTEL_EXPORTER_OTLP_PROTOCOL=http/json. Python: the SDK cannot emit http/json (grpc + http/protobuf only) — bridge through the Collector config above.
  • Spans missing span_id, trace_id or start_time are skipped and counted in the response's rejected tally with per-span error strings — the endpoint tells you exactly what it dropped.
  • Cost is only computed when a model attribute is present; content capture follows your instrumentation's privacy settings (OpenLLMetry gates prompt/completion capture behind its own flag).