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

# Bring your own OpenTelemetry

> Point any OTLP exporter at Glassray instead of using the SDK — and the one delivery contract you must honor: whole trace per request.

If you're already instrumented with OpenTelemetry — via the OTel SDK, the Vercel AI SDK's telemetry, OpenLLMetry, OpenInference, or a Collector — you don't need `@glassray/tracing`. Point your exporter at the OTLP endpoint:

```
POST https://app.glassray.ai/api/public/otel/v1/traces
Authorization: Bearer <your-api-key>
```

The key comes from adding an **OpenTelemetry** source under **Settings → Sources** — the same key the SDK uses. The full endpoint reference (formats, gzip, size limits, error codes) is on the [OpenTelemetry ingestion page](/otlp-ingestion).

## The delivery contract: whole trace per request

This is the one thing raw-OTel users must know. Glassray's ingest **replaces, not merges, per trace ID**: within one request, spans are grouped by `traceId` into one document per trace (multiple traces per request are fine) — but across requests, the latest push of a trace ID **overwrites** the previous one.

That interacts badly with the default OTel export strategies:

<Warning>
  * A vanilla **`BatchSpanProcessor`** flushes on size/time, splitting one run's spans across several requests — every batch overwrites the last, silently losing the earlier spans of long runs.
  * A **`SimpleSpanProcessor`** sends one request per span — the trace degenerates to whichever span arrived last.
</Warning>

## The fix: force-flush per run

Make each run's spans ship together by forcing a flush when the run's root span ends:

```ts theme={null}
const provider = new NodeTracerProvider({ spanProcessors: [processor] });

const runAgent = async (input: Input) => {
  try {
    return await tracer.startActiveSpan("handle-ticket", async (span) => {
      // ... your agent ...
    });
  } finally {
    await provider.forceFlush(); // ship this run's spans as one request
  }
};
```

With a Collector in the path, the same logic applies to its `batch` processor — one run's spans must leave in one export. If your runs are short-lived processes (a CLI, a job), flushing on process exit is enough.

<Note>
  This is exactly the contract `@glassray/tracing` automates: it buffers each run and POSTs once when the root settles. If you're not already invested in OTel, the [SDK](/sdk-quickstart) is the path with no caveats. Server-side span merging (which would lift this contract for arbitrary collectors) is on the roadmap, not available today.
</Note>

## Attributes Glassray reads

Standard vocabularies light up automatically: `gen_ai.*` (operation, model, provider, token usage, `gen_ai.input.messages` / `gen_ai.output.messages`, plus the older indexed `gen_ai.prompt.{i}.*` / `gen_ai.completion.{i}.*` family) and OpenInference's generic `input.value` / `output.value`. Support for other vendor-specific content attributes (Vercel AI SDK `ai.*`, OpenInference's indexed `llm.*` messages, Traceloop) is still expanding — with those instrumentations, structure/model/tokens land today and content coverage depends on which of the attributes above they emit. Trace-level input/output is read from the **root span's** content attributes, and `session.id` groups conversation turns. To carry Glassray's [metadata convention](/sdk-metadata), set `glassray.customer` / `glassray.agent` / `glassray.flow` as resource attributes (root-span attributes override). The former `glassray.environment` / `deployment.environment.name` dimension is **retired** — the ingest key selects the project, so both attributes are ignored.
