AI agents & MCP
Everything you do on a case — arranging its board, cleaning it up, following watches, weighing hypotheses — an AI client can do as well, over the MCP server: Claude, Cursor, Codex or your own agent. The assistant in the app uses the very same tools, and asks you before every change it makes.
An agent’s changes are ordinary board changes: they land on the timeline under mcp, other people’s open boards update, and a closed case stays read-only.
Give the client a token
Settings → MCP Server → Create token. Switch All tools off and tick what
the client needs:
| Scope | For |
|---|---|
| Cases | Finding cases, putting evidence in, watches, clean-up and finding rules, hypotheses, the timeline |
| Case Board | Reading the board, notes, frames, links and stances, placing and tidying, connections, snapshots |
| Findings, Assets | Searching for the evidence to put on the board |
A client that should only read and arrange boards still needs Cases to find
a case by name (search_cases); without it, give it the case id.
Case Board is its own scope. Its tools used to be part of Cases, and tokens that had Cases were given Case Board automatically on upgrade. A token created since needs Case Board ticked.
Read the board
get_case_board returns the board as a labelled summary: every item with
what it stands for, so a client never has to join ids across lists.
{
"board": { "caseId": "3f5c…", "version": 12, "readOnly": false, "caseStatus": "OPEN" },
"counts": { "evidence": 3, "findings": 7, "hypotheses": 1, "notes": 1, "frames": 1, "links": 1, "stances": 2, "unplaced": 1 },
"items": [
{
"id": "8f0c…", "kind": "EVIDENCE", "label": "payroll-2024.xlsx", "x": 40, "y": 60, "parentId": "c2d1…",
"assetId": "a9e2…", "findingCount": 2,
"findings": [{ "findingId": "f-17", "type": "IBAN", "value": "AT61 1904 3002 3457 3201", "severity": "HIGH", "state": "new" }]
},
{
"id": "5b7e…", "kind": "HYPOTHESIS", "label": "Payroll left via the share", "unplaced": true,
"status": "PROPOSED", "stances": { "supports": 2, "contradicts": 0, "neutral": 0 }
}
],
"links": [], "stances": [], "relations": [{ "source": "8f0c…", "target": "a41b…" }]
}- A finding’s state is the one the board shows:
open,new,gone,resolved,dismissedordeleted(see Evidence & findings). - Positions:
x/yof an item with aparentId— inside a frame, or a comment pin — are relative to that parent.unplaceditems have no position yet. - relations are the lines the platform draws between evidence (lineage, duplicates). Nobody can change those on the board.
board.versiongoes back asbaseVersionwhen changing the board.view: "full"returns the raw board instead;snapshotIdreads a snapshot in the same shape.
Change the board
apply_case_board_ops takes a batch of changes, applied in order in one go.
Each change succeeds or is refused on its own (rejected, with a reason), so
one stale item never sinks the rest.
{
"caseId": "3f5c…",
"baseVersion": 12,
"ops": [
{ "type": "item.create", "id": "0b6e2a7c-0f4e-4c2a-9a55-3c1d2e4f5a6b", "kind": "NOTE", "x": 900, "y": 40,
"content": { "text": "Both exports carry the same IBAN. Who ran them?" }, "style": { "color": "yellow" } },
{ "type": "link.create", "id": "9d1fb3e2-7c4a-4f1e-8b2d-5e6f7a8b9c0d", "kind": "related_to", "certainty": "SUSPECTED",
"source": { "itemId": "0b6e2a7c-0f4e-4c2a-9a55-3c1d2e4f5a6b" }, "target": { "itemId": "8f0c…", "findingId": "f-17" } },
{ "type": "stance.set", "hypothesisItemId": "5b7e…", "target": { "itemId": "8f0c…" }, "stance": "SUPPORTS", "note": "Same IBAN" }
]
}| On the board you… | The op |
|---|---|
| Add a note or a frame | item.create (NOTE with content.text, FRAME with content.title) |
| Move, resize, recolour, highlight, collapse, move into a frame | item.update (x, y, width, height, parentId, style, collapsed) |
| Delete a note, frame, pin or hypothesis card, and undo it | item.delete, item.restore |
| Draw a link, edit it, delete it | link.create, link.update, link.delete / link.restore |
| Make a link a global relationship | link.promote |
| Add evidence, remove it from the case | evidence.add (an asset, or a finding with its asset), evidence.remove |
| Attach or detach a finding | finding.attach, finding.detach |
| Add a hypothesis, set its stances | hypothesis.create, stance.set, stance.remove |
| Comment, resolve a comment | comment.create, comment.resolve |
| Put a thread back on the board | thread.place |
- Ids are the client’s: every new item or link gets a fresh UUID, so a batch sent twice changes nothing twice, and later ops of the same batch can refer to it (the link above points at the note created before it).
- Undo is the matching
restore; removed evidence comes back with its findings, notes and stances. - x/y can be left out when adding evidence, hypotheses or comments; then let the board place them.
Arrange the board
| Tool | Does | Changes what people arranged? |
|---|---|---|
place_case_board_items | Gives every item without a position a spot next to what it connects to, as the board does when someone opens it | No: placed items never move |
frame_case_board_items | Groups items into a new frame (laid out anew, or kept where they stand with arrangement: "keep") or into an existing one, which grows to fit | Only the items named |
tidy_case_board | Tidy up: lays the whole board out again, left to right along its relations, and sends dragged findings back to their spots | Yes, all of it |
tidy_case_board replaces the arrangement people made. Run it with
dryRun: true first to see every move, and take a
snapshot first if the current layout matters. Each move reports
where the item was (from), so a tidy can be walked back with item.update.
The agent’s layouts use the same sizes as the board in the browser — assets with their findings fanned out around them, cards, notes, frames — so what an agent places does not land on top of what is there.
Trace connections
trace_case_connections is Show connections for an agent: what the case’s
assets connect to beyond the board, upstream (what feeds them),
downstream (what they feed) and alongside (duplicates, look-alikes),
hop by hop. Every node says whether it is in the case already (inCase, with
its board item). An agent brings one in with evidence.add. See
Connections & neighbours.
Clean-up, filters and escalation
Every rule that takes things out of a case can be previewed first, without changing anything:
| Preview | Then |
|---|---|
preview_case_cleanup: what the clean-up switches would take out now | update_case with removeGoneFindings, removeResolvedFindings or removeGoneAssets |
preview_case_finding_filters: what filters would take out, or escalations mark | add_case_finding_filters, update_case_finding_filter, remove_case_finding_filter |
clear_case_escalations takes escalation marks off once they are dealt with.
The rules themselves are explained in
Clean-up & filters and
Escalation.
Watches and hypotheses
- Hypothesis rules:
list_case_hypothesis_rules,add_case_hypothesis_rule,update_case_hypothesis_ruleandremove_case_hypothesis_rulehand a watch’s answers to a hypothesis as supporting, contradicting or neutral, and land them beside it on the board. See Hypothesis rules. - Watches:
link_case_inquirieslinks questions;set_case_inquiry_auto_pullturns a watch’s auto-add on or off;unlink_case_inquirystops the case following it (its per-watch rules go with the link). - Verdicts:
update_case_threadsets a hypothesis’s status (Supported, Refuted, …) and confidence, and writes the change in its log.add_case_thread_entryadds notes, or a new statement of the claim. - Stances on the board are
stance.set/stance.remove.
Snapshots
take_case_board_snapshot freezes the board as it is; list_case_board_snapshots
lists them (closing a case takes one by itself), and get_case_board with a
snapshotId reads one back.
What stays with people
Some things are deliberately not open to agents: deleting a case, deleting a hypothesis with its whole log (an agent can take the card off the board, which keeps the thread), deleting a global relationship everywhere, and exporting the board as an image. They destroy history the case exists to keep, so they stay a person’s decision in the app.
Tool reference
Generated from the MCP server itself. Open a tool for its parameters
(* = required).
Case Board case_board
The canvas a case is arranged on: read it as a labelled summary; add notes, frames, links and hypothesis stances; place new items, tidy the board up and group items into frames; trace what the evidence connects to beyond the board; and take snapshots. Pair with Cases so a client can find cases and change what they hold.
get_case_boardread-onlyRead a case's board, the canvas the case is arranged on. By default a labelled summary: every item with what it stands for — EVIDENCE (the asset, and the findings the case holds on it with their state: open, new, gone, resolved, dismissed or deleted), HYPOTHESIS (title, status, confidence, stance counts), NOTE (its text), FRAME (title, number of members) and COMMENT pins (latest text, what they are pinned to) — plus the links drawn between items or single findings, hypothesis stances, the platform relations between the evidence (lineage, duplicates: drawn by the board, not editable) and threads not on the board. x/y of an item with a parentId (inside a frame, or a comment pin) are relative to that parent; `unplaced` items have no position yet (place_case_board_items gives them one). board.version is the baseVersion for apply_case_board_ops. view "full" returns the raw board payload instead (large; the neighbourhood graph only with includeGraph). snapshotId reads a snapshot (list_case_board_snapshots) in the same shape: the board exactly as it was captured.
| caseId* | string (uuid) | — |
| view | enum | One of: summary, full |
| includeGraph | boolean | view "full" only: include the neighbourhood graph |
| snapshotId | string (uuid) | — |
apply_case_board_opsdestructiveidempotentChange a case's board with a batch of ops, applied in order in one transaction; each op succeeds or is rejected on its own (`rejected`, with a reason and a code such as NOT_FOUND, STALE or ALREADY_ON_BOARD). Read the board first (get_case_board) for item ids, and pass its board.version as baseVersion. Ids of new things are yours: give every new item or link a fresh UUID (id / itemId), so a resent batch is harmless and later ops of the same batch can refer to it. opId is optional (op-1, op-2, … in the result). An endpoint is {itemId, findingId?}: a board item, optionally one finding of an EVIDENCE item. Ops: item.create (NOTE with content.text, FRAME with content.title), item.update (x, y, width, height, z, parentId — a FRAME to put the item in, null to take it out —, collapsed, style: color, highlight, rowHighlights per finding, findingPositions; content), item.delete and item.restore (notes, frames, comment pins and hypothesis cards: a card leaves the board, its thread stays; never evidence), link.create/update/delete/restore (kind such as related_to, same_entity, communicates_with, derived_from, contradicts, precedes or your own; certainty CONFIRMED or SUSPECTED; confidence 0–1), link.promote (copies a link between evidence into the global graph, for every case), evidence.add (an asset, or a finding, which brings its asset) and evidence.remove, finding.attach/detach, hypothesis.create (optionally with supports), stance.set/remove (SUPPORTS, CONTRADICTS, NEUTRAL), comment.create/resolve and thread.place. x/y may be left out of evidence.add, hypothesis.create, comment.create and thread.place: then call place_case_board_items to give them a spot. x/y inside a frame are relative to the frame. Every delete can be undone with the matching restore. Closed cases are read-only.
| caseId* | string (uuid) | — |
| baseVersion | integer | board.version you read; defaults to the current version |
| ops* | unknown[] | — |
place_case_board_itemswritesidempotentGive every item on a case's board that has no position yet — evidence, hypotheses or comments added without x/y, by an agent, a watch or a lead — a spot next to what it connects to: what the board does by itself when someone opens it, done now. Comment pins go by the item they annotate; items with nothing on the board to sit next to go into one block right of the board; a board with nothing placed gets one layered layout. Never moves an item that already has a position. Returns each placed item with its position, and the new board version.
| caseId* | string (uuid) | — |
tidy_case_boarddestructiveidempotentTidy up a case's board, like the board's own Tidy up: lay everything at the top level out again, left to right along its relations (what feeds or supports something sits left of it), each connected group together and the groups packed side by side; items in a frame move with their frame, dragged findings go back to their spots around their asset, and items without a position get one. This replaces the arrangement people made: run it with dryRun first to see the moves, prefer place_case_board_items when only new items need a spot, and take_case_board_snapshot first if the current arrangement matters. Every move says where the item was (`from`); to walk a tidy back, send those positions as item.update ops (apply_case_board_ops).
| caseId* | string (uuid) | — |
| dryRun | boolean | — |
frame_case_board_itemswritesGroup items on a case's board into a frame, a named box whose members move with it. Pass `title` (and optionally `color`) for a new frame, or `frameId` for one already on the board (a `title` then renames it). A new frame lays its members out anew inside, relations left to right, where they were and clear of everything else (arrangement "compact", the default); "keep" wraps them where they stand. Into an existing frame each item takes the next free spot and the frame grows to fit. Items in another frame move over; comment pins stay with the item they annotate. Returns the frame, where each member landed (x/y relative to the frame, and where it was), and what was skipped and why.
| caseId* | string (uuid) | — |
| itemIds* | string[] | — |
| title | string | — |
| frameId | string (uuid) | — |
| color | enum | One of: yellow, blue, green, pink, gray, red, amber, violet |
| arrangement | enum | One of: compact, keep |
trace_case_connectionsread-onlyShow connections: what the assets of a case connect to beyond its board, hop by hop through the global graph — upstream (what feeds them: lineage), downstream (what they feed) and alongside (duplicates, look-alikes, drawn links). Traces from every asset in the case unless assetIds names some. kinds narrows which relations are followed; depth is hops (1–6, 6 meaning as far as it goes; default 2); limit caps the nodes returned (default 150, at most 300), and `truncated` says when it was hit. Each node says whether it is in the case already (inCase, with its board itemId), which side it is on and which node it was reached from (via). Add one to the case with apply_case_board_ops evidence.add.
| caseId* | string (uuid) | — |
| assetIds | string[] | — |
| direction | enum | One of: up, down, both |
| depth | integer | — |
| kinds | enum[] | One of: lineage, links, duplicates, similar, meaning |
| limit | integer | — |
list_case_board_snapshotsread-onlyA case board's snapshots, newest first: frozen copies of the board taken when the case closed (reason CASE_CLOSED) or on request (MANUAL). Read one with get_case_board and its snapshotId.
| caseId* | string (uuid) | — |
take_case_board_snapshotwritesCapture a case's board as it is now in an immutable snapshot (kept for good; the case timeline records it). Take one before a big rearrangement (tidy_case_board) or before handing a case over, so the arrangement a conclusion was drawn from survives later edits.
| caseId* | string (uuid) | — |
Cases cases
Investigation cases: evidence and findings, linked questions (watches), clean-up and finding rules, hypotheses and discussion threads, and the timeline. Pair with Case Board to arrange the canvas.
search_casesread-onlySearch cases with filters and pagination.
| search | string | — |
| status | enum[] | One of: OPEN, IN_PROGRESS, CLOSED, ARCHIVED |
| severity | enum[] | One of: CRITICAL, HIGH, MEDIUM, LOW, INFO |
| skip | integer | — |
| limit | integer | — |
get_caseread-onlyFetch a single case with evidence, findings, and linked questions.
| id* | string (uuid) | — |
create_casewritesCreate an investigation case, optionally linking questions.
| title* | string | — |
| description | string | — |
| status | enum | One of: OPEN, IN_PROGRESS, CLOSED, ARCHIVED |
| severity | enum | One of: CRITICAL, HIGH, MEDIUM, LOW, INFO |
| assignee | string | — |
| createdBy | string | — |
| inquiryIds | string[] | — |
| removeGoneFindings | boolean | Clean-up: findings the scans no longer see (retired by a run, or deleted) leave the case by themselves |
| removeResolvedFindings | boolean | Clean-up: findings someone resolved leave the case |
| removeGoneAssets | boolean | Clean-up: assets deleted from their source leave the case, with their findings |
update_casedestructiveUpdate case metadata, status, severity, AI mode, or its clean-up switches. Switching a clean-up rule on applies it right away: what it takes out is reported in `cleanup` and written on the case timeline — run preview_case_cleanup first to see how much that is. Closing a case goes through close_case (it needs a conclusion and snapshots the board), not through status here.
| id* | string (uuid) | — |
| title | string | — |
| description | string | — |
| status | enum | One of: OPEN, IN_PROGRESS, CLOSED, ARCHIVED |
| severity | enum | One of: CRITICAL, HIGH, MEDIUM, LOW, INFO |
| assignee | string | — |
| conclusion | string | — |
| aiMode | enum | One of: INHERIT, MANAGED, OBSERVE_ONLY |
| removeGoneFindings | boolean | Clean-up: findings the scans no longer see (retired by a run, or deleted) leave the case by themselves |
| removeResolvedFindings | boolean | Clean-up: findings someone resolved leave the case |
| removeGoneAssets | boolean | Clean-up: assets deleted from their source leave the case, with their findings |
close_casewritesClose a case with a conclusion. Linked questions are archived unless they drive another open case, or keepInquiries is true (use it when the questions are standing checks that should keep running after the case is answered).
| id* | string (uuid) | — |
| conclusion* | string | — |
| closedBy | string | — |
| keepInquiries | boolean | — |
reopen_casewritesReopen a closed/archived case and reactivate the questions that were archived alongside it.
| id* | string (uuid) | — |
| note | string | — |
| reopenedBy | string | — |
add_case_evidencewritesAttach an asset as evidence to a case.
| id* | string (uuid) | — |
| entityType* | string | Must be "asset" |
| entityId* | string | Asset UUID |
| note | string | — |
| addedBy | string | — |
attach_case_findingswritesBatch-attach findings to a case by ID. Asset evidence rows are created automatically.
| id* | string (uuid) | — |
| findingIds* | string[] | — |
| addedBy | string | — |
pull_case_from_inquirywritesPull a linked question's current matches into the case as evidence and findings.
| id* | string (uuid) | — |
| inquiryId* | string | — |
| findingIds | string[] | Specific finding IDs (omit = all current matches) |
link_case_inquirieswritesLink additional questions to a case. Already-linked ones are ignored. Ids also named in autoPullInquiryIds will pull their new matches into the case by themselves as later scans land them.
| id* | string (uuid) | — |
| inquiryIds* | string[] | — |
| autoPullInquiryIds | string[] | — |
set_case_inquiry_auto_pullwritesidempotentTurn automatic pulling of a linked question's new matches into a case on or off (the watch's auto-add). On: matches that later scans land join the case by themselves, subject to the case's filters. Off: they wait for pull_case_from_inquiry — escalation rules still bring matching answers in. The question must be linked already (link_case_inquiries).
| id* | string (uuid) | — |
| inquiryId* | string | — |
| autoPull* | boolean | — |
unlink_case_inquirydestructiveUnlink a question (a watch) from a case: the case stops following it. The question itself and the evidence it already brought in stay; the filters and escalation rules scoped to that watch are dropped with the link. To end a case and archive its questions, use close_case instead.
| id* | string (uuid) | — |
| inquiryId* | string | — |
preview_case_cleanupread-onlyWhat a case's automatic clean-up would take out right now, without changing anything: findings the scans no longer see (removeGoneFindings), findings someone resolved (removeResolvedFindings), and assets deleted from their source, with their findings (removeGoneAssets). Name the switches to preview, or leave them all out to preview all three. Returns a count per switch and a sample. Switching one on (update_case) applies it at once.
| id* | string (uuid) | — |
| removeGoneFindings | boolean | Clean-up: findings the scans no longer see (retired by a run, or deleted) leave the case by themselves |
| removeResolvedFindings | boolean | Clean-up: findings someone resolved leave the case |
| removeGoneAssets | boolean | Clean-up: assets deleted from their source leave the case, with their findings |
list_case_finding_filtersread-onlyA case's finding rules, case-wide (inquiryId null) or for one linked question: filters (action EXCLUDE — kinds of finding it does not want) and escalations (action ESCALATE — kinds that need attention). A FINDING_TYPE rule names a finding type exactly; a VALUE_PATTERN rule is a regular expression over the matched value. includeOptions also lists the finding types the case holds and its questions answer, which is what a FINDING_TYPE rule can pick from.
| id* | string (uuid) | — |
| includeOptions | boolean | — |
| inquiryId | string | With includeOptions: the types of this one question |
preview_case_finding_filtersread-onlyWhat unsaved finding rules would do to a case right now, without saving anything: how many findings in the case a filter (action EXCLUDE, the default) would take out or an escalation (ESCALATE) would mark — in total and per rule — with a sample, why a rule could not be saved (`problems`, per rule), and for filters how many assets would be left without a finding in the case (emptiedAssets). Same rules as add_case_finding_filters; also use it to try a new pattern before update_case_finding_filter.
| id* | string (uuid) | — |
| action | enum | One of: EXCLUDE, ESCALATE |
| inquiryId | string | null | A linked question to scope the rules to; omit for the whole case |
| rules* | object[] | — |
add_case_finding_filtersdestructiveAdd finding rules to a case: filters (action EXCLUDE, the default) or escalations (action ESCALATE). A filter keeps a kind of finding out: matches in the case are detached right away (the timeline lists them), and pulls from the case's questions — automatic ones and pull-everything — skip them from then on; attaching one by hand still works. An escalation marks a kind of finding that needs attention: matches already in the case are marked escalated now, and a question brings in new matching answers by itself (even with auto-add off) and raises a notification; filters win over escalations. Scope: omit inquiryId for the whole case, or give a linked question to apply to its answers only. removeEmptiedAssets (filters only) also takes out every asset the filter leaves without a finding in the case. Run with dryRun first: it reports how many findings would be detached or escalated (and emptiedAssets: how many assets would be left without findings).
| id* | string (uuid) | — |
| action | enum | One of: EXCLUDE, ESCALATE |
| inquiryId | string | null | — |
| rules* | object[] | — |
| removeEmptiedAssets | boolean | — |
| dryRun | boolean | — |
update_case_finding_filterdestructiveChange one finding rule of a case (a filter or an escalation): its pattern and/or description. A new pattern applies at once — a filter takes out the findings it now matches (removeEmptiedAssets also takes out assets left without a finding in the case), an escalation marks them — and the timeline records the change. Try the new pattern with preview_case_finding_filters first. A rule's kind, action and scope cannot change: remove it (remove_case_finding_filter) and add another.
| id* | string (uuid) | — |
| filterId* | string (uuid) | — |
| pattern | string | — |
| description | unknown | — |
| removeEmptiedAssets | boolean | — |
remove_case_finding_filterwritesRemove one finding rule (filter or escalation) from a case. Findings a filter detached stay detached, and findings an escalation marked stay marked (clear_case_escalations clears them); the questions simply stop applying the rule.
| id* | string (uuid) | — |
| filterId* | string (uuid) | — |
list_case_hypothesis_rulesread-onlyA case's hypothesis rules: what the case does, by itself, with the answers of one of its linked questions. Each rule names the question (inquiryId), the hypothesis (threadId) and a stance (SUPPORTS, CONTRADICTS or NEUTRAL), and optionally narrows to some answers (kind FINDING_TYPE with the finding type, or VALUE_PATTERN with a regular expression over the matched value; both absent = every answer). linkCount is how many links the rule made and still holds.
| id* | string (uuid) | — |
add_case_hypothesis_rulewritesMake a linked question hand its answers to a hypothesis. From then on every answer the question brings into the case (its auto-add, a pull-everything, or a person picking) is linked to the hypothesis with the stance — SUPPORTS, CONTRADICTS (a reject) or NEUTRAL — and its evidence lands on the board beside the hypothesis, inside the hypothesis's frame when it has one. A rule is the case's own: the question is untouched and other cases keep their own handling. One answer can go to several hypotheses with different stances. applyToExisting also links what the case already holds from the question; links a person made are never changed. Needs the question linked to the case and the hypothesis to be a HYPOTHESIS thread of it.
| id* | string (uuid) | — |
| inquiryId* | string | — |
| threadId* | string | — |
| stance | enum | One of: SUPPORTS, CONTRADICTS, NEUTRAL |
| kind | unknown | — |
| pattern | unknown | — |
| description | unknown | — |
| applyToExisting | boolean | — |
update_case_hypothesis_rulewritesChange a hypothesis rule's hypothesis, stance, matcher or description. A new stance or hypothesis carries the links the rule already made along (updateLinks false leaves them as they are); a new matcher only applies to answers from then on.
| id* | string (uuid) | — |
| ruleId* | string (uuid) | — |
| threadId | string | — |
| stance | enum | One of: SUPPORTS, CONTRADICTS, NEUTRAL |
| kind | unknown | — |
| pattern | unknown | — |
| description | unknown | — |
| updateLinks | boolean | — |
remove_case_hypothesis_ruledestructiveRemove a hypothesis rule. The links it made stay (they become the hypothesis's own links) unless removeLinks is true, which takes those links away; evidence stays in the case and on the board either way.
| id* | string (uuid) | — |
| ruleId* | string (uuid) | — |
| removeLinks | boolean | — |
clear_case_escalationswritesTake the escalation mark off findings of a case once a person has dealt with them (all escalated findings when findingIds is omitted). The findings stay in the case; the timeline records the clearing.
| id* | string (uuid) | — |
| findingIds | string[] | Finding ids (not case-finding ids); omit for all |
get_case_graphread-onlyGet the evidence neighbourhood graph for a case.
| id* | string (uuid) | — |
| depth | integer | — |
get_case_timelineread-onlyPaginated unified case activity feed (newest first). Narrow it with `types` (e.g. FINDINGS_ESCALATED, FINDINGS_AUTO_REMOVED) or to one linked watch with `inquiryId`.
| caseId* | string (uuid) | — |
| cursor | string | — |
| limit | integer | — |
| types | enum[] | Only these activity types One of: CASE_CREATED, CASE_UPDATED, CONCLUSION_UPDATED, INQUIRY_LINKED, INQUIRY_UNLINKED, INQUIRY_PULLED, EVIDENCE_ADDED, EVIDENCE_REMOVED, EVIDENCE_NOTE_UPDATED, FINDING_ADDED, FINDING_REMOVED, FINDING_NOTE_UPDATED, THREAD_CREATED, THREAD_ENTRY_ADDED, THREAD_STATEMENT_UPDATED, THREAD_STATUS_CHANGED, THREAD_CONFIDENCE_CHANGED, SUPPORT_LINKED, SUPPORT_UNLINKED, SUPPORT_UPDATED, LEAD_PROPOSED, LEAD_ACCEPTED, LEAD_DISMISSED, EVENT_ADDED, EVENT_UPDATED, EVENT_REMOVED, BOARD_NOTE_ADDED, BOARD_NOTE_UPDATED, BOARD_NOTE_REMOVED, BOARD_FRAME_ADDED, BOARD_FRAME_UPDATED, BOARD_FRAME_REMOVED, BOARD_LINK_ADDED, BOARD_LINK_UPDATED, BOARD_LINK_REMOVED, BOARD_LINK_PROMOTED, BOARD_ITEM_HIGHLIGHTED, BOARD_ARRANGED, BOARD_SNAPSHOT_TAKEN, COMMENT_RESOLVED, CLEANUP_SETTINGS_UPDATED, FINDING_FILTER_ADDED, FINDING_FILTER_UPDATED, FINDING_FILTER_REMOVED, FINDINGS_AUTO_REMOVED, EVIDENCE_AUTO_REMOVED, INQUIRY_SETTINGS_UPDATED, FINDINGS_ESCALATED, ESCALATION_CLEARED, LEADS_GENERATED, BOARD_TERM_PLACED, BOARD_TERM_REMOVED, MEANING_LINKED, MEANING_UNLINKED, HYPOTHESIS_RULE_ADDED, HYPOTHESIS_RULE_UPDATED, HYPOTHESIS_RULE_REMOVED, FINDINGS_AUTO_LINKED, THREAD_EVIDENCE_REMOVED |
| inquiryId | string (uuid) | — |
list_case_threadsread-onlyList threads (hypothesis + discussion) for a case.
| caseId* | string (uuid) | — |
create_case_threadwritesCreate a thread on a case: a HYPOTHESIS (with status/confidence) or a DISCUSSION.
| caseId* | string (uuid) | — |
| kind | enum | Defaults to HYPOTHESIS One of: HYPOTHESIS, DISCUSSION |
| title* | string | Hypothesis name or discussion topic |
| statement | string | Initial statement body (hypothesis threads) |
| status | enum | One of: PROPOSED, SUPPORTED, REFUTED, INCONCLUSIVE |
| confidence | number | — |
| createdBy | string | — |
add_case_thread_entrywritesAdd an entry to a thread's log. NOTE is commentary and leaves the title alone; STATEMENT revises the claim itself, and the thread title becomes the first 200 characters of its body. STATUS_CHANGE and CONFIDENCE_CHANGE entries only record a remark — they do not change the thread: use update_case_thread to change a hypothesis's status or confidence (it logs the change itself).
| threadId* | string (uuid) | — |
| entryType* | enum | One of: NOTE, STATEMENT, STATUS_CHANGE, CONFIDENCE_CHANGE |
| body | string | — |
| author | string | — |
update_case_threadwritesidempotentUpdate a hypothesis or discussion thread: its title, its colour on the board, its verdict (status PROPOSED, SUPPORTED, REFUTED or INCONCLUSIVE), its confidence (0–1) or its testable predicate. A status or confidence change is written into the thread's log (STATUS_CHANGE / CONFIDENCE_CHANGE) and on the case timeline, so the evolution of a hypothesis stays on record. To revise the claim itself, add a STATEMENT entry (add_case_thread_entry) instead.
| threadId* | string (uuid) | — |
| title | string | — |
| status | enum | One of: PROPOSED, SUPPORTED, REFUTED, INCONCLUSIVE |
| confidence | number | — |
| color | unknown | A CSS colour such as #3b82f6; null for the default |
| testablePredicate | unknown | — |
link_case_thread_supportwritesLink evidence or a finding to a thread as supporting/contradicting/neutral.
| threadId* | string (uuid) | — |
| targetType* | enum | One of: evidence, finding |
| targetId* | string | — |
| stance | enum | One of: SUPPORTS, CONTRADICTS, NEUTRAL |
| weight | number | — |
| note | string | — |