OTLP endpoints and admission
Every ingest path, the headers it needs, and how a request is admitted or refused.
Endpoints #
Sentinel accepts OTLP over HTTP on one host. Each signal type has its own path, and the path is the only thing that differs between them.
POST /otlp/v1/logs OTLP logs
POST /otlp/v1/traces OTLP traces
POST /otlp/v1/metrics OTLP metrics
POST /otlp/v1/dbm Database monitoring spans
POST /otlp/v1/profiles/pprof Profile data
POST /otlp/v1/logs/json Forwarder JSON logs
GET /otlp/v1/probes Dynamic instrumentation configurationAuthentication #
Every ingest request carries an API key in the x-sentinel-api-key header. The key identifies the sending workload, and the organisation it belongs to is taken from the key rather than from the payload, so a sender cannot write into another organisation.
curl -X POST "$SENTINEL_HOST/otlp/v1/logs" \
-H "x-sentinel-api-key: $SENTINEL_API_KEY" \
-H "content-type: application/json" \
--data @logs.json
Admission order #
Requests are admitted in a fixed order, and each stage can end the request. Understanding the order makes a refusal straightforward to diagnose.
- Body size. A body over 4 MiB is refused before it is read.
- Credential. The key must exist, be active, and be within its expiry.
- Scope. The key must carry the scope the endpoint requires.
- Payload. The body must decode, and must contain at least one record.
A request that passes every stage is accepted and queued for indexing. Records that fail later validation are counted rather than retried by the sender.
Status codes #
200 Accepted. Some or all records were queued; check partial_success.
202 Accepted for asynchronous processing.
400 The payload could not be decoded, or contained no records.
401 The API key was missing or not recognised.
403 The key is valid but lacks the scope this endpoint requires.
413 The body exceeded 4 MiB.
429 The sender is over its ingest rate, or its plan's monthly quota.
503 The ingest tier is temporarily unavailable.Partial success #
A request is accepted as a whole even when some of its records are not. When that happens the response reports how many records were rejected and why, using the OTLP partial_success field. Collectors surface this as a warning and keep sending, so one malformed record does not stall a whole batch.
Over-quota data is dropped, not refused
When an organisation exceeds its plan's monthly ingest, requests still return success and the excess records are dropped with a partial-success notice. A sender therefore sees no errors while over quota — check the usage figures on your plan rather than watching for failures.
Query parameters #
/otlp/v1/traces, /otlp/v1/logs and /otlp/v1/metrics accept gzip and deflate request compression. The header Content-Encoding: gzip is enough; no query parameter is required.
Rate limits #
Limits are applied per API key. Exceeding one returns 429 with a Retry-After header. Clients should honour that header and back off exponentially.
- Profile uploads are limited to 600 per minute per key.
- A single profile upload is limited to 4 MiB.
- A single ingest request body is limited to 4 MiB.
Set Content-Encoding: gzip on every ingest request. It typically removes 60–80% of the bytes on log payloads, which is the cheapest way to stay inside rate limits.
Sending from a language #
The OpenTelemetry SDKs send OTLP directly, so no custom client is needed. Set the endpoint and the header once and leave the rest to the SDK.
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(
endpoint=f"{HOST}/otlp/v1/traces",
headers={"x-sentinel-api-key": API_KEY},
)
)
)