API
Everything below is the public HTTP API: create a stream, send raw samples, read the cleaned signal and the analytics next to it. Your API key, your personal base URL and a ready-to-run curl command are shown in your account — the examples use $BASE and $KEY for those two values.
Every request carries your key in a header. Requests that change state (creating a stream, sending data, control) also require an Idempotency-Key — any unique string per operation; retrying with the same key and the same body safely returns the original result instead of repeating the work.
curl -s "$BASE/v2/streams" \
-H "X-API-Key: $KEY" \
-H "Idempotency-Key: create-001" \
-H "Content-Type: application/json" \
-d '{"names":["pressure","temperature"],"fs":1000.0,"auto_sigma":true}'
The response is your stream id: {"id":"<stream UUID>","n":1}. A stream holds 1 to 264 synchronized channels; the order of names is the canonical column order of every output.
Post raw samples as columns plus timestamps. One value per channel per row; send single rows for live feeds or blocks for batches.
curl -s "$BASE/v2/streams/$STREAM/ingest" \
-H "X-API-Key: $KEY" \
-H "Idempotency-Key: ingest-001" \
-H "Content-Type: application/json" \
-d '{
"channels": {"pressure":[1.21,1.19,1.18], "temperature":[42.7,42.8,42.7]},
"t": [1720000000.125, 1720000000.126, 1720000000.127]
}'
Limits per request: up to 100,000 rows per column and 2,000,000 channel-samples total (rows × channels); larger data is simply sent as several requests. All column lengths must match t. A missing observation is null — never substitute a numeric zero, the engine treats them differently.
curl -s "$BASE/v2/streams/$STREAM/filter?mode=online&since=0&max=1000" -H "X-API-Key: $KEY"
Three modes of the same stream: online — zero-lag output for live display; min_latency — a causal smoother that trades a short, fixed delay for most of the achievable quality (the exact delay is declared by your stream in the delay_samples field of this response); offline — the strongest output, re-processing the retained signal tail. max is required (1..20,000); page through history with since.
Honesty during warm-up: while a channel is still calibrating (typically the first few hundred to ~1,500 samples), the response flags it in calibrating and the online output may equal the input — the engine returns a clean passthrough rather than an invented signal.
Each surface is a GET next to /filter:
/health — per-channel state, calibration flags, noise consistency. /anomalies?since=<cursor> — events with explanations; store next_since from each response and pass it back as the cursor. /connections — the directed cross-channel graph with lags. /model?channel=i — the compiled per-channel model diagnostics. /volatility — read-only next-step variance per channel.
curl -s "$BASE/v2/passports" \
-H "X-API-Key: $KEY" -H "Idempotency-Key: passport-001" \
-H "Content-Type: application/json" \
-d '{"names":["pressure"],"fs":1000.0,"sigma":0.02,"data":{"pressure":[...]}}'
# then poll:
curl -s "$BASE/v2/passports/$JOB" -H "X-API-Key: $KEY"
An inline Passport needs at least 1,500 samples per channel; a live Passport can be requested from an existing stream's tail instead.
Control endpoints compute setpoints on your compiled model — your system executes them. Commands are issued only when the model, readiness and safety gates all pass; a dedicated stop lane ({"active":false}) is always available. Control authority is a separate permission from data access: keys that can read and ingest cannot command anything.
Safe rules that cover the sharp edges: never reuse an Idempotency-Key with a different body — that is a conflict by design; on an ambiguous failure, retry the same body with the same key; a missing value is null, not zero; channel names and their order are fixed for the life of a stream — to change them, create a new stream; an unknown or foreign stream id returns 404.
Python SDK: coming soon. The HTTP API above is the primary, stable contract.