OpenTelemetry (OTLP)

OpenTelemetry (OTLP)

The hosted service speaks OTLP/HTTP in both directions. Export sends the trace of every hosted request to your own tracing backend, so CLI decisions sit next to the rest of your application’s traces. Ingest accepts your own OpenTelemetry spans, for example from an agent running in local mode, and files them next to hosted requests.

Export

Add destinations under Observability > Data settings, in the OTLP export section (owners and admins). Each destination has a name, an endpoint, a protocol (http/protobuf, the default, or http/json), and headers. Header values, which usually hold an API key, are encrypted at rest with the same scheme as provider connections and never shown again; Test sends one span to check the destination.

PresetEndpointHeaders to fill in
Datadoghttps://otlp.datadoghq.com/v1/tracesdd-api-key
Langfusehttps://cloud.langfuse.com/api/public/otel/v1/tracesauthorization, x-langfuse-ingestion-version
LangSmithhttps://api.smith.langchain.com/otel/v1/tracesx-api-key, langsmith-project
Arize Phoenixyour Phoenix collector’s /v1/traces URLauthorization
Honeycombhttps://api.honeycomb.io/v1/tracesx-honeycomb-team
Generic OTLP/HTTPany OTLP/HTTP traces endpointas your collector needs

A preset only pre-fills the form: change the endpoint for another region or a self-hosted instance. Endpoints must be https and resolve to public addresses.

What is sent

One trace per hosted request, with the same span tree as the dashboard’s Timeline:

WhereAttributes
Resourceservice.name = "cci-hosted-api", cci.org.id
Root span (named after method and path)cci.request_id, cci.operation.name, http.response.status_code, url.path
Model-call spansgen_ai.provider.name, gen_ai.request.model, gen_ai.response.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.finish_reasons, cci.purpose, cci.query.id, the estimated cost
Other spanscci.* attributes such as cci.query.type, cci.profile.name, cci.binding.decision, cci.guarantee.type, cci.evidence.access_level
Every spanopeninference.span.kind (LLM, CHAIN or EVALUATOR) and langfuse.observation.type (generation, span or evaluator), so Phoenix and Langfuse render model calls and statistics natively

The trace id is the request’s trace id, so a request you sent with a traceparent header joins your own trace in the destination. Prompt and reply previews (gen_ai.input.preview, gen_ai.output.preview) are exported only when payload capture kept them; requests on zero-retention profiles carry none.

Delivery

Traces are queued when they are written and sent every 15 seconds. A failed delivery is retried with exponential backoff, from 15 seconds up to an hour between attempts, and dropped after about ten attempts. Export never delays an API response.

Ingest

POST https://cci.gitdate.ink/api/v1/traces
Authorization: Bearer cci_live_...          (a key with the events:write scope)
Content-Type: application/x-protobuf        (or application/json)
Content-Encoding: gzip                      (optional)

The endpoint is a standard OTLP/HTTP traces receiver, so any OpenTelemetry exporter can send to it. With the Python SDK (pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http):

import os
 
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http import Compression
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
 
exporter = OTLPSpanExporter(
    endpoint="https://cci.gitdate.ink/api/v1/traces",
    headers={"Authorization": f"Bearer {os.environ['CLI_API_KEY']}"},
    compression=Compression.Gzip,
)
provider = TracerProvider(resource=Resource.create({"service.name": "claims-agent"}))
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
 
tracer = trace.get_tracer("claims-agent")
 
with tracer.start_as_current_span("guard payment") as span:
    span.set_attribute("session.id", "triage-run-42")
    span.set_attribute("gen_ai.conversation.id", "th_77")
    answer = local_client.evaluate(context, {"payment": payment_gate}).answers["payment"]
    span.set_attribute("cci.profile.name", "claim-payments-v1")
    span.set_attribute("cci.decision", answer.decision)
    span.set_attribute("cci.guarantee.type", answer.guarantee.type)

The same exporter can be configured from the environment instead: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://cci.gitdate.ink/api/v1/traces and OTEL_EXPORTER_OTLP_TRACES_HEADERS=Authorization=Bearer%20cci_live_... (the header value URL-encoded).

How ingested spans are stored:

  • Spans are grouped by trace id. Each trace becomes one request with source: "otlp" and a request id otl_<16 hex> that is stable for the trace, so spans of one trace sent in several batches land in the same request. Search them with source:otlp.
  • The request’s operation is otlp.<service.name>, its route is the root span’s name, and it is marked as an error when any span has error status.
  • The session comes from the root span’s session.id (or cci.session.id) attribute, and the thread from gen_ai.conversation.id (or cci.thread.id), so ingested traces appear in Sessions next to hosted requests and guard events of the same thread.
  • Provider, model and token counts are read from gen_ai.provider.name (or gen_ai.system), gen_ai.request.model, gen_ai.usage.input_tokens and gen_ai.usage.output_tokens.
  • Content attributes (gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions, gen_ai.tool.call.arguments, gen_ai.tool.call.result, input.value, output.value) are dropped when payload capture is Off or zero retention is the organization default.
Limit or errorValue
Body20 MB, and 16 MB after gzip decompression (413)
Spans per request5,000; spans beyond that, and spans without a valid trace id, are rejected
ResponseAn OTLP ExportTraceServiceResponse in the request’s format, with partialSuccess.rejectedSpans when spans were rejected
Other content types415 unsupported_media_type
A body that does not decode422 otlp_decode_failed

Ingest records traces; it does not evaluate anything. Guarantees still come from evaluate (hosted) or LocalCLIClient (local); ingest makes the local decisions visible in the same place as hosted ones.