Implementation:Langfuse Langfuse OTel Traces Handler
| Knowledge Sources | |
|---|---|
| Domains | Observability, Ingestion, OpenTelemetry |
| Last Updated | 2026-02-14 00:00 GMT |
Overview
Concrete tool for receiving OpenTelemetry ExportTraceServiceRequest payloads over HTTP provided by Langfuse.
Description
This is a Next.js API route handler located at /api/public/otel/v1/traces that serves as the entry point for all OpenTelemetry trace ingestion into Langfuse. The handler:
- Disables the default Next.js body parser to gain control over raw body streaming.
- Wraps the route in
withMiddlewaresfor consistent error handling and CORS support. - Uses
createAuthedProjectAPIRouteto enforce project-level authentication and apply rate limiting under theingestionresource. - Checks for ingestion suspension (usage threshold exceeded) and throws
ForbiddenErrorif suspended. - Marks the project as an OTel user via
markProjectAsOtelUser(). - Streams the raw request body into a Buffer, then decompresses it with gzip if the
Content-Encodingheader indicates gzip compression. - Parses the body as either Protobuf (using the generated
$root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequestdecoder) or JSON, depending on theContent-Typeheader. - Extracts configurable propagated headers for enterprise ingestion masking.
- Instantiates an
OtelIngestionProcessorwith project context and callspublishToOtelIngestionQueue(resourceSpans).
Usage
This handler is invoked by any OpenTelemetry SDK or collector configured to export traces to Langfuse. The endpoint URL follows the OTLP HTTP convention:
POST /api/public/otel/v1/traces
Clients authenticate via Langfuse project API keys passed as standard HTTP Basic Auth or bearer tokens.
Code Reference
Source Location
- Repository: langfuse
- File: web/src/pages/api/public/otel/v1/traces/index.ts
- Lines: 20-133
Signature
withMiddlewares({
POST: createAuthedProjectAPIRoute({
name: "OTel Traces",
querySchema: z.any(),
responseSchema: z.any(),
rateLimitResource: "ingestion",
fn: async ({ req, res, auth }) => {
// ... handler body
},
}),
})
Import
import { withMiddlewares } from "@/src/features/public-api/server/withMiddlewares";
import { createAuthedProjectAPIRoute } from "@/src/features/public-api/server/createAuthedProjectAPIRoute";
import { OtelIngestionProcessor, logger, markProjectAsOtelUser } from "@langfuse/shared/src/server";
import { z } from "zod/v4";
import { $root } from "@/src/pages/api/public/otel/otlp-proto/generated/root";
import { gunzip } from "node:zlib";
import { ForbiddenError } from "@langfuse/shared";
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| req.body | Buffer (ExportTraceServiceRequest) | Yes | Raw HTTP body containing OTel ExportTraceServiceRequest in Protobuf or JSON format |
| Content-Type | string | Yes | Must include application/json or application/x-protobuf
|
| Content-Encoding | string | No | If set to gzip, the body will be decompressed before parsing
|
| auth.scope.projectId | string | Yes | Authenticated project identifier (provided by middleware) |
| auth.scope.publicKey | string | Yes | Public API key for the project |
| auth.scope.orgId | string | No | Organization identifier |
| auth.scope.isIngestionSuspended | boolean | Yes | Whether ingestion is suspended due to usage limits |
Outputs
| Name | Type | Description |
|---|---|---|
| HTTP 200 (empty object) | object | Returned on successful enqueue or when resourceSpans is empty |
| HTTP 400 (error object) | object | Returned when body read, decompression, or parsing fails; includes error message |
| HTTP 403 (ForbiddenError) | Error | Thrown when ingestion is suspended for the project |
| Side effect: S3 upload | void | resourceSpans JSON uploaded to S3 via publishToOtelIngestionQueue |
| Side effect: BullMQ job | void | Job enqueued to OtelIngestionQueue for async processing |
Usage Examples
Sending a JSON OTel Trace
curl -X POST https://cloud.langfuse.com/api/public/otel/v1/traces \
-H "Content-Type: application/json" \
-H "Authorization: Basic <base64(publicKey:secretKey)>" \
-d '{
"resourceSpans": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-service"}}
]
},
"scopeSpans": [{
"scope": {"name": "langfuse-sdk", "version": "3.5.0"},
"spans": [{
"traceId": "abc123",
"spanId": "def456",
"name": "chat-completion",
"startTimeUnixNano": "1700000000000000000",
"endTimeUnixNano": "1700000001000000000",
"attributes": []
}]
}]
}]
}'
Sending a Gzip-Compressed Protobuf Trace
curl -X POST https://cloud.langfuse.com/api/public/otel/v1/traces \
-H "Content-Type: application/x-protobuf" \
-H "Content-Encoding: gzip" \
-H "Authorization: Basic <base64(publicKey:secretKey)>" \
--data-binary @compressed_trace.pb.gz