Three reference playbooks
Each of these is written against the real endpoints above. They are deliberately short — the value is in which call answers which question, not in the plumbing of any one platform.
Playbook A — Segmentation
Trigger. A rule tagged segmentation fires: an east-west conversation
nobody expected. event_type is alert.fired, rule.tags contains
segmentation.
1 — Read the pair off the alert. No parsing of positional data:
src = matches.records[0].client_ip
dst = matches.records[0].server_ip
port = matches.records[0].server_port
2 — Is it already allowed? POST /api/policy/check with
{src, dst, port, protocol: "tcp"}.
covered: true→ the flow matrix already permits this. Close the ticket as a detection-tuning item and stop: the rule and the matrix disagree, and that is a rule problem, not an incident.covered: false→ keep going, and carrynearest_rules[].why_notinto the ticket verbatim. It is the sentence the network owner will reply to.
3 — Who are these machines? The alert’s entities[] usually answers this
already. If entity context is off, or you want the 24-hour picture:
GET /api/context/host?ip=<src>&window=24h&as_of=<fired_at>. new_peers_in_window
is the interesting field: a source that has been talking to this destination for
a week is a documentation gap, one that started an hour ago is an event.
4 — Open the ticket. Key it on dedup_key so the alert.resolved that
follows finds it. Title it with rule.name, assign it from
entities[].carto.group, and paste the checked_pair and the why_not.
5 — Acknowledge. POST /api/alerts/{id}/status with {"status": "ack"} so
the console shows the alert as taken, and so anyone looking at obserae sees the
ticket exists. Returns 204.
Playbook B — Threat intel
Trigger. An internal host talks to a destination a feed lists — a Tor exit
node, a FireHOL entry. In a v2 alert you see it without asking:
entities[].threat_intel[].verdict == "match".
1 — Confirm and expand. GET /api/enrichment/lookup?ip=<external>. The
alert’s context is frozen at fire time — which is what you want for evidence —
so this call tells you what the feeds say now, and threat_intel[].updated_at
tells you how fresh each verdict is. A verdict: "unavailable" here means the
enrichment database could not be consulted; it is not a clean bill of health, and
a playbook that treats it as one will eventually close a real incident.
2 — Was this the only conversation? POST /api/context/peers with
{"ip": "<internal>", "window": "24h", "direction": "out", "scope": "external", "as_of": "<fired_at>"}. One peer on one port is a click on a link. Twenty
external peers, low volume, regular intervals, is a beacon — and the answer is
sorted by volume, so read the tail as well as the head.
3 — Decide. Escalate on: a match verdict, plus either a second internal
host reaching the same destination, or a peer count that has no business
explanation. Otherwise annotate and close.
4 — Propose the indicator. obserae has already published it: the address is
in GET /api/indicators?since=24h&min_severity=high from the moment the rule
fired. Your playbook proposes it to whoever owns the blocklist — obserae does not
push it, and obserae does not block. Send If-None-Match on that poll.
Playbook C — Exfiltration
Trigger. A volume spike. A threshold or anomaly rule fires with
alert.observed_value and alert.threshold (threshold), or with the anomaly
rule’s own scoring.
1 — Get the 24-hour picture.
GET /api/context/host?ip=<host>&window=24h&as_of=<fired_at>. Read
volume.out_bytes, talks_to[] sorted by bytes, and new_peers_in_window.
2 — Compare to the baseline. Same call, same host, as_of shifted back
seven days:
GET /api/context/host?ip=10.0.0.50&window=24h&as_of=2026-05-28T12:00:00Z
Divide today’s volume.out_bytes by that one. A CI runner that egresses 4× its
weekly norm to a destination in new_peers_in_window is the case; the same
multiple spread over its usual peers is a release day.
3 — Name the destination. GET /api/enrichment/lookup?ip=<top peer>. A
cloud region you already use is one conversation; an ASN nobody can account for
is another.
4 — Page. On-call, with: the ratio you computed, the destination with its
ASN and country, entities[].carto.host for the source, and alert.url. Key the
incident on dedup_key — obserae will close it itself when the condition clears,
so the on-call engineer is not left closing a page by hand at 04:00.
A note on windows.
windowaccepts1h,24hand7donly. To compare two periods, moveas_ofand keepwindowfixed, as step 2 does. A widened window is a widened scan, which is why the vocabulary is closed rather than free-form.