Reference: Ingest API (advanced, bring your own OTLP)
The agent's loopback auth-proxy signs and forwards everything automatically. This page documents the raw ingest endpoints and HMAC scheme for the case where you want to send telemetry to AtherOps from a system that cannot run the agent. If you send directly, you handle signing yourself.
Authentication: HMAC-SHA256
Every ingest request is authenticated with three headers. The signature is computed over a fixed message built from the request timestamp, method, and the backend request path.
Message format
msg = "<unix_ts>" + "\n" + "<METHOD>" + "\n" + "<path>"
<unix_ts>: the request time as Unix seconds (string, e.g.1748000000).<METHOD>: the HTTP method, e.g.POST.<path>: the request path as the backend sees it, e.g./ingest,/ingest/traces,/ingest/logs/otlp.
Headers
| Header | Value |
|---|---|
X-Timestamp | <unix_ts> (the same value used in msg). |
X-Key-Hash | hex( sha256( rawKey ) ) (lookup index; identifies the key without sending it) |
X-Signature | hex( hmac_sha256( rawKey, msg ) ). |
rawKeyis your API key. The raw key is never sent (only its SHA-256 hash and the signature go over the wire).- The backend recomputes the signature over the path it receives, so the signed path must equal the actual request path.
- The timestamp must be within ±5 minutes of the server's clock, or the
request is rejected with
401.
Any missing/invalid header, an unknown key hash, or a bad signature returns
HTTP 401.
Endpoints
| Endpoint | Method | Body | Notes |
|---|---|---|---|
/ingest | POST | Prometheus exposition text | Metrics → VictoriaMetrics. |
/ingest/traces | POST | OTLP/JSON (ExportTraceServiceRequest) | Spans → ClickHouse. |
/ingest/logs/otlp | POST | OTLP/JSON (ExportLogsServiceRequest) | Logs → ClickHouse. |
Bodies are limited to 32 MiB. An empty/zero-record body returns 204
without storing anything.
Responses
| Status | Meaning |
|---|---|
204 No Content | Accepted (and stored, if non-empty). |
400 Bad Request | Unreadable body or invalid OTLP payload. |
401 Unauthorized | Missing/invalid auth headers, unknown key, bad signature, or stale timestamp. |
502 Bad Gateway | Storage backend (VictoriaMetrics / ClickHouse) failed. |
Tenant identity is server-derived (you cannot set it)
For every endpoint, the tenant (org_id) and host identity (host_id, node
name) are derived server-side from the key lookup, never from your payload.
For the OTLP logs endpoint specifically, any attribute named org_id,
host_id, instance, or node in your resource or record attributes is
dropped before storage. You cannot spoof another tenant. See
Multi-tenant isolation.
Get a query token
The /query/traces and /query/logs endpoints authenticate with the JWT from your AtherOps web session, not an agent API key. To get that token from the command line, call the login endpoint:
ATHEROPS_JWT=$(curl -s -X POST https://api.atherops.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"your-password"}' \
| jq -r .token)
export ATHEROPS_JWT
The token is a short-lived session JWT. If a query returns 401 Unauthorized, log in again to get a fresh one.
Query your traces back
Ingest is HMAC-signed (above). Reading stored spans uses a different endpoint and a different auth scheme: it is authenticated with the JWT from your logged-in AtherOps web session, and is scoped to your org by that token's claims (you cannot read another tenant's spans).
| Endpoint | Method | Auth | Returns |
|---|---|---|---|
/query/traces | GET | Authorization: Bearer <JWT> | JSON array of spans for your org, newest first. |
curl -s \
-H "Authorization: Bearer $ATHEROPS_JWT" \
"https://api.atherops.com/query/traces?service=api&limit=50"
Query parameters (all optional):
| Param | Default | Meaning |
|---|---|---|
service | (all) | Filter by service name. |
error | false | true returns only error spans. |
trace_id | (none) | Return all spans for one trace (hex trace ID). |
start | 1 hour ago | Range start, RFC3339 (e.g. 2026-05-27T10:00:00Z). |
end | now | Range end, RFC3339. |
limit | 100 | Max rows (capped at 500). |
Each span object includes Timestamp, TraceID, SpanID, ParentSpanID,
Service, Operation, DurationMs, StatusCode, Error, and Attributes.
| Status | Meaning |
|---|---|
200 OK | JSON array of spans (may be empty [] if none match). |
401 Unauthorized | Missing/invalid bearer token. |
405 Method Not Allowed | Use GET. |
502 Bad Gateway | Storage backend query failed. |
This release has no in-product trace view. The query API above is how you confirm and read spans today. See scope.
OTLP logs field mapping
When you POST to /ingest/logs/otlp, fields map as follows:
| OTLP field | Stored as | Notes |
|---|---|---|
log record body (string value) | message | |
severityText | level | lowercased; if empty, mapped from severityNumber; else info |
resource attr service.name | source | falls back to unknown |
timeUnixNano | timestamp | falls back to observedTimeUnixNano, then now |
| other resource + record attributes | labels | record attrs win on key clash; reserved keys dropped |