Events
Discrete, timestamped occurrences — recorded under named streams, automatically scored, and wired straight into forecasts as first-class inputs.
Signals are continuous — a value every tick. Events are discrete:
things that happen. A product launch, an outage, a news headline, a
policy change. Each event is a timestamped piece of text recorded under a
named stream (its key), and streams work like signals do: one name,
many occurrences, listed and charted together.
What makes events useful for forecasting is scoring. Every recorded event is scored automatically in the context of your workspace — its sentiment, how significant it is, how relevant to your domain, and its expected directional impact — so a stream of raw text becomes a numeric series a chart can render and an analysis can reason about.
Events are not annotations. They are forecast inputs: discovery tests every stream against your goals, validated streams feed models continuously, one important event can flip a live forecast into shock mode within seconds, and a known-future event bends the forecast before it happens.
How an event flows
- You record an event (or a webhook
delivers one, or an ingestion mapping
flags a column
is_event). - It's stored immediately with
"status": "pending"— visible right away. - Scoring runs in the background and fills in
scores; the status becomesscored. Scoring is deterministic, so re-submitting the same event re-scores to the same row rather than duplicating it.
Two clocks matter, and they're stored separately:
| Field | Meaning |
|---|---|
occurred_at | When the event actually happened (or will happen) — this is where it plots |
timestamp | When it was recorded — when the platform learned about it |
The two clocks make three arrival shapes possible, and the platform reacts differently to each:
- Fresh — recorded within ~2 forecast cadences of occurring. The stream's model features refresh immediately, so the very next forecast sees the event; if it's important enough it can trigger the shock protocol.
- Stale — recorded long after it occurred (a backfill). The platform
performs a history revision: event features are rebuilt from the
occurrence onward, affected goals are re-checked, and you get a
history_revisednotification. Old news never fires a shock. - Scheduled —
occurred_atin the future (an earnings date, a planned promo). See scheduled events.
Backfilling history is therefore safe: record last month's incident with
occurred_at set to when it happened, and it lands in the right place on
the time axis — and the models that already trained past that date are
told their history moved.
Record an event
curl -X POST "$API_BASE/v1/event/record" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"key": "press_mentions",
"text": "Featured in the Sunday retail roundup — strong coverage of the summer line.",
"occurred_at": "2026-07-12T09:00:00Z"
}'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.post_event_record(json={
"key": "press_mentions",
"text": "Featured in the Sunday retail roundup — strong coverage of the summer line.",
"occurred_at": "2026-07-12T09:00:00Z",
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.postEventRecord({
json: {
key: "press_mentions",
text: "Featured in the Sunday retail roundup — strong coverage of the summer line.",
occurred_at: "2026-07-12T09:00:00Z",
},
});
{
"data": {
"event_id": "9f2c41d0-…",
"stream": "press_mentions",
"status": "pending",
"scheduled": false
}
}scheduled is true when occurred_at is in the future — the event was
recorded as a known-future occurrence.
Request body
| Field | Meaning |
|---|---|
key required | The stream to record under. Created on first use. |
text required | The event's content — what happened. This is what gets scored. |
occurred_at optional | When it happened: epoch seconds, epoch milliseconds, or ISO 8601. Defaults to now. A future time (up to 400 days ahead) records a scheduled event. |
name optional | A short display label. Defaults to the first 200 chars of text. |
source optional | Provenance label ("who reported this"). Defaults to the stream key. |
origin optional | One of manual, api (default), ingestion. |
value optional | A numeric payload (discount %, magnitude, headcount…). When the text carries no interpretable scale — or the event is purely numeric — value becomes the event's severity in the model features, normalized against the stream's own history. |
data_type optional | Content type hint. Defaults to "text". |
raw optional | The original payload, kept for reference. |
Errors
| Status | Why | Example message |
|---|---|---|
400 | No stream | "Event stream ('key') is required" |
400 | No content | "Event 'text' is required" |
400 | Too far out | "Scheduled events can be at most 400 days in the future" |
Fetch one event
Poll this after recording to see the scores land:
curl "$API_BASE/v1/event/$EVENT_ID" \
-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_event_by_event_id(EVENT_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getEventByEventId(EVENT_ID);
{
"data": {
"event_id": "9f2c41d0-…",
"key": "press_mentions",
"name": "Featured in the Sunday retail roundup — strong coverage of the s…",
"text": "Featured in the Sunday retail roundup — strong coverage of the summer line.",
"source": "press_mentions",
"origin": "api",
"data_type": "text",
"value": null,
"occurred_at": 1783587600000,
"timestamp": 1783842900000,
"status": "scored",
"scores": {
"sentiment": "positive",
"polarity": 0.6,
"importance": 0.45,
"relevance": 0.8,
"impact": 0.35,
"rank": 57
},
…
}
}The score fields:
| Field | Range | Meaning |
|---|---|---|
sentiment | negative / neutral / positive | Overall tone |
polarity | −1 … 1 | Signed tone strength |
importance | 0 … 1 | How significant the event is in general |
relevance | 0 … 1 | How relevant it is to this workspace's domain |
impact | −1 … 1 | Expected signed effect on your targets |
rank | 0 … 100 | Composite headline score (importance + relevance + magnitude of impact) |
Purely numeric content (a number column flagged as an event) has nothing
to interpret, so it scores neutral (rank: 0) instantly — its value
carries the weight instead, normalized against the stream's own history.
An unknown event_id returns 404 ("Event not found").
How events move forecasts
Recording is half the story. Behind the scenes every stream is a candidate forecast driver:
- Featurization. Each stream is converted into continuous model
features — decay kernels at several half-lives, counts, signed
influence, time-since-last-event — on every goal's time grid. Severity
comes from the LLM scores, or from
valuewhen the text carries no scale. - Discovery. The discovery engine tests those features against each goal and, when a stream demonstrably moves a target, records a validated edge — including a fitted response curve (how the target typically reacts, step by step) and its half-life. Edges show up in drivers and in the graph, and the stream list summarizes them per stream.
- Live reaction. A fresh, high-importance event on an edged stream
triggers the shock protocol: the goal flips to
shock_state: "shock", serving blends the champion with a zero-shot generalist and the stream's measured response curve, and uncertainty bands widen until the response fades. The importance bar is outcome-calibrated per (goal, stream): a stream whose "important" events reliably preceded real moves earns a lower bar; one that cries wolf earns a higher one. - Learning. Every shock-mode forecast is logged and later graded against what actually happened. Once enough graded episodes exist, the platform refits the shock blend weights per goal — the response to the next shock is shaped by how well the last ones were handled. Event alerts, scenario injections, and the timing sweep all read the same measured curves.
None of this needs configuration — record events on streams with honest timestamps, and discovery does the rest. Alert on the event flow itself with the event-native alert rules.
Scheduled (known-future) events
A future occurred_at records an event you know is coming — an earnings
date, a planned promo, a maintenance window:
curl -X POST "$API_BASE/v1/event/record" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"key": "promotions",
"text": "Summer sale goes live — 20% off the whole catalog.",
"occurred_at": "2026-09-15T08:00:00Z",
"value": 20
}'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.signals.post_event_record(json={
"key": "promotions",
"text": "Summer sale goes live — 20% off the whole catalog.",
"occurred_at": "2026-09-15T08:00:00Z",
"value": 20,
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.postEventRecord({
json: {
key: "promotions",
text: "Summer sale goes live — 20% off the whole catalog.",
occurred_at: "2026-09-15T08:00:00Z",
value: 20,
},
});
A scheduled event behaves differently from a past one, on purpose:
- It's scored and stored immediately (
"scheduled": true), and plots on charts as an upcoming marker. - When its stream holds a validated edge to a goal and the occurrence falls inside the forecast horizon, serving applies the stream's measured response curve at the scheduled offset — the forecast bends for the promo before the promo runs. The applied events ride along in the forecast's metadata, so the bend is always explained, never silent.
- Its full reactions — feature refresh, shock check (at maximal freshness), event alerts — fire when the scheduled time arrives, not at recording time. Until then it cannot shock anything.
- Per-goal opt-out: set
options.scheduled_events: falseon the goal to serve unbent forecasts.
List what's coming with GET /v1/event/upcoming.
List a stream's events
Page through one stream's history, newest occurrence first — the stream detail view:
curl "$API_BASE/v1/event/streams/press_mentions/events?limit=50" \
-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_event_streams_by_stream_events("press_mentions", params={"limit": 50})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getEventStreamsByStreamEvents("press_mentions", { params: { limit: 50 } });
{
"data": {
"stream": "press_mentions",
"events": [
{
"event_id": "9f2c41d0-…",
"name": "Featured in the Sunday retail roundup — …",
"occurred_at": 1783587600000,
"timestamp": 1783842900000,
"scheduled": false,
"status": "scored",
"value": null,
"scores": { "importance": 0.45, "polarity": 0.6, "sentiment": "positive", "rank": 57 }
}
],
"has_more": true
}
}| Query param | Meaning |
|---|---|
limit | Page size. Default 100, max 500. |
before | Epoch-ms cursor: only events that occurred strictly before this. Take the oldest occurred_at on the page and pass it back to fetch the next one. |
Upcoming (scheduled) events
Known-future events across the workspace, soonest first:
curl "$API_BASE/v1/event/upcoming?within_hours=168" \
-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_event_upcoming(params={"within_hours": 168})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getEventUpcoming({ params: { within_hours: 168 } });
{
"data": {
"events": [
{
"event_id": "c81d92f0-…",
"key": "promotions",
"name": "Summer sale goes live — 20% off the whole catalog.",
"occurred_at": 1789459200000,
"scheduled": true,
"value": 20,
"scores": { "importance": 0.7, "polarity": 0.8, "…": "…" }
}
]
}
}| Query param | Meaning |
|---|---|
within_hours | Only events within this window from now. Default 720 (30 days). |
streams | Comma-separated stream keys to filter to. |
List event streams
One row per stream, with a summary and a time-ordered points series
(one point per event, ordered by occurred_at) ready to render as a
sparkline:
curl "$API_BASE/v1/event/streams" \
-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_event_streams()
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.getEventStreams();
{
"data": {
"streams": [
{
"key": "press_mentions",
"count": 14,
"latest_occurred_at": 1783587600000,
"latest_timestamp": 1783842900000,
"latest_text": "Featured in the Sunday retail roundup — strong coverage of the summer line.",
"latest_sentiment": "positive",
"max_rank": 82,
"scheduled_count": 1,
"next_scheduled_at": 1789459200000,
"edges": {
"goals_affected": 2,
"max_lift": 0.21,
"half_life_s": 21600,
"weakening": 0
},
"points": [
{ "occurred_at": 1781000000000, "timestamp": 1781003600000, "rank": 41, "polarity": -0.2, "sentiment": "negative", "impact": -0.15, "importance": 0.4, "scheduled": false },
…
]
}
]
}
}The summary fields beyond the basics:
| Field | Meaning |
|---|---|
scheduled_count / next_scheduled_at | Pending known-future occurrences, and when the next one lands. |
edges | How the prediction engine sees the stream: how many goals it demonstrably moves (goals_affected), the strongest measured lift, the fitted response half-life in seconds, and how many of those edges are currently weakening. null until discovery validates a first edge. |
Event streams also appear directly in
GET /v1/signal
alongside your signals — one row per stream, tagged "type": "event",
with the same edges summary — so one call shows the whole workspace.
Delete a stream
Deletes every event recorded under the stream:
curl -X DELETE "$API_BASE/v1/event/streams/press_mentions" \
-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_event_streams_by_stream("press_mentions")
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.signals.deleteEventStreamsByStream("press_mentions");
{ "data": { "stream": "press_mentions", "deleted": 14 } }Event streams shared into your workspace by subscription are read-only —
deleting one returns 403 ("Subscribed … event streams are read-only.").
Events pair naturally with the
event-style engineered features
on segments: record what happened as events for context and scoring,
and let release_flag / release_surprise capture when your data
moved — the two views complement each other.

