Complete reference for the Vigil REST API and SSE streaming endpoints. All production endpoints are authenticated; public dashboard share tokens work without a key.
All endpoints require a valid session or API key. Browser-based Vigil sessions use HTTP cookies automatically. For programmatic access, generate an API key in the dashboard.
/login/ — the session cookie is sent automatically by the browser.X-API-Key: <your-key> to every request. Generate keys in Settings → API Keys.POST and DELETE requests require the CSRF token. Include X-CSRFToken: <value> (read from the csrftoken cookie) for browser-initiated requests. Requests authenticated with an API key bypass CSRF.
Query devices, their quantity bindings, and associated sites.
| Param | Type | Description | |
|---|---|---|---|
site_id | string | optional | Filter by site UUID |
search | string | optional | Search by display name or hardware_id |
online | bool | optional | Filter online devices (true / false) |
{"count":3,"results":[{"id":"...","display_name":"Pump 1","hardware_id":"SN-PS3-PUMP1","is_online":true,"site":{"id":"...","name":"Pump Station 3"},"binding_count":2}]}curl -H "X-API-Key: <key>" https://yourdomain.com/devices/
import requests
r = requests.get("https://yourdomain.com/devices/",
headers={"X-API-Key": "<key>"})
devices = r.json()["results"]const r = await fetch("/devices/", {
headers: { "X-API-Key": "<key>" }
});
const { results: devices } = await r.json();{"id":"...","display_name":"Pump 1","hardware_id":"SN-PS3-PUMP1","is_online":true,
"last_seen_at":"2026-04-13T12:00:00Z","site":{...},
"bindings":[{"id":"...","quantity":{"name":"Vibration","slug":"vibration.acceleration.rms","base_unit_symbol":"m/s²"},"is_active":true}]}curl -H "X-API-Key: <key>" https://yourdomain.com/devices/<device_id>/
r = requests.get(f"https://yourdomain.com/devices/{device_id}/",
headers={"X-API-Key": "<key>"})
device = r.json()const device = await (await fetch(`/devices/${deviceId}/`,
{ headers: { "X-API-Key": "<key>" } })).json();{"device_id":"...","readings":[{"binding_id":"...","quantity":"Temperature","unit":"K","value":295.3,"time":"2026-04-13T12:00:00Z"}]}curl -H "X-API-Key: <key>" https://yourdomain.com/devices/<id>/latest/
r = requests.get(f"https://yourdomain.com/devices/{device_id}/latest/",
headers={"X-API-Key": "<key>"})
latest = r.json()["readings"]const { readings } = await (await fetch(`/devices/${deviceId}/latest/`,
{ headers: { "X-API-Key": "<key>" } })).json();curl -H "X-API-Key: <key>" https://yourdomain.com/sites/
r = requests.get("https://yourdomain.com/sites/",
headers={"X-API-Key": "<key>"})
sites = r.json()["results"]const { results: sites } = await (await fetch("/sites/",
{ headers: { "X-API-Key": "<key>" } })).json();curl -H "X-API-Key: <key>" https://yourdomain.com/sites/<site_id>/
r = requests.get(f"https://yourdomain.com/sites/{site_id}/",
headers={"X-API-Key": "<key>"})
site = r.json()Query time-series data from TimescaleDB. The /trace/query/ endpoint is the primary data access interface, with automatic downsampling for large time ranges.
Accepts a JSON body with binding IDs and a time range. Returns columnar series data suitable for charting. Points are automatically downsampled to max_points using time-bucket averaging.
| Field | Type | Description | |
|---|---|---|---|
binding_ids | string[] | required | Array of binding UUIDs to query |
start | string | required | ISO 8601 start datetime (UTC) |
end | string | required | ISO 8601 end datetime (UTC) |
max_points | integer | optional | Max data points per series (default 200, max 5000) |
{
"series": [
{
"binding_id": "f3a1b2c4-...",
"label": "Pump 1 / Vibration",
"times": ["2026-04-13T09:00:00Z", "..."],
"values": [1.24, 1.31, 1.28, null, 1.35],
"unit": "m/s²",
"min": 1.12,
"max": 1.89,
"mean": 1.31
}
],
"meta": { "points": 180, "downsampled": true }
}curl -X POST https://yourdomain.com/trace/query/ \
-H "X-API-Key: <key>" \
-H "Content-Type: application/json" \
-d '{"binding_ids":["<uuid>"],"start":"2026-04-13T09:00:00Z","end":"2026-04-13T12:00:00Z","max_points":180}'import requests
r = requests.post(
"https://yourdomain.com/trace/query/",
headers={"X-API-Key": "<key>"},
json={
"binding_ids": ["<binding-uuid>"],
"start": "2026-04-13T09:00:00Z",
"end": "2026-04-13T12:00:00Z",
"max_points": 180,
}
)
series = r.json()["series"]const r = await fetch("/trace/query/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<key>"
},
body: JSON.stringify({
binding_ids: ["<binding-uuid>"],
start: "2026-04-13T09:00:00Z",
end: "2026-04-13T12:00:00Z",
max_points: 180
})
});
const { series } = await r.json();Returns all active device-quantity pairs with their latest value and 6-state alert classification (normal | warning | critical | stale | offline | never). Designed for polling at 10-second intervals to drive status boards and monitoring displays.
| Param | Type | Description | |
|---|---|---|---|
site | string | optional | Filter by site UUID |
qty | string | optional | Filter by quantity slug (e.g. temperature.ambient) |
{
"ts": "2026-04-13T12:00:00Z",
"cards": [
{
"id": "f3a1b2c4-...", // binding_id
"device_id": "...",
"device_name": "Pump 1",
"site_name": "Pump Station 3",
"quantity_name": "Vibration",
"unit": "m/s²",
"value": "1.35", // formatted string
"value_raw": 1.35, // raw float
"updated_at": "2026-04-13T11:58:30Z",
"age_sec": 90,
"alert_state": "normal", // normal|warning|critical|stale|offline|never
"alert_event_id": null // UUID if active alert exists
}
]
}curl -H "X-API-Key: <key>" \ "https://yourdomain.com/status-board/api/?site=<site_uuid>"
r = requests.get(
"https://yourdomain.com/status-board/api/",
headers={"X-API-Key": "<key>"},
params={"site": "<site-uuid>"}
)
cards = r.json()["cards"]
critical = [c for c in cards if c["alert_state"] == "critical"]// Poll every 10 seconds
async function pollStatus() {
const r = await fetch("/status-board/api/", {
headers: { "X-API-Key": "<key>" }
});
const { cards } = await r.json();
const critical = cards.filter(c => c.alert_state === "critical");
console.log(`${critical.length} critical alerts`);
}
setInterval(pollStatus, 10_000);Query alert events, rules, and subscribe to the live SSE alert stream.
| Param | Type | Description | |
|---|---|---|---|
status | string | optional | active, resolved, acknowledged |
severity | string | optional | critical, warning, info |
device_id | string | optional | Filter by device UUID |
curl -H "X-API-Key: <key>" \ "https://yourdomain.com/alerts/?status=active&severity=critical"
r = requests.get("https://yourdomain.com/alerts/",
headers={"X-API-Key": "<key>"},
params={"status": "active", "severity": "critical"})
events = r.json()["events"]const r = await fetch("/alerts/?status=active&severity=critical",
{ headers: { "X-API-Key": "<key>" } });
const { events } = await r.json();curl -H "X-API-Key: <key>" https://yourdomain.com/alerts/<event_id>/
r = requests.get(f"https://yourdomain.com/alerts/{event_id}/",
headers={"X-API-Key": "<key>"})
alert = r.json()const alert = await (await fetch(`/alerts/${eventId}/`,
{ headers: { "X-API-Key": "<key>" } })).json();A long-lived HTTP connection that pushes alert events whenever a new alert fires or resolves. Use this to drive real-time notification badges, external webhooks, or dashboards without polling.
event: alert
data: {"event_id":"...","severity":"critical","device":"Pump 1","quantity":"Vibration","value":4.8,"triggered_at":"2026-04-13T12:01:00Z"}// Browser — EventSource (auto-reconnects)
const src = new EventSource("/alerts/stream/");
src.addEventListener("alert", e => {
const alert = JSON.parse(e.data);
console.log(`ALERT ${alert.severity}: ${alert.device} — ${alert.quantity} = ${alert.value}`);
});
src.onerror = () => console.warn("SSE reconnecting…");import sseclient, requests, json
resp = requests.get("https://yourdomain.com/alerts/stream/",
headers={"X-API-Key": "<key>"}, stream=True)
for event in sseclient.SSEClient(resp):
if event.event == "alert":
alert = json.loads(event.data)
print(f"ALERT: {alert['device']} — {alert['severity']}")curl -H "X-API-Key: <key>" https://yourdomain.com/alerts/rules/
r = requests.get("https://yourdomain.com/alerts/rules/",
headers={"X-API-Key": "<key>"})
rules = r.json()["rules"]const { rules } = await (await fetch("/alerts/rules/",
{ headers: { "X-API-Key": "<key>" } })).json();Access dashboards and their panel data. Public share links work without authentication.
curl -H "X-API-Key: <key>" https://yourdomain.com/canvas/
r = requests.get("https://yourdomain.com/canvas/",
headers={"X-API-Key": "<key>"})
dashboards = r.json()["dashboards"]const { dashboards } = await (await fetch("/canvas/",
{ headers: { "X-API-Key": "<key>" } })).json();No API key required. Use this URL in iframes or to embed dashboards in external portals.
// Navigate directly — no API key needed
window.open("https://yourdomain.com/canvas/public/<token>/");<!-- Embed in an iframe --> <iframe src="https://yourdomain.com/canvas/public/<token>/" width="100%" height="600" frameborder="0" allowfullscreen> </iframe>
Scoped to the bindings visible on the public dashboard. Same body format as /trace/query/.
curl -X POST https://yourdomain.com/canvas/public/<token>/query/ \
-H "Content-Type: application/json" \
-d '{"binding_ids":["<uuid>"],"start":"2026-04-13T09:00:00Z","end":"2026-04-13T12:00:00Z"}'const r = await fetch(`/canvas/public/${token}/query/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
binding_ids: ["<uuid>"],
start: new Date(Date.now() - 3600_000).toISOString(),
end: new Date().toISOString(),
})
});
const { series } = await r.json();Natural language queries and the Argus conversational assistant with tool-use and institutional memory.
| Field | Type | Description | |
|---|---|---|---|
question | string | required | Natural language question about your sensor data |
curl -X POST https://yourdomain.com/api/v1/ai/nlq/ \
-H "X-API-Key: <key>" \
-H "Content-Type: application/json" \
-d '{"question":"What was the average temperature in Building A last week?"}'r = requests.post("https://yourdomain.com/api/v1/ai/nlq/",
headers={"X-API-Key": "<key>"},
json={"question": "What was the peak vibration on Pump 1 this month?"})
result = r.json()
print(result["answer"]) # plain-English answer
print(result["sql"]) # generated SQLconst r = await fetch("/api/v1/ai/nlq/", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": "<key>" },
body: JSON.stringify({ question: "Show CO2 trends for floor 2 this week" })
});
const { answer, sql, chart_data } = await r.json();Sends a message to Argus and returns a streaming SSE response. Argus can autonomously call tools (query data, save memories, build dashboards, configure alerts) within a single ReAct loop. Pass conversation_id to continue an existing thread.
| Field | Type | Description | |
|---|---|---|---|
message | string | required | User message |
conversation_id | string | optional | UUID of existing conversation to continue |
page_context | object | optional | Current UI context: {page, dashboard_id, device_id, site_id} |
data: {"event":"token","text":"Here is what I found..."}
data: {"event":"thinking","tool":"query_data","status":"calling"}
data: {"event":"tool_result","tool":"query_data","result":{...}}
data: {"event":"thinking","tool":"query_data","status":"done"}
data: {"event":"navigate","url":"/canvas/<id>/","label":"Dashboard"}
data: {"event":"done","content":"Full response text","conversation_id":"..."}// Stream Argus response token by token
const resp = await fetch("/api/v1/ai/argus/message/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "<key>"
},
body: JSON.stringify({
message: "Build me a dashboard for pump station monitoring",
page_context: { page: "canvas" }
})
});
const reader = resp.body.getReader();
const dec = new TextDecoder();
let buf = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += dec.decode(value);
for (const line of buf.split("\n\n")) {
if (!line.startsWith("data: ")) continue;
const ev = JSON.parse(line.slice(6));
if (ev.event === "token") process.stdout.write(ev.text);
if (ev.event === "done") { console.log("\nDone:", ev.conversation_id); break; }
}
buf = buf.slice(buf.lastIndexOf("\n\n") + 2);
}import json, requests
resp = requests.post(
"https://yourdomain.com/api/v1/ai/argus/message/",
headers={"X-API-Key": "<key>"},
json={"message": "What is the current vibration level on Pump 1?"},
stream=True,
)
for line in resp.iter_lines():
if not line or not line.startswith(b"data: "):
continue
ev = json.loads(line[6:])
if ev["event"] == "token":
print(ev["text"], end="", flush=True)
elif ev["event"] == "done":
print(f"\n[conversation: {ev['conversation_id']}]")
breakcurl -H "X-API-Key: <key>" \ https://yourdomain.com/api/v1/ai/argus/conversations/
r = requests.get("https://yourdomain.com/api/v1/ai/argus/conversations/",
headers={"X-API-Key": "<key>"})
conversations = r.json()["conversations"]| Param | Type | Description | |
|---|---|---|---|
q | string | optional | Semantic search query |
device_id | string | optional | Filter by device UUID |
site_id | string | optional | Filter by site UUID |
memory_type | string | optional | observation, procedure, anomaly, maintenance |
# Search memories
curl -H "X-API-Key: <key>" \
"https://yourdomain.com/api/v1/ai/argus/memory/?q=pump+bearing+failure"
# Create a memory
curl -X POST https://yourdomain.com/api/v1/ai/argus/memory/ \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-d '{"content":"Pump 2 bearing replaced Jan 2026","memory_type":"maintenance","salience":0.8}'# Search
r = requests.get("https://yourdomain.com/api/v1/ai/argus/memory/",
headers={"X-API-Key": "<key>"},
params={"q": "bearing failure"})
memories = r.json()["memories"]
# Create
r = requests.post("https://yourdomain.com/api/v1/ai/argus/memory/",
headers={"X-API-Key": "<key>"},
json={"content": "CO2 sensor on Floor 2 prone to drift",
"memory_type": "observation", "salience": 0.7})curl -H "X-API-Key: <key>" \ https://yourdomain.com/api/v1/ai/argus/investigations/
r = requests.get("https://yourdomain.com/api/v1/ai/argus/investigations/",
headers={"X-API-Key": "<key>"})
invs = [i for i in r.json()["investigations"] if i["status"] == "complete"]Query computed fields derived from raw sensor data using configurable transformation expressions.
curl -H "X-API-Key: <key>" https://yourdomain.com/prism/fields/
r = requests.get("https://yourdomain.com/prism/fields/",
headers={"X-API-Key": "<key>"})
fields = r.json()["fields"]| Param | Type | Description | |
|---|---|---|---|
start | string | optional | ISO 8601 start (default: 24h ago) |
end | string | optional | ISO 8601 end (default: now) |
curl -H "X-API-Key: <key>" \ "https://yourdomain.com/prism/<field_id>/data/?start=2026-04-13T00:00:00Z"
r = requests.get(f"https://yourdomain.com/prism/{field_id}/data/",
headers={"X-API-Key": "<key>"},
params={"start": "2026-04-13T00:00:00Z"})
data = r.json()