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.
{"email":"…","password":"…"} to /api/v1/auth/login/ — response returns {"access":"<jwt>","refresh":"…"}. Pass the access token as Authorization: Bearer <jwt>. JWTs expire; use the refresh token against /api/v1/auth/token/refresh/.Authorization: Bearer <org-api-key>.gw_… token scoped to data ingestion. Create one via POST /api/v1/ingest/tokens/. Pass as Authorization: Bearer gw_… on ingest requests only./api/v1/canvas/public/{token}/ and its /query/ sub-endpoint require no auth — the share token is embedded in the URL.
Push measurement data from gateway nodes. Ingest endpoints use a gateway token (gw_…), not a user JWT — create one via POST /api/v1/ingest/tokens/.
Authenticated with a gateway token (Authorization: Bearer gw_…). Each measurement references a binding_id (quantity channel UUID) and a float value. Timestamps default to server receive-time if omitted.
| Field | Type | Description | |
|---|---|---|---|
hardware_id | string | required | Device hardware_id matching the gateway token scope |
measurements | array | required | Array of measurement objects (see below) |
| Field | Type | Description | |
|---|---|---|---|
binding_id | string | required | UUID of the quantity binding (channel → quantity mapping) |
value | number|string|object | required | Measured value in the binding's base unit |
timestamp | string | optional | ISO-8601 UTC timestamp (default: server receive-time) |
quality | integer 0–255 | optional | Signal quality byte (0 = unknown) |
curl -X POST https://yourdomain.com/api/v1/ingest/ \
-H "Authorization: Bearer gw_<gateway-token>" \
-H "Content-Type: application/json" \
-d '{
"hardware_id": "SIM-001",
"measurements": [
{"binding_id": "<uuid>", "value": 22.5, "quality": 200},
{"binding_id": "<uuid2>", "value": 1013.2}
]
}'from mnemos_client import MnemosClient
client = MnemosClient.login(base_url="https://yourdomain.com",
email="you@example.com", password="secret")
token_raw = client.tokens.create(hardware_id="SIM-001", name="Dev Token")
client.ingest(token_raw=token_raw, hardware_id="SIM-001", measurements=[
{"binding_id": "<uuid>", "value": 22.5},
{"binding_id": "<uuid2>", "value": 1013.2},
])await fetch("/api/v1/ingest/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer gw_<gateway-token>"
},
body: JSON.stringify({
hardware_id: "SIM-001",
measurements: [
{ binding_id: "<uuid>", value: 22.5 },
{ binding_id: "<uuid2>", value: 1013.2 },
]
})
});Requires a user JWT or org API key. The response includes a raw_token field with the gw_… secret — store it immediately, it is never shown again.
| Field | Type | Description | |
|---|---|---|---|
name | string | required | Human-readable label for the token |
hardware_id | string | optional | Scope the token to a specific device hardware ID |
{"id":"...","name":"Dev Token","hardware_id":"SIM-001","raw_token":"gw_Kx9...","created_at":"2026-04-17T10:00:00Z"}curl -X POST https://yourdomain.com/api/v1/ingest/tokens/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name":"Dev Token","hardware_id":"SIM-001"}'raw_token = client.tokens.create(hardware_id="SIM-001", name="Dev Token") # raw_token == "gw_Kx9..." — store this securely
Manage devices, quantity bindings, and sites. All endpoints require a user JWT or org API key.
| 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 "Authorization: Bearer <token>" https://yourdomain.com/api/v1/devices/
import requests
r = requests.get("https://yourdomain.com/api/v1/devices/",
headers={"Authorization": "Bearer <token>"})
devices = r.json()["results"]const r = await fetch("/api/v1/devices/", {
headers: { "Authorization": "Bearer <token>" }
});
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 "Authorization: Bearer <token>" https://yourdomain.com/api/v1/devices/<device_id>/
r = requests.get(f"https://yourdomain.com/api/v1/devices/{device_id}/",
headers={"Authorization": "Bearer <token>"})
device = r.json()const device = await (await fetch(`/api/v1/devices/${deviceId}/`,
{ headers: { "Authorization": "Bearer <token>" } })).json();{"device_id":"...","readings":[{"binding_id":"...","quantity":"Temperature","unit":"K","value":295.3,"time":"2026-04-13T12:00:00Z"}]}curl -H "Authorization: Bearer <token>" https://yourdomain.com/api/v1/devices/<id>/latest/
r = requests.get(f"https://yourdomain.com/api/v1/devices/{device_id}/latest/",
headers={"Authorization": "Bearer <token>"})
latest = r.json()["readings"]const { readings } = await (await fetch(`/api/v1/devices/${deviceId}/latest/`,
{ headers: { "Authorization": "Bearer <token>" } })).json();| Field | Type | Description | |
|---|---|---|---|
hardware_id | string | required | Unique hardware identifier (e.g. MAC address or serial) |
display_name | string | required | Human-readable device name |
node_type | string | optional | sensor_node (default), gateway, actuator |
site_id | string | optional | UUID of the site to assign the device to |
curl -X POST https://yourdomain.com/api/v1/devices/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"hardware_id":"SIM-001","display_name":"My Sensor","node_type":"sensor_node"}'device = client.devices.create(hardware_id="SIM-001", display_name="My Sensor") print(device["id"]) # UUID to use in binding calls
Each binding maps a physical channel number to a Canonical Physical Ontology (CPO) quantity. The binding_id is the key used in ingest and explorer calls.
[{"id":"<binding-uuid>","channel":0,"quantity":{"slug":"temperature.celsius","name":"Temperature","base_unit_symbol":"°C"},"role":"sensor","is_active":true}]curl -H "Authorization: Bearer <token>" \ https://yourdomain.com/api/v1/devices/<device_id>/bindings/
bindings = client.devices.bindings(device_id="<uuid>")
for b in bindings:
print(b["id"], b["quantity"]["slug"])| Field | Type | Description | |
|---|---|---|---|
channel | integer | required | Physical channel index on the device (0-based) |
quantity_slug | string | required | CPO quantity slug, e.g. temperature.celsius — see Quantities |
role | string | optional | sensor (default) or actuator |
sample_rate_tier | string | optional | low (default), medium, high |
curl -X POST https://yourdomain.com/api/v1/devices/<device_id>/bindings/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"channel":0,"quantity_slug":"temperature.celsius","role":"sensor"}'binding = client.devices.create_binding(
device_id="<uuid>",
channel=0,
quantity_slug="temperature.celsius",
)
print(binding["id"]) # use this as binding_id in ingest callscurl -H "Authorization: Bearer <token>" https://yourdomain.com/api/v1/sites/
r = requests.get("https://yourdomain.com/api/v1/sites/",
headers={"Authorization": "Bearer <token>"})
sites = r.json()["results"]const { results: sites } = await (await fetch("/api/v1/sites/",
{ headers: { "Authorization": "Bearer <token>" } })).json();curl -H "Authorization: Bearer <token>" https://yourdomain.com/api/v1/sites/<site_id>/
r = requests.get(f"https://yourdomain.com/api/v1/sites/{site_id}/",
headers={"Authorization": "Bearer <token>"})
site = r.json()Query time-series data from TimescaleDB via GET /api/v1/explorer/query/. Binding IDs are passed as a comma-separated query parameter. Points are automatically downsampled to max_points using time-bucket averaging.
Returns columnar time-series data. Pass binding_ids as a comma-separated list. start and end accept ISO-8601 strings or relative shorthands like -1h, -7d.
| Param | Type | Description | |
|---|---|---|---|
binding_ids | string | required | Comma-separated binding UUIDs |
start | string | optional | Start time (ISO-8601 or -1h, -7d) |
end | string | optional | End time (default: now) |
max_points | integer | optional | Max data points per series (default 500) |
{
"timestamps": ["2026-04-13T09:00:00Z", "2026-04-13T09:01:00Z", "..."],
"series": [
{
"binding_id": "f3a1b2c4-...",
"label": "Pump 1 / Vibration",
"values": [1.24, 1.31, null, 1.35],
"unit": "m/s²"
}
]
}curl -H "Authorization: Bearer <token>" \ "https://yourdomain.com/api/v1/explorer/query/?binding_ids=<uuid1>,<uuid2>&start=-1h&max_points=500"
import requests
r = requests.get(
"https://yourdomain.com/api/v1/explorer/query/",
headers={"Authorization": "Bearer <token>"},
params={
"binding_ids": "<uuid1>,<uuid2>",
"start": "-1h",
"max_points": 500,
}
)
data = r.json()
timestamps = data["timestamps"]
for series in data["series"]:
print(series["label"], series["values"])const params = new URLSearchParams({
binding_ids: "<uuid1>,<uuid2>",
start: "-1h",
max_points: "500",
});
const r = await fetch(`/api/v1/explorer/query/?${params}`, {
headers: { "Authorization": "Bearer <token>" }
});
const { timestamps, series } = await r.json();Browse the built-in library of physical quantities (CPO). Each quantity has a stable slug used when creating device bindings.
| Param | Type | Description | |
|---|---|---|---|
search | string | optional | Full-text search across name and slug |
phenomenon_class | string | optional | Filter by domain: thermal, mechanical, electromagnetic, chemical, optical |
{"count":142,"results":[{"slug":"temperature.celsius","name":"Temperature","base_unit_symbol":"°C","phenomenon_class":"thermal"},...]}curl -H "Authorization: Bearer <token>" \ "https://yourdomain.com/api/v1/quantities/canonical/?search=vibration"
# List all mechanical quantities
results = client.quantities.list(domain="mechanical")
for q in results["results"]:
print(q["slug"], q["base_unit_symbol"]){"slug":"temperature.celsius","name":"Temperature","base_unit_symbol":"°C","si_unit":"K","phenomenon_class":"thermal","description":"Thermodynamic temperature relative to 273.15 K"}curl -H "Authorization: Bearer <token>" \ https://yourdomain.com/api/v1/quantities/canonical/temperature.celsius/
qty = client.quantities.get("temperature.celsius")
print(qty["si_unit"])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 "Authorization: Bearer <token>" \ "https://yourdomain.com/api/v1/alerts/?status=active&severity=critical"
r = requests.get("https://yourdomain.com/api/v1/alerts/",
headers={"Authorization": "Bearer <token>"},
params={"status": "active", "severity": "critical"})
events = r.json()["events"]const r = await fetch("/api/v1/alerts/?status=active&severity=critical",
{ headers: { "Authorization": "Bearer <token>" } });
const { events } = await r.json();curl -H "Authorization: Bearer <token>" https://yourdomain.com/api/v1/alerts/<event_id>/
r = requests.get(f"https://yourdomain.com/api/v1/alerts/{event_id}/",
headers={"Authorization": "Bearer <token>"})
alert = r.json()const alert = await (await fetch(`/api/v1/alerts/${eventId}/`,
{ headers: { "Authorization": "Bearer <token>" } })).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("/api/v1/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/api/v1/alerts/stream/",
headers={"Authorization": "Bearer <token>"}, 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 "Authorization: Bearer <token>" https://yourdomain.com/api/v1/alerts/rules/
r = requests.get("https://yourdomain.com/api/v1/alerts/rules/",
headers={"Authorization": "Bearer <token>"})
rules = r.json()["rules"]const { rules } = await (await fetch("/api/v1/alerts/rules/",
{ headers: { "Authorization": "Bearer <token>" } })).json();Access dashboards and their panel data. Public share links work without authentication.
curl -H "Authorization: Bearer <token>" https://yourdomain.com/api/v1/canvas/
r = requests.get("https://yourdomain.com/api/v1/canvas/",
headers={"Authorization": "Bearer <token>"})
dashboards = r.json()["dashboards"]const { dashboards } = await (await fetch("/api/v1/canvas/",
{ headers: { "Authorization": "Bearer <token>" } })).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/api/v1/canvas/public/<token>/");<!-- Embed in an iframe --> <iframe src="https://yourdomain.com/api/v1/canvas/public/<token>/" width="100%" height="600" frameborder="0" allowfullscreen> </iframe>
Scoped to the bindings visible on the public dashboard. Same body format as /api/v1/explorer/query/.
curl -X POST https://yourdomain.com/api/v1/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(`/api/v1/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 "Authorization: Bearer <token>" \
-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={"Authorization": "Bearer <token>"},
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", "Authorization": "Bearer <token>" },
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":"/api/v1/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",
"Authorization": "Bearer <token>"
},
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={"Authorization": "Bearer <token>"},
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 "Authorization: Bearer <token>" \ https://yourdomain.com/api/v1/ai/argus/conversations/
r = requests.get("https://yourdomain.com/api/v1/ai/argus/conversations/",
headers={"Authorization": "Bearer <token>"})
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 "Authorization: Bearer <token>" \
"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 "Authorization: Bearer <token>" -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={"Authorization": "Bearer <token>"},
params={"q": "bearing failure"})
memories = r.json()["memories"]
# Create
r = requests.post("https://yourdomain.com/api/v1/ai/argus/memory/",
headers={"Authorization": "Bearer <token>"},
json={"content": "CO2 sensor on Floor 2 prone to drift",
"memory_type": "observation", "salience": 0.7})curl -H "Authorization: Bearer <token>" \ https://yourdomain.com/api/v1/ai/argus/investigations/
r = requests.get("https://yourdomain.com/api/v1/ai/argus/investigations/",
headers={"Authorization": "Bearer <token>"})
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 "Authorization: Bearer <token>" https://yourdomain.com/api/v1/prism/fields/
r = requests.get("https://yourdomain.com/api/v1/prism/fields/",
headers={"Authorization": "Bearer <token>"})
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 "Authorization: Bearer <token>" \ "https://yourdomain.com/api/v1/prism/<field_id>/data/?start=2026-04-13T00:00:00Z"
r = requests.get(f"https://yourdomain.com/api/v1/prism/{field_id}/data/",
headers={"Authorization": "Bearer <token>"},
params={"start": "2026-04-13T00:00:00Z"})
data = r.json()Browse and query the Peripheral Detection Protocol type registry. Each PeripheralType defines the TEDS metadata and quantity bindings for a specific probe model.
| Param | Type | Description | |
|---|---|---|---|
peripheral_class | string | optional | Filter by class: thermal, mechanical, electromagnetic, chemical, optical |
search | string | optional | Search by name or pdp_type_id |
contribution_status | string | optional | official, community, pending |
{"count":24,"results":[{"pdp_type_id":"CLR-T-001","name":"K-Type Thermocouple","peripheral_class":"thermal","version":"1.0.0","contribution_status":"official","is_public":true}]}curl -H "Authorization: Bearer <token>" \ "https://yourdomain.com/api/v1/pdp/types/?peripheral_class=thermal&contribution_status=official"
import requests
r = requests.get("https://yourdomain.com/api/v1/pdp/types/",
headers={"Authorization": "Bearer <token>"},
params={"peripheral_class": "thermal", "contribution_status": "official"})
types = r.json()["results"]Returns the complete set of public peripheral type definitions in a single JSON document. Useful for embedded systems that need a local copy of the type registry for TEDS auto-provisioning.
curl -H "Authorization: Bearer <token>" \ https://yourdomain.com/api/v1/pdp/catalog/export/ \ -o pdp_catalog.json