OTLP Ingestion
Push spans with the OpenTelemetry Protocol over HTTP — protobuf or JSON, optionally gzip. Only accepted spans are stored.
Protocols#
- OTLP/HTTP protobuf —
Content-Type: application/x-protobuf. Recommended; supported by every OpenTelemetry SDK. - OTLP/HTTP JSON —
Content-Type: application/json. Not every SDK can export JSON; prefer protobuf unless yours does. Content-Encoding: gzipfor either format.- OTLP/gRPC is not supported. There is no gRPC listener; configure your exporter or Collector for OTLP/HTTP.
Endpoint: POST /api/traces/v1/otlp. The alias POST /api/otlp/v1/traces lets you set the base OTEL_EXPORTER_OTLP_ENDPOINT=https://mydle.app/api/otlp. Auth: Authorization: Bearer tt_… (tracing source token — create sources under Traces → Ingest; the token is shown once).
Exporter setup#
Node.js, verified end to end with @opentelemetry/exporter-trace-otlp-proto 0.220.0, @opentelemetry/sdk-trace-base 2.9.0, @opentelemetry/resources 2.9.0 and @opentelemetry/api 1.9.1:
npm install @opentelemetry/api @opentelemetry/resources \ @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-proto \ @opentelemetry/otlp-exporter-base
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { CompressionAlgorithm } from "@opentelemetry/otlp-exporter-base";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { BasicTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
const exporter = new OTLPTraceExporter({
url: "https://mydle.app/api/traces/v1/otlp",
headers: { Authorization: `Bearer ${process.env.MYDLE_TRACE_TOKEN}` },
compression: CompressionAlgorithm.GZIP,
});
const provider = new BasicTracerProvider({
resource: resourceFromAttributes({
"service.name": "checkout-api",
"service.version": "2.4.1",
"deployment.environment.name": "production",
}),
spanProcessors: [new BatchSpanProcessor(exporter, { maxExportBatchSize: 500 })],
});The standard environment variables express the same settings for SDKs with OTLP/HTTP protobuf support (not covered by our automated test):
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://mydle.app/api/traces/v1/otlp OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Bearer tt_…" OTEL_EXPORTER_OTLP_TRACES_COMPRESSION=gzip
Limits#
- Per request: 5 MiB body, 5 MiB after gzip, 500 spans, 64 KiB per normalized span. Keep
maxExportBatchSizeat or below 500. - A protobuf request may contain at most 200,000 objects (spans, attributes, values, events) and nest attribute values at most 32 levels deep. More objects return 413
TOO_MANY_OBJECTS; deeper nesting returns 400. - Larger requests return 413 and nothing is stored. A batch with any invalid span is rejected as a whole.
- Monthly usage is measured on the normalized span, so protobuf, JSON and gzip are charged the same.
Responses & retries#
Responses follow the OTLP/HTTP specification and use the request's Content-Type: an empty ExportTraceServiceResponse on success, a google.rpc.Status with ErrorInfo (reason such as INVALID_PROTOBUF, TOO_MANY_SPANS, QUOTA_EXCEEDED) on failure.
| Status | Meaning | Exporter retries |
|---|---|---|
| 200 | Stored. Re-sent spans that already exist are success, not a partial rejection. | — |
| 400 | Malformed protobuf, JSON or gzip; invalid span; missing token. | No |
| 401 | Unknown or disabled token. | No |
| 403 | The workspace plan does not include tracing. | No |
| 413 | Body, decoded body, span count, protobuf object count or one span over the limit. Split the batch. | No |
| 415 | Unsupported Content-Type (for example application/grpc) or Content-Encoding. | No |
| 429 | Monthly span or span-data quota exhausted; Retry-After points at the reset. | Only per Retry-After |
| 500 | Unexpected server error (no internal details). | No |
| 503 | Temporary database unavailability. | Yes, with backoff |
Retries are safe: a span already stored for the workspace (same trace ID and span ID, in either format) is skipped and not charged again.
Stored data#
- Trace and span IDs as lowercase hex; parent links, kind, status, events (including exceptions) and links.
- Resource attributes are merged into span attributes;
service.name,service.version(release) anddeployment.environment.nameare promoted. The instrumentation scope is kept asotel.scope.name/otel.scope.version. - All attribute value types: strings, booleans, integers, doubles, arrays, nested key/value lists and bytes (as base64).
Dual-write#
Primary write is always Postgres. When ClickHouse is configured, a secondary fail-open writer dual-writes envelopes for query acceleration. ClickHouse misconfiguration does not fail the ingest request.
Limitations
- OTLP/gRPC is not supported — use OTLP/HTTP.
- Span times are stored with millisecond precision (span durations are computed from the exact nanoseconds).
- Trace state, trace flags, dropped counts, schema URLs, scope attributes and resource entity references are not stored. Integers beyond ±2^53 are kept as exact decimal strings.
- The per-IP edge rate limit can also answer 429 with a plain JSON body.