Eimdall API v1

Versioned JSON REST API. JWT Bearer auth (HS256, 12h TTL). Air-gapped deployment — no external network dependency.

Base URL (local dev): http://your-server:8080

In production, Central is served over TLS 1.3 (optionally mTLS) — use https:// and the port configured for your deployment. The http://localhost:8080 examples below assume a local, non-TLS dev instance.

Authentication

All routes except /v1/auth/login and /v1/insights/health require an Authorization: Bearer <token> header.

POST /v1/auth/login Get a JWT token public
FieldTypeDescription
username*stringUsername
password*stringPassword
curl -X POST http://localhost:8080/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"constructor_admin","password":"constructor123"}'
{ "token": "eyJ...", "tenant_id": "constructor-acme", "tenant_type": "constructor", "role": "admin" }
POST /v1/auth/refresh Renew a JWT token JWT

Returns a new token from a still-valid token.

curl -X POST http://localhost:8080/v1/auth/refresh \
  -H "Authorization: Bearer $TOKEN"
{ "token": "eyJ..." }

Robots

GET /v1/robots List the tenant's robots JWT
curl http://localhost:8080/v1/robots -H "Authorization: Bearer $TOKEN"
[{ "robot_id": "BDY-042", "tenant_id": "client-alpha", "last_seen_ms": 1744000000000 }]
GET /v1/client/robots/:id/timeline Activity timeline for a robot JWT
curl "http://localhost:8080/v1/client/robots/BDY-042/timeline" \
  -H "Authorization: Bearer $TOKEN"

Anomalies

GET /v1/anomalies List detected anomalies JWT
Query paramTypeDescription
robot_id?stringFilter by robot
from_ms?integerStart of time range (epoch ms)
to_ms?integerEnd of time range (epoch ms)
limit?integerMax results (default 100, max 1000)
curl "http://localhost:8080/v1/anomalies?robot_id=BDY-042&limit=20" \
  -H "Authorization: Bearer $TOKEN"

LLM Insights

GET /v1/insights List generated insights JWT
Query paramTypeDescription
robot_id?stringFilter by robot
limit?integerMax results (default 20, max 100)
curl "http://localhost:8080/v1/insights?limit=5" -H "Authorization: Bearer $TOKEN"
POST /v1/insights/analyze Trigger an LLM analysis JWT
FieldTypeDescription
kind*stringfleet_health | feature_adoption_report | user_journey_analysis | friction_emerging_usage | cross_environment_comparison | reliability_intelligence | robot_anomaly | daily_digest
predictive_maintenance is still accepted as a legacy alias for reliability_intelligence.
robot_id?stringRequired for robot_anomaly, daily_digest
ollama_url?stringDefault: EIMDALL_OLLAMA_URL value, else http://localhost:11434
model?stringDefault: mistral
curl -X POST http://localhost:8080/v1/insights/analyze \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kind":"friction_emerging_usage"}'

Webhooks

Webhooks POST to your URL on every event. Payload optionally signed with an HMAC secret.

GET /v1/webhooks List the tenant's webhooks JWT
curl http://localhost:8080/v1/webhooks -H "Authorization: Bearer $TOKEN"
POST /v1/webhooks Create a webhook JWT

Available events: insight.created · anomaly.critical · prediction.high_risk · robot.offline · report.ready · * (all)

FieldTypeDescription
url*stringDestination URL (HTTPS recommended)
events*string[]List of events to subscribe to
secret?stringHMAC secret for signature verification
curl -X POST http://localhost:8080/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.internal/hooks/eimdall",
    "events": ["insight.created", "anomaly.critical"],
    "secret": "your_hmac_secret"
  }'

Webhook payload

{
  "event": "insight.created",
  "timestamp": "2026-04-08T14:30:00Z",
  "tenant_id": "constructor-acme",
  "data": {
    "insight_id": "ins_abc123",
    "kind": "friction_emerging_usage",
    "severity": "critical"
  }
}
DELETE /v1/webhooks/:id Delete a webhook JWT
curl -X DELETE http://localhost:8080/v1/webhooks/wh_abc123456 \
  -H "Authorization: Bearer $TOKEN"

Client — Predictions

GET /v1/client/fleet-health Client fleet health JWT (client)
curl http://localhost:8080/v1/client/fleet-health \
  -H "Authorization: Bearer $CLIENT_TOKEN"
[{ "robot_id": "BDY-042", "health_score": 82, "risk_level": "low", "predicted_failure": "none" }]
GET /v1/client/predictions Failure predictions JWT (client)
curl http://localhost:8080/v1/client/predictions \
  -H "Authorization: Bearer $CLIENT_TOKEN"

Tenant

GET /v1/tenant/settings Current tenant's settings JWT
curl http://localhost:8080/v1/tenant/settings -H "Authorization: Bearer $TOKEN"
PUT /v1/tenant/settings Configure data sharing with a constructor JWT (client/operator, role admin or operator)

Self-service: a client/operator tenant controls what a constructor tenant sees of its data — none, aggregated, or full.

FieldTypeDescription
constructor_id*stringTarget constructor tenant
sharing*stringnone | aggregated | full
curl -X PUT http://localhost:8080/v1/tenant/settings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"constructor_id":"constructor-acme","sharing":"aggregated"}'

Integration examples

Python

import requests

BASE = "http://localhost:8080"

# Login
r = requests.post(f"{BASE}/v1/auth/login",
    json={"username": "constructor_admin", "password": "constructor123"})
token = r.json()["token"]
headers = {"Authorization": f"Bearer {token}"}

# List insights
insights = requests.get(f"{BASE}/v1/insights?limit=10", headers=headers).json()
for ins in insights:
    print(ins["kind"], ins["analysis_text"][:80])

JavaScript / fetch

const BASE = "http://localhost:8080";

const { token } = await fetch(`${BASE}/v1/auth/login`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ username: "constructor_admin", password: "constructor123" })
}).then(r => r.json());

const insights = await fetch(`${BASE}/v1/insights`, {
  headers: { Authorization: `Bearer ${token}` }
}).then(r => r.json());

console.log(insights);

curl — full flow

TOKEN=$(curl -s -X POST http://localhost:8080/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"constructor_admin","password":"constructor123"}' | jq -r .token)

# Trigger an analysis
INSIGHT_ID=$(curl -s -X POST http://localhost:8080/v1/insights/analyze \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kind":"friction_emerging_usage"}' | jq -r .insight_id)

# Read the result
curl -s "http://localhost:8080/v1/insights?limit=1" \
  -H "Authorization: Bearer $TOKEN" | jq '.[0].analysis_text'