Overview
DriftMind is a cold-start, self-adaptive forecasting engine designed for fast data environments. You create a forecaster, feed time-aligned observations, then request forecasts and anomaly scores.
What Swagger is for
Contract reference: endpoints, schemas, status codes, examples.
What this guide is for
Workflow, best practices, and runnable examples (Java + Python + curl).
Authentication
All requests require an API token passed via the Auth header:
Auth: <YOUR_API_TOKEN>
Environment setup
export DRIFTMIND_TOKEN="your-token-here"
$Env:DRIFTMIND_TOKEN="your-token-here"
Quickstart
The golden path is: Create → Feed → Predict → Delete. The snippet below runs the full cycle end-to-end.
FORECAST_NOT_READY if
insufficient data has been fed.
In that case, feed additional observations and retry.
import os, requests, time
BASE_URL = "https://api.thingbook.io/access/api/driftmind/v1"
headers = {"Auth": os.environ["DRIFTMIND_TOKEN"], "Content-Type": "application/json"}
# 1. Create
r = requests.post(f"{BASE_URL}/forecasters", headers=headers, json={
"forecasterName": "My First Forecaster",
"features": ["temperature", "humidity"],
"inputSize": 30,
"outputSize": 1
})
r.raise_for_status()
fid = r.json()["forecasterId"]
print("Created:", fid)
# 2. Feed — send enough observations to satisfy inputSize
observations = {
"temperature": [18.0 + i * 0.1 for i in range(35)],
"humidity": [0.60 + i * 0.002 for i in range(35)]
}
r = requests.post(f"{BASE_URL}/forecasters/{fid}/observations", headers=headers, json=observations)
r.raise_for_status()
print("Fed observations")
# 3. Predict
r = requests.get(f"{BASE_URL}/forecasters/{fid}/predictions", headers=headers)
r.raise_for_status()
result = r.json()
print(f"Anomaly score: {result['anomalyScore']}")
print(f"Temperature forecast: {result['features']['temperature']['predictions']}")
# 4. Delete
r = requests.delete(f"{BASE_URL}/forecasters/{fid}", headers=headers)
r.raise_for_status()
print("Deleted")
# 1. Create — save the returned forecasterId
FID=$(curl -s -X POST https://api.thingbook.io/access/api/driftmind/v1/forecasters \
-H "Auth: $DRIFTMIND_TOKEN" -H "Content-Type: application/json" \
-d '{"forecasterName":"My First Forecaster","features":["temperature","humidity"],"inputSize":30,"outputSize":1}' \
| grep -o '"forecasterId":"[^"]*"' | cut -d'"' -f4)
echo "Created: $FID"
# 2. Feed
curl -s -X POST https://api.thingbook.io/access/api/driftmind/v1/forecasters/$FID/observations \
-H "Auth: $DRIFTMIND_TOKEN" -H "Content-Type: application/json" \
-d '{"temperature":[18.0,18.1,18.2,18.3,18.4,18.5,18.6,18.7,18.8,18.9,19.0,19.1,19.2,19.3,19.4,19.5,19.6,19.7,19.8,19.9,20.0,20.1,20.2,20.3,20.4,20.5,20.6,20.7,20.8,20.9,21.0,21.1,21.2,21.3,21.4],"humidity":[0.60,0.60,0.61,0.61,0.62,0.62,0.60,0.59,0.58,0.60,0.61,0.62,0.60,0.59,0.61,0.62,0.63,0.60,0.59,0.58,0.60,0.61,0.62,0.63,0.60,0.61,0.62,0.59,0.58,0.60,0.61,0.62,0.63,0.60,0.59]}'
# 3. Predict
curl -s -X GET https://api.thingbook.io/access/api/driftmind/v1/forecasters/$FID/predictions \
-H "Auth: $DRIFTMIND_TOKEN"
# 4. Delete
curl -s -X DELETE https://api.thingbook.io/access/api/driftmind/v1/forecasters/$FID \
-H "Auth: $DRIFTMIND_TOKEN"
Create a Forecaster
Creates a multivariate forecaster and returns a forecasterId.
POST /driftmind/v1/forecasters
| Field | Description |
|---|---|
forecasterName |
Human-readable label for this forecaster. |
features |
Array of feature names. Each name must match the keys you send in observation payloads. |
inputSize |
Sliding window size — the number of past observations the model uses to generate each forecast. Larger values capture longer patterns but require more data before the first prediction is ready. |
outputSize |
Number of future time steps to forecast. Use 1 for single-step prediction or a higher value for a multi-step horizon. |
curl
curl -X POST https://api.thingbook.io/access/api/driftmind/v1/forecasters \
-H "Auth: $DRIFTMIND_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"forecasterName": "Temperature Forecaster",
"features": ["temperature", "humidity"],
"inputSize": 30,
"outputSize": 1
}'
Python (requests)
import os
import requests
# Note the /v1/ at the end
BASE_URL = "https://api.thingbook.io/access/api/driftmind/v1"
headers = {"Auth": os.environ["DRIFTMIND_TOKEN"], "Content-Type": "application/json"}
payload = {
"forecasterName": "Temperature Forecaster",
"features": ["temperature", "humidity"],
"inputSize": 30,
"outputSize": 1
}
r = requests.post(f"{BASE_URL}/forecasters", json=payload, headers=headers)
r.raise_for_status()
forecaster_id = r.json()["forecasterId"]
print("forecasterId:", forecaster_id)
Java (JAX-RS Client)
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
public class CreateForecasterExample {
public static void main(String[] args) {
String baseUrl = "https://api.thingbook.io/access/api/driftmind/v1";
String token = System.getenv("DRIFTMIND_TOKEN");
String payload = """
{
"forecasterName": "Temperature Forecaster",
"features": ["temperature", "humidity"],
"inputSize": 30,
"outputSize": 1
}
""";
Client client = ClientBuilder.newClient();
Response resp = client.target(baseUrl + "/forecasters")
.request(MediaType.APPLICATION_JSON)
.header("Auth", token)
.post(Entity.json(payload));
String body = resp.readEntity(String.class);
System.out.println(resp.getStatus() + " " + body);
}
}
Feed Observations
Feed time-aligned arrays for each feature. All feature arrays must have the same length.
POST /driftmind/v1/forecasters/{id}/observations
curl
curl -X POST https://api.thingbook.io/access/api/driftmind/v1/forecasters/abc123-xyz789/observations \
-H "Auth: $DRIFTMIND_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"temperature": [18.0, 19.5, 20.1, 20.7],
"humidity": [0.62, 0.60, 0.58, 0.59]
}'
Python
import os
import requests
BASE_URL = "https://api.thingbook.io/access/api/driftmind/v1"
headers = {"Auth": os.environ["DRIFTMIND_TOKEN"], "Content-Type": "application/json"}
forecaster_id = "abc123-xyz789"
payload = {
"temperature": [18.0, 19.5, 20.1, 20.7],
"humidity": [0.62, 0.60, 0.58, 0.59]
}
# Note: POST to .../forecasters/{id}/observations
url = f"{BASE_URL}/forecasters/{forecaster_id}/observations"
r = requests.post(url, json=payload, headers=headers)
r.raise_for_status()
print(r.json())
Java (JAX-RS Client)
String forecasterId = "abc123-xyz789";
String payload = """
{
"temperature": [18.0, 19.5, 20.1, 20.7],
"humidity": [0.62, 0.60, 0.58, 0.59]
}
""";
Response resp = client.target(baseUrl + "/forecasters/" + forecasterId + "/observations")
.request(MediaType.APPLICATION_JSON)
.header("Auth", token)
.post(Entity.json(payload));
System.out.println(resp.getStatus() + " " + resp.readEntity(String.class));
Response
The feed also reports the forecaster's anomaly state right after this push, so you don't need a follow-up call to Current Anomaly Status.
{
"message": "FORECASTER_FED",
"abnormal": true,
"anomalyEvent": "5158a799-ff88-4a90-b2ef-95bd36fcebe5"
}
| Field | Description |
|---|---|
message | Always FORECASTER_FED on success. |
abnormal | true when, after this feed, the forecaster is inside an open anomaly event; false otherwise. |
anomalyEvent | Id of the open event when abnormal, or "N/A" when the forecaster is normal. |
Feed Multiple Forecasters
Feed several forecasters in a single call. Each entry in payloadsList carries its
own forecasterId and a data map of variable name to an aligned array of
values. This is the most efficient way to push a batch of streams at once.
PATCH /driftmind/v1/forecasters/observations
Request body
{
"payloadsList": [
{
"forecasterId": "abc123-xyz789",
"data": { "temperature": [18.0, 19.5, 20.1], "humidity": [0.62, 0.60, 0.58] }
},
{
"forecasterId": "def456-uvw012",
"data": { "flow_rate": [11.2, 11.4, 11.1] }
}
]
}
curl
curl -X PATCH https://api.thingbook.io/access/api/driftmind/v1/forecasters/observations \
-H "Auth: $DRIFTMIND_TOKEN" -H "Content-Type: application/json" \
-d '{"payloadsList":[{"forecasterId":"abc123-xyz789","data":{"temperature":[18.0,19.5,20.1],"humidity":[0.62,0.60,0.58]}}]}'
Response
Each payload is validated and fed independently. A 200 means every
payload succeeded; a 206 means the batch was partially applied —
inspect details for the per-forecaster outcome.
{
"message": "PARTIAL_SUCCESS",
"details": [
{ "forecasterId": "abc123-xyz789", "status": 200, "message": "OK" },
{ "forecasterId": "def456-uvw012", "status": 404, "message": "FORECASTER_NOT_FOUND" }
]
}
| Field | Description |
|---|---|
message | Overall batch outcome. |
details[].forecasterId | The forecaster this result applies to. |
details[].status | Per-forecaster HTTP-style status (200 = fed, 404 = not found, 400 = invalid payload). |
details[].message | Human-readable result for that forecaster. |
Request Predictions
Request a forecast for a forecaster. If insufficient data exists, the API returns
422 FORECAST_NOT_READY.
GET /driftmind/v1/forecasters/{id}/predictions
curl -X GET https://api.thingbook.io/access/api/driftmind/v1/forecasters/abc123-xyz789/predictions \
-H "Auth: $DRIFTMIND_TOKEN"
Not enough data (422)
{
"error": "FORECAST_NOT_READY"
}
Understand the Response
The prediction response includes an overall anomaly score and a per-feature results map.
{
"anomalyScore": 0.27,
"numberOfClusters": 12,
"features": {
"temperature": {
"timestamps": ["2025-12-31 10:00:00"],
"predictions": [20.7],
"upperConfidence": [21.3],
"lowerConfidence": [20.1],
"anomalyScore": 0.12,
"forecastingMethod": "CLUSTER",
"numberOfClusters": 4
},
"humidity": {
"timestamps": ["2025-12-31 10:00:00"],
"predictions": [0.59],
"upperConfidence": [0.61],
"lowerConfidence": [0.57],
"anomalyScore": 0.42,
"forecastingMethod": "CLUSTER",
"numberOfClusters": 8
}
}
}
| Field | Description |
|---|---|
anomalyScore |
Overall anomaly score for this request across all features (0–1; higher is more anomalous, rounded to 2 decimals). |
numberOfClusters |
Total clusters across all features (sum of per-feature values). |
features |
Map keyed by feature name containing per-feature forecast outputs and metadata. |
predictions |
Forecasted values for the horizon. |
upperConfidence / lowerConfidence |
Confidence bounds per forecast point. |
forecastingMethod |
CLUSTER — normal operation; the model has formed stable clusters and is generating confident forecasts. FALLBACK — the model is still warming up or encountered an unstable state; predictions are based on simpler heuristics and anomaly scores are less reliable. Feed more observations and the method will transition to CLUSTER automatically. |
List all Forecasters
Retrieve a list of all forecasters belonging to your API token, including metadata like creation time and usage statistics.
GET /driftmind/v1/forecasters
curl
curl -X GET https://api.thingbook.io/access/api/driftmind/v1/forecasters \
-H "Auth: $DRIFTMIND_TOKEN"
Python (requests)
# Assumes BASE_URL and headers are defined
r = requests.get(f"{BASE_URL}/forecasters", headers=headers)
r.raise_for_status()
print(r.json())
Java (JAX-RS Client)
Client client = ClientBuilder.newClient();
Response resp = client.target(baseUrl + "/forecasters")
.request(MediaType.APPLICATION_JSON)
.header("Auth", token)
.get();
System.out.println(resp.readEntity(String.class));
Example response
[
{
"objectId": "abc123-xyz789",
"objectName": "Web Traffic Forecaster",
"createdAt": "2026-05-12",
"createdBy": "VfYZ3hLQk6FohdPTkKXB0lC30DworzI5Mz....",
"objectType": "FORECASTER",
"dataProcessed": 14120.42,
"requestsProcessed": 328,
"anomalyCount24h": 3,
"echoCount24h": 1
}
]
| Field | Description |
|---|---|
dataProcessed |
Megabytes of payload metered against this forecaster over the last 30 days. |
requestsProcessed |
Total number of API requests that touched this forecaster over the last 30 days. |
anomalyCount24h |
Number of anomaly events this forecaster has produced in the last 24 hours of model-clock time (the timestamp the forecaster itself wrote when the event closed, NOT the wall time the row landed in the database). Drives the Anomalies pill in the dashboard's Active Forecasters table. |
echoCount24h |
Number of Echo pattern matches this forecaster has discovered in the last 24 hours of model-clock time. Drives the Echo pill in the dashboard's Active Forecasters table. |
Get Forecaster Data
Retrieve the most recent observations currently held in the forecaster's memory buffer. This is useful for validation and debugging to ensure data is being fed correctly.
GET /driftmind/v1/forecasters/{id}/observations
curl
curl -X GET https://api.thingbook.io/access/api/driftmind/v1/forecasters/abc123-xyz789/observations \
-H "Auth: $DRIFTMIND_TOKEN"
Python (requests)
forecaster_id = "abc123-xyz789"
url = f"{BASE_URL}/forecasters/{forecaster_id}/observations"
r = requests.get(url, headers=headers)
r.raise_for_status()
print(r.json())
Java (JAX-RS Client)
String forecasterId = "abc123-xyz789";
Response resp = client.target(baseUrl + "/forecasters/" + forecasterId + "/observations")
.request(MediaType.APPLICATION_JSON)
.header("Auth", token)
.get();
System.out.println(resp.readEntity(String.class));
Example response
{
"observations": [
{
"timestamp": "05-01-2026 11:42:50",
"anomalyScore": 0.00,
"anomalyEvent": "N/A",
"values": { "temperature": 20.4, "humidity": 0.58 },
"anomalyScores": { "temperature": 0.00, "humidity": 0.12 }
},
{
"timestamp": "05-01-2026 11:43:50",
"anomalyScore": 0.33,
"anomalyEvent": "3f2a9c1e-5b7d-4e8a-9c21-7d4e5f6a8b90",
"values": { "temperature": 24.1, "humidity": 0.55 },
"anomalyScores": { "temperature": 0.41, "humidity": 0.09 }
}
]
}
| Field | Description |
|---|---|
observations | Per-timestep rows, oldest first (the forecaster's inputSize + outputSize most recent steps). |
timestamp | Step time, on the forecaster's own timeline. |
anomalyScore | Overall (device-level) anomaly score for that step. |
anomalyEvent | Identifier of the event covering that step, or "N/A". |
values | Each variable's fed value at that step, keyed by variable name. |
anomalyScores | Each variable's anomaly score at that step, keyed by variable name. |
Get Forecaster Details
Retrieve the specific configuration of a forecaster, including its input/output sizes, feature names, and internal parameters.
GET /driftmind/v1/forecasters/{id}
curl
curl -X GET https://api.thingbook.io/access/api/driftmind/v1/forecasters/abc123-xyz789 \
-H "Auth: $DRIFTMIND_TOKEN"
Python (requests)
forecaster_id = "abc123-xyz789"
url = f"{BASE_URL}/forecasters/{forecaster_id}"
r = requests.get(url, headers=headers)
r.raise_for_status()
details = r.json()
print(f"Name: {details['forecasterName']}")
print(f"Config: {details['configuration']}")
Java (JAX-RS Client)
String forecasterId = "abc123-xyz789";
Response resp = client.target(baseUrl + "/forecasters/" + forecasterId)
.request(MediaType.APPLICATION_JSON)
.header("Auth", token)
.get();
System.out.println(resp.readEntity(String.class));
Error Handling
All errors are returned as:
{
"error": "ERROR_CODE",
"details": ["optional", "context"]
}
| Status | Code | Meaning | Action |
|---|---|---|---|
| 401 | TOKEN_REJECTED |
Token missing or invalid | Check the Auth header |
| 402 | CREDIT_EXHAUSTED |
Insufficient credit | Top up / change plan / reduce calls |
| 404 | FORECASTER_NOT_FOUND |
Forecaster does not exist | Verify forecasterId |
| 422 | FORECAST_NOT_READY |
Not enough data to forecast | Feed more observations and retry |
| 500 | STORAGE_UNAVAILABLE |
Temporary backend issue | Retry with backoff |
| 400 | PATTERN_VALIDATION_FAILED |
Umbrella code for any structural problem with a POST /echo/patterns
payload. The details array carries a classified sub-code per problem
in the form "<SUB_CODE>: <message>" so a UI can branch on
the specific cause.
|
Inspect details and surface the sub-codes verbatim |
| 409 | PATTERN_NAME_TAKEN |
A pattern with that patternName already exists for the customer |
Pick a different name, or update / delete the existing pattern |
Pattern validation sub-codes
These ride inside details on a 400 PATTERN_VALIDATION_FAILED. A payload
can fail several at once — all are reported in one response so the caller can fix them in one
round trip.
| Sub-code | Meaning |
|---|---|
PATTERN_NAME_MISSING |
patternName is null, blank, or longer than 64 characters. |
PATTERN_FEATURES_MISSING |
The features map is null, empty, or contains zero entries. |
PATTERN_FEATURE_LENGTH_MISMATCH |
Feature arrays disagree on length. Every feature must have the same number of samples. |
PATTERN_LENGTH_OUT_OF_RANGE |
Each feature must have between 3 and 200 samples. |
PATTERN_FEATURE_NON_FINITE |
A feature array contains NaN or ±Infinity; all values must be finite numbers. |
PATTERN_FEATURE_CONSTANT |
A feature has zero standard deviation (every sample is the same value). Echo's matcher is Pearson-correlation based, which is undefined for a constant reference signal — the pattern must carry at least some variation per feature. |
PATTERN_FEATURE_COUNT_OUT_OF_RANGE |
The number of features is outside the supported 1–32 range. |
PATTERN_SEVERITY_INVALID |
severity is not one of info, warning, error, success. |
Delete a Forecaster
Deletes a forecaster and its associated data. This cannot be undone.
DELETE /driftmind/v1/forecasters/{id}
curl -X DELETE https://api.thingbook.io/access/api/driftmind/v1/forecasters/abc123-xyz789 \
-H "Auth: $DRIFTMIND_TOKEN"
Success response
{
"message": "FORECASTER_DELETED"
}
Current Anomaly Status
Returns the anomaly event a forecaster has in progress right now, if any. When an event is
open it comes back as a single row (the same shape as List Anomaly
Events, with ongoing: true and no end). When nothing is open, it returns a
plain {"anomalyEvent": "N/A"} marker.
GET /driftmind/v1/forecasters/{id}/anomalies
curl
curl -X GET https://api.thingbook.io/access/api/driftmind/v1/forecasters/abc123-xyz789/anomalies \
-H "Auth: $DRIFTMIND_TOKEN"
Response — event in progress
{
"eventUuid": "7b151995-86d1-4a9d-a7ae-153a8bdaa23b",
"forecasterId": "abc123-xyz789",
"startTs": "2026-09-11 10:05:23",
"peakScore": 0.62,
"peakTs": "2026-09-11 10:08:23",
"ongoing": true
}
Response — nothing open
{ "anomalyEvent": "N/A" }
| Field | Description |
|---|---|
eventUuid | Stable identifier of the open event, the same value shown as anomalyEvent elsewhere. |
forecasterId | Forecaster the event belongs to. |
startTs | Event start time, on the forecaster's own timeline. There is no end time while the event is ongoing. |
peakScore / peakTs | Highest overall anomaly score reached so far, and when. |
ongoing | Always true here — this endpoint only ever returns an open event. |
List Anomaly Events
Anomaly events recorded for the customer — the forecaster, the start and end time, and the
highest anomaly score reached with its time. Both query parameters are optional, which selects
one of four behaviours (see below). An event still in progress has no end and is marked
ongoing: true. This is the same list shown in the dashboard's Anomalies view.
GET /driftmind/v1/anomaly-events
Query parameters
| Parameter | Description |
|---|---|
forecasterId | Optional. Restrict to a single forecaster. |
eventId | Optional. A single event's eventUuid. |
Behaviour
| Parameters given | Returns |
|---|---|
| Neither | Every recorded event for the customer. |
forecasterId only | That forecaster's recorded events, plus its event in progress if one is ongoing. |
eventId only | That one recorded event. |
| Both | That exact event for that forecaster, whether already recorded or still ongoing. |
A named forecaster or event that does not exist for this customer returns 404.
Events are ordered oldest first (an ongoing event sorts last).
curl
curl -X GET "https://api.thingbook.io/access/api/driftmind/v1/anomaly-events?forecasterId=abc123-xyz789" \
-H "Auth: $DRIFTMIND_TOKEN"
Response
{
"count": 2,
"events": [
{
"eventUuid": "7b151995-86d1-4a9d-a7ae-153a8bdaa23b",
"forecasterId": "abc123-xyz789",
"startTs": "2026-09-11 10:05:23",
"endTs": "2026-09-11 10:12:23",
"peakScore": 0.62,
"peakTs": "2026-09-11 10:08:23",
"ongoing": false
},
{
"eventUuid": "a81bd0c2-3e44-4f91-9d7a-16b0c5e8f012",
"forecasterId": "abc123-xyz789",
"startTs": "2026-09-11 11:40:00",
"peakScore": 0.71,
"peakTs": "2026-09-11 11:48:00",
"ongoing": true
}
]
}
| Field | Description |
|---|---|
count | Number of events returned. |
events[].eventUuid | Stable identifier for the event, the same value shown as anomalyEvent elsewhere. The same whether the event is ongoing or recorded. |
events[].forecasterId | Forecaster the event belongs to. |
events[].startTs / endTs | Event start and end times. endTs is omitted while the event is still ongoing. |
events[].peakScore / peakTs | Highest overall anomaly score reached so far, and when. |
events[].ongoing | true while the event is still in progress; false for a completed event. |
anomalyScores.
Echo Patterns
Echo is the pattern-of-interest layer that runs alongside the forecaster. You register a named,
multi-feature reference signal once, attach it to one or more forecasters, and from then on every
observation tick is silently scored for similarity. When the running similarity crosses 60% on
the rising edge, an echo_detections row is written and an alert is emitted to the
dashboard's alerts panel.
/driftmind/v1/echo
Create a pattern
Endpoint: POST /driftmind/v1/echo/patterns. A pattern is 1–32 features, each a
length-3–200 array of finite numbers. Every feature array must be the same length, and every
feature must have non-zero standard deviation (a constant / flat-line feature is rejected
because Echo's correlation matcher needs variation in the reference signal).
curl -X POST https://api.thingbook.io/access/api/driftmind/v1/echo/patterns \
-H "Auth: $DRIFTMIND_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"patternName": "bearing-failure",
"description": "Rising temperature + vibration signature that precedes bearing seizure.",
"severity": "error",
"runbookUrl": "https://runbooks.example.com/bearing-failure",
"features": {
"temperature": [22, 24, 28, 35, 44, 55, 68],
"vibration": [0.3, 0.5, 0.8, 1.2, 2.1, 3.0, 4.5]
}
}'
Python (requests)
payload = {
"patternName": "bearing-failure",
"description": "Rising temperature + vibration signature that precedes bearing seizure.",
"severity": "error",
"runbookUrl": "https://runbooks.example.com/bearing-failure",
"features": {
"temperature": [22, 24, 28, 35, 44, 55, 68],
"vibration": [0.3, 0.5, 0.8, 1.2, 2.1, 3.0, 4.5],
},
}
r = requests.post(f"{BASE_URL}/echo/patterns", json=payload, headers=headers)
r.raise_for_status()
pattern_id = r.json()["patternId"]
Success response (201 Created)
{
"patternId": "9c3b1e4f-3c0c-4b7e-9b1f-2a3b4d5e6f70",
"patternName": "bearing-failure",
"severity": "error",
"length": 7,
"featureNames": ["temperature", "vibration"]
}
List, get, update, delete
| Method | Path | Purpose |
|---|---|---|
GET |
/driftmind/v1/echo/patterns |
List every pattern owned by your token. Optional ?createdBy=<token> filter. |
GET |
/driftmind/v1/echo/patterns/{patternId} |
Get the full pattern (metadata + every feature array). |
PUT |
/driftmind/v1/echo/patterns/{patternId} |
Update editable metadata (description, severity, runbookUrl). Features are immutable — delete and re-create to change them. |
DELETE |
/driftmind/v1/echo/patterns/{patternId} |
Remove the pattern. Cascades to any attachments and any historical detection rows. |
GET |
/driftmind/v1/echo/summary |
Roll-up the dashboard's Echo card uses: total patterns + count of detections in the last 24h. |
Attach a pattern to a forecaster
Pattern-of-interest matching only runs against forecasters the pattern is attached to. Attach the pattern to as many forecasters as you like — the matcher state for each (forecaster, pattern) pair is independent.
POST /driftmind/v1/echo/patterns/{patternId}/attachments/{forecasterId}
curl -X POST https://api.thingbook.io/access/api/driftmind/v1/echo/patterns/$PATTERN_ID/attachments/$FORECASTER_ID \
-H "Auth: $DRIFTMIND_TOKEN"
Detach with the same path and DELETE. Detaching does not remove historical
detection rows — only stops new matching going forward.
info / warning / error / success). Pick it
deliberately at create time: it controls the colour of the alert pill in the dashboard's alerts
panel and determines whether the match is treated as informational or as an actionable incident.
Echo Detections
Every time the rolling similarity for a (forecaster, pattern) pair crosses 60% on the
rising edge, the matcher writes one row to echo_detections and emits a
matching alert. Rows are timestamped with the forecaster's model clock at the moment of the
crossing — not the wall time at which the row physically landed in the database — so a replay
produces the same timeline as the original observation stream.
List recent detections
GET /driftmind/v1/echo/detections (optional ?limit=N, default
200, max 500)
curl -X GET "https://api.thingbook.io/access/api/driftmind/v1/echo/detections?limit=50" \
-H "Auth: $DRIFTMIND_TOKEN"
Example response
[
{
"eventId": 1042,
"forecasterId": "abc123-xyz789",
"forecasterName": "mill-line-2",
"patternId": "9c3b1e4f-3c0c-4b7e-9b1f-2a3b4d5e6f70",
"patternName": "bearing-failure",
"probability": 82.34,
"detectedAt": "2026-06-01T10:14:08"
}
]
probability is the percentage 0–100 (the rising-edge crossing happens at
>= 60). detectedAt is the model-clock instant — the moment on the
forecaster's internal timeline when the match was confirmed.
Delete a single detection
Same shape and semantics as the anomaly-event delete: customer-scoped, idempotent, returns
removed: 0 rather than 404 when the id does not match a row you own.
DELETE /driftmind/v1/echo/detections/{eventId}
curl -X DELETE https://api.thingbook.io/access/api/driftmind/v1/echo/detections/1042 \
-H "Auth: $DRIFTMIND_TOKEN"
Success response
{
"message": "ECHO_DETECTION_DELETED",
"eventId": 1042,
"removed": 1
}
echo_detections. It
does NOT remove the pattern it matched (use
DELETE /driftmind/v1/echo/patterns/{patternId}) and does NOT affect any
anomaly events on the same forecaster.
Device Gateway
The device gateway lets you push raw device measurements without naming a forecaster on
every call. You send measurements tagged with a deviceId; the gateway routes each
device's data to whatever forecaster(s) you have bound it to. Unbound devices are still catalogued
so you can discover them and bind them later.
https://api.thingbook.io/access/api/feed/v1.
Ingest measurements
POST /feed/v1/measurements — send a batch. No forecaster id required; routing is by
binding. A 200 means all measurements were accepted; a
206 means the batch was partially accepted.
curl -X POST https://api.thingbook.io/access/api/feed/v1/measurements \
-H "Auth: $DRIFTMIND_TOKEN" -H "Content-Type: application/json" \
-d '{"measurements":[{"deviceId":"DP-47","deviceClass":"pump","timestamp":"2026-09-11T10:15:00+00:00","values":{"flow_rate":11.2,"pressure":3.4}}]}'
Response
{
"message": "OK",
"devices": [
{ "deviceId": "DP-47", "monitored": true, "pointsForwarded": 2, "pointsDiscarded": 0 }
]
}
| Field | Description |
|---|---|
devices[].monitored | true when at least one binding routed this device's data to a forecaster. |
devices[].pointsForwarded | Datapoints forwarded to bound forecasters in this batch. |
devices[].pointsDiscarded | Datapoints discarded because the device was unbound. |
Bind a device to a forecaster
POST /feed/v1/bindings — route a device's stream into a forecaster. Provide an
optional featureMap when the device's variable names differ from the forecaster's
feature names; omit it for an identity mapping.
curl -X POST https://api.thingbook.io/access/api/feed/v1/bindings \
-H "Auth: $DRIFTMIND_TOKEN" -H "Content-Type: application/json" \
-d '{"deviceId":"DP-47","forecasterId":"abc123-xyz789","featureMap":{"flow_rate":"flow_rate","pressure":"pressure"}}'
Discover and inspect devices
| Endpoint | Purpose |
|---|---|
GET /feed/v1/catalogue | List the devices the gateway has seen, bound or not. |
GET /feed/v1/devices/{deviceId}/recent?points=10 | Most recent raw datapoints for one device. |
DELETE /feed/v1/bindings/{deviceId}/{forecasterId} | Remove one device-to-forecaster binding. |
DELETE /feed/v1/bindings/{deviceId} | Remove every binding of a device (its data is discarded again). |
DELETE /feed/v1/devices/{deviceId} | Remove a device from the catalogue. |