Skip to main content
https://otlp.lumenstat.com is an OpenTelemetry Collector that accepts OTLP over HTTP. It turns server spans into per-route request counts and latency histograms, keeps three HTTP latency metric series, and drops everything else. Everything on this page is read from apps/collector/config.yaml and the writer in apps/writer/src.

Endpoint

Any other path on otlp.lumenstat.com returns 404 at the edge.

Protocol

In the current pilot otlp.lumenstat.com is served through Cloudflare, which sees requests in transit. The production design serves it directly from EU servers. See Privacy.
string
required
The project’s ingest key from the Setup page: scf_ingest_ followed by 32 URL-safe characters (24 random bytes, base64url). Lumenstat stores only its SHA-256 hash.
What the collector and writer do with it:
  1. The collector’s resource/tenant processor deletes any scf.ingest_key resource attribute the client sent, then inserts the header value as scf.ingest_key. The otlp receiver runs with include_metadata: true on both protocols, which makes the request header available to the processor.
  2. This happens before spanmetrics aggregates, so spans from different keys never share a series.
  3. The writer hashes the key and looks it up. The key must not be revoked and its tenant must be active. Any tenant field in the payload, such as a tenant.id resource attribute, is ignored.
  4. Lookups are cached for 60 seconds, so a key revoked in the database can keep working for up to a minute.

Requests without a valid key

The collector does not check the key. A request with a missing, unknown or revoked key gets the same success response as a valid one. Its spans are aggregated like any other, and the writer then drops every resulting data point with the reason unknown_ingest_key. Nothing is stored and the sender sees no error. Use Is data arriving? on Setup to confirm a key works.
Checking ingest keys at the edge, instead of only in the writer, is planned for Milestone 2. See the roadmap.

Accepted metric series

The collector’s filter/http-server-latency processor and the writer’s ACCEPTED_METRICS keep these histogram series and nothing else.
  • Units. The writer converts s, ms, us and ns to milliseconds using the metric’s unit. An empty or unknown unit falls back to the default in the table.
  • Temporality. The collector converts cumulative series from SDKs to delta with its cumulativetodelta processor. The writer drops any point that still arrives as cumulative.
  • Routes from SDK metrics. HTTP server metrics carry http.route but not the raw path. A metric point without http.route is stored under the route {unknown}.
  • Both sources for one service. If a service sends both SDK metrics and spans, the read side uses only the SDK metrics for that service in the requested time range, so no request is counted twice. A service that switches between the two within one range is counted from its metrics only.

spanmetrics

The traces pipeline is otlp receiver → resource/tenant → spanmetrics connector. It has no exporter, so spans are not stored or forwarded.

Dimensions

spanmetrics adds its default dimensions service.name, span.name, span.kind and status.code, plus these configured ones: Both the current and the older HTTP semantic conventions work. The writer reads span.kind to keep server spans and ignores span.name and status.code. Of the resource attributes, it reads only service.name and scf.ingest_key.

How attributes become stored fields

Histogram buckets

Every latency histogram is stored as 15 counts on one fixed set of bounds in milliseconds:
These are the bucket boundaries OpenTelemetry recommends for http.server.request.duration, converted from seconds, and the ones the collector’s spanmetrics uses. An SDK histogram with the default boundaries maps one to one.
  • Other boundaries. Each source bucket is counted in the fixed bucket that holds its upper bound. A source histogram with no boundaries is placed at its mean.
  • Why fixed. Counts on the same boundaries add up without loss. p50, p95 and p99 stay accurate to the bucket when Lumenstat sums several instances of a service or a long time range.
  • Percentiles. The read side interpolates linearly inside the bucket that holds the requested rank and rounds to 0.1 ms. A percentile that falls in slot 14 reads 10,000 ms.

Storage

The writer adds each point to one row of scf_api.api_metric_minute: The primary key is every column from tenant_id to bucket_start. A point for an existing row adds its count, duration and bucket counts to it. The schema is in apps/writer/db/migrations/001_init.sql.

Drop reasons

The writer counts every data point it drops, by reason, in its own log. These counts are not shown to projects.
Last modified on November 14, 2026