OpenTelemetry

Send OTLP spans carrying the gen_ai.* semantic conventions to Currai from any language, and migrate a Langfuse app by changing one URL.

If you're already instrumented with OpenTelemetry, you don't need the Currai SDK. Point your OTLP exporter at Currai's endpoint and your spans become traces and generations — your existing code, spans, and exporters keep working.

The OTLP endpoint

Send OTLP/HTTP traces to:

https://currai.app/api/public/otel/v1/traces
  • Method: POST
  • Auth: HTTP Basic (base64(publicKey:secretKey)) or a Bearer secret key — same credentials as the SDKs (see Authentication).
  • Body: OTLP application/x-protobuf or application/json.

A span is recorded as a generation when it carries any of gen_ai.system, gen_ai.operation.name, or gen_ai.request.model; otherwise it's recorded as a span.

Attributes Currai reads

Currai understands the OpenTelemetry GenAI conventions (plus OpenInference and OpenLLMetry). The most useful keys:

AttributeMaps to
gen_ai.request.modelgeneration model (for cost)
gen_ai.usage.input_tokensinput tokens
gen_ai.usage.output_tokensoutput tokens
gen_ai.usage.total_tokenstotal tokens
gen_ai.request.temperaturemodel parameter
gen_ai.request.max_tokensmodel parameter
gen_ai.promptgeneration input
gen_ai.completiongeneration output
currai.user.id / user.idtrace user
currai.session.id / session.idtrace session
currai.tagstrace tags (string array)
deployment.environmenttrace environment (resource attr)

Example: Rust

Point the standard OTLP/HTTP exporter at Currai, then emit spans with the conventions above. This uses the official opentelemetry, opentelemetry_sdk, and opentelemetry-otlp crates:

use std::collections::HashMap;
use opentelemetry::{global, KeyValue};
use opentelemetry_otlp::{Protocol, SpanExporter, WithExportConfig, WithHttpConfig};
use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};

let exporter = SpanExporter::builder()
    .with_http()
    .with_endpoint("https://currai.app/api/public/otel/v1/traces")
    .with_protocol(Protocol::HttpBinary)
    .with_headers(HashMap::from([(
        "Authorization".into(),
        "Bearer sk-lf-...".into(),
    )]))
    .build()?;

let provider = SdkTracerProvider::builder()
    .with_batch_exporter(exporter)
    .with_resource(
        Resource::builder()
            .with_attribute(KeyValue::new("service.name", "chat-app"))
            .build(),
    )
    .build();
global::set_tracer_provider(provider);

Then record a generation:

use opentelemetry::{global, trace::{Span, Tracer}, KeyValue};

let tracer = global::tracer("chat-app");

let mut span = tracer.start("openai.chat.completions");
span.set_attribute(KeyValue::new("gen_ai.system", "openai"));
span.set_attribute(KeyValue::new("gen_ai.request.model", "gpt-4o-mini"));
span.set_attribute(KeyValue::new("gen_ai.usage.input_tokens", 312_i64));
span.set_attribute(KeyValue::new("gen_ai.usage.output_tokens", 88_i64));
span.end();

The same approach works from any OpenTelemetry SDK — Python, Node, Go, Java — just set the endpoint, headers, and the gen_ai.* attributes.

Migrating from Langfuse

Currai is wire-compatible with the Langfuse SDKs. If you're already on Langfuse, change the host and keep your code:

from langfuse import Langfuse

client = Langfuse(
    public_key="pk-lf-...",
    secret_key="sk-lf-...",
    host="https://currai.app",  # only this line changes
)

See the full API reference for endpoints, auth, and event types.