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

# OpenTelemetry ingestion

> Send the gen_ai.* traces your agent already emits to the Niadra OTLP endpoint.

If your agent already emits OpenTelemetry traces with the `gen_ai.*` attributes, it can write to Niadra without new calls in the conversation path: point an OTLP exporter at Niadra and every model call becomes the messages of the conversation, with who spoke and when. This is the fastest way to start building memory from an agent you do not want to touch, and a common way to capture a vendor platform that exports traces but has no Niadra integration.

Reading still happens through `context()`, the history tools or MCP. OpenTelemetry covers the write side.

## The endpoint

Niadra accepts OTLP over HTTP with a JSON body (`Content-Type: application/json`):

```text theme={null}
POST https://acme-prod.us-east-1.api.niadra.com/v1/otel/v1/traces
```

Authenticate with the source key in `Authorization: Bearer`, the same key your agent would use for the SDK, with the `track` scope. The answer is the standard OTLP one: rejected spans are counted in `partialSuccess.rejectedSpans`, with the reasons in `errorMessage`. A protobuf body is refused with 422, so set your exporter to `http/json`.

## What Niadra reads from a span

The `gen_ai` semantic conventions are still changing, and several generations of attributes live side by side in the libraries in use today. Niadra reads them in cascade, so you do not have to pin a library version for us:

| What Niadra needs | Where it looks, in order                                                                                                                                                                                                                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The messages      | `gen_ai.input.messages` and `gen_ai.output.messages` (current); then `gen_ai.prompt` and `gen_ai.completion` as JSON; then the indexed `gen_ai.prompt.{n}.role`/`content` and `gen_ai.completion.{n}.role`/`content`; then OpenInference `llm.input_messages.{n}.message.*` and `llm.output_messages.{n}.message.*` |
| Who spoke         | The message role: `user` and `human` become the customer, `assistant` and `model` the AI agent. System, developer and tool messages are left out of the conversation                                                                                                                                                |
| The conversation  | `gen_ai.conversation.id`, then `session.id`, then the trace id                                                                                                                                                                                                                                                      |
| When              | The span start time for input messages, the end time for output messages                                                                                                                                                                                                                                            |

Two things OpenTelemetry has no attribute for, so you set them yourself, on the span or on the resource:

| Attribute              | Example                                 | Why                                                                                                                         |
| ---------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `niadra.channel`       | `whatsapp`                              | There is no OpenTelemetry attribute for the customer channel. Without it, the source default applies                        |
| `niadra.handle.<type>` | `niadra.handle.app_user_id = "u-48213"` | The customer the conversation is about, one attribute per handle type. `enduser.id` also works and becomes an `app_user_id` |

Every span repeats the conversation so far, so each message gets the idempotency key of its position in the conversation: a re-exported history deduplicates instead of duplicating.

<Warning>
  Span attributes can end up in every backend your collector exports to. Prefer an internal id (`app_user_id` or `enduser.id`) over a phone number or an e-mail, and send the Niadra attributes only on the pipeline that goes to Niadra. Personal data never travels in a URL.
</Warning>

## Steps

### 1. Point an exporter at Niadra

Add an OTLP exporter next to the one you already have, with the JSON encoding.

The OpenTelemetry SDK for Node.js has a JSON exporter. The Python SDK exports protobuf only, so send its spans to an OpenTelemetry Collector and let the Collector forward them to Niadra as JSON.

<CodeGroup>
  ```yaml Collector theme={null}
  exporters:
    otlphttp/niadra:
      traces_endpoint: https://acme-prod.us-east-1.api.niadra.com/v1/otel/v1/traces
      encoding: json
      headers:
        Authorization: "Bearer ${env:NIADRA_API_KEY}"

  service:
    pipelines:
      traces/niadra:
        receivers: [otlp]
        exporters: [otlphttp/niadra]
  ```

  ```python Python theme={null}
  # The agent exports to the Collector as usual; the Collector forwards JSON to Niadra.
  from opentelemetry import trace
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
  from opentelemetry.sdk.trace import TracerProvider
  from opentelemetry.sdk.trace.export import BatchSpanProcessor

  provider = TracerProvider()
  provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")))
  trace.set_tracer_provider(provider)
  ```

  ```typescript TypeScript theme={null}
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; // JSON over HTTP

  const niadraExporter = new OTLPTraceExporter({
    url: "https://acme-prod.us-east-1.api.niadra.com/v1/otel/v1/traces",
    headers: { Authorization: `Bearer ${process.env.NIADRA_API_KEY}` },
  });

  const provider = new NodeTracerProvider({
    spanProcessors: [new BatchSpanProcessor(niadraExporter)],
  });
  provider.register();
  ```

  ```sh Shell theme={null}
  # Zero-code instrumentation in Node.js, straight to Niadra
  export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://acme-prod.us-east-1.api.niadra.com/v1/otel/v1/traces"
  export OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Bearer ${NIADRA_API_KEY}"
  export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="http/json"
  ```
</CodeGroup>

### 2. Set the channel and the customer

Set the Niadra attributes on the span that carries the messages, or once on the resource for a process that serves one channel. Keep `gen_ai.conversation.id` stable for the whole conversation: it becomes the `conversation_id`.

<CodeGroup>
  ```python Python theme={null}
  tracer = trace.get_tracer("support-agent")

  with tracer.start_as_current_span("chat wa-8812") as span:
      span.set_attribute("gen_ai.conversation.id", "wa-8812")
      span.set_attribute("niadra.channel", "whatsapp")
      span.set_attribute("niadra.handle.app_user_id", "u-48213")
      reply = client.chat.completions.create(model=MODEL, messages=messages)
  ```

  ```typescript TypeScript theme={null}
  import { trace } from "@opentelemetry/api";

  const tracer = trace.getTracer("support-agent");

  await tracer.startActiveSpan("chat wa-8812", async (span) => {
    span.setAttributes({
      "gen_ai.conversation.id": "wa-8812",
      "niadra.channel": "whatsapp",
      "niadra.handle.app_user_id": "u-48213",
    });
    const reply = await client.chat.completions.create({ model: MODEL, messages });
    span.end();
    return reply;
  });
  ```
</CodeGroup>

Attributes are read from the span itself and from its resource. When your model-client instrumentation writes the messages on a child span, set the Niadra attributes on the resource, or copy them to that span with a span processor.

### 3. Turn on content capture

Most `gen_ai` instrumentations leave message content out by default. Niadra needs it to build memory, so enable content capture in the instrumentation you use (for many Python instrumentations, `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`). If you prefer to keep content out of your other backends, run a second pipeline for Niadra only.

### 4. Confirm the events arrived

A span becomes messages with the same guarantees as a batch: the raw content is stored before the answer and events are ordered by the time they happened. Read the timeline of the customer to check:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://acme-prod.us-east-1.api.niadra.com/v1/history/timeline" \
    -H "Authorization: Bearer $NIADRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "subject": { "type": "app_user_id", "value": "u-48213" }, "limit": 5 }'
  ```
</CodeGroup>

## When to use the SDK instead

OpenTelemetry records what the model saw and said. The SDK adds what traces do not carry: the context read before the answer and the `context_stamp` of each agent turn, verification with `verify()`, links between handles with `identify()`, agent actions with `closes`, handoffs and the end of the conversation. Those feed the [context use](/en/concepts/context-use) measurement. A common path is to start with OpenTelemetry to build memory from day one, then add `context()` and the SDK writes to the agents that answer customers.

## Next steps

<CardGroup cols={2}>
  <Card title="Events and the batch" href="/en/concepts/events">
    what an event is and how it is deduplicated.
  </Card>

  <Card title="Quickstart" href="/en/quickstart">
    reading context with the SDK.
  </Card>

  <Card title="Receive OTLP traces" href="/en/api/otel-traces">
    the endpoint reference.
  </Card>

  <Card title="Webhooks from your systems" href="/en/guides/system-webhooks">
    events from CRM, ERP and help desk.
  </Card>
</CardGroup>
