Skip to Content
SettingsMCP Server

MCP Server Integration Guide

Streamable HTTP · Bearer Token

Connect Classifyre MCP to Claude, Cursor, Codex CLI, Gemini CLI, Windsurf, VS Code, and custom clients over HTTP.

Connection Details

PropertyValue
TransportStreamable HTTP (JSON response mode)
AuthorizationBearer token
MethodPOST
Endpointhttps://<your-host>/<workspace>/mcp

Every endpoint is scoped to one workspace (namespace): <workspace> is a workspace slug or its immutable UUID, and a client connected to it sees only that workspace’s data. A bare POST /mcp with no workspace returns 404 — connect one client per workspace.

Request Pattern

POST https://<your-host>/<workspace>/mcp
Authorization: Bearer inmcp_<id>.<secret>
Content-Type: application/json

Before You Connect

  1. Enable MCP in the dashboard: Settings -> MCP Server -> Enable MCP.
  2. Create a token in the same settings page, and choose its tool access: all tools, or only the scopes the client needs.
  3. Copy the token immediately. Classifyre returns plaintext only once.

If MCP is disabled, POST /<workspace>/mcp returns 503.

How Tokens Are Created

Classifyre exposes MCP token lifecycle endpoints under <workspace>/instance-settings/mcp/tokens.

Create Token

curl -X POST https://<your-host>/<workspace>/instance-settings/mcp/tokens \
  -H "Content-Type: application/json" \
  -d '{"name":"Cursor local workspace"}'

Response includes:

  • plainTextToken: full bearer token (one-time only)
  • tokenPreview: masked preview stored for later display
  • token metadata (id, name, toolGroupIds, timestamps, active status)

List Tokens

curl https://<your-host>/<workspace>/instance-settings/mcp/tokens

Only masked previews are returned. Raw token values are never returned again.

Revoke / Reactivate Token

curl -X PATCH https://<your-host>/<workspace>/instance-settings/mcp/tokens/<token-id> \
  -H "Content-Type: application/json" \
  -d '{"isActive":false}'

Delete Token

curl -X DELETE https://<your-host>/<workspace>/instance-settings/mcp/tokens/<token-id>

Choose What a Token Can Do (scopes)

Every token has tool access: either all tools, or a list of capability groups (scopes). A client connected with a scoped token sees and can call only the tools of those groups — the rest are left out of tools/list and refused.

In the app: Settings -> MCP Server -> Create token, switch All tools off and tick the groups the client needs. Each group shows how many tools it turns on; hover it to see their names. Edit changes the scope of an existing token; the client picks it up on its next request.

Over the API: send toolGroupIds with the ids from the table below (an unknown id is refused with 400). null or no toolGroupIds means all tools, including groups added in later versions.

curl -X POST https://<your-host>/<workspace>/instance-settings/mcp/tokens \
  -H "Content-Type: application/json" \
  -d '{"name":"Claude, case work","toolGroupIds":["cases","case_board","findings","assets"]}'
 
curl -X PATCH https://<your-host>/<workspace>/instance-settings/mcp/tokens/<token-id> \
  -H "Content-Type: application/json" \
  -d '{"toolGroupIds":["cases","case_board"]}'
ScopetoolGroupIds idWhat it enablesTools
SourcessourcesCreate, validate, update, delete, inspect, and run ingestion sources.10
Custom Detectorscustom_detectorsManage regex, classifier, entity, AI, decision and code detectors and train them on feedback. Writing a code detector (CODE_DETECTOR) also needs custom_source_code.14
RunsrunsInspect ingestion runs, stop stuck runs, and fetch logs for debugging.5
FindingsfindingsSearch, inspect, and update findings including bulk status changes.12
AssetsassetsInspect assets, search asset inventory, and link findings back to content.4
InquiriesinquiriesCreate and manage saved questions — standing finding queries that surface new matches over time.10
CasescasesInvestigation 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.30
Case Boardcase_boardThe 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.8
AI AutopilotautopilotObserve and control the autonomous agents: runs, decisions, memory, per-agent enable/disable, and manual triggers.10
CorrelationcorrelationTune and inspect deterministic asset correlation (evidence fingerprints and duplicate detection).6
Custom Source Codecustom_source_codeAuthor, run, and debug Python notebooks: the connector behind a CUSTOM source, and code detectors (CODE_DETECTOR) -- cells, packages, local folders, uploaded files, and executions.18
ExtractionsextractionsInspect structured field extractions and how much of a run’s content was actually covered.4
Case Leadscase_leadsAI-proposed next steps for a case: generate, review, and track supporting events.7
GlossaryglossaryThe shared vocabulary of concepts and entities: list, look up and curate terms, schemes and relations; bind detector outputs to meaning; read the semantic links (Meaning) derived from findings; review proposals and see the semantic map.31
EntitiesentitiesThe named things findings refer to — people, organisations, accounts: search them, read where each is mentioned and with whom, create one from a finding, review candidate values and merge duplicates.9
LineagelineageTrace how data flows between assets via the stitched edge graph, and look up what a relation type means.2

Scopes that work together. Most scopes read better with a neighbour: the Case Board tools need a case id, which Cases finds (search_cases); putting findings on a board goes better with Findings and Assets to search for them. A scope never grants anything outside its own tools.

Case Board is its own scope since v0.6.3. Its tools used to belong to Cases. Tokens that had Cases were given Case Board as well when they upgraded, so nothing they could do before went away; a token created afterwards needs Case Board ticked to arrange boards.

Token Format and Validation

  • Prefix: inmcp
  • Format: inmcp_<uuid>.<secret>
  • Secret is generated by the API (crypto.randomBytes(32) in current implementation).
  • Tokens are stored hashed (HMAC-SHA256) and verified with constant-time comparison.

Client Configuration

Replace:

  • <your-host> with your API host
  • <workspace> with the workspace (namespace) slug you want the client to see — the exact URL is shown in Settings -> MCP Server
  • inmcp_<id>.<secret> with your token

Claude Desktop

Docs

Config file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\\Claude\\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "classifyre-mcp": {
      "type": "http",
      "url": "https://<your-host>/<workspace>/mcp",
      "headers": {
        "Authorization": "Bearer inmcp_<id>.<secret>"
      }
    }
  }
}

Claude Code

Docs

claude mcp add --transport http classifyre-mcp \
  --url https://<your-host>/<workspace>/mcp \
  --header "Authorization: Bearer inmcp_<id>.<secret>"

Cursor

Docs

{
  "mcpServers": {
    "classifyre-mcp": {
      "type": "http",
      "url": "https://<your-host>/<workspace>/mcp",
      "headers": {
        "Authorization": "Bearer inmcp_<id>.<secret>"
      }
    }
  }
}

OpenAI Codex CLI

Docs

~/.codex/config.json (or project codex.json):

{
  "mcpServers": {
    "classifyre-mcp": {
      "type": "http",
      "url": "https://<your-host>/<workspace>/mcp",
      "headers": {
        "Authorization": "Bearer inmcp_<id>.<secret>"
      }
    }
  }
}

Run:

codex --mcp-server classifyre-mcp

Gemini CLI

Docs

~/.gemini/settings.json:

{
  "mcpServers": {
    "classifyre-mcp": {
      "httpUrl": "https://<your-host>/<workspace>/mcp",
      "headers": {
        "Authorization": "Bearer inmcp_<id>.<secret>"
      }
    }
  }
}

Windsurf (Codeium)

Docs

~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "classifyre-mcp": {
      "serverUrl": "https://<your-host>/<workspace>/mcp",
      "headers": {
        "Authorization": "Bearer inmcp_<id>.<secret>"
      }
    }
  }
}

VS Code (Copilot)

Docs

.vscode/mcp.json:

{
  "servers": {
    "classifyre-mcp": {
      "type": "http",
      "url": "https://<your-host>/<workspace>/mcp",
      "headers": {
        "Authorization": "Bearer inmcp_<id>.<secret>"
      }
    }
  }
}

Continue Extension

Docs

~/.continue/config.json:

{
  "experimental": {
    "modelContextProtocolServers": [
      {
        "transport": {
          "type": "http",
          "url": "https://<your-host>/<workspace>/mcp",
          "headers": {
            "Authorization": "Bearer inmcp_<id>.<secret>"
          }
        }
      }
    ]
  }
}

Direct API Usage

curl -X POST https://<your-host>/<workspace>/mcp \
  -H "Authorization: Bearer inmcp_<id>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {}
  }'

Classifyre MCP Capability Map

Generated from the tools the server registers (the same source as the tool catalog under Settings -> MCP Server), so it always matches what a client sees. read-only tools never change anything; destructive tools can take things away (clients usually ask before calling them); idempotent tools can be repeated safely. Open a tool for its parameters (* = required).

Sources sources

Create, validate, update, delete, inspect, and run ingestion sources.

list_source_typesread-onlyList every source type that can be created (type id + label). Call this before create_source when unsure of the exact type id.

No parameters.

get_source_schemaread-onlyThe full JSON Schema for one source type config — exact field names, required/masked/optional sections, defaults and enums. Call this before create_source/update_source and build the config to match.
type*stringSource type id from list_source_types, e.g. POSTGRESQL
search_sourcesread-onlySearch and filter sources with latest runner summaries.
filtersobjectSource filters. Omit entirely to match all sources.
filters.searchstringFilter by source name (case-insensitive contains).
filters.typestring[]Filter by source type id (e.g. POSTGRESQL, CONFLUENCE). See list_source_types.
filters.statusenum[]Filter by the status of each source’s latest run. One of: PENDING, RUNNING, COMPLETED, WARNING, ERROR, STOPPED
pageobjectPagination and sorting.
page.skipintegerOffset. Defaults to 0.
page.limitintegerMax results (1–200). Defaults to 25.
page.sortByenumSort field. Defaults to CREATED_AT. One of: NAME, TYPE, STATUS, CREATED_AT, UPDATED_AT, LAST_RUN_AT
page.sortOrderenumDefaults to DESC. One of: ASC, DESC
get_sourceread-onlyFetch a single source by ID. A CUSTOM source's notebook cells are summarised rather than inlined — the whole connector program would otherwise land in context on every read. Pass include_notebook when you actually need the code, or use get_notebook.
id*string (uuid)—
include_notebookbooleanInline the notebook cells instead of just { revision, cellCount }
create_sourcewritesCreate a new source after validating the config against the source JSON Schema.
type*string—
namestring—
config*object—
scheduleEnabledboolean—
scheduleCronstring—
scheduleTimezonestring—
update_sourcewritesUpdate a source and optionally its schedule. Source configs are revalidated before save.
id*string (uuid)—
typestring—
namestring—
configobject—
scheduleEnabledboolean—
scheduleCronstring—
scheduleTimezonestring—
delete_sourcedestructiveDelete a source and its associated schedules and data.
id*string (uuid)—
test_source_connectionread-onlyRun a lightweight connectivity test for a source.
id*string (uuid)—
start_source_runwritesTrigger a new ingestion run for a source.
sourceId*string (uuid)—
triggerTypeenumOne of: MANUAL, SCHEDULED, WEBHOOK, API
triggeredBystring—
validate_source_configread-onlyDry-run validate a source config against its JSON Schema without creating anything. Returns normalized config on success or a list of validation errors on failure.
type*string—
config*unknown—

Custom Detectors custom_detectors

Manage regex, classifier, entity, AI, decision and code detectors and train them on feedback. Writing a code detector (CODE_DETECTOR) also needs custom_source_code.

list_custom_detectorsread-onlyList custom detectors and usage statistics.
includeInactiveboolean—
get_custom_detectorread-onlyFetch a single custom detector.
id*string (uuid)—
list_custom_detector_examplesread-onlyReturn starter examples for every engine: rulesets, classifiers, entity detectors, AI (LLM) detectors, decision detectors (DECISION) and code detectors (CODE_DETECTOR). Code-detector templates include their notebook and ready-made testScenarios -- copy both when creating one.

No parameters.

create_custom_detectorwritesCreate a custom detector. The pipeline_schema.type selects the engine: GLINER2 (default), REGEX, LLM (AI), DECISION, TEXT_CLASSIFICATION, IMAGE_CLASSIFICATION, OBJECT_DETECTION, TAG or CODE_DETECTOR. GLiNER2 needs at least one entity or classification task. LLM detectors require aiProviderConfigId and a system_prompt. TAG is a placeholder that runs nothing: it exists so a CUSTOM connector notebook can assert a fact it already knows with Asset(tags={"<key>": "<value>"}), and it is not selectable on a source. DECISION asks a decision model typed questions and gets a probability back instead of text: yes_no (probability that the answer is yes; OpenAI "predicate", System One "noul"), choice (one of 2-255 options), score (a level on a 2-10 step scale, options ordered lowest first). Choose it when the answer is a judgement with a fixed shape -- is this a complaint, which team owns it, how severe is it -- and no text needs to come back; use LLM when you need extracted fields or free text. model is a LiteLLM name "<provider>/<model>" (typesafe/jev-latest, openai/gpt-6-luna, cloudflare/clef, perplexity/..., openrouter/..., strands_decider/<model> for any self-hosted /v1/systemone server); api_base is needed for self-hosted servers and Cloudflare. The provider key is secrets.api_key: write-only, encrypted, returned only as secretKeys; on update `secrets` is a patch (omit it to keep the stored key, null removes it). The stored key is bound to its provider and api_base: change either and the key is dropped unless the same update sends it again. It takes no aiProviderConfigId. Findings: a yes_no question yields finding type <name> when the probability reaches its threshold (default 0.5); choice and score yield <name>:<option label> with the model confidence, unless that option has report: false. Severity resolves option -> question -> detector. Ask about something observable, one thing per question, and give choice questions a catch-all option with report: false. CODE_DETECTOR is a code detector: a Python notebook defining detect(asset, ctx) that yields Finding(label, value, severity=, location={row, column_name, line, ...}, fields={...}, identity=, normalized_value=). Choose it when the rule is a CHECK no pattern or model expresses: totals that must add up (asset.rows()), a value compared with metadata or a threshold (asset.metadata), list screening against an uploaded file (ctx.file), co-occurrence of other detectors' findings (needs_findings: true, asset.findings), checksummed identifiers, or scoring with your own model. Prefer REGEX for a token pattern, GLINER2/LLM for meaning in prose, and TAG only when a CUSTOM connector already knows the fact. Rules: severity is a ceiling; give every finding a stable identity (row id, list entry) so re-runs update it instead of churning; set normalized_value only when the value should link assets in the value index; declare every key of fields; set deterministic: false if the verdict depends on anything outside the asset. Workflow: list_custom_detector_examples (copy a CODE_DETECTOR template and its testScenarios) -> create_custom_detector -> upload_custom_detector_file if the rule reads one -> run_custom_detector_notebook mode "preview_detect" on a real asset -> create_detector_test_scenario (input_asset fixtures) and run_detector_tests -> attach it to a source. Writing one needs the custom_source_code capability group.
keystring—
name*string—
descriptionstring—
aiProviderConfigIdstring (uuid)AI provider credential ID. Required for LLM (AI) detectors.
pipeline_schema*objectPipeline schema. GLiNER2 example: { type: "GLINER2", entities: { order_id: { description: "Order ID like ORD-123", required: true } }, classification: { intent: { labels: ["refund", "bug"], multi_label: false } } }. LLM (AI) example: { type: "LLM", system_prompt: "Classify the sentiment of the text.", labels: [{ name: "good" }, { name: "bad" }, { name: "violent" }], severity_map: [{ pattern: "violent", severity: "critical" }], output_fields: [{ name: "language", type: "string" }] }. TAG example: { type: "TAG", label: "Cardholder data", severity: "high" }. DECISION example: { type: "DECISION", model: "typesafe/jev-latest", secrets: { api_key: "<provider key>" }, severity: "medium", questions: [{ name: "refund_request", type: "yes_no", instructions: "Is the customer asking for a refund?" }, { name: "urgency", type: "choice", instructions: "How urgent is this?", options: [{ label: "low", description: "can wait a week", report: false }, { label: "high", description: "needs action today", severity: "high" }] }, { name: "frustration", type: "score", instructions: "How frustrated is the customer?", options: [{ label: "calm", report: false }, { label: "annoyed", severity: "low" }, { label: "angry", severity: "high" }] }] }. CODE_DETECTOR example: { type: "CODE_DETECTOR", notebook: { cells: [{ id: "c1", type: "code", source: "def detect(asset, ctx):\n for i, row in enumerate(asset.rows()):\n if row['total'] != row['a'] + row['b']:\n yield Finding(label='total_mismatch', value=str(row['total']), identity=f'row-{i}', location={'row': i})\n" }] }, severity: "high", category: "QUALITY", fields: [{ name: "expected", type: "number" }], variables: {}, secrets: {}, needs_findings: false }
isActiveboolean—
update_custom_detectorwritesUpdate detector metadata, pipeline schema, AI provider credential, or activation status. For a CODE_DETECTOR, send the whole pipeline_schema (notebook included); `secrets` is a patch -- a string sets a key, null deletes it, and omitting `secrets` keeps every stored secret (values are never returned, only secretKeys). Saving a changed notebook bumps notebook.revision. A DECISION detector follows the same `secrets` patch rule for its api_key: send the whole pipeline_schema and leave `secrets` out to keep the stored key.
id*string (uuid)—
keystring—
namestring—
descriptionstring | null—
aiProviderConfigIdstring (uuid)AI provider credential ID. Required for LLM (AI) detectors.
pipeline_schemaobjectUpdated pipeline schema (any supported type).
isActiveboolean—
delete_custom_detectordestructiveDelete a custom detector.
id*string (uuid)—
retire_out_of_scope_findingsdestructiveResolve OPEN findings a custom detector can no longer produce because its scope.asset_kinds was narrowed or regex patterns were removed — a rescan never resolves those. Two steps, each a background operation (follow the returned id with get_findings_bulk_operation): (1) dryRun (default) changes nothing and reports, under counts: candidates, byReason, citedByCase, watchedByInquiries, inquiries, wouldRetire and notProvable. (2) dryRun: false with fromOperationId (that COMPLETED dry run), expectedCount (its wouldRetire) and confirm: true. It never retires more than expectedCount. Findings a case cites or an ACTIVE inquiry watches are never retired by this tool. Retired findings are RESOLVED without detector feedback; widening the scope again lets re-detection reopen them.
customDetectorId*string (uuid)—
dryRunbooleanDefault true: count only.
sourceIdsstring[]Dry run only: restrict to these sources.
fromOperationIdstring (uuid)Retire only: the completed dry run operation id.
expectedCountintegerRetire only: the dry run's counts.wouldRetire.
confirmbooleanRetire only: required.
train_custom_detectorwritesTrigger custom detector training, optionally scoped to a single source.
id*string (uuid)—
sourceIdstring (uuid)—
get_custom_detector_training_historyread-onlyList recent training runs for a detector.
id*string (uuid)—
takeinteger—
validate_detector_configread-onlyDry-run validate a custom detector pipeline schema against both the JSON Schema and the detector-specific validation rules, without creating anything.
pipelineSchema*object—
list_detector_test_scenariosread-onlyList all test scenarios for a custom detector.
detector_id*stringCustom detector ID
create_detector_test_scenariowritesCreate a test scenario for a custom detector. Expected outcome shapes by detector type: REGEX/RULESET {"shouldMatch": true|false}; classifier/LLM {"label": "...", "minConfidence": 0.6}; entity {"entities": [{"label": "PersonName", "text": "Ostap"}]}. The nested pipeline-output shape ({"classification": {task: {label, confidence}}} / {"entities": {label: [{value}]}}) is also accepted. Labels compare case-insensitively with underscores treated as spaces, so "market_gaming_instruction" matches "Market gaming instruction". DECISION detectors: expected {"label": "<question name>"} for a yes_no question or {"label": "<question name>:<option label>"} for choice and score, with an optional "minConfidence". CODE_DETECTOR (code) detectors: expected {"findings": [{"label": "total_mismatch", "identity": "row-2", "severity": "high", "count": 1}], "match": "subset"|"exact"} or {"shouldMatch": false}; and instead of input_text they may take input_asset, a whole asset: {"name": "t.csv", "kind": "table", "mime_type": "text/csv", "metadata": {...}, "rows": [{...}], "pages": ["..."], "text": "..."}.
detector_id*string—
name*stringShort scenario name
descriptionstring—
input_textstringText to test against the detector (or give input_asset)
input_assetobjectCODE_DETECTOR only: an asset fixture { name, kind?, mime_type?, metadata?, text?, pages?, rows? }
expected_outcome*objectExpected outcome — see tool description for the per-detector-type shapes
run_detector_testswritesRun test scenarios for a custom detector and return a pass/fail matrix. Pass scenario_ids to re-run only specific scenarios (avoids re-running every scenario — each LLM-detector scenario costs a model call); omit to run all. FAIL results include an expected-vs-actual explanation in errorMessage.
detector_id*string—
scenario_idsstring[]Optional scenario IDs to run; omit to run every scenario for the detector
delete_detector_test_scenariodestructiveDelete a test scenario (and its past results) from a custom detector.
detector_id*stringCustom detector ID
scenario_id*stringTest scenario ID to delete

Runs runs

Inspect ingestion runs, stop stuck runs, and fetch logs for debugging.

search_runsread-onlySearch runner history with filters and pagination.
filtersobjectRun (runner) filters. Omit entirely to match all runs.
filters.searchstringCase-insensitive text search across runner id, source name/type, triggeredBy, and error message.
filters.sourceIdstring[]Restrict to these source UUIDs.
filters.sourceTypestring[]Restrict to these source type ids. See list_source_types.
filters.statusenum[]Restrict to runs in these statuses. One of: PENDING, RUNNING, COMPLETED, WARNING, ERROR, STOPPED
filters.triggerTypeenum[]Restrict to runs started by these trigger types. One of: MANUAL, SCHEDULED, WEBHOOK, API, AUTOPILOT
filters.triggeredBystring[]Restrict to runs started by these actors.
filters.triggeredAfterstringISO-8601 timestamp. Only runs triggered at or after this.
filters.triggeredBeforestringISO-8601 timestamp. Only runs triggered at or before this.
pageobjectPagination and sorting.
page.skipintegerOffset. Defaults to 0.
page.limitintegerMax results (1–200). Defaults to 50.
page.sortByenumSort field. Defaults to TRIGGERED_AT. One of: TRIGGERED_AT, STATUS, SOURCE_NAME, DURATION_MS, TOTAL_FINDINGS
page.sortOrderenumDefaults to DESC. One of: ASC, DESC
get_runread-onlyFetch a single runner record.
runnerId*string (uuid)—
get_run_logsread-onlyFetch paginated runner logs for debugging.
runnerId*string (uuid)—
cursorstring—
takeinteger—
stop_rundestructiveStop a running scan or cancel a queued (PENDING) one. Both end STOPPED.
runnerId*string (uuid)—
list_source_runsread-onlyList runs for a single source.
sourceId*string (uuid)—
statusenumFilter to runs in this status. One of: PENDING, RUNNING, COMPLETED, WARNING, ERROR, STOPPED
skipintegerOffset. Defaults to 0.
takeintegerMax results (1–200).

Findings findings

Search, inspect, and update findings including bulk status changes.

search_findingsread-onlySearch findings using filters, text search, and pagination.
filtersobjectFinding filters. Omit entirely to match all findings. Unknown keys are rejected.
filters.searchstringCase-insensitive text search across finding fields and related asset/source names.
filters.sourceIdstring[]Restrict to these source UUIDs.
filters.assetIdstring[]Restrict to these asset UUIDs.
filters.runnerIdstring[]Restrict to findings produced by these run (runner) UUIDs.
filters.detectorTypeenum[]Restrict to these built-in detector types. Use CUSTOM for custom detectors (optionally narrow with customDetectorKey). One of: SECRETS, PII, YARA, BROKEN_LINKS, CODE_SECURITY, CUSTOM
filters.customDetectorKeystring[]Restrict to findings from these custom detector keys.
filters.findingTypestring[]Restrict to these finding type identifiers (e.g. specific secret or PII types).
filters.categorystring[]Restrict to these finding categories.
filters.severityenum[]Restrict to these severities. One of: CRITICAL, HIGH, MEDIUM, LOW, INFO
filters.statusenum[]Restrict to these statuses. Without it, RESOLVED findings are excluded unless includeResolved is true. One of: OPEN, FALSE_POSITIVE, RESOLVED, IGNORED
filters.includeResolvedbooleanInclude RESOLVED findings when no status is given. Defaults to false.
filters.detectionIdentitystring[]Restrict to these detection identities.
filters.firstDetectedAfterstringISO-8601 timestamp. Only findings first detected at or after this.
filters.lastDetectedBeforestringISO-8601 timestamp. Only findings last detected at or before this.
filters.excludeIdsstring[]Leave out these finding ids.
filters.termstring[]Glossary term keys: only findings that are evidence of one of these terms (an APPROVED binding of their detector output, a manual link, or — for an entity — a mention of one of its confirmed values). Prefer this over regex on values when a term exists. Unknown keys are rejected.
filters.includeNarrowerbooleanWith term: include the narrower concepts too.
filters.meaningMethodenum[]With term: only evidence by these methods. One of: BINDING, MANUAL, MENTION
pageobjectPagination.
page.skipintegerNumber of results to skip (offset). Defaults to 0.
page.limitintegerMax results to return (1–200). Defaults to 50.
semantic_querystringNatural-language query. Uses hybrid lexical + semantic ranking and returns score reasons.
semantic_modeenumSemantic ranking mode. Defaults to hybrid. One of: hybrid, vector, off
rankingenumCorpus browsing order when semantic_query is omitted. Defaults to importance. One of: importance, newest, severity
get_findingread-onlyFetch a single finding with asset and source context.
id*string (uuid)—
update_findingwritesUpdate finding status, severity, or resolution context for a single finding.
id*string (uuid)—
statusenumOne of: OPEN, RESOLVED, FALSE_POSITIVE, IGNORED
severityenumOne of: CRITICAL, HIGH, MEDIUM, LOW, INFO
changeReasonstring—
commentstring—
bulk_update_findingsdestructiveBulk update findings by IDs (at most 1,000) or by filters, including status, severity, and comment. Filters use the same keys as search_findings; an unknown key is an error, never a wider match. Run with dryRun: true first — it returns the exact count (wouldUpdate) and whether the filters narrow the corpus — then pass that count as expectedCount: if more findings match when the update runs, nothing is written. Filters that narrow nothing beyond status require confirm: true. A selection over 2,000 findings is queued as a background operation: the result carries operationId — follow it with get_findings_bulk_operation.
idsstring[]—
filtersobjectFinding filters. Omit entirely to match all findings. Unknown keys are rejected.
filters.searchstringCase-insensitive text search across finding fields and related asset/source names.
filters.sourceIdstring[]Restrict to these source UUIDs.
filters.assetIdstring[]Restrict to these asset UUIDs.
filters.runnerIdstring[]Restrict to findings produced by these run (runner) UUIDs.
filters.detectorTypeenum[]Restrict to these built-in detector types. Use CUSTOM for custom detectors (optionally narrow with customDetectorKey). One of: SECRETS, PII, YARA, BROKEN_LINKS, CODE_SECURITY, CUSTOM
filters.customDetectorKeystring[]Restrict to findings from these custom detector keys.
filters.findingTypestring[]Restrict to these finding type identifiers (e.g. specific secret or PII types).
filters.categorystring[]Restrict to these finding categories.
filters.severityenum[]Restrict to these severities. One of: CRITICAL, HIGH, MEDIUM, LOW, INFO
filters.statusenum[]Restrict to these statuses. Without it, RESOLVED findings are excluded unless includeResolved is true. One of: OPEN, FALSE_POSITIVE, RESOLVED, IGNORED
filters.includeResolvedbooleanInclude RESOLVED findings when no status is given. Defaults to false.
filters.detectionIdentitystring[]Restrict to these detection identities.
filters.firstDetectedAfterstringISO-8601 timestamp. Only findings first detected at or after this.
filters.lastDetectedBeforestringISO-8601 timestamp. Only findings last detected at or before this.
filters.excludeIdsstring[]Leave out these finding ids.
filters.termstring[]Glossary term keys: only findings that are evidence of one of these terms (an APPROVED binding of their detector output, a manual link, or — for an entity — a mention of one of its confirmed values). Prefer this over regex on values when a term exists. Unknown keys are rejected.
filters.includeNarrowerbooleanWith term: include the narrower concepts too.
filters.meaningMethodenum[]With term: only evidence by these methods. One of: BINDING, MANUAL, MENTION
statusenumOne of: OPEN, RESOLVED, FALSE_POSITIVE, IGNORED
severityenumOne of: CRITICAL, HIGH, MEDIUM, LOW, INFO
commentstring—
dryRunbooleanCount what would be updated without writing anything.
expectedCountintegerThe dryRun count. If more findings match at update time, the update is refused (409) and nothing is written.
confirmbooleanRequired (true) when the filters narrow nothing beyond status, includeResolved and excludeIds — the update would apply to every finding in the namespace.
get_findings_bulk_operationread-onlyProgress of a background bulk finding operation — the operationId bulk_update_findings returns when a selection is too large to change in one request, and the id retire_out_of_scope_findings returns for its dry run and its retire. Reports status (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED), how many findings were examined, changed and exempted, percent, and operation-specific counts (a retire dry run puts wouldRetire and its exemptions there).
operationId*string (uuid)—
cancel_findings_bulk_operationwritesidempotentStop a background bulk finding operation. A queued one is cancelled outright; a running one stops after its current page. Findings it already changed stay changed — this stops the operation, it does not undo it.
operationId*string (uuid)—
get_findings_discoveryread-onlyReturn discovery totals, review-state mix, activity, and top assets for findings. Severity is a priority level, not a threat level — it says how much a finding matters, not how dangerous it is.
windowDaysunknown—
includeResolvedboolean—
purge_source_findingsdestructivePermanently delete EVERY finding of a source — all statuses, including resolved and false-positive. Irreversible; finding history is lost. Case evidence snapshots survive by design, and correlation fingerprints are recomputed in the background afterwards. Useful when iterating on detector configurations and the accumulated findings are pure noise. To clean up only findings from removed/disabled detectors, rely instead on the cleanup_removed_detector_findings source option (default on) and rescan.
source_id*stringSource whose findings to purge
confirm*booleanMust be true — acknowledges the purge is irreversible
purge_source_assetsdestructivePermanently delete assets of a source, and with them every finding, extraction, correlation value and chunk derived from those assets. Heavier than purge_source_findings: it removes the ingested material itself, so the source must be re-scanned before anything from it can be examined again. Correlation fingerprints are recomputed afterwards. With no filter this deletes EVERY asset of the source. The filters exist for the case a scan can never resolve on its own: when a connector narrows its scope, the assets it used to produce are stranded, because retirement requires a run that saw the whole scope and found them gone — and a rotating-sample connector never has one. ALWAYS call once with dry_run first and report what it matched before deleting.
source_id*stringSource whose assets to purge
confirm*booleanMust be true — acknowledges the purge is irreversible
external_id_prefixstringOnly assets whose connector-assigned id (metadata.external_id) starts with this, e.g. 'fin-'
asset_kindstringOnly assets of this catalog kind: record | document | page | file | table
name_prefixstringOnly assets whose display name starts with this
urn_prefixstringOnly assets whose URN starts with this
not_scanned_sincestringISO-8601 timestamp; only assets last scanned before it, plus assets never scanned
dry_runbooleanReport what matches and delete nothing
find_similar_findingsread-onlyReturn semantic neighbours for one finding, including similarity, duplicate/noise signals, and ranking explanations.
findingId*string (uuid)—
limitinteger—
find_boilerplate_clustersread-onlyFind repeated or near-duplicate finding groups, ordered by cluster size. Omit sourceIds to scan the whole corpus — clusters spanning multiple sources (sourceCount > 1) are the same content circulating between systems, which can itself be a lead. Use this to separate bulk boilerplate from distinctive evidence.
sourceIdsstring[]—
thresholdnumber—
limitinteger—
explain_findingread-onlyOne-call evidence explanation for a finding: importance/quality ranking with its reasons and raw signals, duplicate-group size, top semantic neighbours, and the detector severity/confidence kept as a separate axis. Use before escalating or attaching a finding as case evidence. Similarity and importance are triage signals, not proof.
id*string (uuid)—

Assets assets

Inspect assets, search asset inventory, and link findings back to content.

search_assetsread-onlySearch assets and their nested findings. Narrow by asset attributes (assets), by the findings attached to each asset (findings), or both.
assetsobjectAsset-level filters. Omit entirely to match all assets.
assets.searchstringFilter by asset name (case-insensitive contains).
assets.sourceIdstringRestrict to a single source UUID.
assets.runnerIdstringRestrict to a single run UUID.
assets.statusenum[]Restrict to assets in these ingestion statuses. One of: NEW, UPDATED, UNCHANGED, DELETED
assets.sourceTypesstring[]Restrict to these source type ids. See list_source_types.
assets.metadataobjectMatch connector-authored metadata exactly, e.g. {"legal_form_code": {"in": ["GES", "AG"]}}. Values compare as JSON: a number matches only a number. Keys may be nested with dots.
assets.termstring[]Glossary term keys: assets with a current semantic link to one of these concepts (any method). Unknown keys are rejected.
assets.includeNarrowerbooleanWith term: include the narrower concepts too.
assets.meaningMethodenum[]With term: only links by these methods. One of: BINDING, DECLARED, MANUAL, SUGGESTED, MENTION
findingsobjectFinding-level filters — narrow assets by the findings attached to them. Omit to ignore findings when filtering.
findings.detectorTypeenum[]Only assets with findings from these detector types. One of: SECRETS, PII, YARA, BROKEN_LINKS, CODE_SECURITY, CUSTOM
findings.customDetectorKeystring[]Only assets with findings from these custom detector keys.
findings.runnerIdstring[]Only findings produced by these run UUIDs.
findings.findingTypestring[]Only assets with these finding types.
findings.categorystring[]Only assets with findings in these categories.
findings.severityenum[]Only assets with findings of these severities. One of: CRITICAL, HIGH, MEDIUM, LOW, INFO
findings.statusenum[]Only assets with findings in these statuses. One of: OPEN, FALSE_POSITIVE, RESOLVED, IGNORED
findings.includeResolvedbooleanInclude RESOLVED/IGNORED findings when matching. Default false.
pageobjectPagination and sorting.
page.skipintegerOffset. Defaults to 0.
page.limitintegerMax results (1–200). Defaults to 50.
page.sortByenumSort field. Defaults to LAST_SCANNED_AT. One of: NAME, SOURCE_ID, ASSET_TYPE, STATUS, LAST_SCANNED_AT, UPDATED_AT, CREATED_AT
page.sortOrderenumDefaults to DESC. One of: ASC, DESC
optionsobjectResult-shaping options.
options.excludeFindingsbooleanSkip the findings join and return assets with empty findings arrays. Default false.
options.includeAssetsWithoutFindingsbooleanInclude assets even if they have no findings matching the finding filters. Default false.
semantic_querystringMeaning-based query over extracted asset text chunks.
semantic_modeenumHybrid combines asset-name and semantic rank. Defaults to hybrid. One of: off, hybrid, vector
get_assetread-onlyFetch a single asset by ID.
id*string (uuid)—
list_source_assetsread-onlyList assets belonging to a single source.
sourceId*string (uuid)—
skipinteger—
takeinteger—
assetTypeenumOne of: TXT, IMAGE, VIDEO, AUDIO, URL, TABLE, BINARY, OTHER
statusenumOne of: NEW, UPDATED, UNCHANGED
list_asset_finding_summariesread-onlyReturn asset-level finding summaries and rollups for remediation workflows.
sourceIdstring (uuid)—
assetIdstring (uuid)—
runnerIdstring (uuid)—
detectorTypestring—
findingTypestring—
severitystring—
statusstring—
includeResolvedboolean—
sortenumOne of: LATEST, MOST_FINDINGS, HIGHEST_SEVERITY
skipinteger—
limitinteger—

Inquiries inquiries

Create and manage saved questions — standing finding queries that surface new matches over time.

list_inquiriesread-onlyList saved questions (standing finding queries) with pagination and filters.
searchstring—
statusenum[]One of: ACTIVE, ARCHIVED
caseIdstringFilter to a linked case, or "none" for unlinked
skipinteger—
limitinteger—
get_inquiryread-onlyFetch a single saved question by ID.
id*string (uuid)—
create_inquirywritesCreate a saved question (a standing finding query). Matches are computed immediately.
title*string—
descriptionstring—
createdBystring—
matchAllSourcesbooleanMatch findings from any source (ignores sourceIds)
sourceIdsstring[]—
detectorTypesenum[]Empty = any detector One of: SECRETS, PII, YARA, BROKEN_LINKS, CODE_SECURITY, CUSTOM
customDetectorKeysstring[]—
findingTypesstring[]—
findingTypeRegexstring[]—
findingValueRegexstring[]—
termKeysstring[]Glossary term keys: the finding must be evidence of one of these concepts (an APPROVED binding of its output, or a manual link). Prefer this over a value regex when a concept exists. Unknown keys are rejected.
termsIncludeNarrowerbooleanInclude the narrower concepts of termKeys.
update_inquirywritesUpdate a saved question. Matches are recomputed if any matcher field is provided.
id*string (uuid)—
titlestring—
descriptionstring—
statusenumOne of: ACTIVE, ARCHIVED
aiModeenumOne of: INHERIT, MANAGED, OBSERVE_ONLY
matchAllSourcesbooleanMatch findings from any source (ignores sourceIds)
sourceIdsstring[]—
detectorTypesenum[]Empty = any detector One of: SECRETS, PII, YARA, BROKEN_LINKS, CODE_SECURITY, CUSTOM
customDetectorKeysstring[]—
findingTypesstring[]—
findingTypeRegexstring[]—
findingValueRegexstring[]—
termKeysstring[]Glossary term keys: the finding must be evidence of one of these concepts (an APPROVED binding of its output, or a manual link). Prefer this over a value regex when a concept exists. Unknown keys are rejected.
termsIncludeNarrowerbooleanInclude the narrower concepts of termKeys.
delete_inquirydestructiveDelete a saved question.
id*string (uuid)—
list_inquiry_matchesread-onlyFindings currently matching a saved question (live query, never persisted). NEW means the latest completed run of the finding's source created it; GONE means that run retired it, so it answers the question but no longer exists. Defaults to NEW and ONGOING — ask for GONE by name.
id*string (uuid)—
searchstring—
severityenum[]One of: CRITICAL, HIGH, MEDIUM, LOW, INFO
stateenum[]One of: NEW, ONGOING, GONE
onlyNewboolean—
skipinteger—
limitinteger—
get_inquiry_timelineread-onlyA saved question's own history: matcher changes, and what each run landed or retired. The durable record — a match's NEW state is live and expires the next time its source runs, but the run that produced it stays here.
id*string (uuid)—
cursorstring—
limitinteger—
rematch_inquirywritesRecompute the persisted match count for a saved question on demand.
id*string (uuid)—
preview_inquiry_matchersread-onlyPreview what a matcher configuration currently selects, before saving a question.
matchAllSourcesbooleanMatch findings from any source (ignores sourceIds)
sourceIdsstring[]—
detectorTypesenum[]Empty = any detector One of: SECRETS, PII, YARA, BROKEN_LINKS, CODE_SECURITY, CUSTOM
customDetectorKeysstring[]—
findingTypesstring[]—
findingTypeRegexstring[]—
findingValueRegexstring[]—
termKeysstring[]Glossary term keys: the finding must be evidence of one of these concepts (an APPROVED binding of its output, or a manual link). Prefer this over a value regex when a concept exists. Unknown keys are rejected.
termsIncludeNarrowerbooleanInclude the narrower concepts of termKeys.
get_inquiry_match_optionsread-onlyFilter options for building a question: sources, custom detectors, and distinct finding types.
sourceIdsstring[]Scope finding type counts to these sources

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.
searchstring—
statusenum[]One of: OPEN, IN_PROGRESS, CLOSED, ARCHIVED
severityenum[]One of: CRITICAL, HIGH, MEDIUM, LOW, INFO
skipinteger—
limitinteger—
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—
descriptionstring—
statusenumOne of: OPEN, IN_PROGRESS, CLOSED, ARCHIVED
severityenumOne of: CRITICAL, HIGH, MEDIUM, LOW, INFO
assigneestring—
createdBystring—
inquiryIdsstring[]—
removeGoneFindingsbooleanClean-up: findings the scans no longer see (retired by a run, or deleted) leave the case by themselves
removeResolvedFindingsbooleanClean-up: findings someone resolved leave the case
removeGoneAssetsbooleanClean-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)—
titlestring—
descriptionstring—
statusenumOne of: OPEN, IN_PROGRESS, CLOSED, ARCHIVED
severityenumOne of: CRITICAL, HIGH, MEDIUM, LOW, INFO
assigneestring—
conclusionstring—
aiModeenumOne of: INHERIT, MANAGED, OBSERVE_ONLY
removeGoneFindingsbooleanClean-up: findings the scans no longer see (retired by a run, or deleted) leave the case by themselves
removeResolvedFindingsbooleanClean-up: findings someone resolved leave the case
removeGoneAssetsbooleanClean-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—
closedBystring—
keepInquiriesboolean—
reopen_casewritesReopen a closed/archived case and reactivate the questions that were archived alongside it.
id*string (uuid)—
notestring—
reopenedBystring—
add_case_evidencewritesAttach an asset as evidence to a case.
id*string (uuid)—
entityType*stringMust be "asset"
entityId*stringAsset UUID
notestring—
addedBystring—
attach_case_findingswritesBatch-attach findings to a case by ID. Asset evidence rows are created automatically.
id*string (uuid)—
findingIds*string[]—
addedBystring—
pull_case_from_inquirywritesPull a linked question's current matches into the case as evidence and findings.
id*string (uuid)—
inquiryId*string—
findingIdsstring[]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[]—
autoPullInquiryIdsstring[]—
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)—
removeGoneFindingsbooleanClean-up: findings the scans no longer see (retired by a run, or deleted) leave the case by themselves
removeResolvedFindingsbooleanClean-up: findings someone resolved leave the case
removeGoneAssetsbooleanClean-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)—
includeOptionsboolean—
inquiryIdstringWith 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)—
actionenumOne of: EXCLUDE, ESCALATE
inquiryIdstring | nullA 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)—
actionenumOne of: EXCLUDE, ESCALATE
inquiryIdstring | null—
rules*object[]—
removeEmptiedAssetsboolean—
dryRunboolean—
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)—
patternstring—
descriptionunknown—
removeEmptiedAssetsboolean—
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—
stanceenumOne of: SUPPORTS, CONTRADICTS, NEUTRAL
kindunknown—
patternunknown—
descriptionunknown—
applyToExistingboolean—
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)—
threadIdstring—
stanceenumOne of: SUPPORTS, CONTRADICTS, NEUTRAL
kindunknown—
patternunknown—
descriptionunknown—
updateLinksboolean—
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)—
removeLinksboolean—
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)—
findingIdsstring[]Finding ids (not case-finding ids); omit for all
get_case_graphread-onlyGet the evidence neighbourhood graph for a case.
id*string (uuid)—
depthinteger—
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)—
cursorstring—
limitinteger—
typesenum[]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
inquiryIdstring (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)—
kindenumDefaults to HYPOTHESIS One of: HYPOTHESIS, DISCUSSION
title*stringHypothesis name or discussion topic
statementstringInitial statement body (hypothesis threads)
statusenumOne of: PROPOSED, SUPPORTED, REFUTED, INCONCLUSIVE
confidencenumber—
createdBystring—
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*enumOne of: NOTE, STATEMENT, STATUS_CHANGE, CONFIDENCE_CHANGE
bodystring—
authorstring—
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)—
titlestring—
statusenumOne of: PROPOSED, SUPPORTED, REFUTED, INCONCLUSIVE
confidencenumber—
colorunknownA CSS colour such as #3b82f6; null for the default
testablePredicateunknown—
link_case_thread_supportwritesLink evidence or a finding to a thread as supporting/contradicting/neutral.
threadId*string (uuid)—
targetType*enumOne of: evidence, finding
targetId*string—
stanceenumOne of: SUPPORTS, CONTRADICTS, NEUTRAL
weightnumber—
notestring—

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)—
viewenumOne of: summary, full
includeGraphbooleanview "full" only: include the neighbourhood graph
snapshotIdstring (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)—
baseVersionintegerboard.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)—
dryRunboolean—
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[]—
titlestring—
frameIdstring (uuid)—
colorenumOne of: yellow, blue, green, pink, gray, red, amber, violet
arrangementenumOne 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)—
assetIdsstring[]—
directionenumOne of: up, down, both
depthinteger—
kindsenum[]One of: lineage, links, duplicates, similar, meaning
limitinteger—
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)—

AI Autopilot autopilot

Observe and control the autonomous agents: runs, decisions, memory, per-agent enable/disable, and manual triggers.

list_autopilot_agentsread-onlyList every AI-autopilot agent with its configuration: kind, whether it is enabled for scan cycles, its goal (default + any override), iteration budget, and assigned tools. INQUIRY/CASE/CONFIG/DETECTOR_AUTHOR/ESCALATION are toggleable; DUPLICATES (deterministic fingerprinting) and DREAM (scheduled memory consolidation) always run and cannot be toggled.

No parameters.

update_autopilot_agentwritesEnable/disable an autopilot agent for scan cycles, or override its goal or iteration budget. Only INQUIRY, CASE, CONFIG, DETECTOR_AUTHOR and ESCALATION accept enable/disable. Pass goal: null or max_iterations: null to reset to the factory default.
kind*enumAgent kind to update One of: INQUIRY, CASE, CONFIG, DETECTOR_AUTHOR, ESCALATION
enabledbooleanEnable or disable the agent on scan cycles
goalstring | nullGoal override; null resets to the factory default
max_iterationsunknownIteration budget override (1-50); null resets to default
list_autopilot_runsread-onlyList AI-autopilot agent runs (newest first) with status, trigger, summary and error. Filter by agent kind, source, case, status, trigger origin (scan_completed | manual | schedule), free-text search, or time window.
agent_kindenumOne of: INQUIRY, CASE, DREAM, DUPLICATES, CONFIG, DETECTOR_AUTHOR, ESCALATION, CHAT, SUPERVISOR
source_idstringOnly runs for this source
case_idstringOnly runs focused on this case
statusenumOne of: PENDING, RUNNING, COMPLETED, FAILED, SKIPPED, CANCELLED
triggerstringscan_completed | manual | schedule
searchstringSubstring search over summary, instruction and error
sincestringISO time lower bound
untilstringISO time upper bound
skipinteger—
limitinteger—
get_autopilot_runread-onlyFull detail of one autopilot agent run, including every decision it made (action, outcome, target entity, rationale, payload).
id*stringAgent run ID
get_autopilot_run_logsread-onlyStep-by-step logs of one autopilot run. channel BUSINESS is the analyst narrative; TECHNICAL includes mechanics and raw model I/O.
run_id*stringAgent run ID
channelenumOne of: BUSINESS, TECHNICAL
levelenumOne of: DEBUG, INFO, WARN, ERROR
searchstringSubstring search over the message
list_autopilot_activityread-onlyCross-run timeline of autopilot decisions (what each agent did, to which entity, with what outcome and rationale). This is the audit surface for "what did the AI change?" — filter by agent kind, action, outcome, entity type (inquiry | case | source | detector | memory | system | asset), rationale search, or time window.
agent_kindenumOne of: INQUIRY, CASE, DREAM, DUPLICATES, CONFIG, DETECTOR_AUTHOR, ESCALATION, CHAT, SUPERVISOR
entity_typestringinquiry | case | source | detector | memory | system | asset
searchstringSubstring search over the rationale
sincestringISO time lower bound
untilstringISO time upper bound
skipinteger—
limitinteger—
list_autopilot_memoryread-onlyList the autopilot agents' persistent memory entries (glossary terms, decision precedents, source profiles, detector lessons). Higher weight = recalled first.
searchstringSubstring search over key and content
skipinteger—
limitinteger—
get_autopilot_statsread-onlyAggregate autopilot health: runs by status and agent kind, recent failures, decision counts.

No parameters.

trigger_autopilotwritesManually enqueue an autopilot cycle. Pipeline agents (INQUIRY, CASE, CONFIG, DETECTOR_AUTHOR, ESCALATION) run in canonical order; pass agent_kinds to run a subset, source_id to focus on one source, case_id to focus the CASE agent on one case, and instruction to steer the cycle.
instructionstringHighest-priority steering prompt for this cycle
source_idstringLimit the review to one source; omit for all sources
agent_kindsenum[]Which agents to run; omit for the full pipeline One of: INQUIRY, CASE, DREAM, DUPLICATES, CONFIG, DETECTOR_AUTHOR, ESCALATION, CHAT, SUPERVISOR
case_idstringFocus the CASE agent on one case
cancel_autopilot_runwritesCancel a pending or running autopilot agent run. The runtime aborts before its next step.
id*stringAgent run ID to cancel

Correlation correlation

Tune and inspect deterministic asset correlation (evidence fingerprints and duplicate detection).

get_correlation_configread-onlyCorrelation tuning: per-label weights (dynamic) plus related/duplicate match thresholds.

No parameters.

save_correlation_configwritesUpdate correlation tuning (weights/thresholds/exclusions) and schedule a background recompute.
defaultWeightinteger—
relatedMinnumber—
duplicateMinnumber—
labelWeightsobjectPer-label weight overrides
exclusionsobject[]Full replacement list of exclusion rules
add_correlation_exclusionwritesAdd a correlation exclusion rule (ignore a noisy value/regex/label) and schedule a background recompute.
mode*enumOne of: value, regex, label
labelstring | null—
valuestring | null—
remove_correlation_exclusionwritesRemove a correlation exclusion rule by ID and schedule a background recompute.
id*string—
recompute_correlationwritesRecompute correlation. Pass assetId to recompute a single asset synchronously; omit it to schedule a full background recompute (avoids blocking on large instances).
assetIdstring (uuid)Recompute just this asset synchronously
get_value_occurrencesread-onlyWhere else a normalized finding value appears across assets (reverse index).
labelstring—
valuestring—
valueHashstring—

Custom Source Code custom_source_code

Author, run, and debug Python notebooks: the connector behind a CUSTOM source, and code detectors (CODE_DETECTOR) -- cells, packages, local folders, uploaded files, and executions.

get_notebookread-onlyRead a source’s notebook: cells, revision, variables, secret keys, packages, and local folders.
sourceId*string (uuid)—
scopeenumWhich notebook: the CUSTOM source’s connector notebook, or the per-asset augmentation notebook. One of: connector, augmentation
add_notebook_cellwritesInsert a new cell into a notebook. Appended at the end unless afterCellId is given.
sourceId*string (uuid)—
scopeenumWhich notebook: the CUSTOM source’s connector notebook, or the per-asset augmentation notebook. One of: connector, augmentation
baseRevision*integer—
cellIdstringDefaults to a generated id if omitted.
typeenumOne of: code, markdown
source*string—
afterCellIdstring—
update_notebook_cellwritesReplace one cell’s source (and optionally its type).
sourceId*string (uuid)—
scopeenumWhich notebook: the CUSTOM source’s connector notebook, or the per-asset augmentation notebook. One of: connector, augmentation
baseRevision*integer—
cellId*string—
source*string—
typeenumOne of: code, markdown
delete_notebook_celldestructiveRemove one cell from a notebook.
sourceId*string (uuid)—
scopeenumWhich notebook: the CUSTOM source’s connector notebook, or the per-asset augmentation notebook. One of: connector, augmentation
baseRevision*integer—
cellId*string—
set_notebook_packageswritesReplace the Python packages installed into the notebook’s run environment before any cell executes. Call list_notebook_runtime_packages first — the base image’s own dependencies do not need listing.
sourceId*string (uuid)—
scopeenumWhich notebook: the CUSTOM source’s connector notebook, or the per-asset augmentation notebook. One of: connector, augmentation
packages*object[]—
set_notebook_local_folderswritesReplace the local folders a notebook reads with ctx.folder("name"). Needs a deployment that exposes host folders to scans: the all-in-one Docker image (bind-mount them) or a Kubernetes chart with api.localFolders configured. Otherwise upload files to the source instead (upload_notebook_file).
sourceId*string (uuid)—
scopeenumWhich notebook: the CUSTOM source’s connector notebook, or the per-asset augmentation notebook. One of: connector, augmentation
folders*object[]—
list_notebook_runtime_packagesread-onlyPython packages already baked into the notebook runtime image — do not declare these again with set_notebook_packages.

No parameters.

upload_notebook_filewritesUpload a file for the notebook to read via ctx.files. Stored in Postgres and deduplicated by content hash.
sourceId*string (uuid)—
fileName*string—
contentBase64*stringFile bytes, base64-encoded.
mimeTypestring—
list_notebook_filesread-onlyList files uploaded to a CUSTOM source.
sourceId*string (uuid)—
delete_notebook_filedestructiveDelete one uploaded file from a CUSTOM source.
sourceId*string (uuid)—
fileId*string (uuid)—
run_notebookwritesStart a notebook execution and return immediately — poll get_notebook_execution for the result. Modes: "cell" runs one cell (requires targetCellId), "test_connection" is the connection/auth smoke test, "preview_extract" samples a few assets end-to-end, "preview_augment" runs the augmentation notebook over a sample of real assets and reports per-asset diffs, "all" replays every cell in order (the closest thing to a full local test of the whole connector).
sourceId*string (uuid)—
scopeenumWhich notebook: the CUSTOM source’s connector notebook, or the per-asset augmentation notebook. One of: connector, augmentation
mode*enumOne of: cell, all, test_connection, preview_extract, preview_augment
targetCellIdstringRequired when mode is "cell".
maxAssetsinteger—
get_notebook_executionread-onlyPoll one notebook execution: status, per-cell outputs, and structured error/failedCellId once it finishes.
executionId*string (uuid)—
list_notebook_executionsread-onlyRecent notebook executions for a source, newest first.
sourceId*string (uuid)—
limitinteger—
cancel_notebook_executionwritesStop a running notebook execution. Cells cannot be interrupted from inside Python, so this ends the process or deletes the Job.
executionId*string (uuid)—
run_custom_detector_notebookwritesRun a CODE_DETECTOR (code) detector's notebook and return immediately -- poll get_notebook_execution for the result. "cell" runs one cell (targetCellId) and "all" replays every cell, with the detector's variables, secrets and files but no asset: use them to debug helpers with print(). "preview_detect" runs setup() and detect() exactly as a scan would on a real asset of sourceId (assetId), or on a small sample of that source when assetId is omitted, and reports the findings it WOULD record (outputs.assets[].findings, outputs.result.logs) without writing anything. Always preview on a real asset before attaching a new rule to a source. The revision is read from the detector, so save (update_custom_detector) first.
detectorId*string (uuid)—
mode*enumOne of: cell, all, preview_detect
targetCellIdstringRequired for "cell".
sourceIdstring (uuid)Required for "preview_detect".
assetIdstring (uuid)preview_detect: one asset of sourceId; omit to sample.
maxAssetsinteger—
list_custom_detector_filesread-onlyFiles uploaded to a CODE_DETECTOR detector (lists, models, reference tables); the rule opens them with ctx.file(name).
detectorId*string (uuid)—
upload_custom_detector_filewritesUpload a file a CODE_DETECTOR detector reads with ctx.file(fileName) -- a sanctions list CSV, a joblib/ONNX model, a lookup table. A file with the same name is replaced, and the detector version is bumped so the next scan re-runs the rule.
detectorId*string (uuid)—
fileName*string—
contentBase64*stringFile bytes, base64-encoded.
mimeTypestring—
delete_custom_detector_filedestructiveDelete one file from a CODE_DETECTOR detector.
detectorId*string (uuid)—
fileId*string (uuid)—

Extractions extractions

Inspect structured field extractions and how much of a run’s content was actually covered.

get_finding_extractionread-onlyGet structured extraction data for a specific finding.
finding_id*string (uuid)—
search_extractionsread-onlySearch structured extraction records across custom detector findings.
custom_detector_keystring—
custom_detector_idstring (uuid)—
source_idstring (uuid)—
takeinteger—
skipinteger—
get_extraction_coverageread-onlyGet field-level coverage statistics for a custom detector's extractions.
custom_detector_id*string (uuid)—
list_extractor_schemaread-onlyShow the extractor field schema for a custom detector plus a recent extraction example.
custom_detector_id*string (uuid)—

Case Leads case_leads

AI-proposed next steps for a case: generate, review, and track supporting events.

list_case_leadsread-onlyList a case's lead queue: ranked candidates awaiting accept/dismiss review, plus reviewed history. Origins: SEMANTIC_NEIGHBOR (similar to evidence; details.sameValue when it is the very same value), INQUIRY (important answer of a linked watch), DUPLICATE (a look-alike document from the duplicates engine — an ASSET lead with findingId null), AUTOPILOT, MANUAL. Each lead names what it hangs off (viaFindingId / viaAssetId / viaInquiryId, viaLabel) and where it is (assetName, sourceName). For PROPOSED leads, state OPEN waits for review; IN_CASE and GONE are settled by the next refresh.
caseId*string (uuid)—
statusenumOne of: PROPOSED, ACCEPTED, DISMISSED
propose_case_leadwritesPropose a finding as a lead for a case (instead of attaching it as evidence directly). Leads are reviewed by a human; a dismissed lead is never re-proposed.
caseId*string (uuid)—
findingId*string (uuid)—
rationale*string—
generate_case_leadswritesRefresh leads for a case now from its own evidence: findings similar to it, high-importance answers of its linked watches, and look-alike documents the duplicates engine pairs with its evidence (pairs rejected or split in Duplicate review are never suggested). The case also refreshes its leads by itself whenever its evidence or watches change, so this is only needed for an immediate refresh. Bounded (per-kind quotas, at most 60 waiting) and idempotent (existing/dismissed leads are skipped).
caseId*string (uuid)—
review_case_leadwritesAccept a lead into case evidence (a finding lead attaches its finding; an asset lead adds the document) or dismiss it. Dismissals are remembered as precedents so agents stop re-proposing the finding or document.
caseId*string (uuid)—
leadId*string (uuid)—
action*enumOne of: ACCEPT, DISMISS
reasonstring—
list_case_eventsread-onlyList the case chronology: dated real-world events reconstructed from evidence, ordered by date. Distinct from get_case_timeline (the app activity/audit log).
caseId*string (uuid)—
create_case_eventwritesAdd a dated real-world event to the case chronology. Cite the findingIds/evidenceIds the date came from; unsupported dates do not belong in a chronology.
caseId*string (uuid)—
occurredAt*stringISO date or datetime
precisionenumOne of: DAY, MONTH, YEAR
title*string—
descriptionstring—
confidencenumber—
findingIdsstring[]—
evidenceIdsstring[]—
delete_case_eventdestructiveRemove an event from the case chronology.
caseId*string (uuid)—
eventId*string (uuid)—

Glossary glossary

The shared vocabulary of concepts and entities: list, look up and curate terms, schemes and relations; bind detector outputs to meaning; read the semantic links (Meaning) derived from findings; review proposals and see the semantic map.

list_glossary_termsread-onlyList the shared glossary: concepts (kinds of things) and entities (particular things), with keys, schemes, aliases, codes and status. Filter by kind, scheme, status or steward.
querystring—
entityTypeenumOne of: PERSON, ORGANIZATION, LOCATION, REFERENCE, TERM, OTHER
kindenumOne of: CONCEPT, ENTITY
schemeKeystring—
statusenum[]One of: DRAFT, APPROVED, DEPRECATED
stewardstring—
takeinteger—
skipinteger—
lookup_glossaryread-onlyResolve a name, alias, code or concept against the glossary (exact, code, alias, prefix and semantic matching); each hit says what it matched on. Use before treating two spellings as separate entities.
query*string—
limitinteger—
kindenumOne of: CONCEPT, ENTITY
schemeKeystring—
statusenum[]One of: DRAFT, APPROVED, DEPRECATED
includeDeprecatedboolean—
get_glossary_termread-onlyOne term by id or key: definition, scheme, labels, relations, broader chain and narrower concepts.
term*stringTerm id or key (previous keys redirect).
upsert_glossary_termwritesCreate or update a glossary term. kind CONCEPT for kinds of things (GmbH, IBAN), ENTITY for particular things (ACME Holding GmbH). codes are exact notations; single letters go to hiddenAliases. Terms written through MCP are operator-curated and APPROVED.
idstring—
term*string—
kindenumOne of: CONCEPT, ENTITY
keystring—
aliasesstring[]—
codesstring[]—
hiddenAliasesstring[]—
definitionstring—
schemeKeystring—
entityTypeenumOne of: PERSON, ORGANIZATION, LOCATION, REFERENCE, TERM, OTHER
stewardstring—
notesstring—
createNewboolean—
deprecate_glossary_termwritesDeprecate a term, optionally naming the term that replaces it. Its bindings stop producing links; history is kept.
term*stringTerm id or key (previous keys redirect).
replacedBystringTerm id or key (previous keys redirect).
list_glossary_schemesread-onlySchemes are controlled vocabularies (Rechtsformen, GDPR categories), with term counts.

No parameters.

upsert_glossary_schemewritesCreate or rename a scheme. The key is generated from the name when omitted.
idstring—
keystring—
name*string—
descriptionstring—
colorstring—
list_glossary_relationsread-onlyRelations between terms (BROADER, RELATED, PART_OF, INSTANCE_OF, CUSTOM), optionally for one term.
termstringTerm id or key (previous keys redirect).
typeenumOne of: BROADER, RELATED, PART_OF, INSTANCE_OF, CUSTOM
statusenumOne of: DRAFT, APPROVED, DEPRECATED
takeinteger—
relate_glossary_termswritesRelate two terms. BROADER only when the first concept is a kind of the second; INSTANCE_OF from an entity to its concept; CUSTOM needs a label. Cycles are refused with the path.
from*stringTerm id or key (previous keys redirect).
to*stringTerm id or key (previous keys redirect).
type*enumOne of: BROADER, RELATED, PART_OF, INSTANCE_OF, CUSTOM
labelstring—
notestring—
remove_glossary_relationdestructiveRemove one relation.
relationId*string—
export_glossaryread-onlyExport the glossary as CSV or SKOS (JSON-LD), optionally one scheme.
format*enumOne of: csv, skos
schemeKeystring—
kindsenum[]One of: CONCEPT, ENTITY
import_glossarywritesImport CSV or SKOS. Defaults to a dry run that reports what would be created, updated and skipped; pass dryRun: false to write.
format*enumOne of: csv, skos
content*string—
dryRunboolean—
conflictenumOne of: skip, overwrite, merge-labels
asDraftboolean—
schemeKeystring—
languagestring—
list_vocabularyread-onlyThe detector outputs and metadata fields the data produces, with counts, whether each has a meaning (binding), and suggestions. bound=false lists what has no meaning yet.
kindenumOne of: outputs, fields, all
boundenumOne of: true, false, any
sourceIdstring—
qstring—
takeinteger—
skipinteger—
list_bindingsread-onlyBindings: what detector outputs and metadata fields mean, with status and the term or scheme they point to.
termstringTerm id or key (previous keys redirect).
statusenumOne of: DRAFT, APPROVED, DISABLED
detectorTypestring—
customDetectorKeystring—
findingTypestring—
takeinteger—
skipinteger—
preview_bindingread-onlyWhat a binding would do before it exists: open findings, assets and sources it gives meaning, samples, the lookup table with unmatched values, and warnings.
spec*objectBinding spec (C9): what a detector output (OUTPUT*) or asset metadata field (METADATA_*) means. Never regex.
spec.mode*enumOne of: OUTPUT, OUTPUT_VALUES, OUTPUT_LOOKUP, METADATA_VALUES, METADATA_LOOKUP
spec.outputunknown—
spec.fieldstring | null—
spec.valuesstring[]—
spec.splitDelimiterunknown—
spec.lookupunknown—
spec.termKeystring | null—
spec.termIdstring | null—
spec.noMeaningboolean—
spec.sourceIdsstring[]—
spec.confidencenumber—
create_bindingwritesCreate a binding (APPROVED by default; DRAFT to leave it for review). Links follow without a re-scan.
spec*objectBinding spec (C9): what a detector output (OUTPUT*) or asset metadata field (METADATA_*) means. Never regex.
spec.mode*enumOne of: OUTPUT, OUTPUT_VALUES, OUTPUT_LOOKUP, METADATA_VALUES, METADATA_LOOKUP
spec.outputunknown—
spec.fieldstring | null—
spec.valuesstring[]—
spec.splitDelimiterunknown—
spec.lookupunknown—
spec.termKeystring | null—
spec.termIdstring | null—
spec.noMeaningboolean—
spec.sourceIdsstring[]—
spec.confidencenumber—
statusenumOne of: DRAFT, APPROVED
notestring—
approve_bindingwritesApprove a DRAFT binding.
bindingId*string—
disable_bindingdestructiveDisable an APPROVED binding; its links become GONE (history kept).
bindingId*string—
find_term_in_textwritesTurn a concept that appears only as words into a tested REGEX detector bound to it. Previews by default; create: true creates the detector.
term*stringTerm id or key (previous keys redirect).
createbooleanfalse (default) previews; true creates the detector.
wholeWordsboolean—
continuationsboolean—
severityenumOne of: info, low, medium, high, critical
sourceIdsstring[]—
install_glossary_packwritesInstall a glossary pack (a starter pack by key, or a pack JSON): schemes, concepts, relations and bindings. Defaults to a dry run.
keystring—
packobject—
dryRunboolean—
resolutionsobject—
get_finding_meaningread-onlyWhat a finding means: the concepts it is evidence of, through which binding or manual link, and the broader concepts implied.
findingId*string—
get_asset_meaningread-onlyAn asset's concepts with method (binding, declared, manual, suggested), support and confidence.
assetId*string—
includeHistoryboolean—
get_term_evidenceread-onlyAssets that are evidence of a concept, sorted by severity then support. includeNarrower rolls up the taxonomy.
term*stringTerm id or key (previous keys redirect).
includeNarrowerboolean—
sourceIdstring—
methodenumOne of: BINDING, DECLARED, MANUAL, SUGGESTED, MENTION
statusenumOne of: current, gone, all
pageinteger—
get_term_summaryread-onlyA concept's evidence counts by method, source and severity, with a weekly trend.
term*stringTerm id or key (previous keys redirect).
includeNarrowerboolean—
link_termwritesSay by hand that an asset, finding or case is about a term (a MANUAL link).
term*stringTerm id or key (previous keys redirect).
targetType*enumOne of: asset, finding, case
targetId*string—
notestring—
unlink_termdestructiveRemove a manual link.
referenceId*string—
list_glossary_proposalsread-onlyThe review queue: term, alias, relation, binding, document-link and term-reference proposals, one list, highest score first.
kindenumOne of: TERM, ALIAS, RELATION, BINDING, LINK, TERM_REF
minScorenumber—
takeinteger—
skipinteger—
decide_glossary_proposalwritesAccept, edit and accept, dismiss, dismiss forever, or skip one proposal. Operator level: every kind.
kind*enumOne of: TERM, ALIAS, RELATION, BINDING, LINK, TERM_REF
id*string—
decision*enumOne of: accept, edit, dismiss, dismiss_forever, skip
editobject—
reasonstring—
refresh_glossary_suggestionswritesRun the suggestion generators now (bindings for unbound vocabulary, document links, relations).
generatorsenum[]Default: all enabled generators. One of: binding, link, relation
glossary_suggestion_statsread-onlyAcceptance rates per generator and score band, so thresholds can be tuned.
daysinteger—
get_semantic_mapread-onlyA compact summary of the ontology: the top concepts by evidence, their relations, and how much vocabulary is still unbound.
limitinteger—

Entities entities

The named things findings refer to — people, organisations, accounts: search them, read where each is mentioned and with whom, create one from a finding, review candidate values and merge duplicates.

search_entitiesread-onlyFind entities — the named things findings refer to (a person, an organisation, an account) — by name, alias, key or identifier value, with their mention counters. An entity is an ENTITY-kind glossary term, so its key works wherever a term key does (the `term` finding filter, watches, Ref.term).
querystringMatches names, aliases, keys and identifier values.
entityTypeenumOne of: PERSON, ORGANIZATION, LOCATION, REFERENCE, OTHER
statusenum[]One of: DRAFT, APPROVED, DEPRECATED
sortenumOne of: mentions, name, lastSeen
takeinteger—
skipinteger—
get_entityread-onlyOne entity: its names and identifiers (confirmed values), live mention counters, the record it is anchored to, pending candidates, and which names it shares with other entities (ambiguous, not wrong). Mentions link — and count as meaning — only while the entity is APPROVED.
entity*stringEntity id or glossary key (previous keys redirect).
get_entity_mentionsread-onlyWhere an entity is mentioned, across every source: asset, source, finding, the value that matched and a snippet. Derived by joining the entity's confirmed values with the value index, so a value confirmed today lists documents scanned long ago. Keyset-paged: pass `next` back as `after`.
entity*stringEntity id or glossary key (previous keys redirect).
sourceIdstring—
limitinteger—
afterstringThe `next` cursor of the previous page.
get_co_mentioned_entitiesread-onlyThe entities that appear in the same assets as this one, most shared first: who appears with whom. Computed on demand over the latest mention assets; nothing is stored as graph edges.
entity*stringEntity id or glossary key (previous keys redirect).
limitinteger—
create_entitywritesCreate an entity, optionally from a finding (`findingId`) or an indexed value (`value`), which becomes its first alias or identifier. Every existing and future finding carrying one of its names or identifiers is then a mention, with no further step. Returns the entity with its mentions counted, and any identifier another entity already holds as `conflicts` (those wait in the review queue).
namestringRequired unless the promoted value is itself a name (a person or organisation finding).
entityTypeenumOne of: PERSON, ORGANIZATION, LOCATION, REFERENCE, OTHER
aliasesstring[]—
identifiersobject[]—
definitionstring—
notesstring—
findingIdstringPromote this finding's value: it becomes the entity's first alias (a name) or identifier.
valueobjectOr promote a value from get_value_occurrences.
value.label*stringThe finding label the value is detected under (e.g. iban_code, email_address, a custom detector label). It is what links findings to the entity.
value.value*string—
createNewbooleanCreate a second entity even if one has this name.
update_entitywritesAdd identifiers to an entity, remove values, or set its anchor URN and attributes. Rename it or edit aliases with upsert_glossary_term (it is a glossary term). An identifier another entity holds is not double-linked: it comes back under `conflicts` and waits for review.
entity*stringEntity id or glossary key (previous keys redirect).
addIdentifiersobject[]—
removeValueIdsstring[]—
anchorUrnunknownURN of the record that is this entity; null clears it.
attributesunknown—
list_entity_candidatesread-onlyThe entity review queue: values that may refer to an entity (a spelling variant found by sound or by folded spelling), each with a score, how many assets carry it and up to three occurrences to judge it by — plus identifier conflicts. Nothing here links until it is accepted.
entitystringEntity id or glossary key (previous keys redirect).
kindenummention: a spelling variant to confirm. conflict: an identifier two entities claim. One of: mention, conflict
minScorenumber—
takeinteger—
skipinteger—
review_entity_candidateswritesAccept or reject candidates in one call. Accepting confirms the value for its entity, so every asset that carries it becomes a mention; rejecting is remembered and the value is not proposed for that entity again.
decisions*object[]—
merge_entitiesdestructiveMerge one entity into another: its names, identifiers, values, references, relations and watches move to the target, and it becomes deprecated and redirects there. Two entities are never merged automatically. This cannot be undone by a single call.
from*stringThe entity that goes away (it will redirect).
into*stringThe entity that stays; it must be APPROVED.

Lineage lineage

Trace how data flows between assets via the stitched edge graph, and look up what a relation type means.

get_asset_lineageread-onlyTrace an asset's FLOW lineage (where its data came from / what depends on it), walking edges up to `depth` hops. Returns the same hydrated node/edge graph the web graph explorer renders, so this is how to verify a stitched cross-source lineage edge (e.g. two assets linked by a shared external URN) without a browser.
assetId*string (uuid)—
directionenumOne of: up, down, both
depthinteger—
collapseContainersboolean—
mergeIdentityboolean—
get_relation_typesread-onlyEdge relation types actually in use, plus builtin suggestions, each classified as FLOW | CONTAINMENT | IDENTITY | REFERENCE | USAGE. Call before get_asset_lineage to know what a relationType on a returned edge means.

No parameters.

Resources and prompts

URI / nameWhat it holds
classifyre://overviewConnection details, every capability group and its tools
classifyre://investigation-guideThe intended investigation loop: coverage → ranked findings → watches → cases and their board → conclusion
classifyre://capabilities/{groupId}One capability group in detail
brainstorm_custom_detector (prompt)Guides a client to propose regex, classifier or entity detector configs before training

Resources are descriptive and readable with any token.

How To Add New MCP Capabilities

When adding MCP functionality in API code:

  1. Register the tool in apps/api/src/mcp-server.factory.ts with a clear description (what it does, when to use it, what it returns), a zod input schema — z.strictObject for anything destructive — and honest annotations (readOnlyHint, destructiveHint, idempotentHint).
  2. Put its name in one capability group in apps/api/src/mcp-catalog.ts (or add a group). mcp-catalog.completeness.spec.ts fails on a tool without a group, since a scoped token could never reach it.
  3. Destructive tools go in mcp-server.factory.destructive-tools.spec.ts.
  4. Run bun run codegen: it regenerates this page’s catalog (apps/docs/src/_generated/mcp-catalog.json); commit it with the change.
  5. Validate with tools/list and a tools/call through POST /<workspace>/mcp.

Security Notes

  • Never commit tokens.
  • Use HTTPS in production.
  • Generate one token per client/workspace.
  • Rotate by creating a replacement token, then revoke old token.

Troubleshooting

SymptomLikely causeFix
401 UnauthorizedInvalid/missing tokenVerify Authorization: Bearer ...
A tool is missing from tools/listThe token’s scope does not include its groupEdit the token under Settings -> MCP Server and tick the group (see the capability map)
DEMO_MODE_READ_ONLY from a write toolThe instance runs in demo modeWrites are refused on demo instances; read tools still work
404 Not FoundWrong URLConfirm host and /mcp path
503 Service UnavailableMCP disabledEnable MCP in Settings
Connection refusedHost not reachableCheck port, firewall, reverse proxy

Further Reading

Last updated on