Skip to main content
ZeroEval accepts standard OTLP trace data at POST /v1/traces, so any OpenTelemetry-instrumented application can export directly — through a collector, or straight from your app using an OTLP exporter.

When to use OTLP

Use this integration when:
  • Your application already uses OpenTelemetry for instrumentation
  • You want to fan out traces to multiple backends (ZeroEval + Datadog, Jaeger, etc.)
  • You’re running infrastructure you can’t modify but can route through a collector
  • You prefer a vendor-neutral instrumentation layer
If you’re starting fresh, the Python SDK or TypeScript SDK provide a simpler setup with automatic LLM instrumentation.

Endpoint Reference

POST https://api.zeroeval.com/v1/traces
HeaderValue
AuthorizationBearer YOUR_ZEROEVAL_API_KEY
Content-Typeapplication/json or application/x-protobuf
The endpoint accepts the standard ExportTraceServiceRequest payload defined in the OTLP specification. Spans are converted to ZeroEval’s internal format — trace IDs, parent-child relationships, attributes, and status are all preserved.

Option 1: OpenTelemetry Collector

Route traces through a collector when you need batching, processing, or multi-destination fan-out.

Collector Configuration

Create otel-collector-config.yaml:
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024

exporters:
  otlphttp:
    endpoint: https://api.zeroeval.com
    headers:
      Authorization: "Bearer ${env:ZEROEVAL_API_KEY}"
    traces_endpoint: https://api.zeroeval.com/v1/traces

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

Run with Docker Compose

services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"
      - "4318:4318"
    environment:
      - ZEROEVAL_API_KEY=${ZEROEVAL_API_KEY}
    restart: unless-stopped

Run with Kubernetes

apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-config
data:
  config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
    processors:
      batch:
        timeout: 1s
      k8sattributes:
        extract:
          metadata:
            - k8s.namespace.name
            - k8s.deployment.name
            - k8s.pod.name
    exporters:
      otlphttp:
        endpoint: https://api.zeroeval.com
        headers:
          Authorization: "Bearer ${env:ZEROEVAL_API_KEY}"
        traces_endpoint: https://api.zeroeval.com/v1/traces
    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch, k8sattributes]
          exporters: [otlphttp]
---
apiVersion: v1
kind: Secret
metadata:
  name: zeroeval-secret
type: Opaque
stringData:
  api-key: "YOUR_ZEROEVAL_API_KEY"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector
spec:
  replicas: 2
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
        - name: otel-collector
          image: otel/opentelemetry-collector-contrib:latest
          args: ["--config=/etc/config.yaml"]
          env:
            - name: ZEROEVAL_API_KEY
              valueFrom:
                secretKeyRef:
                  name: zeroeval-secret
                  key: api-key
          ports:
            - containerPort: 4317
              name: otlp-grpc
            - containerPort: 4318
              name: otlp-http
          volumeMounts:
            - name: config
              mountPath: /etc/config.yaml
              subPath: config.yaml
      volumes:
        - name: config
          configMap:
            name: otel-collector-config
---
apiVersion: v1
kind: Service
metadata:
  name: otel-collector
spec:
  selector:
    app: otel-collector
  ports:
    - name: otlp-grpc
      port: 4317
    - name: otlp-http
      port: 4318

Option 2: Direct from Python

Export OTLP traces directly from your Python application without a collector. Use the ZeroEvalOTLPProvider included in the SDK, or configure a standard OTLPSpanExporter.

Using ZeroEvalOTLPProvider

from opentelemetry import trace
from zeroeval.providers import ZeroEvalOTLPProvider

provider = ZeroEvalOTLPProvider(
    api_key="YOUR_ZEROEVAL_API_KEY",
    service_name="my-service"
)
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("my-service")

with tracer.start_as_current_span("process_request") as span:
    span.set_attribute("user.id", "12345")
    result = do_work()
    span.set_attribute("result.status", "ok")

Using standard OTLPSpanExporter

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

exporter = OTLPSpanExporter(
    endpoint="https://api.zeroeval.com/v1/traces",
    headers={"Authorization": "Bearer YOUR_ZEROEVAL_API_KEY"}
)

provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)

Option 3: Direct from Node.js

import { NodeTracerProvider } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";

const exporter = new OTLPTraceExporter({
  url: "https://api.zeroeval.com/v1/traces",
  headers: {
    Authorization: "Bearer YOUR_ZEROEVAL_API_KEY",
  },
});

const provider = new NodeTracerProvider();
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();

Attribute Mapping

ZeroEval maps standard OpenTelemetry span attributes to its internal format:
OTLP AttributeZeroEval FieldNotes
span.namenameSpan name
span.kindkindMapped to generic, llm, etc.
span.statusstatusok, error, unset
span.start_timestarted_atNanosecond timestamp converted to ISO 8601
span.end_timeended_atNanosecond timestamp converted to ISO 8601
span.trace_idtrace_idHex-encoded trace ID
span.parent_span_idparent_span_idHex-encoded span ID
span.attributes.*attributesAll attributes preserved
resource.service.nameService identificationUsed for grouping

LLM Spans

To get LLM-specific features (cost calculation, token tracking), set these attributes on your spans:
AttributeDescription
llm.provider or gen_ai.systemProvider name (openai, anthropic, etc.)
llm.model or gen_ai.request.modelModel identifier
llm.input_tokens or gen_ai.usage.prompt_tokensInput token count
llm.output_tokens or gen_ai.usage.completion_tokensOutput token count

Sessions

Attach session context to your OTLP spans via attributes:
AttributeDescription
zeroeval.session.idSession ID for grouping traces
zeroeval.session.nameHuman-readable session name
The ZeroEvalOTLPProvider stamps these automatically when you configure a session via environment variables (ZEROEVAL_SESSION_ID, ZEROEVAL_SESSION_NAME).