# OpenTelemetry Integration

Revenium accepts OTLP (OpenTelemetry Protocol) data directly from your instrumented applications. If your app already emits telemetry via an OTLP exporter—using LangChain, the OpenAI Python SDK with OTel instrumentation, or your own custom spans—you can point it at Revenium with a few lines of configuration and start seeing AI usage data immediately.

This page covers Revenium-specific configuration. It assumes you already understand OTLP and have an exporter set up.&#x20;

***

## Endpoints

Revenium accepts both OTLP/HTTP and OTLP/gRPC.

### OTLP/HTTP

| Path                                         | Accepts                                          |
| -------------------------------------------- | ------------------------------------------------ |
| `https://api.revenium.ai/v2/otlp/v1/traces`  | Traces (JSON or protobuf)                        |
| `https://api.revenium.ai/v2/otlp/v1/logs`    | Logs (JSON or protobuf)                          |
| `https://api.revenium.ai/v2/otlp/v1/metrics` | Metrics (JSON or protobuf)                       |
| `https://api.revenium.ai/v2/otlp`            | Unified (traces + logs + metrics in one request) |

Both `application/json` and `application/x-protobuf` content types are accepted.

### OTLP/gRPC

| Host              | Port   | Service                                               |
| ----------------- | ------ | ----------------------------------------------------- |
| `api.revenium.ai` | `4317` | `opentelemetry.proto.collector.trace.v1.TraceService` |

The gRPC endpoint accepts traces. For logs and metrics, use the HTTP endpoints.

***

## Authentication

Your Revenium API key goes in the `Authorization` header as a Bearer token. Both `Authorization: Bearer` and `x-api-key` headers are accepted.

```
Authorization: Bearer hak_your_tenant_yourkey
```

or

```
x-api-key: hak_your_tenant_yourkey
```

For frameworks or SDKs that cannot set custom headers (e.g., some CLI tools), you can pass the API key as an OTLP resource attribute instead:

```bash
export OTEL_RESOURCE_ATTRIBUTES="revenium.api_key=hak_your_tenant_yourkey"
```

This travels inside the encrypted OTLP payload body and is secure over TLS. Note: this fallback is **not** available for OTLP/gRPC (interceptors run before message deserialization on the gRPC path).

You can find your API key in the Revenium app under **Settings → API Keys**.

***

## Quick Start

### 1. Configure your OTLP exporter

Set the exporter endpoint and auth header. The exact env vars depend on your SDK, but the standard OpenTelemetry environment variables work with all OTel SDKs:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.revenium.ai/v2/otlp
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer hak_your_tenant_yourkey"
```

If your framework uses separate endpoint vars for signals:

```bash
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.revenium.ai/v2/otlp/v1/traces
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://api.revenium.ai/v2/otlp/v1/logs
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer hak_your_tenant_yourkey"
```

{% hint style="warning" %}
**Python SDK note:** The Python OpenTelemetry SDK requires header values in `OTEL_EXPORTER_OTLP_HEADERS` to be percent-encoded. Use `Bearer%20` instead of `Bearer` (with a space):

```bash
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20hak_your_tenant_yourkey"
```

Other SDKs (Node.js, Go, Java) accept the unencoded space. If data is not arriving from a Python app, check for a "Header format invalid" error and switch to the percent-encoded form.
{% endhint %}

### 2. Send a trace

Run your application normally. The OTel instrumentation in your app creates spans when AI calls are made, and the exporter sends them to Revenium.

### 3. See it in Revenium

Go to **System & Transaction Logs** or **Trace Analytics**. If the span contained recognized GenAI attributes (see below), you'll see token counts, model, provider, and cost data populated automatically.

***

## Supported Frameworks and SDKs

Revenium auto-detects the source based on resource attributes and instrumentation scope names. No special configuration in Revenium is required—just point your exporter at the endpoint.

### Python: opentelemetry-instrumentation-openai

```python
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

tracer_provider = TracerProvider()
exporter = OTLPSpanExporter(
    endpoint="https://api.revenium.ai/v2/otlp/v1/traces",
    headers={"Authorization": "Bearer hak_your_tenant_yourkey"},
)
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)
```

### Python: opentelemetry-instrumentation-anthropic

```python
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

tracer_provider = TracerProvider()
exporter = OTLPSpanExporter(
    endpoint="https://api.revenium.ai/v2/otlp/v1/traces",
    headers={"Authorization": "Bearer hak_your_tenant_yourkey"},
)
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
AnthropicInstrumentor().instrument(tracer_provider=tracer_provider)
```

### Python: LangChain via OpenInference or OpenLLMetry

Both [OpenInference](https://github.com/Arize-ai/openinference) and [OpenLLMetry](https://github.com/traceloop/openllmetry) emit GenAI semantic convention spans. Configure the OTLP exporter as shown above; Revenium picks them up automatically.

### Node.js: @opentelemetry/instrumentation-openai

```javascript
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { OpenAIInstrumentation } = require('@opentelemetry/instrumentation-openai');

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'https://api.revenium.ai/v2/otlp/v1/traces',
    headers: { 'Authorization': 'Bearer hak_your_tenant_yourkey' },
  }),
  instrumentations: [new OpenAIInstrumentation()],
});

sdk.start();
```

### Go SDK (gRPC)

```go
import (
    "context"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "google.golang.org/grpc/credentials"
)

ctx := context.Background()
exporter, _ := otlptracegrpc.New(ctx,
    otlptracegrpc.WithEndpoint("api.revenium.ai:4317"),
    otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")),
    otlptracegrpc.WithHeaders(map[string]string{
        "Authorization": "Bearer hak_your_tenant_yourkey",
    }),
)
```

### OpenTelemetry Collector

If you're already running an OTel Collector, add Revenium as an exporter:

```yaml
exporters:
  otlphttp/revenium:
    endpoint: https://api.revenium.ai/v2/otlp
    headers:
      Authorization: "Bearer hak_your_tenant_yourkey"

service:
  pipelines:
    traces:
      exporters: [otlphttp/revenium]
    logs:
      exporters: [otlphttp/revenium]
```

***

## What Gets Captured Automatically

Revenium reads [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) from your spans and log records. If your instrumentation library follows the spec, these fields populate automatically—no extra code required.

### Token Counts

| GenAI Attribute                            | Revenium Field        | Notes                                                    |
| ------------------------------------------ | --------------------- | -------------------------------------------------------- |
| `gen_ai.usage.input_tokens`                | Input tokens          | Also accepts deprecated `gen_ai.usage.prompt_tokens`     |
| `gen_ai.usage.output_tokens`               | Output tokens         | Also accepts deprecated `gen_ai.usage.completion_tokens` |
| `gen_ai.usage.cache_read_input_tokens`     | Cache read tokens     | Also accepts `gen_ai.usage.cache_read_tokens`            |
| `gen_ai.usage.cache_creation_input_tokens` | Cache creation tokens | Also accepts `gen_ai.usage.cache_creation_tokens`        |

### Model and Provider

| GenAI Attribute         | Revenium Field  | Notes                                           |
| ----------------------- | --------------- | ----------------------------------------------- |
| `gen_ai.response.model` | Model           | Preferred; falls back to `gen_ai.request.model` |
| `gen_ai.provider.name`  | Provider        | Also accepts deprecated `gen_ai.system`         |
| `gen_ai.request.model`  | Model (request) | Used when response model is absent              |

### Operation Type and Finish Reason

| GenAI Attribute                  | Revenium Field | Values                                                                                   |
| -------------------------------- | -------------- | ---------------------------------------------------------------------------------------- |
| `gen_ai.operation.name`          | Operation type | `chat` → Chat; `embeddings` → Embed; `text_completion` → Chat; `generate_content` → Chat |
| `gen_ai.response.finish_reasons` | Stop reason    | Array; Revenium uses the first element — see mapping table below                         |

**Finish reason mapping:**

| `gen_ai.response.finish_reasons` value | Revenium stop reason |
| -------------------------------------- | -------------------- |
| `stop`, `end_turn`                     | End                  |
| `max_tokens`, `length`                 | Token Limit          |
| `stop_sequence`                        | End Sequence         |
| `content_filter`                       | Error                |
| `tool_calls`, `function_call`          | End                  |
| *(any other value)*                    | End                  |

### Request Parameters

| GenAI Attribute              | Revenium Field     | Notes                                           |
| ---------------------------- | ------------------ | ----------------------------------------------- |
| `gen_ai.request.temperature` | Temperature        | Standard OTel attribute; captured automatically |
| `gen_ai.response.id`         | System fingerprint | Response identifier from the model provider     |

### Timing and Errors

Span start/end nanosecond timestamps (`startTimeUnixNano`, `endTimeUnixNano`) are used to populate request time, response time, and duration. Error details are read from `exception.message` (primary) and `error.type` (fallback). HTTP status codes are read from `http.response.status_code`.

### Infrastructure Context

| Standard OTel Attribute  | Revenium Field |
| ------------------------ | -------------- |
| `deployment.environment` | Environment    |
| `cloud.region`           | Region         |

### Span Hierarchy

`traceId`, `spanId`, and `parentSpanId` from spans are mapped to Revenium's trace, transaction, and parent transaction fields respectively. This powers the Trace Analytics dependency tree and waterfall visualizations.

{% hint style="info" %}
Revenium skips spans with `gen_ai.operation.name` of `execute_tool`, `invoke_agent`, or `create_agent` — these are orchestration spans, not LLM calls. Only spans that represent actual model invocations are metered.
{% endhint %}

***

## Deep Attribution with `revenium.*` Attributes

Standard OTel tells you *what happened* — which model was called, how many tokens were used. `revenium.*` attributes tell Revenium *why it happened and who it happened for*. This is what enables per-customer, per-product, per-agent, and per-job cost attribution that goes beyond what the OTel spec captures.

Set these on spans or log records (record-level attributes take precedence over resource-level attributes, so you can set defaults at the resource level and override on individual spans).

### System Fingerprint

{% hint style="info" %}
`revenium.system.fingerprint` is one of Revenium's most powerful attribution tools. It lets you tag each AI call with an identifier that represents *what configuration produced it* — your system prompt version, your prompt template ID, your agent configuration hash, or any other identifier you use to distinguish one variant from another.

This is how you answer questions like: "Which version of my system prompt is more expensive?" or "Did my prompt optimization actually reduce costs?" Standard OTel has no equivalent.
{% endhint %}

| Attribute                     | Revenium Field     | Notes                                                                                       |
| ----------------------------- | ------------------ | ------------------------------------------------------------------------------------------- |
| `gen_ai.response.id`          | System fingerprint | Populated automatically if your provider returns a response ID                              |
| `revenium.system.fingerprint` | System fingerprint | Use this to set your own fingerprint — overrides `gen_ai.response.id` when both are present |

```python
span.set_attribute("revenium.system.fingerprint", "system-prompt-v3-2025-03-12")
```

Use any value that uniquely identifies the configuration: a version string, a git SHA, a prompt template ID, or a hash of your system prompt content.

### Organization and Product

| Attribute                    | Revenium Field | Example                 |
| ---------------------------- | -------------- | ----------------------- |
| `revenium.organization.name` | Organization   | `"acme-corp"`           |
| `revenium.product.name`      | Product        | `"document-summarizer"` |
| `revenium.subscription.id`   | Subscription   | `"enterprise-plan-q1"`  |

### Subscribers and Users

| Attribute                              | Revenium Field   | Example              |
| -------------------------------------- | ---------------- | -------------------- |
| `revenium.subscriber.id`               | Subscriber ID    | `"user-12345"`       |
| `revenium.subscriber.email`            | Subscriber email | `"user@example.com"` |
| `revenium.subscriber.credential.name`  | Credential name  | `"api-key-prod"`     |
| `revenium.subscriber.credential.value` | Credential value | `"pk-abc123"`        |

### Agents and Agentic Workflows

| Attribute                   | Revenium Field   | Example                       |
| --------------------------- | ---------------- | ----------------------------- |
| `revenium.agent.name`       | Agent            | `"support-agent-v2"`          |
| `revenium.task.type`        | Task type        | `"summarize"`                 |
| `revenium.trace.type`       | Trace type       | `"rag-pipeline"`              |
| `revenium.trace.name`       | Trace name       | `"support-ticket-resolution"` |
| `revenium.transaction.name` | Transaction name | `"retrieve-context"`          |

### Squads (Multi-Agent Teams)

| Attribute             | Revenium Field | Example                   |
| --------------------- | -------------- | ------------------------- |
| `revenium.squad.id`   | Squad ID       | `"squad-billing"`         |
| `revenium.squad.name` | Squad name     | `"Billing Support Squad"` |
| `revenium.squad.role` | Role in squad  | `"orchestrator"`          |

### Agentic Jobs

| Attribute              | Revenium Field | Example                |
| ---------------------- | -------------- | ---------------------- |
| `revenium.job.id`      | Job ID         | `"job-20250312-001"`   |
| `revenium.job.name`    | Job name       | `"nightly-report-gen"` |
| `revenium.job.type`    | Job type       | `"batch"`              |
| `revenium.job.version` | Job version    | `"2.1.0"`              |

### Other Fields

| Attribute                    | Revenium Field    | Notes                                   |
| ---------------------------- | ----------------- | --------------------------------------- |
| `revenium.operation.subtype` | Operation subtype | Free-form sub-classification            |
| `revenium.retry.number`      | Retry number      | Integer; useful for tracking retry cost |
| `revenium.request.stream`    | Is streamed       | Boolean                                 |
| `revenium.middleware.source` | Middleware source | Identifies the SDK or integration layer |

### Setting Attribution Attributes (Python Example)

```python
from opentelemetry import trace

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("llm-call") as span:
    span.set_attribute("revenium.organization.name", "acme-corp")
    span.set_attribute("revenium.product.name", "support-bot")
    span.set_attribute("revenium.agent.name", "tier1-support-agent")
    span.set_attribute("revenium.task.type", "classify-ticket")
    span.set_attribute("revenium.subscriber.id", "user-98765")
    span.set_attribute("revenium.system.fingerprint", "system-prompt-v3")
    # ... make your LLM call
```

You can also set defaults at the resource level so they apply to all spans from your service, and override on individual spans where needed:

```python
from opentelemetry.sdk.resources import Resource

resource = Resource(attributes={
    "service.name": "support-bot",
    "revenium.organization.name": "acme-corp",
    "revenium.product.name": "support-bot",
})
```

Record-level (span) attributes always win over resource-level attributes when the same key is set at both levels.

***

## Troubleshooting

**Data not appearing in Revenium**

Check that:

1. Your API key starts with `hak_` and is in the correct format (`hak_<tenant>_<secret>`).
2. Your spans include at least one of the following so Revenium can route them to the correct mapper:
   * `gen_ai.provider.name` or `gen_ai.system` attribute (resource-level or span-level)
   * An instrumentation scope name starting with `gen_ai`, `openai`, `anthropic`, `opentelemetry.instrumentation.openai`, or `opentelemetry.instrumentation.anthropic`
3. The exporter endpoint URL is correct and includes the full path (`/v2/otlp/v1/traces`, not `/v2/otlp`), unless your SDK sends all signals to a single base URL—in that case, use `/v2/otlp`.

**Tokens showing as zero**

Verify your instrumentation library emits `gen_ai.usage.input_tokens` and `gen_ai.usage.output_tokens` (or their deprecated equivalents `gen_ai.usage.prompt_tokens` / `gen_ai.usage.completion_tokens`) as numeric attributes on the span.

**Tool call and agent orchestration spans are not appearing**

This is expected behavior. Revenium filters out `execute_tool`, `invoke_agent`, and `create_agent` spans because they are not LLM calls. Only spans that represent actual model invocations produce metering records.

***

## Related Documentation

* [Integration Options for AI Metering](https://docs.revenium.io/integration-options-for-ai-metering) — SDK and direct API options
* [AI Coding Data Reference](https://docs.revenium.io/ai-coding-data-reference) — Complete list of data points collected from AI coding assistants
* [Trace Analytics](https://docs.revenium.io/trace-analytics) — Visualizing trace hierarchies and costs
* [Cost & Performance Alerts](https://docs.revenium.io/cost-and-performance-alerts) — Set thresholds on usage and cost
* [System & Transaction Logs](https://docs.revenium.io/system-and-transaction-logs) — Per-transaction detail view
