Orkestia
Blog
Lumen

Query API

Read and mutate Lumen over HTTP — logs, error groups, traces, metrics, Pulse, dashboards, and rule JSON

Base: https://lumen-api.orkestia.dev

Writes: Send data. UI mapping: Use Lumen. Agent: MCP.

Auth (read / mutate)

PrincipalHeaderCan
Org member / agent tokenAuthorization: BearerGET + mutations (resolve, rules, keys)
lumk_… scope readX-Api-KeyGET/HEAD only — mutations → 403 WRITE_ACCESS_REQUIRED
Session cookie(browser, lumen.orkestia.dev)Same as org member

Org is taken from the token. Query errors: 401 AUTH_REQUIRED, 403 ORG_ACCESS_REQUIRED / WRITE_ACCESS_REQUIRED. Time windows are capped at 90 days.

Logs

GET /api/logs
GET /api/logs/{log_id}          # public UUID (or legacy int)
GET /api/logs/stats
GET /api/logs/pulse

GET /api/logs query params:

ParamDefaultNotes
project*Wildcard
level, source—Exact
processed—true / false
environment, release, fingerprint—Exact (fingerprint = 64-hex SHA-256)
q—ILIKE on message + traceback
error_type—Substring on traceback
workflow_state_id—int
from, tolast 24h if omittedISO-8601 on received_at
limit50max 500
offset0
curl -sG "https://lumen-api.orkestia.dev/api/logs" \
  --data-urlencode "project=billing-api" \
  --data-urlencode "level=ERROR" \
  --data-urlencode "environment=production" \
  --data-urlencode "from=2026-09-04T00:00:00Z" \
  -H "Authorization: Bearer $ORKESTIA_TOKEN"

/api/logs/stats: project, from/to (default 24h), interval = 5m | 1h | 1d, by_level_series=true.

/api/logs/pulse: interval = 1m | 5m | 15m | 1h | 1d (default 1m); window ≈ 60 buckets, zero-filled per level.

Error groups

GET  /api/error-groups
GET  /api/error-groups/{group_id}     # + 10 recent occurrences
PATCH /api/error-groups/{group_id}/resolve
PATCH /api/error-groups/{group_id}/ignore
PATCH /api/error-groups/{group_id}/reopen
PATCH /api/error-groups/{group_id}/assign
PATCH /api/error-groups/{group_id}/severity
PATCH /api/error-groups/{group_id}/link-ticket
DELETE /api/error-groups/{group_id}/link-ticket

List params:

ParamDefaultNotes
statusopenopen | resolved | ignored | regressed | all
project—primary_project wildcard
environment, release, assignee, team, severity—
unassigned—true
q—title + error_type
error_type—substring
from, to—on last_seen_at
limit50max 200
offset0

Resolve body includes root-cause / solution text — that is triage memory. GET /api/memory/similar, /api/memory/context/{trace_id}, /api/memory/unresolved read it back.

How a group is created: Send data → Fingerprint.

Traces and spans

GET /api/traces/{trace_id}     # { trace, timeline, error_groups }
GET /api/spans/stats
GET /api/spans/slow
GET /api/spans/errors

/spans/stats: project (default *), from/to (default 24h), operation (prefix), min_count (default 1), limit (max 500).

/spans/slow: + min_duration ms (default 1000), operation exact or *.

/spans/errors: + status filter (non-ok spans by operation).

Open/close traces: Send data → Traces (POST /api/traces, org session — not ingest key).

Metrics

GET /api/metrics                 # raw points — metric_name required (wildcard ok)
GET /api/metrics/aggregate
GET /api/metrics/names           # optional project
GET /api/metrics/dimensions      # metric_name required
ParamWhereNotes
metric_namepoints, aggregate, dimensionsRequired on points/dimensions
project, service_name
from, topoints default 1h
filterrepeatableattrs.key:value or resource_attrs.key:value
limit / offsetpointsmax 5000
intervalaggregate5m | 1h | 1d
aggaggregateavg | sum | min | max | count
group_byaggregateproject | service_name | attrs.<k> | resource_attrs.<k>
max_groupsaggregatedefault 20, max 100
curl -sG "https://lumen-api.orkestia.dev/api/metrics/aggregate" \
  --data-urlencode "metric_name=checkout.latency_ms" \
  --data-urlencode "interval=5m" \
  --data-urlencode "agg=avg" \
  --data-urlencode "group_by=attrs.route" \
  -H "Authorization: Bearer $ORKESTIA_TOKEN"

Dashboards, Pulse, usage

GET /api/projects
GET /api/dashboard/summary|top-error-groups|events|projects     # window=15m|1h|6h|24h|7d (default 24h)
GET /api/dashboards[/{id}]
GET /api/product/events
GET /api/product/stats           # counts by event_name
GET /api/usage
GET /api/org-plans/{organization_uuid}

Saved dashboards: POST / PATCH / DELETE /api/dashboards[/{id}] (write principal).

Rules (JSON)

Applied along the pipeline: ingest (before persist) → fingerprint (hash override) → alert (after group write).

Ingest — POST /api/ingest-rules

{
  "project": "*",
  "level": "",
  "source": "",
  "action": "drop",
  "sample_rate": 1.0,
  "enabled": true
}

action: drop | sample. sample_rate 0.0–1.0. Dropped data is gone. CRUD: GET/PATCH/DELETE /api/ingest-rules[/{id}].

Fingerprint — POST /api/fingerprint-rules

{
  "project": "*",
  "pattern": "timeout talking to [A-Z0-9-]+",
  "match_field": "message",
  "fingerprint_key": "billing-timeout",
  "title_override": "Billing upstream timeout",
  "enabled": true,
  "priority": 0
}

match_field: message | traceback | any. Lower priority wins. Match → sha256(fingerprint_key) instead of the default algorithm.

Alert — POST /api/alert-rules

{
  "project": "*",
  "event_type": "new_group",
  "min_occurrences": 1,
  "level_filter": "",
  "channel": "webhook",
  "webhook_url": "https://example.com/hooks/lumen",
  "email_to": "",
  "secret": "optional-hmac",
  "enabled": true
}
FieldValues
event_typenew_group | regressed | threshold
channelwebhook | slack | email | ticket

webhook / slack need webhook_url; email needs email_to; ticket publishes to Kafka lumen.alert.fired. Always set a severity floor / window in the app or you page on every new group.

API keys (programmatic)

GET    /api/api-keys
POST   /api/api-keys          # plaintext secret returned once
GET    /api/api-keys/{key_ref}
DELETE /api/api-keys/{key_ref}

List returns prefixes only. Same mint UX as Enable → Mint a key.

Projects

GET /api/projects — names that have already ingested, with counts. There is no create-project call.