Push & manage
Push values, list and inspect signals, delete, and manage display names, units, and groups.
Push a value
One call, one value:
curl -X POST "$API_BASE/v1/signal/push" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "key": "daily_sales", "value": 12841.5, "timestamp": "2026-07-15T00:00:00Z" }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.post_signal_push(json={
"key": "daily_sales",
"value": 12841.5,
"timestamp": "2026-07-15T00:00:00Z",
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.postSignalPush({
json: {
key: "daily_sales",
value: 12841.5,
timestamp: "2026-07-15T00:00:00Z",
},
});
{ "status": "ok" }Request body
| Field | Meaning |
|---|---|
key required | The signal's key. If it's new, the signal is created implicitly. |
value required | The value — number, string, or boolean. |
timestamp optional | ISO 8601. Defaults to now. |
Duplicate pushes for the same key and timestamp are safe — the newest value wins. Each push counts as one streaming event against the workspace's monthly allowance (Billing).
Errors
| Status | Why | Example message |
|---|---|---|
400 | Missing key or value, or empty body | "Missing required fields in signal data" |
400 | New key would exceed your plan's signal-type cap | "Cannot add new signal type 'daily_sales'. Your plan allows a maximum of 25 signal types per workspace." |
400 | Total stored values would exceed your plan's cap | "Signal limit reached. Your plan allows 100000 signals per workspace." |
403 | The key is a subscribed read-only signal | "Cannot push to a … subscribed signal. Subscribed signals are read-only." |
List signals
Paginated rows with pre-binned chart data per key:
curl "$API_BASE/v1/signal?page=1&per_page=25" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.get_signal(params={"page": 1, "per_page": 25})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getSignal({ params: { page: 1, per_page: 25 } });
{
"data": {
"signals": [
{
"key": "daily_sales",
"type": "signal",
"value_types": ["continuous_numerical"],
"signal_count": 730,
"bins": [ { "from": "2026-06-01T00:00:00+00:00", "count": 20, "avgValue": 12100.4 }, … ],
"min_timestamp": "2024-07-15T00:00:00+00:00",
"max_timestamp": "2026-07-14T00:00:00+00:00",
"total_duration": 63072000,
"display_name": "Daily sales",
"group": "Store ops",
"group_origin": "user",
"unit": "$",
"unit_origin": "user",
…
}
],
"event_markers": [ … ],
"pagination": {
"total_pages": 1,
"total_items": 12,
"current_page": 1,
"per_page": 25
}
}
}Query parameters
| Parameter | Meaning |
|---|---|
page / per_page optional | Pagination. Defaults 1 and 100. |
start_date / end_date optional | Window to bin over (interpreted in the X-Timezone header's zone, default UTC). Defaults to the full data range. |
number_of_bins optional | Chart resolution per row. Defaults to 36. |
group optional | Only signals in one group; pagination reflects the filtered set. |
sort_by optional | relevance (default), records, recency, name, published, unpublished. |
sort_order optional | asc or desc (default). |
published_filter optional | all (default), published, unpublished. |
Rows arrive annotated with display_name, group, and unit so you
don't need follow-up calls to render a signal table.
Workspace overview
/list returns one row per key. For a chart that stays readable at any
scale, /overview resolves a hierarchical view — per-signal series for
small workspaces, group envelopes (median + band) when there are many:
curl "$API_BASE/v1/signal/overview?number_of_bins=240" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.get_signal_overview(params={"number_of_bins": 240})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getSignalOverview({ params: { number_of_bins: 240 } });
{
"data": {
"mode": "groups",
"path": [],
"groups": [ { "id": "sales", "label": "Sales", "count": 14, "bins": [ … ] }, … ],
"signals": [],
"pinned": [],
"catalog": [ { "key": "daily_sales", "group": "sales" }, … ],
"total_signals": 220,
"member_total": 220,
"events": [ … ]
}
}Query parameters
| Parameter | Meaning |
|---|---|
group optional | Drill into one group — its members come back as individual series. |
pins optional | Comma-separated keys to always show as individual series. |
start_date / end_date optional | Window to bin over, as on /list. |
number_of_bins optional | Chart resolution. Defaults to 240. |
Check for data
A cheap probe — does this workspace have any signal data at all?
curl "$API_BASE/v1/signal/has-data" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.get_signal_has_data()
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getSignalHasData();
{
"data": {
"has_data": true,
"signal_count": 48210,
"subscribed_signals_count": 0
}
}Delete one signal
Deletes every stored value for a key:
curl -X DELETE "$API_BASE/v1/signal?signal_type=daily_sales" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.delete_signal(params={"signal_type": "daily_sales"})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.deleteSignal({ params: { signal_type: "daily_sales" } });
{ "status": "deleted" }Query parameters
| Parameter | Meaning |
|---|---|
signal_type required | The key to delete. |
force optional | If the signal is published for sharing, the first call returns 409 with a summary of what deleting would delist; confirm by re-sending with force=true. |
Errors
| Status | Why | Example message |
|---|---|---|
400 | Missing signal_type | "Signal type is missing" |
403 | The key is a subscribed read-only signal | "Cannot delete a … subscribed signal. Cancel the subscription … instead." |
409 | The signal is published for sharing; re-send with force=true | "This signal is published … with 2 active subscribers. …" |
404 | Nothing stored under that key | "No signals found to delete" |
Delete in bulk
One batch, many keys:
curl -X POST "$API_BASE/v1/signal/delete" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "keys": ["daily_sales", "web_traffic"], "force": false }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.post_signal_delete(json={"keys": ["daily_sales", "web_traffic"], "force": False})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.postSignalDelete({
json: { keys: ["daily_sales", "web_traffic"], force: false },
});
{
"data": {
"deleted": ["daily_sales", "web_traffic"],
"deleted_event_streams": [],
"skipped_marketplace": []
}
}Request body
| Field | Meaning |
|---|---|
keys required | The signal keys to delete. |
force optional | Confirm deletion of keys that are published for sharing. Defaults to false. |
Subscribed read-only keys are never deleted — they're skipped and
reported in skipped_marketplace. If any key in the batch is published
for sharing and force isn't set, the whole batch is rejected with 409
and a listing summary; re-send with "force": true to confirm. A batch
of only read-only keys returns 403. Group assignments and display names
of deleted keys are cleaned up automatically.
Display names
Set (or clear, with an empty name) the friendly label for one key:
curl -X POST "$API_BASE/v1/signal/display-name" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "key": "o", "display_name": "Open" }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.post_signal_display_name(json={"key": "o", "display_name": "Open"})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.postSignalDisplayName({ json: { key: "o", display_name: "Open" } });
{ "data": { "key": "o", "display_name": "Open" } }| Field | Meaning |
|---|---|
key required | The signal key to label. |
display_name optional | The friendly label. An empty string clears it. |
Fetch the whole map in one call:
curl "$API_BASE/v1/signal/display-names" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.get_signal_display_names()
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getSignalDisplayNames();
{ "data": { "o": "Open", "h": "High", "daily_sales": "Daily sales" } }A missing key returns 400 ("key is required").
Units
Assign a measurement unit to one or more keys; omit or empty unit to
clear (the key reverts to the automatically inferred unit, or none):
curl -X POST "$API_BASE/v1/signal/units/assign" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "keys": ["spot_price", "day_ahead_price"], "unit": "$/MWh" }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.post_signal_units_assign(json={
"keys": ["spot_price", "day_ahead_price"],
"unit": "$/MWh",
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.postSignalUnitsAssign({
json: {
keys: ["spot_price", "day_ahead_price"],
unit: "$/MWh",
},
});
{ "data": { "updated": 2, "unit": "$/MWh" } }| Field | Meaning |
|---|---|
keys required | The signal keys to assign the unit to. |
unit optional | The unit string, e.g. "$/MWh". Omit or send empty to clear — the key reverts to the automatically inferred unit, or none. |
GET /v1/signal/units returns every persisted unit:
{
"data": {
"units": {
"spot_price": { "unit": "$/MWh", "origin": "user" },
"load_mw": { "unit": "MW", "origin": "llm" }
}
}
}Groups
Groups organize large workspaces. GET /v1/signal/groups returns the
resolved grouping for every key plus your explicit overrides:
curl "$API_BASE/v1/signal/groups" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.get_signal_groups()
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getSignalGroups();
{
"data": {
"groups": [
{ "name": "Store ops", "count": 14, "origins": { "user": 2, "inferred": 12 } },
…
],
"assignments": {
"daily_sales": { "group": "Store ops", "origin": "user" }
}
}
}Assign keys to a group (your assignment always wins over automatic grouping):
curl -X POST "$API_BASE/v1/signal/groups/assign" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "keys": ["daily_sales", "foot_traffic"], "group": "Store ops" }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.post_signal_groups_assign(json={
"keys": ["daily_sales", "foot_traffic"],
"group": "Store ops",
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.postSignalGroupsAssign({
json: {
keys: ["daily_sales", "foot_traffic"],
group: "Store ops",
},
});
{ "data": { "assigned": 2, "group": "Store ops" } }| Field | Meaning |
|---|---|
keys required | The signal keys to assign. |
group required | The group name. Created on first use. |
Remove explicit assignments — the keys revert to automatic grouping:
curl -X POST "$API_BASE/v1/signal/groups/unassign" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "keys": ["foot_traffic"] }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.post_signal_groups_unassign(json={"keys": ["foot_traffic"]})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.postSignalGroupsUnassign({ json: { keys: ["foot_traffic"] } });
{ "data": { "unassigned": 1 } }All three write endpoints return 400 ("keys is required") when keys
is missing or empty.

