OTLP endpoints and admission

Every ingest path, the headers it needs, and how a request is admitted or refused.

1 min read Updated Oct 21, 2026 Sentinel Platform Team
otlpingestendpointsapi-keylimits

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.

text
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 configuration

Authentication #

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.

Authenticated request
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 #

text
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.

Python 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},
        )
    )
)