OpenTelemetry (OTLP) Ingest & Query
Temps ingests OpenTelemetry traces, metrics, and logs over OTLP/HTTP and stores them in TimescaleDB, then exposes authenticated query endpoints that power the monitoring UI. Use it when you want to point any OTel SDK or the OpenTelemetry Collector at your Temps instance instead of running a separate observability backend.
Overview
OTel support is provided by the temps-otel plugin crate. It accepts protobuf-encoded OTLP payloads (with gzip or zstd compression) for metrics, traces, and logs, persists them to TimescaleDB hypertables, and serves read endpoints for the console.
All temps-otel routes are declared inside the crate as /otel/..., but the control plane nests every plugin route under /api (temps-core applies .nest("/api", ...)). The served paths are therefore /api/otel/... exactly as shown on this page. If you call /otel/... without the /api prefix you will hit the SPA catch-all, not the ingest handler.
How It Works
- An OTel SDK or Collector exports OTLP/HTTP protobuf to one of the ingest routes below.
- The ingest handler authenticates the request, decompresses the body (gzip/zstd), and decodes the protobuf.
- Telemetry is written to TimescaleDB hypertables, associated with a project (and optionally environment and deployment).
- The console reads it back through the authenticated query endpoints, including GenAI trace summaries derived from
gen_ai.*span attributes.
There are two ingest styles:
- Header-authenticated — the project (and environment/deployment) are resolved from the token. Best for app-level SDK instrumentation.
- Path-scoped — the project, environment, and deployment IDs are carried directly in the URL path. Useful when one credential ships telemetry for many targets.
Ingest Endpoints
All ingest endpoints are POST, accept OTLP/HTTP protobuf bodies, and support Content-Encoding: gzip or zstd.
Header-authenticated
| Method | Path | Signal |
|---|---|---|
POST | /api/otel/v1/metrics | Metrics |
POST | /api/otel/v1/traces | Traces |
POST | /api/otel/v1/logs | Logs |
Path-scoped
The project, environment, and deployment IDs are embedded in the path:
| Method | Path | Signal |
|---|---|---|
POST | /api/otel/v1/{project_id}/{environment_id}/{deployment_id}/metrics | Metrics |
POST | /api/otel/v1/{project_id}/{environment_id}/{deployment_id}/traces | Traces |
POST | /api/otel/v1/{project_id}/{environment_id}/{deployment_id}/logs | Logs |
Authentication
Ingest requests read the token from either header:
| Header | Value |
|---|---|
Authorization | Bearer <token> |
X-Temps-Api-Key | <token> |
Two token types are accepted:
| Prefix | Type | Extra requirement |
|---|---|---|
tk_ | API key | Also send X-Temps-Project-Id so the key resolves to a project |
dt_ | Deployment token | Carries its own project/environment/deployment association |
Which one should I use? Use tk_ API keys when you want to route telemetry for a single project from one exporter. Use dt_ deployment tokens when you're shipping telemetry from multiple deployments or environments through the same exporter — the token already knows which project, environment, and deployment it belongs to, so no extra header is needed.
The query endpoints (below) are a separate, fully authenticated surface. They require a logged-in session or API key with the OtelRead permission — they are not the header-token ingest path.
Query Endpoints
These power the monitoring UI and the unified Observe page, which merges these traces with requests, errors, and revenue events into one stream. Every one requires authentication plus the OtelRead permission. All are GET. Endpoints that take a {project_id} carry it in the path; the others accept project_id as a query parameter.
| Method | Path | Returns |
|---|---|---|
GET | /api/otel/metrics | Metric data points |
GET | /api/otel/metric-names/{project_id} | Distinct metric names for a project |
GET | /api/otel/traces | Trace list |
GET | /api/otel/trace-summaries | Aggregated trace summaries |
GET | /api/otel/traces/{project_id}/{trace_id} | Spans for a single trace |
GET | /api/otel/logs | Log records |
GET | /api/otel/insights/{project_id} | Computed insights for a project |
GET | /api/otel/health/{project_id} | Health view for a project |
GET | /api/otel/quota/{project_id} | Storage quota usage for a project |
GET | /api/otel/pipeline-stats | Ingest pipeline statistics |
GET | /api/otel/genai/traces | GenAI trace summaries (see below) |
GET | /api/otel/genai/traces/{project_id}/{trace_id} | A single GenAI trace |
GenAI Trace Summaries
The GenAI views are derived from regular OTel spans — there is no separate ingest path. A span is treated as a GenAI span when its attributes contain either gen_ai.system or gen_ai.provider.name. The provider shown in the UI is taken from gen_ai.provider.name, falling back to the deprecated gen_ai.system when the newer attribute is absent.
This means any OTel SDK that emits OpenTelemetry gen_ai.* semantic-convention spans (for example, AI SDK instrumentation) shows up automatically in GET /api/otel/genai/traces once those spans land via the normal trace ingest routes.
Configuration
All configuration is via TEMPS_OTEL_* environment variables. Every one is optional and has a default — OTel ingest works out of the box with none of them set.
| Variable | Default | Purpose |
|---|---|---|
TEMPS_OTEL_RATE_LIMIT | 1000 | Max ingest requests per project per window |
TEMPS_OTEL_RATE_LIMIT_WINDOW_SECS | 60 | Rate-limit window length, in seconds |
TEMPS_OTEL_RETENTION_DAYS | 7 | How long telemetry is retained |
TEMPS_OTEL_S3_REGION | (unset) | S3 region for optional log archival |
TEMPS_OTEL_S3_ENDPOINT | (unset) | Custom S3 endpoint (e.g. for S3-compatible storage) |
TEMPS_OTEL_S3_ACCESS_KEY | (unset) | S3 access key |
TEMPS_OTEL_S3_SECRET_KEY | (unset) | S3 secret key |
TEMPS_OTEL_S3_BUCKET | (unset) | S3 bucket for archived logs |
TEMPS_OTEL_S3_PREFIX | otel-logs | Key prefix for archived log objects |
TEMPS_OTEL_QUOTA_GB | 10 | Per-project storage quota, in GB |
TEMPS_OTEL_ENABLE_HEALTH_COMPUTE | true | Run the background health-compute task |
TEMPS_OTEL_ENABLE_ANOMALY_DETECTION | true | Run the background anomaly-detection task |
S3 log archival is off by default. It only activates when all four of TEMPS_OTEL_S3_REGION, TEMPS_OTEL_S3_ACCESS_KEY, TEMPS_OTEL_S3_SECRET_KEY, and TEMPS_OTEL_S3_BUCKET are set. TEMPS_OTEL_S3_ENDPOINT and TEMPS_OTEL_S3_PREFIX are additional knobs that only matter once archival is enabled.
When a project exceeds its rate limit, ingest returns 429 Too Many Requests and the error reports the limiter's actual configured value (not a hardcoded number), so if you raise TEMPS_OTEL_RATE_LIMIT the message reflects the new limit.
Example: send a trace
The most common setup is to point the OpenTelemetry Collector (or any OTLP/HTTP exporter) at your Temps instance. The exporter must use the HTTP/protobuf OTLP protocol.
OpenTelemetry Collector — otlphttp exporter
exporters:
otlphttp/temps:
# Base endpoint; the collector appends /v1/traces, /v1/metrics, /v1/logs
endpoint: https://your-temps-instance.com/api/otel
headers:
Authorization: "Bearer dt_your-deployment-token"
compression: gzip
service:
pipelines:
traces:
exporters: [otlphttp/temps]
metrics:
exporters: [otlphttp/temps]
logs:
exporters: [otlphttp/temps]
With a tk_ API key instead of a deployment token, also send the project ID:
cURL — header-authenticated trace ingest (API key)
# Body must be an OTLP ExportTraceServiceRequest encoded as protobuf.
curl -X POST https://your-temps-instance.com/api/otel/v1/traces \
-H "Authorization: Bearer tk_your-api-key" \
-H "X-Temps-Project-Id: 42" \
-H "Content-Type: application/x-protobuf" \
-H "Content-Encoding: gzip" \
--data-binary @trace.pb.gz
You don't need a Collector in front of Temps — any OTel SDK can export directly over OTLP/HTTP. With Python's opentelemetry-exporter-otlp-proto-http, point the exporter at the full traces path and pass the token as a header:
Python — export spans directly with the OTLP/HTTP SDK
# pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
exporter = OTLPSpanExporter(
endpoint="https://your-temps-instance.com/api/otel/v1/traces",
headers={"Authorization": "Bearer dt_your-deployment-token"},
)
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("example-app")
with tracer.start_as_current_span("hello"):
print("span sent to Temps")
With a tk_ API key instead of a deployment token, add the project header to the exporter — pass headers={"Authorization": "Bearer tk_your-api-key", "X-Temps-Project-Id": "42"}.
Then read traces back from the authenticated query surface:
cURL — list traces (requires OtelRead)
curl "https://your-temps-instance.com/api/otel/traces?project_id=42" \
-H "Authorization: Bearer <temps-api-key-with-OtelRead>"
Notes & Gotchas
- Protobuf only. Ingest accepts OTLP/HTTP with protobuf-encoded bodies. Configure your exporter for the
http/protobufprotocol, notgrpcand not OTLP/JSON. - Use the
/apiprefix. Routes are served under/api/otel/.... Hitting/otel/...directly falls through to the SPA. tk_keys needX-Temps-Project-Id. API keys must include the project header on ingest; deployment tokens (dt_) already carry their project/environment/deployment context.- Query endpoints need
OtelRead. They are the console's authenticated read surface, distinct from the header-token ingest path. - GenAI views are derived, not ingested. They come from trace spans carrying
gen_ai.system/gen_ai.provider.nameattributes — there is no separate GenAI ingest endpoint. - Storage is TimescaleDB. OTel data lands in TimescaleDB hypertables, so a TimescaleDB-enabled control plane database is required.
Related pages
Once telemetry is flowing, these pages cover the surfaces that read it back:
- Observe — the unified, time-ordered stream that merges OTel traces with requests, errors, and revenue events for a project.
- Monitoring — uptime checks and alerts on top of the telemetry you ingest here.
- Logs & Debugging — viewing and searching log records.
- Errors — error tracking and grouping.