API Reference

Vigil Platform API v1

Complete reference for the Vigil REST API and SSE streaming endpoints. All production endpoints are authenticated; public dashboard share tokens work without a key.

Authentication

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.

Session auth: Log in at /login/ — the session cookie is sent automatically by the browser.

API key: Add the header X-API-Key: <your-key> to every request. Generate keys in Settings → API Keys.

CSRF: 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.

Devices & Sites

Query devices, their quantity bindings, and associated sites.

GET /devices/ List all devices with site, status, and binding counts
ParamTypeDescription
site_idstringoptionalFilter by site UUID
searchstringoptionalSearch by display name or hardware_id
onlinebooloptionalFilter online devices (true / false)
Response
{"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();
GET /devices/{device_id}/ Full device detail including all active bindings and quantity metadata
Response
{"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();
GET /devices/{device_id}/latest/ Latest measurement for every active binding — ideal for real-time widgets
Response
{"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();
GET /sites/ List all sites in the organization
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();
GET /sites/{site_id}/ Site detail with device list
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()

Measurements

Query time-series data from TimescaleDB. The /trace/query/ endpoint is the primary data access interface, with automatic downsampling for large time ranges.

POST /trace/query/ Query time-series data with automatic downsampling

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.

Request body
FieldTypeDescription
binding_idsstring[]requiredArray of binding UUIDs to query
startstringrequiredISO 8601 start datetime (UTC)
endstringrequiredISO 8601 end datetime (UTC)
max_pointsintegeroptionalMax data points per series (default 200, max 5000)
Response
{
  "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();
GET /status-board/api/ Real-time device status grid — latest values and alert states

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.

ParamTypeDescription
sitestringoptionalFilter by site UUID
qtystringoptionalFilter by quantity slug (e.g. temperature.ambient)
Response
{
  "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);

Alerts

Query alert events, rules, and subscribe to the live SSE alert stream.

GET /alerts/ List alert events with optional filtering
ParamTypeDescription
statusstringoptionalactive, resolved, acknowledged
severitystringoptionalcritical, warning, info
device_idstringoptionalFilter 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();
GET /alerts/{event_id}/ Full alert event detail including trigger value, rule, and resolution
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();
SSE /alerts/stream/ Server-Sent Events stream — pushed instantly on every new alert

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 format
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']}")
GET /alerts/rules/ List configured alert rules for the organization
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();

Canvas & Dashboards

Access dashboards and their panel data. Public share links work without authentication.

GET /canvas/ List all dashboards (name, panel count, public link status)
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();
GET /canvas/public/{token}/ Unauthenticated — view a shared dashboard by public token

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>
POST /canvas/public/{token}/query/ Unauthenticated — query time-series data for a shared dashboard

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();

AI & Argus

Natural language queries and the Argus conversational assistant with tool-use and institutional memory.

POST /api/v1/ai/nlq/ Natural language query — converts plain English to SQL and returns data
Request body
FieldTypeDescription
questionstringrequiredNatural 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 SQL
const 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();
POST /api/v1/ai/argus/message/ Argus conversational assistant — streaming SSE with tool use

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.

Request body
FieldTypeDescription
messagestringrequiredUser message
conversation_idstringoptionalUUID of existing conversation to continue
page_contextobjectoptionalCurrent UI context: {page, dashboard_id, device_id, site_id}
SSE event types
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']}]")
        break
GET /api/v1/ai/argus/conversations/ List conversation history (last 50)
curl -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"]
GET /api/v1/ai/argus/memory/ List or search institutional memories (supports POST to create)
ParamTypeDescription
qstringoptionalSemantic search query
device_idstringoptionalFilter by device UUID
site_idstringoptionalFilter by site UUID
memory_typestringoptionalobservation, 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})
GET /api/v1/ai/argus/investigations/ List background AI investigations and their conclusions
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"]

Prism — Derived Fields

Query computed fields derived from raw sensor data using configurable transformation expressions.

GET /prism/fields/ List all Prism derived field definitions
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"]
GET /prism/{field_id}/data/ Compute and return time-series data for a derived field
ParamTypeDescription
startstringoptionalISO 8601 start (default: 24h ago)
endstringoptionalISO 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()