Sending data with OpenTelemetry
ObserveKit accepts traces, metrics, and logs over the OpenTelemetry Protocol (OTLP). Any app — running on Kubernetes, Docker, an EC2 / cloud VM, an Azure WebApp, on-prem, anywhere with outbound HTTP — can push data without an ObserveKit agent.
You don't construct request bodies by hand
A common first question: *"what JSON do I POST to /v1/traces?"*
You don't. Your app's OpenTelemetry SDK handles the body for you. You import the SDK in your code, configure the endpoint and API-key header (env vars below), and call tracer.startSpan(...) / meter.add(...) / logger.info(...) like normal. The SDK batches those calls, serializes them into the standard OTLP wire format (protobuf-over-HTTP, or JSON-over-HTTP per the OTel spec), and POSTs them to ObserveKit on a background timer.
Every language SDK (Java, Node, Python, .NET, Go, Ruby, PHP, Rust) implements the same OTLP spec, so the body shape is identical across languages — you just pick the SDK that matches your stack.
If you really want to see the wire shape (for debugging, or to write your own exporter from scratch), the OTLP spec is at opentelemetry.io/docs/specs/otlp. Almost no one needs to read it.
OTLP endpoints
All three signal types use the same base URL with different paths:
| Signal | HTTP endpoint |
|---|---|
| Traces | https://observekit-api.expeed.com/v1/traces |
| Metrics | https://observekit-api.expeed.com/v1/metrics |
| Logs | https://observekit-api.expeed.com/v1/logs |
If your app runs in a Kubernetes cluster that already has the ObserveKit agent installed, you can also point at the agent on localhost:4317 (gRPC) or localhost:4318 (HTTP) and let the agent forward to the server. Direct-to-server is fine for any environment; agent-relay is just a convenience for in-cluster apps that want to avoid setting outbound DNS.
Compression & body size
Enable gzip (or zstd) on your exporter — ObserveKit decompresses both, and it meaningfully cuts egress. Each request body is capped at 10 MiB (compressed *and* decompressed); if you batch aggressively and get a 400, lower the SDK's max export batch size.
Authentication
Every OTLP request must carry an ObserveKit API key. Pass it as either:
X-API-Key: <your-key>
or
Authorization: Bearer <your-key>
API keys are issued per source. Each app or service typically gets its own source so you can revoke / rotate / scope access independently. See "Provisioning a service source" below.
Per-language SDK setup
The OpenTelemetry SDK config is identical in shape across languages — set the OTLP endpoint, the API-key header, and the service name. The official OpenTelemetry SDK docs cover each language in detail.
Java / Spring Boot (auto-instrumentation agent)
-javaagent:/path/to/opentelemetry-javaagent.jar -Dotel.exporter.otlp.endpoint=https://observekit-api.expeed.com -Dotel.exporter.otlp.headers=X-API-Key=<your-key> -Dotel.service.name=cargospot-api -Dotel.resource.attributes=deployment.environment=prod
Java / Micronaut (compile-time modules, no javaagent)
Micronaut apps usually use the first-party micronaut-tracing-opentelemetry-* modules rather than the javaagent. Two things differ from the agent flow and silently break telemetry if missed:
- Config uses the OpenTelemetry autoconfigure schema. In
application.yml, you must *select* the exporter and pass the key as a single header string — a nestedheaders:map, or a missingotel.traces.exporter, ships nothing (no error):
```yaml
otel:
traces:
exporter: otlp
logs:
exporter: otlp
exporter:
otlp:
endpoint: https://observekit-api.expeed.com
headers: "X-API-Key=${OBSERVEKIT_API_KEY}"
compression: gzip
```
- Logs need the appender installed. Without the javaagent, the Logback
OpenTelemetryAppenderhas no SDK to emit through unless you install it once at startup — otherwise records buffer and drop (numLogsCapturedBeforeOtelInstall … too small):
```java
// in an ApplicationEventListener<ServerStartupEvent>, injecting the OpenTelemetry bean
OpenTelemetryAppender.install(openTelemetry);
```
The full Micronaut walkthrough (dependencies, sampling, log correlation) lives in the observekit Claude Code plugin's Micronaut reference.
Node.js / TypeScript
OTEL_EXPORTER_OTLP_ENDPOINT=https://observekit-api.expeed.com OTEL_EXPORTER_OTLP_HEADERS=X-API-Key=<your-key> OTEL_SERVICE_NAME=billing-api node --require @opentelemetry/auto-instrumentations-node/register app.js
Python
OTEL_EXPORTER_OTLP_ENDPOINT=https://observekit-api.expeed.com OTEL_EXPORTER_OTLP_HEADERS=X-API-Key=<your-key> OTEL_SERVICE_NAME=ingest-worker opentelemetry-instrument python app.py
.NET
OTEL_EXPORTER_OTLP_ENDPOINT=https://observekit-api.expeed.com OTEL_EXPORTER_OTLP_HEADERS=X-API-Key=<your-key> OTEL_SERVICE_NAME=accounting-service dotnet AccountingService.dll
Go
Go has no auto-instrumentation agent — wire the SDK explicitly. A minimal HTTP exporter setup:
exporter, _ := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint("observekit-api.expeed.com"),
otlptracehttp.WithHeaders(map[string]string{"X-API-Key": "<your-key>"}),
)
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewSchemaless(
attribute.String("service.name", "search-service"),
)),
)
otel.SetTracerProvider(provider)Provisioning a service source
For an OTel-only app, the operator creates a service-type source in ObserveKit's Sources page. Service sources don't run an agent — they exist to issue an API key and group OTLP data by source.
- Open Sources → Add Source → pick type service.
- Enter a name (typically the service name, e.g.
cargospot-api). - Save. The install dialog returns the API key + the OTLP endpoints the app should target. Hand both to the developer.
- Once the app starts pushing OTLP data, the source's badge on the Sources list will flip from no data yet → OTLP receiving within a few minutes.
If the operator doesn't want to create a per-app source, an existing kubernetes / docker source's API key also works for OTLP push. Filtering then happens by service.name in the Traces, Metrics, and Logs views instead of by source.
What you DO see for a service source
- Traces — every span, filterable by
service.name. - Services — auto-derived service list from incoming traces.
- Service map — auto-derived dependency topology.
- Metrics (Explorer) — every metric the app pushes, plottable ad-hoc or as the basis for an alert rule.
- Logs — every log line the app pushes via OTLP.
- Cost Analytics — ingestion bytes per source, including service sources.
- Alerts (user-input templates) —
composite,simple_anomaly,metric_change_percent,log_pattern_match,high_error_rateall fire against pushed data when their conditions match.
What you DO NOT see for a service source
These features are built around agent-collected data and are explicitly NOT collected for service sources:
- Infrastructure topology (the Infrastructure page + the Infrastructure card on the source detail page) — no pods, containers, or nodes are collected because no agent runs.
- Metrics Analytics tab (KPI tiles, Node Health, Pod / Container Analytics) — built from agent-emitted metrics like
kube_pod_status_phaseandcontainer_cpu_usage_percent. - Certificates card (the Phase C TLS scanner) — runs as part of the K8s agent.
- K8s-specific alert templates (
high_cpu,pod_pending,pod_crashloop,kube_deployment_replicas,kube_pod_not_ready,node_not_ready,deployment_zero_available,low_disk,high_memory,certificate_expiring) — these reference metric names only the agent emits. The rule-creation picker hides them when the active source is a service source; selecting them while the picker shows "All sources" surfaces a⚠ requires kuberneteswarning.
Pages where these features would normally render show a "Not collected for this source type" banner so it's clear ObserveKit is honest about the gap rather than presenting an empty dashboard.
Environment-specific guidance
Azure WebApps
Azure App Service auto-enables Application Insights. To send the same data to ObserveKit, use the Azure Monitor OpenTelemetry Distro (Microsoft's OTel-spec-compatible distribution) or the plain OTel SDK. Set the OTLP env vars in App Service → Configuration → Application settings, then restart. No App Service redeploy needed.
EC2, GCP Compute, on-prem Linux VMs
Same as any other environment — instrument the app with OTel and point at ObserveKit's /v1/* endpoints. Host-level metrics (CPU, memory, disk usage of the VM itself) are NOT collected on bare VMs without Kubernetes or Docker — instrument the app with OTel for its own signals.
Docker (without K8s)
If the app runs in Docker on a host that already has the ObserveKit Docker agent, both paths work:
- Agent collects host + container metrics; ObserveKit aggregates them as the existing Docker source.
- The app's own request metrics / business counters / traces are NOT collected by the Docker agent — instrument with OTel as above to surface them.
Kubernetes
The cleanest pattern is a per-cluster K8s source for infra signals (pods, nodes, certificates) plus a per-app service source for app signals. Or instrument the app with OTel pointing at the in-cluster agent and let the agent forward to the server using the cluster's existing API key.
Troubleshooting
"My OTLP push returns 401 Unauthorized" — the API key isn't being sent. Check OTEL_EXPORTER_OTLP_HEADERS (case matters; some SDKs use OTEL_EXPORTER_OTLP_TRACES_HEADERS per-signal-type). Also verify the key matches a source on this server — keys are env-specific.
"Source shows 'no data yet' even though my app started" — the SDK is buffering. Most OTel SDKs batch + flush every 5 seconds. Generate some traffic and wait one batch interval.
"Metrics arrive but `service.name` shows up as `unknown_service`" — set OTEL_SERVICE_NAME (or service.name resource attribute). Many SDKs default to unknown_service when neither is configured.
"Service source's badge stuck on 'OTLP idle'" — the SDK has stopped pushing. Check the app logs for OTLP exporter errors; check the agent / network path can reach observekit-api.expeed.com.
"OTLP push returns 400 Bad Request / unable to parse" — the body couldn't be decoded. Point the exporter at the base URL (not a hard-coded /v1/... path), use http/protobuf for the HTTPS endpoint (port 4318; 4317 is gRPC), keep batches under the 10 MiB cap, and note gzip/zstd are both accepted.
"Traces work but logs don't (Micronaut / no javaagent)" — the Logback appender was never handed the SDK. Call OpenTelemetryAppender.install(openTelemetry) at startup and set otel.logs.exporter: otlp.