> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bentolabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OTel transport

> The lower-level surface. Wire Bento into an existing OpenTelemetry pipeline.

The OTel transport is the `SpanProcessor` the analytics layer (`bentolabs_sdk.analytics`) sits on top of. Wire it into an OpenTelemetry pipeline directly when you already have one configured, and skip the analytics layer entirely.

This is the path when a `TracerProvider` is already in place and a second one isn't welcome, when batching, sampling, or resource attributes need to stay under your control, or when a framework like LangChain, LlamaIndex, or the OpenAI Agents SDK already emits OTel spans that need a destination. For the ergonomic `track_ai` / `begin` / decorator surface without managing OTel objects, use the [analytics layer](/python/track-ai) instead.

## Install

```bash theme={null}
pip install bentolabs-sdk opentelemetry-api opentelemetry-sdk
```

## Usage

```python theme={null}
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider

from bentolabs_sdk import BentoLabsSpanProcessor

provider = TracerProvider()
provider.add_span_processor(BentoLabsSpanProcessor())
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("example")
with tracer.start_as_current_span("agent.run") as span:
    span.set_attribute("input.value", "Summarize the incident")
    span.set_attribute("output.value", "The incident is resolved.")

provider.force_flush()
provider.shutdown()
```

`BentoLabsSpanProcessor` batches spans with OpenTelemetry defaults and POSTs them to `${base_url}/v1/traces` with `Authorization: Bearer bl_pk_...`.

## What ends up in the dashboard

These OTel attributes map to the same dashboard columns the analytics layer fills in. Set them on your spans:

| OTel attribute                   | Lands in              | Set via                   |
| -------------------------------- | --------------------- | ------------------------- |
| `span.name`                      | `spans.name`          | `tracer.start_span(name)` |
| `gen_ai.user.id`                 | `traces.user_id`      | `span.set_attribute(...)` |
| `gen_ai.conversation.id`         | `traces.session_id`   | `span.set_attribute(...)` |
| `gen_ai.request.model`           | `spans.model`         | `span.set_attribute(...)` |
| `gen_ai.system`                  | `spans.provider`      | `span.set_attribute(...)` |
| `input.value`                    | `spans.input`         | `span.set_attribute(...)` |
| `output.value`                   | `spans.output`        | `span.set_attribute(...)` |
| `openinference.span.kind="tool"` | `spans.kind = "tool"` | `span.set_attribute(...)` |

See [Attributes](/concepts/attributes) for the full table.

## Wire the exporter directly

To wire a custom `SpanProcessor` (a `SimpleSpanProcessor` for tests, or a `BatchSpanProcessor` with custom limits), use `BentoLabsTraceExporter` directly:

```python theme={null}
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

from bentolabs_sdk import BentoLabsTraceExporter

provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(BentoLabsTraceExporter()))
```

## Errors at boot

`BentoLabsSpanProcessor` and `BentoLabsTraceExporter` both resolve options at construction time, which can raise `BentoAuthError`:

```python theme={null}
from bentolabs_sdk import BentoAuthError, BentoLabsSpanProcessor

try:
    processor = BentoLabsSpanProcessor(base_url="http://localhost:8080")
except BentoAuthError as exc:
    if exc.code == "missing_api_key":
        # telemetry disabled or misconfigured
        pass
    raise
```

See [Configuration → Errors](/python/configuration#errors) for the full set of error codes.
