> ## 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.

# Send traces to Coach

> Instrument your agent with the Glassray SDK or any OpenTelemetry exporter and point it at your local Coach.

Coach ingests **OTLP/JSON** at `http://127.0.0.1:5899/v1/traces`, bearer-authed with a locally generated key. Any of the three ways below works - pick whichever fits your stack.

## Get your ingest key

Every Coach install generates a local key. It's printed in the terminal when Coach starts and shown on the **Waiting for traces** screen - or read it from the API:

```bash theme={null}
curl -s http://127.0.0.1:5899/api/info | grep -o '"apiKey":"[^"]*"'
```

<Tabs>
  <Tab title="Glassray SDK">
    The [`@glassray/tracing`](https://www.npmjs.com/package/@glassray/tracing) SDK handles OTLP encoding, gzip, batching, and retries. Wrap your agent with `trace`, and each step with `llm` / `tool`:

    ```bash theme={null}
    npm install @glassray/tracing
    ```

    ```ts theme={null}
    import { Glassray } from "@glassray/tracing";

    const glassray = new Glassray({ environment: "local", agent: "support-bot" });

    await glassray.trace("handle-ticket", { customer: "acme" }, async (t) => {
      // Capture the user's question as the trace input (the SDK can't see closures).
      t.setInput(userMessage);

      const plan = await t.llm(
        "answer",
        { model: "claude-opus-4-8", provider: "anthropic", input: userMessage },
        () => anthropic.messages.create(request), // usage + output captured from the response
      );

      const order = await t.tool("lookup-order", { input: { orderId: "4821" } }, () =>
        lookupOrder("4821"),
      );

      return plan;
    });

    await glassray.flush(); // serverless: waitUntil(glassray.flush())
    ```

    Point the SDK at your local Coach (it defaults to Glassray Cloud):

    ```bash theme={null}
    export GLASSRAY_ENDPOINT="http://127.0.0.1:5899"
    export GLASSRAY_API_KEY="<your-local-key>"
    ```

    <Info>
      Token usage is extracted automatically from the model response (`usage.input_tokens`/`output_tokens`, or the OpenAI equivalents). `model` and `provider` on `t.llm` populate the LLM span; `t.setInput` / an `input` option capture prompts the SDK can't otherwise see.
    </Info>
  </Tab>

  <Tab title="OpenTelemetry exporter">
    Already instrumented with OpenTelemetry? Point your existing exporter at Coach - no Glassray SDK. Coach speaks OTLP/HTTP **JSON**.

    ```bash theme={null}
    export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:5899"
    export OTEL_EXPORTER_OTLP_PROTOCOL="http/json"
    export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <your-local-key>"
    ```

    Works with any OTel-compatible GenAI instrumentation (OpenLIT, OpenLLMetry, OpenInference, or a self-hosted Collector). The same wire format powers hosted Glassray - see [OpenTelemetry ingestion](/otlp-ingestion) for the attribute contract.
  </Tab>

  <Tab title="Raw OTLP">
    Send an OTLP/JSON envelope directly - the wire format the SDK produces. Gzip is accepted and decoded transparently.

    ```bash theme={null}
    curl -X POST http://127.0.0.1:5899/v1/traces \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer <your-local-key>" \
      -d '{ "resourceSpans": [ /* ... */ ] }'
    ```

    A `200` with an empty `{}` body acknowledges the batch.
  </Tab>
</Tabs>

## How ingest behaves

<CardGroup cols={2}>
  <Card title="Live" icon="wave-pulse">
    Every ingested trace is pushed to the dashboard over SSE - the view updates without a refresh.
  </Card>

  <Card title="Batch-friendly" icon="layer-group">
    Spans are merged by span id, so a batch exporter that flushes one trace across several POSTs accumulates instead of overwriting.
  </Card>

  <Card title="Fault-tolerant" icon="shield-check">
    A malformed trace in a batch is skipped and logged - the batch's other traces still land.
  </Card>

  <Card title="Bounded" icon="ruler">
    Request bodies are capped at 16 MiB - an over-limit POST is rejected whole, so keep exporter batches under the cap.
  </Card>
</CardGroup>

<Note>
  Once traces are flowing, head to [Find & fix deviations](/coach/analyze) to run discovery and turn what it finds into evals.
</Note>
