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
| Field | Type | Description |
|---|---|---|
| username* | string | Username |
| password* | string | Password |
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 param | Type | Description |
|---|---|---|
| robot_id? | string | Filter by robot |
| from_ms? | integer | Start of time range (epoch ms) |
| to_ms? | integer | End of time range (epoch ms) |
| limit? | integer | Max 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 param | Type | Description |
|---|---|---|
| robot_id? | string | Filter by robot |
| limit? | integer | Max 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
| Field | Type | Description |
|---|---|---|
| kind* | string | fleet_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? | string | Required for robot_anomaly, daily_digest |
| ollama_url? | string | Default: EIMDALL_OLLAMA_URL value, else http://localhost:11434 |
| model? | string | Default: 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)
| Field | Type | Description |
|---|---|---|
| url* | string | Destination URL (HTTPS recommended) |
| events* | string[] | List of events to subscribe to |
| secret? | string | HMAC 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.
| Field | Type | Description |
|---|---|---|
| constructor_id* | string | Target constructor tenant |
| sharing* | string | none | 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'