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

# OpenTelemetry Export (OTLP)

> Stream KnoxCall request traces and integration logs to your own OpenTelemetry collector or SIEM — Grafana, Datadog, Elastic, SigNoz, or Splunk — using the OTLP/HTTP protocol with standard semantic conventions.

# OpenTelemetry Export (OTLP)

KnoxCall's integration logging is aligned to **OpenTelemetry standards**. Every request KnoxCall proxies and every webhook it delivers can be exported, in real time, to your own observability backend over the standard **OTLP/HTTP** protocol — so the same event stream you see in the dashboard lands in your SIEM with no custom glue.

## What gets exported

When an OTLP endpoint is configured, KnoxCall emits three signals:

### Traces

One **`SERVER` span per HTTP request** through the platform, plus dedicated spans for AI Gateway calls (using the OpenTelemetry [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/)) and security events. Spans use the standard HTTP attributes (`http.request.method`, `url.path`, `http.response.status_code`) with KnoxCall-specific context under the `knoxcall.*` namespace.

KnoxCall participates in **W3C distributed tracing**:

* Inbound `traceparent` headers are honoured, so a request that arrives already traced continues the **same** trace.
* Outbound calls — both the proxied upstream request and webhook deliveries — carry an injected `traceparent`, so the trace extends end-to-end through KnoxCall into your upstream APIs.

### Logs (integration logs)

Every proxied request and every webhook delivery is emitted as an **OTLP `LogRecord`**:

| Event               | `event.name`                | Key attributes                                                                                                                                                              |
| ------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Proxied API request | `knoxcall.proxy.request`    | `http.request.method`, `url.path`, `http.response.status_code`, `server.address`, `knoxcall.request_id`, `knoxcall.tenant_id`, `knoxcall.route_name`, `knoxcall.latency_ms` |
| Webhook delivery    | `knoxcall.webhook.delivery` | `http.request.method`, `http.response.status_code`, `server.address`, `knoxcall.webhook.event_type`, `knoxcall.webhook.success`, `knoxcall.webhook.retry_count`             |

Severity maps from outcome: `2xx/3xx → INFO`, `4xx → WARN`, `5xx` or transport error `→ ERROR`.

<Note>
  Log records carry the **structured envelope** (method, path, status, IDs, latency) — not request or response bodies. Full payloads remain (obfuscated) in the [API Logs](/monitoring/api-logs) store, so the OTLP stream stays cheap to ship and index.
</Note>

### Metrics (analytics)

Per-request analytics are exported as OTLP metrics on a 60-second interval, so request volume, error rate, and latency show up as native dashboards/monitors in your backend:

| Instrument                        | Type      | Unit        | Dimensions                                                                                                        |
| --------------------------------- | --------- | ----------- | ----------------------------------------------------------------------------------------------------------------- |
| `knoxcall.proxy.requests`         | Counter   | `{request}` | `http.response.status_code`, `http.response.status_class`, `knoxcall.route_name`, `knoxcall.environment`, `error` |
| `knoxcall.proxy.request.duration` | Histogram | `ms`        | (same)                                                                                                            |

On the operator export these carry a `knoxcall.tenant_id` dimension; on a per-tenant export (below) they don't, since every point already belongs to that tenant.

## Configuring the endpoint

There are two ways to point KnoxCall at a collector. **Environment variables take precedence** when set; otherwise the admin-UI integration is used.

### Option 1 — Admin UI (recommended for self-hosted)

1. Go to **Settings → Integrations** and switch to the **Global** scope (admins only; available on both cloud and self-hosted deployments).
2. Open **OpenTelemetry export**.
3. Fill in:
   * **OTLP HTTP endpoint** — the base URL of your collector, e.g. `https://otlp.your-collector.com`. KnoxCall appends `/v1/traces`, `/v1/logs`, and `/v1/metrics`.
   * **Service name** *(optional)* — the resource `service.name` (defaults to `knoxcall`).
   * **Auth headers** *(optional, encrypted at rest)* — comma-separated `key=value` pairs, e.g. `Authorization=Bearer <token>, X-Scope-OrgID=acme`.
   * **Export traces / logs / metrics** — per-signal toggles (all on by default); turn off any signal you don't want shipped.
4. Save. Export activates immediately — **no restart required**.

### Option 2 — Environment variables

| Variable                      | Purpose                                      |
| ----------------------------- | -------------------------------------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP/HTTP endpoint (enables export)     |
| `OTEL_EXPORTER_OTLP_HEADERS`  | Auth headers, `key=value,key2=value2`        |
| `OTEL_SERVICE_NAME`           | Resource `service.name` (default `knoxcall`) |
| `OTEL_TRACES_EXPORTER=none`   | Disable the traces signal only               |
| `OTEL_LOGS_EXPORTER=none`     | Disable the logs signal only                 |

When neither an env endpoint nor the integration is configured, export is fully off and adds no overhead.

## Compatible backends

Any OTLP/HTTP-compatible collector or backend, including:

* **Grafana** (Tempo for traces, Loki for logs) / Grafana Cloud
* **Datadog** (OTLP intake)
* **Elastic** APM
* **SigNoz**
* **Splunk** (via the OpenTelemetry Collector)
* A self-run **OpenTelemetry Collector** that fans out to anything downstream

## Operator export vs per-tenant export

There are two independent OTLP destinations, for two different audiences:

* **Operator export** (this page's *OpenTelemetry (OTLP export)* card, **Global** scope, or the env vars). Ships the **whole gateway's** traces + logs + metrics to the operator's collector — for whoever runs the deployment to monitor KnoxCall itself. Operator metrics/logs carry a `knoxcall.tenant_id` dimension so you can facet by tenant.
* **Per-tenant export** (the *Telemetry export (OTLP)* integration, tenant scope). Each tenant points KnoxCall at **their own** collector/SIEM and receives **only their own** request logs and analytics metrics — nothing from other tenants, and no traces. This is configured per workspace, right next to Email/Twilio/S3 in **Settings → Integrations**.

Both can run at once: a request fans out to the operator's collector **and** the originating tenant's collector.

### Configure per-tenant export

1. As a workspace admin, go to **Settings → Integrations → Telemetry export (OTLP)**.
2. Set the **OTLP HTTP endpoint** (your collector — e.g. a Datadog Agent or OTel Collector), optional **service name**, and optional **auth headers** (encrypted at rest).
3. Save. Only that workspace's `knoxcall.proxy.request` logs and `knoxcall.proxy.requests` / `knoxcall.proxy.request.duration` metrics ship to it; applies on the next request, no restart.

## Error tracking (Sentry)

The OTLP signals above are for shipping telemetry to your own collector. To
**catch errors** — with native grouping, alerting, and release health — KnoxCall
ships an optional [Sentry](https://sentry.io) integration covering both tiers:

* **Browser** (`@sentry/react`) — admin-UI uncaught exceptions, React render
  crashes, and unhandled promise rejections.
* **Backend** (`@sentry/node`) — server-side exceptions on any `5xx`, plus
  uncaught exceptions and unhandled rejections outside the request cycle.

It's a **separate** integration card — **Error tracking (Sentry)** — independent
of the OpenTelemetry export above. Configure either, both, or neither.

### Configure

1. Create Sentry project(s) and copy the **DSN(s)** (`https://<key>@o0.ingest.sentry.io/0`).
   A DSN identifies a project, not a secret. Using **separate browser and server
   projects is recommended** (cleaner source maps, releases, and access control),
   but one project for both works too.
2. In **Settings → Integrations** (Global scope) → **Error tracking (Sentry)**:
   * **Sentry DSN — browser (admin UI errors)** → the browser project DSN.
   * **Sentry DSN — server (backend errors)** → the backend project DSN.
   * Save. (Or set `SENTRY_DSN` / `SENTRY_DSN_BACKEND` env vars — env wins, same
     as the OTLP endpoint. `SENTRY_ENVIRONMENT` sets the environment tag.)
3. The browser DSN is delivered to the admin UI at page-load time (no rebuild);
   the backend DSN stays server-side and applies immediately on save.

When a DSN isn't set, that SDK never initialises and adds no overhead.

### What it captures, and what it deliberately doesn't

* **Captures:** browser uncaught errors / rejections / React render errors (with
  the component stack); backend `5xx` exceptions and process-level crashes —
  tagged with release and environment. Backend errors carry the opaque
  `errorId` that's also in the server log and the client response, for
  correlation.
* **Tenant-filterable:** events are tagged with `tenant_id` (and identified by
  user **UUID** only — never email/name) on both tiers, so you can filter "all
  errors for tenant X" in Sentry. These are internal identifiers, not PII, but
  they are a data category sent to Sentry — see the note below.
* **No session replay.** Replay records the live admin DOM, which on a
  multi-tenant console can capture other tenants' data — off by design.
* **No PII.** Cookies, IPs, and request bodies are never attached
  (`sendDefaultPii: false`); token/secret-shaped values are redacted before an
  event leaves the process; the backend reports the request **path** only (no
  query string).
* **No tracing interference.** The backend SDK runs as a pure error reporter
  (`skipOpenTelemetrySetup`, no auto-instrumentation) — OpenTelemetry above keeps
  owning all traces and propagation. (Backend errors are therefore not
  OTel-trace-linked; the traces live in your OTLP backend.)

### Sentry as an OTLP backend (alternative)

If you'd rather not run the native backend SDK, you can instead point the **OTLP
HTTP endpoint** above at Sentry's OTLP ingest; `5xx` exceptions recorded on the
request span then surface as Sentry errors. Sentry's OTLP ingest is newer and
thinner on grouping/breadcrumbs than the native SDK, so prefer the **Sentry DSN —
server** field above unless you're consolidating on OTLP.

<Note>
  Configuring a Sentry DSN sends error reports to Sentry. For the hosted service
  that makes Sentry a **sub-processor**; if you run KnoxCall yourself the data goes
  to **your** Sentry project. Either way, review your sub-processor disclosures
  before enabling it in production.
</Note>

## Related

* [API Request Logs](/monitoring/api-logs) — full request/response history in the dashboard
* [Audit Logs](/monitoring/audit-logs) — security and configuration events (also deliverable via the `audit.event` webhook)
* [Webhooks](/webhooks/overview) — push individual events to your own endpoints
