Skip to main content

Sending events with OTLP

The OpenTelemetry logs endpoint: exact path, JSON-only transport, which attributes map to which fields, and the one defect that should decide your choice.

Written for whoever runs IT7 min readUpdated

If you already run an OpenTelemetry Collector, you already have a log pipeline, and pointing one more exporter at Qorionix is less work than deploying anything new. That is the entire case for this endpoint, and it is a good case. This article covers the exact contract, what maps to what, and the one honest reason you might choose the other endpoint instead.

The endpoint

The mounted path
POST /api/v1/siem/otlp/v1/logs

Note the shape carefully. OpenTelemetry exporters conventionally append /v1/logs to whatever base you configure, so the value you put in your exporter configuration is the path without that suffix:

What to configure where
Exporter endpoint:  https://<your-api-host>/api/v1/siem/otlp
Resulting request:  POST https://<your-api-host>/api/v1/siem/otlp/v1/logs

If your exporter sends to the literal endpoint you configure without appending anything, configure the full path including /v1/logs. Getting this wrong produces a 404 from the router rather than an error from the handler, which is a useful way to tell the two mistakes apart.

Protocol: HTTP with JSON, and nothing else

This is the constraint most likely to catch you out, because it is not the default in most tooling.

  • OTLP over HTTP only. gRPC is not supported. The reasoning is recorded in the source: gRPC transport was out of scope for the first version, so that the standard library HTTP server already in use could be kept and the OpenTelemetry protobuf dependency avoided. That is a legitimate trade, and it is also a real limitation if your fleet is standardised on gRPC.
  • JSON encoding only. Protobuf over HTTP is not accepted either — the body is parsed as JSON. Most collectors default to protobuf when exporting over HTTP, so this usually needs an explicit setting.
An OpenTelemetry Collector exporter, sketched
exporters:
  otlphttp/qorionix:
    endpoint: https://<your-api-host>/api/v1/siem/otlp
    encoding: json
    headers:
      Authorization: Bearer zqi_YOUR_TOKEN_HERE

service:
  pipelines:
    logs:
      exporters: [otlphttp/qorionix]

The option names vary between collector versions and between the collector and the language SDKs — check your own version's reference for the exact key that selects JSON encoding. The two facts that do not vary are the path and that the body must be JSON.

Authentication

The same ingest token and the same header as the HTTP Event Collector. Both prefixes are accepted, matched case-insensitively:

Either form
Authorization: Bearer zqi_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Authorization: Splunk zqi_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

One difference matters operationally: OTLP traffic is not counted against the token's usage figures. The collector endpoint records events, bytes, last-used time and last-used address on every accepted request; this endpoint does not. A token used only for OTLP will show events_ingested of zero forever, which is not evidence that nothing arrived. Combined with the swallowed storage errors above, this means the token counters cannot be used to verify an OTLP pipeline at all. Search is the only check that works.

The rate limiter applies to this endpoint exactly as it does to the collector — 1,000 events per second steady and 2,000 burst per token per process — and it misreports a throttle as 401 invalid token here too. See Ingest tokens.

What maps to what

The handler reads a deliberately small part of the OTLP envelope: resourceLogs, each entry's resource.attributes, then scopeLogs and the logRecords within. Everything else in the envelope is ignored rather than rejected, so a payload with instrumentation scope detail or trace correlation identifiers is accepted — those fields simply do not survive into the stored event.

OTLP fields and attributes the handler reads, and the field each becomes. Set these attribute keys and your events arrive with populated fields instead of a body and nothing else.
OTLP locationBecomes
logRecords[].timeUnixNanoThe event timestamp.
logRecords[].observedTimeUnixNanoThe timestamp, when timeUnixNano is absent or zero. If both are missing, the time of arrival is used.
logRecords[].severityNumberSeverity, mapped as in the next table.
logRecords[].bodyThe raw event text.
resource.attributes["host.name"]The host.
logRecords[].attributes["category"]The event category.
logRecords[].attributes["action"]The action.
logRecords[].attributes["outcome"]The outcome — success or failure.
logRecords[].attributes["user.name"]The user.
logRecords[].attributes["source.ip"]The source address.
logRecords[].attributes["destination.ip"]The destination address.

Every record attribute is also retained as a general field, so an attribute not in that list is not lost — it is simply not one of the named fields that searches and detection selections read directly. host.name is read from the resource attributes rather than the record attributes, which is where OpenTelemetry conventionally puts it; setting it per record will not populate the host.

The source name on every event from this endpoint is the fixed string otlp. It is not derived from the token's parser and it cannot be set per record. This matters because normalisation chooses its mapping from the source name, so an OTLP event is mapped generically regardless of what the payload actually describes. If you are shipping a specific, recognisable format — Microsoft audit records, CloudTrail, a Cisco firewall — you will get materially better field extraction by posting it to the collector endpoint with the matching parser instead.

OpenTelemetry severity numbers as mapped onto the platform's 0-10 severity scale.
OTLP severityNumberStored severity
21 and above (FATAL)10
17 to 20 (ERROR)8
13 to 16 (WARN)6
9 to 12 (INFO)4
5 to 8 (DEBUG)2
4 and below, or unset1

Responses

Success is 200, not the 202 the collector endpoint returns. The body is the OTLP partial-success envelope with nothing in it, which is the protocol's way of saying every record was taken.

Success
HTTP/1.1 200 OK
Content-Type: application/json

{"partialSuccess":{}}
Every rejection this endpoint produces
405 Method Not Allowed   method not allowed
401 Unauthorized         missing bearer token
401 Unauthorized         invalid token
400 Bad Request          read failed
400 Bad Request          empty OTLP payload
400 Bad Request          <the JSON decoder's own message>

There is no 503 in that list, and its absence is the defect described at the top of this article rather than evidence that storage cannot fail. Note also that a payload which is valid JSON but contains no resourceLogs is accepted with 200 and produces zero events — structurally indistinguishable, from the client's side, from a payload that produced thousands.

When to choose OTLP over the collector

The honest summary: choose OTLP when you already have an OpenTelemetry pipeline and the operational saving of not adding a second shipper outweighs the loss of delivery confirmation. Choose the collector endpoint in every other case.

A direct comparison of the two push endpoints, on the points that actually differ.
PropertyHTTP Event CollectorOTLP
Tells you when storage failedYes — 503No — still 200
Counts against token usage figuresYesNo
Format-specific parsingYes, selected by the tokenNo — always generic, source is always otlp
Accepts plain text linesYesNo — JSON envelope only
Works with an existing OpenTelemetry CollectorNeeds an extra exporter or shipperYes, one exporter block

A reasonable middle path, if you are committed to OpenTelemetry: keep the OTLP exporter for volume and convenience, and additionally post your highest-value source — identity events, firewall denials — to the collector endpoint with the correct parser, so the events you will actually search on are properly mapped and confirmed stored.

What to read next

  1. Sending events with the HTTP Event Collector — the endpoint with delivery confirmation.
  2. Ingest tokens — the credential both endpoints share.
  3. Searching your logs — the only reliable way to verify an OTLP pipeline.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in logs and detection