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));
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 |
Average anomaly score across all features (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));
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 | REDIS_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"
}
Delete an Anomaly Event
Removes a single anomaly event by its server-assigned eventId. The endpoint is
customer-scoped — a caller can never delete a row that belongs to another tenant, even with a
hand-crafted request. Use this to clear false positives from the dashboard, or to retract events
produced during a backfill / replay you'd rather hide.
DELETE /driftmind/v1/anomaly-events/{eventId}
curl -X DELETE https://api.thingbook.io/access/api/driftmind/v1/anomaly-events/1042 \
-H "Auth: $DRIFTMIND_TOKEN"
Success response
{
"message": "ANOMALY_EVENT_DELETED",
"eventId": 1042,
"removed": 1
}
removed is 1 when the row existed and was deleted, or 0
when the id did not match any row owned by this customer. The endpoint is idempotent — a repeat
call still returns 200, not 404.
anomaly_events.
It does NOT cascade to Echo detections, Echo patterns, alerts, or any other
table — an anomaly event and an Echo match are produced by different subsystems and are
independent surfaces. To remove an Echo match, use
DELETE /driftmind/v1/echo/detections/{eventId}.
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.