> For the complete documentation index, see [llms.txt](https://docs.revenium.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.revenium.io/integrations/otlp-integration.md).

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

***

## Endpoints

Revenium accepts both OTLP/HTTP and OTLP/gRPC.

### OTLP/HTTP

| Path                                               | Accepts                                                                           |
| -------------------------------------------------- | --------------------------------------------------------------------------------- |
| `https://api.revenium.ai/meter/v2/otlp/v1/traces`  | Traces (JSON or protobuf)                                                         |
| `https://api.revenium.ai/meter/v2/otlp/v1/logs`    | Logs (JSON or protobuf)                                                           |
| `https://api.revenium.ai/meter/v2/otlp/v1/metrics` | Nothing: answers `410 Gone` (see below)                                           |
| `https://api.revenium.ai/meter/v2/otlp`            | Unified (traces + logs in one request; metrics in the same request are discarded) |

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

{% hint style="warning" %}
Revenium meters from logs and traces and does not accept the OTLP metrics signal. The `/v1/metrics` endpoint answers `410 Gone` with a JSON body explaining this. OTLP exporters treat 410 as a permanent error, so they log one export error per interval and drop the batch without retrying. Set `OTEL_METRICS_EXPORTER=none`, or point metrics at another backend, and the errors stop. Traces and logs are unaffected. Metrics sent inside a unified `/v2/otlp` request are discarded silently.
{% endhint %}

### OTLP/gRPC

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

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

{% hint style="warning" %}
**gRPC authentication note.** The gRPC path accepts the API key as either `x-api-key` or `Authorization: Bearer <key>` metadata. It cannot use `OTEL_RESOURCE_ATTRIBUTES=revenium.api_key=...` because authentication happens before the trace payload is deserialized.
{% endhint %}

### Direct Metering Endpoints

If you are not using OTLP, Revenium also accepts direct metering payloads:

| Endpoint                                                                      | Purpose                                 |
| ----------------------------------------------------------------------------- | --------------------------------------- |
| `https://api.revenium.ai/meter/v2/ai/completions`                             | LLM completions, tokens, and cost       |
| `https://api.revenium.ai/meter/v2/tool/events`                                | Agent tool executions                   |
| `https://api.revenium.ai/meter/v2/ai/images`                                  | Image generation usage                  |
| `https://api.revenium.ai/meter/v2/ai/audio`                                   | Audio generation or transcription usage |
| `https://api.revenium.ai/meter/v2/ai/video`                                   | Video generation usage                  |
| `https://api.revenium.ai/profitstream/v2/api/jobs/{jobId}/outcome?teamId=...` | Terminal job outcomes                   |

The `/meter/v2/...` endpoints are metering-ingest endpoints for usage events. The `/profitstream/v2/api/...` endpoint is the Jobs API used to close an already-metered job with its terminal business outcome. Use a metering key (`rev_mk_*`) for ingest-only calls and a write-scope key (`rev_sk_*`) when reporting outcomes or managing Jobs resources.

For choosing between SDKs, OTLP, and direct API calls, see [Integration Options](/integrations/integrations.md).

***

## Authentication

For both OTLP/HTTP and OTLP/gRPC, your Revenium API key can be sent as either `x-api-key` or `Authorization: Bearer`. OTLP ingest requires a metering key (`rev_mk_*`) — if your workflow also reports business outcomes or manages Revenium resources, use a write-scope key (`rev_sk_*`) for those calls; see [API Key Permissions](/integrations/api-key-permissions.md).

```
Authorization: Bearer rev_mk_your_tenant_yourkey
```

or

```
x-api-key: rev_mk_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=rev_mk_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).

Authentication priority is:

1. `x-api-key: <key>` HTTP header
2. `Authorization: Bearer <key>` header
3. `OTEL_RESOURCE_ATTRIBUTES=revenium.api_key=<key>` for OTLP/HTTP only

Headers always win over the resource attribute. If no key is available in either place, the request is rejected before metering.

You can find your API key in the Revenium app under **Integrations > SDK Setup**.

***

## 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/meter/v2/otlp
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer rev_mk_your_tenant_yourkey"
export OTEL_METRICS_EXPORTER=none
```

With a base endpoint the SDK appends `/v1/metrics` for its metrics signal, and Revenium answers that path with `410 Gone`, so turn the metrics exporter off. If the app needs to keep exporting metrics to another backend, use the per-signal variables below instead.

If your framework uses separate endpoint vars for signals:

```bash
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.revenium.ai/meter/v2/otlp/v1/traces
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://api.revenium.ai/meter/v2/otlp/v1/logs
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Bearer rev_mk_your_tenant_yourkey"
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="Authorization=Bearer rev_mk_your_tenant_yourkey"
```

These variables apply only to traces and logs, so metrics keep whatever endpoint and headers the app already uses. If the app has no other metrics destination, set `OTEL_METRICS_EXPORTER=none` so the SDK does not try to export them; a metrics exporter left pointed at Revenium logs a `410 Gone` error each interval.

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

With the base endpoint:

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

With the per-signal variables:

```bash
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Bearer%20rev_mk_your_tenant_yourkey"
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="Authorization=Bearer%20rev_mk_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.

Minimal OTLP/JSON trace shape:

```json
{
  "resourceSpans": [{
    "resource": {
      "attributes": [
        {"key": "service.name", "value": {"stringValue": "support-bot"}},
        {"key": "revenium.product.name", "value": {"stringValue": "support-bot"}}
      ]
    },
    "scopeSpans": [{
      "scope": {"name": "opentelemetry.instrumentation.anthropic"},
      "spans": [{
        "traceId": "...",
        "spanId": "...",
        "name": "claude_client.messages.create",
        "attributes": [
          {"key": "gen_ai.provider.name", "value": {"stringValue": "anthropic"}},
          {"key": "gen_ai.request.model", "value": {"stringValue": "claude-sonnet-4-6"}},
          {"key": "gen_ai.usage.input_tokens", "value": {"intValue": 150}},
          {"key": "gen_ai.usage.output_tokens", "value": {"intValue": 75}},
          {"key": "revenium.subscriber.id", "value": {"stringValue": "user-12345"}}
        ]
      }]
    }]
  }]
}
```

### 3. See it in Revenium

Go to **Logs > Transactions** or **Logs > Traces**. 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/meter/v2/otlp/v1/traces",
    headers={"Authorization": "Bearer rev_mk_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/meter/v2/otlp/v1/traces",
    headers={"Authorization": "Bearer rev_mk_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/meter/v2/otlp/v1/traces',
    headers: { 'Authorization': 'Bearer rev_mk_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 rev_mk_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/meter/v2/otlp
    headers:
      Authorization: "Bearer rev_mk_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 %}

***

## Attribute Precedence and Source Detection

{% hint style="warning" %}
**This section applies to OTLP telemetry from your own instrumented applications** — the LangChain, OpenAI SDK, and custom-span scenarios described at the top of this page. **Do not use this table to configure a coding assistant.** Claude Code, Claude Cowork, Codex, Copilot, Cursor, and Gemini CLI are each attributed from their own set of attributes. Those sets overlap this table only in part, they differ from one assistant to the next, and an attribute a given assistant does not accept is ignored silently — no error, and the value simply does not appear. Configure those deployments from their own setup pages, which list what each one accepts: see [Analyze AI Tooling Spend](/track-and-control-costs/analyze-ai-tooling-spend.md) for the setup guide per assistant, and the [AI Assistant Data Reference](/track-and-control-costs/analyze-ai-tooling-spend/ai-coding-data-reference.md) for the data each one collects.
{% endhint %}

Revenium resolves attribution values in this order:

```
span attribute > resource attribute > computed default
```

| Category               | Attributes                                                                                | Span override | Resource default |
| ---------------------- | ----------------------------------------------------------------------------------------- | ------------- | ---------------- |
| System fingerprint     | `revenium.system.fingerprint`                                                             | Yes           | Yes              |
| Organization / product | `revenium.organization.name`, `revenium.product.name`                                     | Yes           | Yes              |
| Subscriber             | `revenium.subscriber.id`, `revenium.subscriber.email`, `revenium.subscriber.credential.*` | Yes           | Yes              |
| Agent and job          | `revenium.agent.name`, `revenium.task.type`, `revenium.trace.type`, `revenium.job.id`     | Yes           | Yes              |
| Squad                  | `revenium.squad.id`, `revenium.squad.name`, `revenium.squad.role`                         | Yes           | Yes              |
| Operational            | `revenium.operation.subtype`, `revenium.retry.number`, `revenium.request.stream`          | Yes           | Yes              |

Revenium identifies GenAI spans using this detection chain:

1. Span attribute `gen_ai.provider.name`
2. Span attribute `gen_ai.system`
3. Instrumentation scope names such as `gen_ai.*`, `openai.*`, `anthropic.*`, or `opentelemetry.instrumentation.*`
4. Resource-level GenAI attributes
5. If none match, the span is logged but not metered

***

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

***

### OpenTelemetry Collector Fan-Out

Standard OpenTelemetry environment variables configure one endpoint per signal. To send the same telemetry to Revenium and another backend, place an OpenTelemetry Collector between your producers and the destinations. Applications that configure multiple exporters in code may not need a Collector.

Use a Collector when you need to:

* send telemetry to more than one destination;
* keep vendor credentials off application hosts;
* apply different filtering or redaction rules per destination; or
* change backends without reconfiguring every producer.

A Collector becomes part of your telemetry infrastructure. If Revenium is your only destination and you do not need centralized filtering, export directly to Revenium as described earlier on this page.

**Architecture**

```mermaid
flowchart TB
    P["Producers<br/>Apps · CI · Agents"]

    subgraph C[" "]
        direction TB
        H["OpenTelemetry Collector gateway"]
        R["OTLP receivers<br/>gRPC:4317 · HTTP:4318"]
        H --> R
        R --> REV["Revenium branch<br/>Unmodified telemetry"]
        R --> OBS["Observability branch<br/>Filtered telemetry"]
    end

    P -->|"OTLP/HTTP or OTLP/gRPC"| H
    REV --> RA["Revenium"]
    OBS --> OA["Observability backend"]

    classDef producer fill:#FFFFFF,stroke:#42526E,stroke-width:2px,color:#172033;
    classDef receiver fill:#EAF7F5,stroke:#168F7A,stroke-width:2px,color:#123C35;
    classDef revenium fill:#E9F3FF,stroke:#2878C8,stroke-width:2px,color:#163A5F;
    classDef observability fill:#F4EEFF,stroke:#7656B3,stroke-width:2px,color:#3D2D62;

    class P producer;
    class H producer;
    class R receiver;
    class REV,RA revenium;
    class OBS,OA observability;

    style H fill:transparent,stroke:transparent,color:#172033,font-weight:bold;
    style C fill:#F7FAFB,stroke:#8DA4AE,stroke-width:2px,color:#172033;
```

A gateway Collector runs centrally and accepts telemetry from many producers. An agent Collector runs beside one producer. A gateway is usually the better fit for services, CI runners, and developer tools that all need the same routing policy.

**Configuration requirements**

Use the `otlphttp` exporter for the Revenium branch. Revenium accepts traces over OTLP/gRPC, but logs require OTLP/HTTP. Set the exporter endpoint to the unified base path shown below; the exporter appends `/v1/traces` or `/v1/logs` for each signal it sends to Revenium. Do not attach a metrics pipeline to the Revenium exporter: `/v1/metrics` answers `410 Gone`, which the Collector treats as a permanent export failure.

Send the Revenium metering key in an exporter header. Do not put `revenium.api_key` in resource attributes when using a Collector. Resource attributes travel with the telemetry and may be copied to every destination.

The following example sends traces and logs to Revenium, and all three signals to a general observability backend. It removes two example identity attributes only from the observability branch. Adjust the redaction processors to match the attributes your producers emit and the policy for each destination.

```yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 2048
    spike_limit_mib: 512

  batch:
    timeout: 10s
    send_batch_size: 8192

exporters:
  otlphttp/revenium:
    endpoint: https://api.revenium.ai/meter/v2/otlp
    headers:
      x-api-key: ${env:REVENIUM_METERING_KEY}
    compression: gzip
    sending_queue:
      enabled: true
      queue_size: 10000
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s
      max_elapsed_time: 300s

  otlphttp/observability:
    endpoint: ${env:OBS_OTLP_ENDPOINT}
    headers:
      authorization: ${env:OBS_OTLP_AUTHORIZATION}
    compression: gzip
    sending_queue:
      enabled: true
      queue_size: 10000
    retry_on_failure:
      enabled: true

service:
  telemetry:
    metrics:
      level: detailed

  pipelines:
    traces/revenium:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp/revenium]

    traces/observability:
      receivers: [otlp]
      processors: [memory_limiter, attributes/redact_observability, resource/redact_observability, batch]
      exporters: [otlphttp/observability]

    logs/revenium:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp/revenium]

    logs/observability:
      receivers: [otlp]
      processors: [memory_limiter, attributes/redact_observability, resource/redact_observability, batch]
      exporters: [otlphttp/observability]

    metrics/observability:
      receivers: [otlp]
      processors: [memory_limiter, attributes/redact_observability, resource/redact_observability, batch]
      exporters: [otlphttp/observability]
```

`REVENIUM_METERING_KEY` must contain a metering key (`rev_mk_*`). See API Key Permissions for key scopes.

Each exporter has its own sending queue and retry policy. This prevents a temporary failure at one destination from immediately blocking exports to the other. Size the queues for your traffic and expected outage window. If a queue fills, that destination can still apply backpressure or drop data according to its configured behavior.

**Route only selected telemetry to Revenium**

The configuration above copies traces and logs to both destinations. If the gateway receives telemetry that Revenium should not ingest, use the routing connector to select the relevant records.

This example routes Claude Code logs to both destinations and sends other logs only to the observability backend:

```yaml
connectors:
  routing:
    default_pipelines: [logs/observability]
    table:
      - context: resource
        condition: resource.attributes["service.name"] == "claude-code"
        pipelines: [logs/revenium, logs/observability]

service:
  pipelines:
    logs/ingest:
      receivers: [otlp]
      processors: [memory_limiter]
      exporters: [routing]

    logs/revenium:
      receivers: [routing]
      processors: [batch]
      exporters: [otlphttp/revenium]

    logs/observability:
      receivers: [routing]
      processors: [attributes/redact_observability, resource/redact_observability, batch]
      exporters: [otlphttp/observability]
```

Add a condition for each producer that should reach Revenium. The AI Assistant Data Reference lists the source identifiers emitted by supported assistants.

For Collector component syntax and deployment options, see the [OpenTelemetry Collector configuration documentation](https://opentelemetry.io/docs/collector/configuration/).

## Troubleshooting

**Data not appearing in Revenium**

Check that:

1. Your API key starts with `rev_mk_` and is in the correct format (`rev_mk_<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`.
4. Your exporter sends traces or logs. Revenium rejects the metrics signal with `410 Gone`, so an exporter that only sends metrics shows nothing in Revenium and logs export errors.

**`410 Gone` errors from the exporter**

The metrics signal is pointed at Revenium. Set `OTEL_METRICS_EXPORTER=none`, or route metrics to another backend with the per-signal endpoint variables. The error repeats once per export interval until the metrics exporter is turned off; traces, logs, and metering data are not affected.

**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](/integrations/integrations.md) — SDK and direct API options
* [AI Coding Data Reference](/track-and-control-costs/analyze-ai-tooling-spend/ai-coding-data-reference.md) — Complete list of data points collected from AI coding assistants
* [Debug Logs & Traces](/optimize-performance/debug-logs-and-traces.md) — Visualizing trace hierarchies, transaction details, and costs
* [Set Budgets & Alerts](/track-and-control-costs/set-budgets-and-alerts.md) — Set thresholds on usage and cost


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.revenium.io/integrations/otlp-integration.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
