# Data

# Tools: data

75 tool(s) in this category.

### `company.get`

**Get Company**

Fetch the interactions-catalog entry for a single company by name (case-insensitive exact match). Use when the agent already has a company name from input_params.company_list and wants the conversation count before deciding whether to include it. Returns null when no interactions exist for that company in this business.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | yes |  |


### `company.list`

**List Companies**

List distinct company names present in this business's interactions data, ordered by name. Use when populating the v2 ICP / company-focus picker or when the agent needs a starting set before narrowing a research delegation. Returns each company's conversation count so the UI can flag low-signal accounts. Accepts an optional limit (default 50, max 500).

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | `integer` | no |  |


### `company.search`

**Search Companies**

Substring-search companies present in this business's interactions by name. Use when the user types into a company typeahead or when the agent wants to shortlist accounts for input_params.company_list before delegating to researcher. Accepts q (required) and limit (default 20, max 100). Each result carries the conversation count so low-signal matches are easy to filter.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `q` | `string` | yes |  |
| `limit` | `integer` | no |  |


### `connections.connect`

**Connect an Integration**

Establish a new integration for a workspace, routing on the connector type: collect an API key, kick off an OAuth redirect, or track a social handle. Use when a user picks a provider from the catalog and wants it connected. Returns the created connection, or an OAuth authorize URL to redirect to. Creates and stores the connection without starting a sync.

- HTTP: `POST /connections`
- Required scopes: `connections:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `connections`, `connect`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `connector_type` | `string` | yes | Catalog connector type to connect (e.g. hubspot, fathom, twitter). |
| `name` | `string` | no | Optional display label for this connection instance. |
| `scope` | `enum("business" | "personal")` | no | Ownership: business (workspace-wide) or personal. Defaults from the catalog. |
| `api_key` | `string` | no | For api_key connectors: the API key / token to store (encrypted). |
| `account_handle` | `string` | no | For social (handle) connectors: the resolved bare account handle. |
| `external_account_id` | `string` | no | For social (handle) connectors: the provider's stable account id. |
| `display_name` | `string` | no | For social (handle) connectors: display name snapshot. |
| `avatar_url` | `string` | no | For social (handle) connectors: avatar image URL snapshot. |
| `followers` | `integer` | no | For social (handle) connectors: follower count snapshot. |
| `return_to` | `string` | no | For oauth connectors: console URL to redirect back to after the handshake (first-party origins only). |


### `connections.disconnect`

**Disconnect an Integration**

Disconnect one established integration by its id, scoped to the workspace. Use when a user wants to stop an integration: the source (a CRM / calls / docs connector or a tracked X / LinkedIn account) is marked disconnected. Retains the row for re-connect; a non-existent or cross-tenant id is a clean not-found that never reveals another workspace.

- HTTP: `DELETE /connections/:id`
- Required scopes: `connections:delete`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `connections`, `disconnect`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to disconnect. |


### `connections.get`

**Get Connection By Id**

Fetch one established integration by its id, resolved across both first-party data sources and tracked social accounts and scoped to the caller's workspace. Read when opening a connection detail view or confirming a specific integration's configuration. Returns the normalized record, or null when the id belongs to another tenant or does not exist - a not-found that never reveals existence.

- HTTP: `GET /connections/:id`
- Required scopes: `connections:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to read. |


### `connections.get_status`

**Get Connection Sync State**

Fetch the live sync state of one integration - its normalized health, the in-flight syncing flag, the last good sync time, and any last error - without pulling the whole record. Read when polling a connecting / syncing badge after a connect or refresh kicks off. Resolved across both stacks and scoped to the workspace; null when the id is not in the tenant.

- HTTP: `GET /connections/:id/status`
- Required scopes: `connections:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to read the sync state for. |


### `connections.list`

**List Established Connections**

Read every integration a workspace has actually established, newest first, merging first-party data sources and tracked social accounts into one normalized list with a uniform status, scope, and last-synced field. Read when rendering the connections inventory or answering which integrations are live for the tenant. This is the established-instances view, distinct from the catalog of available providers.

- HTTP: `GET /connections`
- Required scopes: `connections:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

_No structured input._


### `connections.list_catalog`

**List Connector Catalog**

Read the catalog of integrations a workspace can hook up - CRM, calls, comms, docs, support, and social - with each entry's connect flow, ownership, and the data streams it brings in. Read when rendering the "add a connection" picker or answering which providers are supported. Describes what CAN be connected, not what is already connected. Optional kind / category filters narrow the menu.

- HTTP: `GET /connections/catalog`
- Required scopes: `connections:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `enum("first_party")` | no | Filter to one ingestion stack. first_party (amdahl-data pullers) is the only stack today; use category=social to filter the social UI grouping. |
| `category` | `enum("crm" | "calls" | "comms" | "docs" | "support" | "social")` | no | Filter to one UI category. |


### `connections.list_runs`

**List Connection Sync Runs**

Read the recent sync-run history of one integration - each run's outcome, what triggered it, when it started and finished, how many streams ran or failed, the rows written, and a bucketed failure reason. Read when showing a connection activity log or diagnosing why data is not flowing. Scoped to the workspace; an id outside the tenant returns an empty list.

- HTTP: `GET /connections/:id/runs`
- Required scopes: `connections:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to list sync runs for. |
| `limit` | `integer` | no | Max runs to return, newest first (default 25, capped at 100). |


### `connections.reconnect`

**Reconnect an Integration**

Restore a connection stuck in an error or needs-reauth state, in place on the existing data source so its sync history and id survive (no duplicate row). Use when a user clicks reconnect on a broken connection: an OAuth provider returns a fresh authorize URL to redirect through, an api_key connector takes a replacement key, and a tracked social handle simply re-triggers its sync. A non-existent or cross-tenant id is a clean not-found.

- HTTP: `POST /connections/:id/reconnect`
- Required scopes: `connections:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `connections`, `reconnect`, `reauth`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to reconnect. |
| `api_key` | `string` | no | For api_key connectors: the replacement API key / token to store (encrypted) on the existing source. Ignored for oauth and social-handle connectors. |
| `return_to` | `string` | no | For oauth connectors: console URL to redirect back to after the re-handshake (first-party origins only). |


### `connections.update`

**Update a Connection**

Rename an established connection, or set/clear its owner - the workspace member a personal source (e.g. Gmail, Outlook) belongs to. Use when a user wants to relabel a connection to tell multiple instances apart, attribute a personal connection to a teammate, or clear that attribution. Owner applies to personal connectors only; returns the updated connection. A non-existent or cross-tenant id is a clean not-found.

- HTTP: `PATCH /connections/:id`
- Required scopes: `connections:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `connections`, `update`, `owner`, `rename`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to update. |
| `name` | `string` | no | New display label for this connection instance (trimmed; must be non-empty). Renames any connection so multiple instances of the same connector type can be told apart. |
| `owner_user_id` | `string | null` | no | The workspace member (auth user id) this personal connection belongs to. Pass null to clear the owner. Only valid for personal connectors (Gmail, Outlook). |


### `console.issues_inbox.list`

**List Issues Inbox**

List the business's issues inbox: pipeline-detected data-quality, enrichment, or signal problems surfaced for human review. Each issue has a category (e.g. unknown_speakers, unmapped_stages), severity, status (open/snoozed/resolved), and a link_surface + link_params pointing at the affected resource. Response includes per-status counts. Use when an agent needs to surface active pipeline health issues, triage by category, or check whether issues need attention before a sync.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | `string` | no | Filter by issue status: open (default), snoozed, resolved. |
| `category` | `string` | no | Filter by issue category (e.g. data_quality, enrichment_gap). Omit for all. |
| `limit` | `integer` | no | Page size 1–200 (default 50). |
| `offset` | `integer` | no | Pagination offset (default 0). |


### `console.review_tasks.list`

**List Review Queue**

List the pipeline's open review tasks for this business. Each task is a data-quality issue the pipeline flagged for human review: it may suggest merging identity records, overriding a misclassified entity field, or confirming a confidence-scored claim. Use when an agent needs to surface actionable pipeline issues, count the open queue, or inspect which entity records need human confirmation. Defaults to open tasks; pass status=all for resolved/dismissed. Paged with limit/offset.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | `string` | no | Filter by task status: open (default), resolved, dismissed. |
| `kind` | `string` | no | Filter by task kind (e.g. link_identity, field_override). Omit for all kinds. |
| `limit` | `integer` | no | Page size 1–200 (default 50). |
| `offset` | `integer` | no | Pagination offset (default 0). |


### `data.ask_sql`

**Ask SQL (natural language → SQL)**

Use when a caller wants SQL but does not know the schema. Takes a plain-English question plus optional prior turns and returns a read-only SELECT against the `interactions` table with a one-sentence explanation. SELECT-only by construction (system prompt + post-validation reject DML/DDL). Stateless: pass last N turns in `priorTurns` for follow-ups like "break that down by company". Caller runs the SQL via data.query; this op only writes the query.

- HTTP: `POST /data/ask`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `beta`
- Version: `1.0`
- Tags: `read`, `sql`, `nl2sql`, `ai`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `question` | `string` | yes | Plain-English question describing what the user wants to know. Example: "How many objection-tagged utterances came from Finance Managers in the last 30 days?" |
| `priorTurns` | `array<object>` | no | Optional rolling buffer of prior (question, sql, explanation) turns from the same conversational session. Replayed as Anthropic message history so the model can resolve follow-ups like "now break that down by company". Capped at the most recent 10 entries server-side. Frontend owns the buffer; this op is stateless. |


### `data.cluster_detail`

**Get Cluster Detail**

Full detail for a single pre-computed conversation cluster: label, description, insight, statistical measures (JSD, size anomaly), full feature distribution, representative quotes, temporal snapshots, and entity signals. Use after `data.cluster_search` has surfaced a cluster_id worth drilling into. Quotes are real but the selection and grouping are ML-generated.

- HTTP: `GET /data/clusters/:cluster_id`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.1`
- Tags: `read`, `clusters`, `drill-in`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `cluster_id` | `integer | string` | yes | The cluster_id returned by `data.cluster_search` (or any prior cluster listing). Integer on V1; opaque string on V2 — pass it back verbatim. |
| `limit` | `integer` | no | Optional cap on related content. Reserved for future use; service applies its own caps today. |
| `as_of` | `string` | no | Optional ISO-8601 date/timestamp for a themes-as-of detail read (e.g. "2026-05-12") — the cluster generation in effect then, for backtesting. Metadata + baked quotes are as-of-T; trend snapshots + entity signals are omitted (no historical snapshot). V2 pipeline only; not yet for data-scoped members. Omit for the current generation. |


### `data.cluster_search`

**Search Conversation Clusters**

Semantic pattern discovery across pre-computed ML clusters from customer conversations. Use to find recurring themes, pain points, objections, competitive mentions. Returns cluster summaries (label, top features, representative quotes, entity rollups). For verbatim quotes / named accounts / specific call evidence, follow up with `data.cluster_detail` on the most relevant cluster_id. Verify counts via `data.query`.

- HTTP: `POST /data/clusters/search`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.1`
- Tags: `read`, `clusters`, `patterns`, `search`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | `string` | yes | Natural-language query describing the pattern or theme you want to find. |
| `match_threshold` | `number` | no | Minimum cluster similarity 0-1. Defaults to 0.3. |
| `limit` | `integer` | no | Max clusters returned. Floor 10, default 20, cap 50. Higher values surface more pattern variation and improve synthesis on divergence-detection paths; lower values starve the LLM. Values outside [floor, cap] are clamped silently. Ask for MORE clusters, not fewer — defaults are tuned for fusion synthesis quality. |
| `target` | `enum("interactions" | "voice_corpus")` | no | Which corpus to search. Default 'interactions' (the original ML pattern clusters over customer conversations). Pass 'voice_corpus' to scope the search to the tenant's author voice profiles + their writing samples + scraped posts; results then represent one author voice profile each, with the standard cluster_search envelope plus voice-specific extras (author_id, voice_profile_id, confidence_score, sample_draft_count) and a 'target: voice_corpus' discriminator. Tiebreaker on equal score: higher confidence_score ranks first. Use this to pick which author should write a content piece on a given topic. |
| `as_of` | `string` | no | Optional ISO-8601 date/timestamp for a themes-as-of read (e.g. "2026-05-12") — the cluster generation in effect at that past instant, for backtesting. The tool swaps to the snapshot table-functions; do NOT hand-write a time predicate. Trend snapshots are omitted (no historical snapshot). V2 pipeline only; not yet supported for data-scoped members. Omit for current themes. |


### `data.query`

**Query Interactions (SQL)**

Use when the user wants a count, aggregate, or rows from the `interactions` table. Read-only SELECT, auto-scoped (never put `business_id` in WHERE). Explicit columns only. Enum casing matters: `interaction_type` is UPPERCASE (CALL/EMAIL/MEETING/NOTE/UTTERANCE), `speaker_type` lowercase (internal/external) — see data://schema for the rest. On 0 rows the response may include a `hint` to retry with a canonical value — follow it; NEVER substitute a different metric without asking.

- HTTP: `POST /data/query`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.2`
- Tags: `read`, `bigquery`, `sql`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `sql` | `string` | yes | SQL SELECT against the `interactions` table. Column names must be explicit (no SELECT *). `business_id` is injected automatically — do not reference it in WHERE. Enum value casing matters: `interaction_type` is UPPERCASE ('UTTERANCE'/'EMAIL'/'CALL'/'MEETING'/'NOTE'; V1 also 'INCOMING_EMAIL', folded into 'EMAIL' on V2), `speaker_type` is lowercase ('internal'/'external'). Count calls via COUNT(DISTINCT parent_interaction_id) WHERE parent_interaction_type='CALL', not interaction_type='CALL'. If unsure of casing or column existence, read `data://schema` first. |
| `limit` | `integer` | no | Row cap. Floor 1, default 100, cap 1000. Higher values surface more rows for synthesis and improve divergence detection on aggregate-heavy queries; lower values save tokens but reduce statistical confidence. Values outside [floor, cap] are clamped silently. Ask for MORE rows when synthesizing across customer segments — defaults are tuned for fusion synthesis quality. |
| `as_of` | `string` | no | Optional ISO-8601 date or timestamp for a temporal "as-of" read — answer the SAME SQL as it would have stood at a PAST instant, e.g. "2026-05-12" or "2026-05-12T00:00:00Z". The tool rewrites the query for you (do NOT hand-write a snapshot/time predicate). On `interactions` it bounds by event time; on `deal_qualification` it reads the qualification-score snapshot in effect then. On `deals` it returns a point-in-time stage/win-loss reconstruction (columns: deal_id, company_id, as_of_stage, status[won\|lost\|open], is_won, is_closed, close_date, deal_amount, source) via deal_winloss_as_of — for tenants with bridged deal stage-change history (HubSpot today; Salesforce/Pipedrive as their history backfills). Deals lacking stage history at the as_of instant return status='open'. NOTE the as_of deals projection is the reconstruction's columns only — deal columns outside that set (e.g. deal_name, pipeline_name) are not available under as_of. Omit for current data. This is the per-read primitive for backtesting how a blueprint would have performed. |


### `data.schema`

**Data Inventory**

Data inventory for the current business: interactions schema plus sample values, cluster availability, and knowledge base document count. Use when orienting at the start of a data workflow, before writing SQL or searching, so you know what exists. Replaces the old data.explore tool.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

_No structured input._


### `data.search`

**Search Knowledge Base**

Hybrid semantic + keyword search across the business knowledge base: saved research, uploaded documents, and past content outputs. Use when you need qualitative evidence, precedent, or supporting context from ingested documents - not structured fact lookup (use data.query for that). Returns one entry per document with up to three representative snippets and an origin field so you can weigh source reliability.

- HTTP: `POST /data/search`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `read`, `search`, `knowledge-base`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | `string` | yes | Natural-language search query. |
| `mode` | `enum("hybrid" | "semantic" | "keyword")` | no | Retrieval mode. hybrid merges semantic + FTS via RRF (default). semantic=pgvector only. keyword=FTS only. |
| `limit` | `integer` | no | Max chunks fetched before dedupe to documents. Defaults to 20. |
| `source_type` | `enum("saved_research" | "knowledge_bank" | "content_creation")` | no | Restrict to one document origin. Omit to search all origins. |


### `external_search.execute`

**External Search (fused market + tenant research)**

Use when answering market / competitive / industry / topic-positioning questions. Returns a divergence_map flagging where market chatter (web+news+reddit+twitter) disagrees with the tenant's own customer voice (interactions) — the unique signal vs vanilla web search. Actions: search (free-text), enrich_company (domain/name + LinkedIn+Crunchbase), enrich_person (linkedin_url/email/name), enrich_topic (topic). Tenant-aware query rewrite runs first.

- HTTP: `POST /external-search/execute`
- Required scopes: `external_search:execute`
- Required role: `viewer`
- Stability: `beta`
- Version: `1.0`
- Tags: `external`, `web`, `social`, `enrichment`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `action` | `enum("search" | "enrich_company" | "enrich_person" | "enrich_topic")` | yes | Composite picker. See the description for action grammar. |
| `query` | `string` | no | Free-text query (search action; reserved for the unified search shape). |
| `domain` | `string` | no | Company domain (e.g. stripe.com) for enrich_company. The underlying scrapers resolve the canonical platform URL via web search (cached 90d). |
| `name` | `string` | no | Name. For enrich_person this is the person name; for enrich_company this is the company name. |
| `email` | `string` | no | Email for enrich_person. Server derives both name (from local-part) and company (from domain). |
| `linkedin_url` | `string` | no | LinkedIn URL for enrich_person. |
| `twitter_handle` | `string` | no | Twitter handle for enrich_person. |
| `topic` | `string` | no | Topic for enrich_topic. |
| `since` | `string` | no | ISO-8601 lower bound for enrich_topic. |
| `personas` | `array<string>` | no | Persona labels for enrich_topic synthesis. |
| `sources` | `array<enum("web" | "reddit" | "twitter" | "news")>` | no | Source mix for enrich_topic. Default [web, reddit, twitter]. |
| `max_results` | `integer` | no | Result cap for free-text web / news fan-outs inside the composites. |
| `max_items` | `integer` | no | Item cap for twitter / reddit fan-outs inside the composites. |
| `include_domains` | `array<string>` | no | Web/news include allowlist applied during free-text fan-outs. |
| `exclude_domains` | `array<string>` | no | Web/news exclude list applied during free-text fan-outs. |
| `force_refresh` | `boolean` | no | Bypass the brief cache check and run a fresh fan-out + synthesis regardless of TTL. Default false. |
| `skip_persist` | `boolean` | no | Skip the brief write entirely. Inline result is still returned. Default false. |
| `recency_bias` | `enum("auto" | "news" | "general")` | no | For action=search: weight news vs general web in synthesis. "auto" lets synthesis decide; "news" leans recent editorial; "general" leans evergreen pages. |
| `skip_query_enrichment` | `boolean` | no | For action=search: opt out of the Haiku query-enrichment rewrite. Default false. |
| `include_internal` | `boolean` | no | For action=search: include the tenant interaction corpus pull. Default true. Set false for a market-only brief. |
| `include_social` | `boolean` | no | For action=search: include the reddit + twitter fan-out. Default true. Set false to skip social signals. |
| `mode` | `enum("brief" | "evidence")` | no | Output mode. "brief" (default) runs synthesis and returns the structured fusion brief. "evidence" skips synthesis and returns raw post-rerank items. |


### `knowledge_base.chat`

**Chat With Knowledge Base**

Ask a natural-language question against the knowledge bank and get a synthesized answer. Use when you need a composed answer drawing from multiple documents rather than raw chunks. Pass one document_id to chat with a single document; omit or pass several to chat across the bank. Streams status + source progress events; final payload contains the answer and cited sources when include_sources is true.

- HTTP: `POST /knowledge_base/chat`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `read-only`, `long-running`, `streaming`, `knowledge-base`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | `string` | yes | The user question to answer against the knowledge bank. |
| `document_ids` | `array<string>` | no | Optional scope. Pass one id for single-document chat; pass several to restrict cross-document retrieval to those documents. |
| `include_sources` | `boolean` | no | When true (default), return the retrieved source chunks alongside the answer. |
| `max_turns` | `integer` | no | Informational cap on retrieval/generation iterations. The underlying service runs a single RAG turn today; this field is reserved for future agentic expansion. |


### `knowledge_base.create_comment`

**Create Knowledge Base Version Comment**

Add a review comment to a living-doc version. Use when a reviewer replies to the agent’s rationale or opens a new thread anchored to a changed block. Pass body (required); optionally block_id (the block the thread anchors to) and thread_root_id (when replying). versioning:write + editor, mirroring the human-gated promote write.

- HTTP: `POST /knowledge-base/:id/comments`
- Required scopes: `versioning:write`
- Required role: `editor`
- Stability: `beta`
- Version: `1.0`
- Tags: `living-doc`, `review`, `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The version (knowledge_bank_documents id). |
| `body` | `string` | yes | The comment text. |
| `block_id` | `string` | no | Optional block id the thread anchors to (from the diff ops / block anchors). |
| `thread_root_id` | `string` | no | Optional root comment id when replying; omit to start a new thread. |


### `knowledge_base.delete`

**Delete Knowledge Base Document**

Remove a knowledge bank document. Use when retiring stale content from the knowledge base. Default mode is a soft archive (sets is_archived=true) so the row, storage files, and embeddings remain intact and reversible. Pass hard_delete=true to permanently remove storage files, chunk embeddings, and the document row; hard delete requires the knowledge_base:delete scope in addition to knowledge_base:write.

- HTTP: `DELETE /knowledge_base/documents/:id`
- Required scopes: `knowledge_base:delete`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `destructive`, `knowledge-base`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the document. |
| `hard_delete` | `boolean` | no | When true, permanently remove storage files, chunks, and the row. Requires knowledge_base:delete scope. |


### `knowledge_base.diff`

**Diff Knowledge Base Document Versions**

Compute a line-level unified diff of a knowledge base living-doc version (:id, the target) against a base version (defaults to the immediate predecessor; ?base=<version_id> or ?base=current). Use for the doc-page version-diff view; returns hunks, +/- stats, a truncated flag, and base_missing=true for v1, or null when the target is missing or cross-business. Alias of blueprint_output://<id>/diff - the same server-computed diff so the doc page and Workflows tab agree.

- HTTP: `GET /knowledge-base/:id/diff`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |
| `base` | `string` | no |  |


### `knowledge_base.get`

**Get Knowledge Base Document**

Fetch a single knowledge bank document by UUID. Use when the caller already has the document id and wants its full metadata + content markdown. Returns null when the document does not exist or belongs to a different business.

- HTTP: `GET /knowledge-base/:id`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |


### `knowledge_base.get_access`

**Get Knowledge Base Document Access**

Fetch the per-document read ACL for one knowledge bank document by UUID, scoped to this business. Read when you need to see which members and roles a restricted document is shared with. An empty entries list means the document carries no ACL and is workspace-wide readable by every member. Each entry resolves the subject display name (and email for members).

- HTTP: `GET /knowledge-base/:id/access`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |


### `knowledge_base.get_content`

**Get Knowledge Base Document Content**

Fetch the converted markdown body of one knowledge bank document by UUID, scoped to this business. Read when you need the full document text itself (to quote, summarise, or ground an answer) rather than the metadata row that knowledge_base.get returns. Returns an empty string while conversion is still pending, or null when the document does not exist or belongs to another business.

- HTTP: `GET /knowledge-base/:id/content`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |


### `knowledge_base.get_versions`

**List Knowledge Base Document Versions**

List all versions of a knowledge base document (its version family: current, proposed, superseded), newest first, resolved from the supplied document UUID and each row carrying its version_status. Use for the version-history view; enforces the same per-document read ACL as knowledge_base.get, so a restricted document never leaks its history and an empty list means the doc does not resolve or the caller may not read it.

- HTTP: `GET /knowledge-base/:id/versions`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |


### `knowledge_base.list`

**List Knowledge Base Documents**

List knowledge bank documents (uploads, saved research, style references) for this business. Use when enumerating documents by status, embedding_status, type, or a plain-text search. For full-content semantic search, call the knowledge_base.search tool.

- HTTP: `GET /knowledge-base`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | `string` | no |  |
| `embedding_status` | `string` | no |  |
| `document_type` | `string` | no |  |
| `search` | `string` | no |  |
| `is_archived` | `string` | no |  |
| `include_versions` | `string` | no |  |
| `limit` | `integer` | no |  |
| `offset` | `integer` | no |  |


### `knowledge_base.list_comments`

**List Knowledge Base Version Comments**

List the Level 3 review threads on a living-doc version (:id, a knowledge_bank_documents id). Use when rendering the version-review panel: agent rationale comments anchored to changed blocks plus human replies, grouped into threads with a resolved flag, with the version’s persisted block anchors. Same per-document read ACL as the version diff; null when missing or cross-business.

- HTTP: `GET /knowledge-base/:id/comments`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `beta`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |


### `knowledge_base.resolve_comment`

**Resolve Knowledge Base Version Comment**

Mark a living-doc review thread resolved (or reopen it with resolved=false). Use when a reviewer finishes acting on a thread. Operates on the thread root; passing a reply id resolves its root. versioning:write + editor, mirroring the human-gated promote write.

- HTTP: `POST /knowledge-base/:id/comments/:comment_id/resolve`
- Required scopes: `versioning:write`
- Required role: `editor`
- Stability: `beta`
- Version: `1.0`
- Tags: `living-doc`, `review`, `write`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The version the thread lives on. |
| `comment_id` | `string` | yes | The comment (or its thread root) to resolve. |
| `resolved` | `boolean` | no | true to resolve (default), false to reopen. |


### `knowledge_base.search`

**Search Knowledge Base**

Hybrid semantic + keyword search across knowledge bank chunks. Use when looking up qualitative evidence, precedent, or supporting quotes from ingested documents. Defaults to knowledge_bank source_type; pass source_types to widen into saved_research or content_creation documents, or document_ids to restrict to specific documents. Returns chunks with document titles, heading context, and a combined similarity score.

- HTTP: `POST /knowledge_base/search`
- Required scopes: `knowledge_base:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `read-only`, `search`, `knowledge-base`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | `string` | yes | Natural-language search query. |
| `mode` | `enum("hybrid" | "semantic" | "keyword")` | no | Retrieval mode. hybrid merges pgvector similarity and FTS via RRF (default). semantic uses pgvector only; keyword uses FTS only. |
| `limit` | `integer` | no | Max chunks to return. 1-100, default 20. |
| `document_ids` | `array<string>` | no | Optional list of document source_id UUIDs to restrict the search to. |
| `source_types` | `array<enum("knowledge_bank" | "saved_research" | "content_creation")>` | no | Optional list of source types to include. Defaults to ["knowledge_bank"]. |


### `knowledge_base.star`

**Star Knowledge Base Document**

Pin a knowledge bank document as workspace canon. Read when reviewing reference material (messaging frameworks, brand pillars, market research) the agent should always cite. Knowledge bank docs come from file uploads with extracted summaries already populated; pinning them surfaces the pre-extracted summary into every copilot turn for free. Idempotent. Cap 20 across all canon items workspace-wide. Admin-only.

- HTTP: `POST /knowledge-base/:id/star`
- Required scopes: `artifacts:write`
- Required role: `admin`
- Stability: `beta`
- Version: `1.0`
- Tags: `canon`, `curation`, `admin`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the KB document. |


### `knowledge_base.unstar`

**Unstar Knowledge Base Document**

Remove a knowledge bank doc from workspace canon. Use when a previously authoritative reference (messaging deck, brand pillars) is now stale or replaced. The KB document itself stays intact — only the system-prompt pre-load is dropped. Idempotent. Admin-only.

- HTTP: `POST /knowledge-base/:id/unstar`
- Required scopes: `artifacts:write`
- Required role: `admin`
- Stability: `beta`
- Version: `1.0`
- Tags: `canon`, `curation`, `admin`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the KB document. |


### `knowledge_base.update`

**Update Knowledge Base Document**

Patch metadata on an existing knowledge bank document. Use when the user asks to rename a doc, retag a doc, archive or unarchive, edit the description or notes, or flip visibility between private and public. Accepts any subset of description, document_type, filename, user_notes, is_archived, visibility; the row content (chunks, embeddings, storage files) is immutable through this path. To change content, hard-delete and re-upload.

- HTTP: `PATCH /knowledge_base/documents/:id`
- Required scopes: `knowledge_base:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `knowledge-base`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the document to patch. |
| `description` | `string | null` | no | Short human-readable description shown in the KB UI. Pass null to clear the existing value. |
| `document_type` | `string | null` | no | Free-form category tag (e.g. "proposal", "sales_call", "icp_research"). Pass null to clear. |
| `filename` | `string` | no | Original filename including extension. Stored as `original_filename` on the row. |
| `user_notes` | `string | null` | no | Caller-authored notes about the document. Pass null to clear. |
| `is_archived` | `boolean` | no | Soft-archive flag. Archived docs are excluded from default list responses; pass false to unarchive. |
| `visibility` | `enum("private" | "public")` | no | Toggle tenant-only ("private") vs anonymous-readable ("public") reads via /api/public/kb/:id. Mirrors platform_artifacts.visibility. |
| `agent_usage_instructions` | `string | null` | no | Guidance on HOW agents should use this doc (distinct from description, which says WHAT it is). Pass null to clear. |
| `review_interval_days` | `integer | null` | no | Freshness-review cadence in days. Pass null to clear (the doc then never goes stale). |
| `last_reviewed_at` | `string` | no | ISO-8601 timestamp the doc was last reviewed. The console "mark reviewed" action sets this to now, resetting the freshness clock. |


### `knowledge_base.upload`

**Upload Knowledge Base Document**

Upload a document into the business knowledge bank (PDF/DOCX/PPTX/markdown). Use when adding an asset the KB should index. Accepts base64 file_content (Datalab Marker conversion) or raw markdown_content (straight to chunk + embed). Returns the new document id plus conversion + embedding status; files are async, so poll knowledge_base.get until ready. When appending a new version of a living document, also pass version_changes (per-section change notes) for the version-review UI.

- HTTP: `POST /knowledge_base/documents`
- Required scopes: `knowledge_base:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `destructive`, `async`, `knowledge-base`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `filename` | `string` | yes | Original filename with extension (e.g. "proposal.pdf", "notes.md"). Required. |
| `file_content` | `string` | no | Base64-encoded raw file bytes. Use this for PDF, DOCX, PPTX, and similar binary formats that need Marker conversion. Max 50 MB decoded. |
| `markdown_content` | `string` | no | Plain markdown body. Use this when you already have markdown and want to skip Marker. The document is chunked and embedded synchronously. |
| `title` | `string` | no | Optional display title. Defaults to the filename when omitted. |
| `description` | `string` | no | Optional description used in UI listings. |
| `document_type` | `string` | no | Optional document type tag (e.g. "proposal", "contract", "meeting-notes"). |
| `agent_usage_instructions` | `string` | no | Optional guidance on HOW agents should use this doc (distinct from description, which says WHAT it is). Injected into agent prompts in a later wave. |
| `review_interval_days` | `integer` | no | Optional freshness-review cadence in days. Omit (or null) for a doc that never goes stale; otherwise the console flags the doc for review every N days. |
| `tags` | `array<string>` | no | Optional free-form tags. Stored as-is on the document row if present. |
| `auto_convert` | `boolean` | no | When true (default), trigger Marker conversion + embedding. Set false to stage the raw file without kicking off the pipeline. |
| `document_group_id` | `string` | no | Optional version family to append into. Omit to start a NEW document (version 1, promoted/current). Pass an existing group id to append a NEW VERSION — it lands status=proposed (a human promotes it) and does not replace the current version until promoted. Recurring blueprints pass the living document’s group id here. |
| `version_label` | `string` | no | Optional human-readable label for the version (e.g. "Jun 2026"). |
| `version_as_of` | `string` | no | Optional ISO-8601 instant the version’s content reflects (e.g. a report date). |
| `version_source` | `enum("blueprint_run" | "manual")` | no | Provenance of the version. Defaults to "manual". "blueprint_run" marks an automated append (it still lands proposed — same human gate). |
| `source_run_id` | `string` | no | Optional id of the blueprint run that produced this version (audit only; not an FK). |
| `version_changes` | `array<object>` | no | Optional per-section change notes when appending a NEW VERSION of a living document. One entry per section you changed: what changed, why, and which source tags you consulted. Persisted on the version row and surfaced in the version-review UI. |
| `base_version_id` | `string` | no | Level 3 authored patch: the id of the prior living-doc version your edit_plan is authored against (the predecessor supplied in the run prompt). Required with edit_plan. |
| `edit_plan` | `array<object>` | no | Level 3 authored patch: an ORDERED array of edit ops against the prior version’s blocks, so the version diff shows your intended changes (not a mechanical text guess) and each op’s rationale becomes a review thread. Supply alongside markdown_content (your full new body, used as a fallback if the plan does not apply). Each op is one of: { op:"replace", block_id, content_hash, content, rationale, evidence_refs[] }, { op:"delete_block", block_id, content_hash, rationale, evidence_refs[] }, or { op:"insert", anchor (a block_id, or "start"/"end"), content, rationale, evidence_refs[] }. Quote the block_id + content_hash from the PRIOR VERSION BLOCKS listing in the prompt. |
| `async` | `boolean` | no | Present for parity with other async tools. The Marker path is always async; the markdown path is always synchronous. This flag is informational and does not change behavior. |


### `notion_sync.backfill`

**Backfill Notion Sync**

Trigger a full backfill of the outbound Notion sync for a connection: enqueue every current knowledge-base document for (re)mirroring into Notion. Use when first setting up the sync or to force a full reconciliation; unchanged documents are skipped cheaply so it is safe to run repeatedly.

- HTTP: `POST /notion-sync/:id/backfill`
- Required scopes: `notion_sync:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `notion_sync`, `backfill`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The outbound Notion sync connection id. |


### `notion_sync.configure`

**Configure Notion Sync**

Configure the outbound Notion knowledge-base sync for a connection: designate the parent Notion page, provision the synced database under it, set the version + delete + drift policy and include filters, and start a full backfill. Use when a workspace sets up or reconfigures where its knowledge base mirrors into Notion.

- HTTP: `POST /notion-sync/:id/configure`
- Required scopes: `notion_sync:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `notion_sync`, `configure`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The outbound Notion sync connection id. |
| `parent_page_id` | `string` | yes | The Notion page the synced database is created under. |
| `database_title` | `string` | no | Optional title for the synced database. |
| `enabled` | `boolean` | no | Whether the sync is active (default true). |
| `version_policy` | `enum("current_only" | "all_promoted")` | no | Which versions to mirror. |
| `on_kb_delete` | `enum("archive" | "leave")` | no | What to do with the Notion page when a KB doc is removed. |
| `drift_policy` | `enum("preserve" | "overwrite")` | no | How to handle a human-edited Notion page. |
| `include` | `object` | no | Optional scope filters. |


### `notion_sync.get`

**Get Notion Sync Config**

Fetch the outbound Notion knowledge-base sync configuration for one connection - target database, version policy, include filters, and how many documents are currently mirrored. Read when opening the Notion sync settings panel for a connection. Returns null when the id is not a Notion sync connection in the caller's workspace - a not-found that never reveals existence.

- HTTP: `GET /notion-sync/:id`
- Required scopes: `notion_sync:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The outbound Notion sync connection id. |


### `notion_sync.list_pages`

**List Accessible Notion Pages**

List the Notion pages the connected integration can access, so a workspace can pick which one the synced knowledge-base database is created under. Read when a Notion authorize granted more than one page and the parent-page picker needs to be shown. Returns an empty list for a non-Notion-sync or cross-tenant id - a not-found that never reveals existence.

- HTTP: `GET /notion-sync/:id/pages`
- Required scopes: `notion_sync:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The outbound Notion sync connection id. |


### `notion_sync.list_sends`

**List Notion Sync Activity**

List the recent outbound Notion sync ledger for a connection (per-document synced / skipped / failed outcomes), newest-first, with an hourly summary. Read when showing the Notion sync activity log or diagnosing why a document did or did not mirror. Returns an empty list for a non-Notion-sync or cross-tenant id.

- HTTP: `GET /notion-sync/:id/sends`
- Required scopes: `notion_sync:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The outbound Notion sync connection id. |
| `limit` | `number` | no | Max rows (1-200, default 50). |


### `notion_sync.status`

**Get Notion Sync Status**

Fetch the live status of an outbound Notion sync: enabled, provisioned, the connect-only provisioning state, mirrored-document count, and a recent-activity summary (synced / skipped / failed in the last hour). Read when polling a Notion sync status badge or deciding whether a parent-page picker is needed. Returns null for a non-Notion-sync or cross-tenant id.

- HTTP: `GET /notion-sync/:id/status`
- Required scopes: `notion_sync:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The outbound Notion sync connection id. |


### `notion_sync.unsync`

**Unsync Notion Sync**

Tear down the outbound Notion sync for a connection: disable it and clear the mapping ledger so a later reconfigure starts clean. Use when a workspace wants to stop mirroring its knowledge base to Notion. The existing Notion pages are left in place - their data is preserved; this only stops future syncing.

- HTTP: `POST /notion-sync/:id/unsync`
- Required scopes: `notion_sync:delete`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `notion_sync`, `unsync`, `destructive`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | The outbound Notion sync connection id. |


### `prompts.data.account_deep_dive`

**Account Deep Dive Prompt**

Use when the user asks for a focused briefing on a single named account or company. Produces a three-step investigation plan (interactions, sentiment, recent summaries) that the agent executes through data.query.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `data`, `account`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account_name` | `string` | yes | Company name to investigate. |


### `prompts.data.schema_tour`

**Schema Tour Prompt**

Use when the user asks "what data is available?" or is exploring a business for the first time. Directs the agent to fetch the data://schema resource, read the analysisStrategies field, and summarize what exists before writing any SQL.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `data`, `orientation`

**Input**

_No structured input._


### `prompts.data.top_companies`

**Top Companies Prompt**

Use when the user asks which companies drove the most calls or activity recently. Produces a ranked query template and summary directive the agent runs via data.query, scoped to a configurable day window and result limit.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `data`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `days` | `number` | no | Window size in days. Defaults to 30. |
| `limit` | `number` | no | Number of companies. Defaults to 10. |


### `prompts.data.weekly_pulse`

**Weekly Pulse Prompt**

Use when the user asks for a weekly or N-day activity snapshot across interaction types (calls, emails, meetings). Produces a histogram query template plus a summary directive the agent runs via data.query.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `data`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `days` | `number` | no | Window size in days. Defaults to 7. |


### `prompts.system.account_prep`

**Account Prep Brief**

Use when the user is preparing for a scheduled sales call, customer meeting, QBR, renewal review, kickoff, or exec touchpoint with a specific account. Pulls deal stage, recent conversation themes, open opportunities, and risk signals; produces a stage-aware prep brief with talking points and questions to ask.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `system`, `gtm`, `sales-prep`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `account` | `string` | yes | Account / company name |
| `meeting_context` | `string` | no | Optional: what the meeting is for |


### `prompts.system.amdahl_gtm_playbook`

**Amdahl GTM Playbook**

Use when starting ANY go-to-market task in this workspace (research, enrichment, positioning, messaging, content, sales call prep, win-loss, outbound) and you need the live tool-routing guide for which Amdahl primitive handles which job, plus the operational defaults that make every call return useful results.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `system`, `gtm`, `routing`

**Input**

_No structured input._


### `prompts.system.competitive_positioning`

**Competitive Positioning Check**

Use when the user wants to test, validate, or sharpen current positioning, value prop, or category framing — what we claim vs what buyers echo back, where the message lands, where it misses by segment. Compares positioning entries against speaker-attributed customer voice clusters and rep-vs-buyer language gaps.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `system`, `gtm`, `positioning`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `focus` | `string` | no | Optional: specific positioning theme, pillar, or competitor angle to check |


### `prompts.system.messaging_check`

**Messaging Check**

Use when the user wants to test, refine, or audit a specific message, claim, hook, headline, or value-prop line — checking whether buyers echo it back, where it lands by segment, what they say instead. Surfaces rep-vs-buyer language gaps using the speaker-attributed call corpus.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `system`, `gtm`, `messaging`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `message` | `string` | yes | The message, claim, hook, or phrase to test |


### `prompts.system.research_competitor`

**Research Competitor**

Use when researching a specific competitor — their positioning, pricing, recent moves, market chatter, and where they show up in our own call transcripts. Pulls live external signal (news + social + web) and cross-references against our customer voice and competitive context entries.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `system`, `gtm`, `competitive-intel`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `competitor` | `string` | yes | Competitor name or domain |


### `prompts.system.research_customer`

**Research Customer**

Use when the user names a customer, account, or prospect company and asks for context, history, recent signals, or deal stage. Pulls the external company brief (firmographics + recent web/news) alongside the internal corpus (deal stage + transcripts where they appear) to build the full picture from both directions.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `system`, `gtm`, `customer-research`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `company` | `string` | yes | Company name or domain |


### `prompts.system.win_loss_analysis`

**Win/Loss Analysis**

Use when the user asks about win or loss patterns, deal outcomes, why deals close or stall, or what separates closed-won from closed-lost — across all deals, a segment cohort, or a specific competitor. Pulls deal-stage metadata, first-call patterns, competitor mentions, and objection clusters across the won and lost cohorts.

- HTTP: (not exposed as REST)
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `prompt`, `system`, `gtm`, `win-loss`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `cohort` | `string` | no | Optional: segment / vertical / size to scope the analysis |
| `competitor` | `string` | no | Optional: focus on deals where this competitor was mentioned |


### `social.connect_account`

**Connect Social Account**

Save an already-verified social account as a tracked social connection for the tenant. Use when the user has confirmed the right profile (typically right after verify_connection) and wants it tracked going forward. Dedup is automatic: re-connecting an account that is already tracked returns an already_connected marker with the existing id instead of creating a duplicate. Persists one first-party data_sources row.

- HTTP: `POST /social/connections`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `social`, `connect`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `channel` | `enum("twitter" | "linkedin")` | yes | Social channel of the account. Supported: twitter, linkedin. |
| `handle` | `string` | yes | Resolved bare handle of the account being tracked. |
| `external_account_id` | `string` | yes | Provider's stable account id (e.g. Twitter id_str); the dedup key. |
| `display_name` | `string` | no | Display name snapshot at connect time. |
| `nickname` | `string` | no | Optional tenant-defined label for this account (e.g. 'Founder - personal'), distinct from the provider display_name. |
| `avatar_url` | `string` | no | Avatar image URL snapshot. |
| `followers` | `integer` | no | Follower count snapshot. |
| `author_id` | `string` | no | Optional author profile this account belongs to. |


### `social.create_subject`

**Create Tracked Subject**

Create a tracked subject - a person or org you follow - that groups one or more social handles under one identity. Use when the user wants to start tracking someone (mark external) or register their own / a teammate's presence (mark internal). External subjects cannot have a member_user_id; an internal one may name a workspace member or be brand-owned. Link handles to it afterward.

- HTTP: `POST /social/subjects`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `social`, `subject`, `identity`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `relationship` | `enum("internal" | "external")` | yes | 'internal' = your own / a workspace member's account; 'external' = someone you track. |
| `display_name` | `string` | no | Display label for the subject (the person/org name). |
| `member_user_id` | `string` | no | The workspace member this subject IS (internal + member-owned only). Must be a member of this workspace. Omit for a brand/org-owned internal subject; never set on an external one. |
| `crm_contact_ref` | `object` | no | Optional CRM linkage (schema only in v1 - no resolver yet). Shape: { contact_id (required, non-empty), source?, label? }. Extra keys are dropped. |
| `notes` | `string` | no | Freeform notes about who this is / why tracked. |


### `social.delete_subject`

**Delete Tracked Subject**

Delete a tracked subject by id. Use when the user wants to stop tracking a person/org as a grouped identity. The social connections grouped under it are kept - they just become ungrouped (their handle data and metrics history are untouched). A non-existent or cross-tenant id is a clean not-found.

- HTTP: `DELETE /social/subjects/:id`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `social`, `subject`, `delete`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the subject to delete. |


### `social.disconnect_account`

**Disconnect Social Account**

Stop tracking a social account by removing its social connection (soft delete: the row is kept for audit but no longer synced or listed). Use when the user wants to untrack a profile they previously connected. The raw metrics history is owned by amdahl-data and is not purged here.

- HTTP: `DELETE /social/connections/:id`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.1`
- Tags: `write`, `social`, `disconnect`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to disconnect. |


### `social.get_connection`

**Get Social Connection**

Fetch a single tracked social account (social connection) by its id for the tenant. Use when you already have a connection id and need its current details — handle, display name, follower snapshot, status, last sync time. Returns one connection or a not-found error. Read-only; scoped to the calling tenant.

- HTTP: `GET /social/connections/:id`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `read`, `social`, `get`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to fetch. |


### `social.get_metric_trend`

**Get Social Metric Trend**

Read a bucketed metric trend for one tracked social account: the follower series over time (scope=connection, default) or a single post's engagement trajectory (scope=post + post_id). Read when rendering an over-time chart or answering how a metric moved. One series per channel metric; tune the window_days + granularity (hour/day/week). Returns null when the connection is not in the tenant; empty series when nothing is captured yet.

- HTTP: `GET /social/connections/:id/trend`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to read the trend for. |
| `scope` | `enum("connection" | "post")` | no | connection (follower series, default) or post (one post over time). |
| `post_id` | `string` | no | Required when scope=post: the post id to chart. |
| `granularity` | `enum("hour" | "day" | "week")` | no | Bucket size for the series. Defaults to day. |
| `window_days` | `integer` | no | Lookback window in days (default 30, capped at 3650). |


### `social.get_metrics_summary`

**Get Social Connection Summary**

Read aggregate stats for one tracked social account: current followers + growth over the window (both null when the channel does not expose followers — LinkedIn personal profiles do not via the current actor), total engagement, post count, top post, and last sync. Read when rendering the summary header or answering how an account is doing. Identify by id; tune window_days for the growth figures. Returns null when the connection is not in the tenant.

- HTTP: `GET /social/connections/:id/summary`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to summarize. |
| `window_days` | `integer` | no | Window in days for the follower-growth figure (default 30, capped 3650). |


### `social.get_portfolio_summary`

**Get Social Portfolio Summary**

Read aggregate stats across every active tracked account in the tenant: total followers + growth (null when no in-scope connection reports followers — LinkedIn personal profiles do not via the current actor), total engagement, post count, the best post across all accounts, and last sync. Read when rendering the connectors index header or answering how the whole portfolio is doing. No id — rolls up the tenant. Tune window_days for the growth figures.

- HTTP: `GET /social/summary`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `window_days` | `integer` | no | Window in days for the follower-growth figure (default 30, capped 3650). |


### `social.get_subject`

**Get Tracked Subject**

Read a single tracked subject by id for the tenant, with the social handles grouped under it. Read when you have a subject id and need its details - relationship tag, the member it represents, its linked handles. Returns one subject or null when it is not in the tenant. Scoped to the calling tenant.

- HTTP: `GET /social/subjects/:id`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the subject to fetch. |


### `social.get_subject_summary`

**Get Tracked Subject Summary**

Read aggregate stats across every social handle grouped under a tracked subject: total followers (null when no handle reports a count), total engagement, post count, a per-channel breakdown, and last sync. Read when answering how a tracked person/org is doing across all their accounts at once. Identify by subject id; tune window_days. Returns null when the subject is not in the tenant; a zeroed summary when it has no handles.

- HTTP: `GET /social/subjects/:id/summary`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the subject to summarize. |
| `window_days` | `integer` | no | Window in days for the follower-growth math (default 30, capped 3650). |


### `social.link_handle`

**Link Handle to Subject**

Group a tracked social connection under a subject by linking it - e.g. attach a person's X account to the subject that already holds their LinkedIn. Use when consolidating multiple handles for one person/org under a single tracked identity. Both must be in the tenant and the connection must be a live social handle. Missing either side is a clean not-found.

- HTTP: `POST /social/subjects/:subject_id/link`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `social`, `subject`, `link`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `subject_id` | `string` | yes | UUID of the subject to group the handle under. |
| `connection_id` | `string` | yes | UUID of the social connection (data source) to link. |


### `social.list_channel_capabilities`

**List Social Channel Capabilities**

Read the per-channel capability map: for each supported social channel (X / Twitter, LinkedIn), which entities (post / profile) it reports on and which metric keys + labels + formats each carries. Read when rendering metric toggles, legends, or column headers so the UI stays descriptor-driven and a new channel needs no front-end change. No id - it is the static, tenant-agnostic capability catalog.

- HTTP: `GET /social/capabilities`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

_No structured input._


### `social.list_connections`

**List Social Connections**

Return every tracked social account (social connection) for the tenant, newest first, excluding ones that have been disconnected. Use when the user wants to see which profiles are currently hooked up for social tracking, or before connecting a new one to check what's already there. Read-only; touches nothing.

- HTTP: `GET /social/connections`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `read`, `social`, `list`

**Input**

_No structured input._


### `social.list_post_metrics`

**List Social Post Metrics**

Read the latest engagement snapshot per post for one tracked social account: likes, reposts, replies, quotes, bookmarks, and views, newest post first, capped at 25. Read when rendering a connection’s social view or answering how its recent posts are doing. Identify the connection by its id; returns the metrics plus the sync state, or null when the connection is not in the tenant.

- HTTP: `GET /social/connections/:id/metrics`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to read metrics for. |


### `social.list_subjects`

**List Tracked Subjects**

Read every tracked subject for the tenant, newest first - the people and orgs being followed, each with the social handles grouped under it. Read when showing who is tracked, or before creating / linking to check what already exists. Filter by relationship (internal / external). A subject with no handles yet returns an empty handles list.

- HTTP: `GET /social/subjects`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `relationship` | `enum("internal" | "external")` | no | Optional filter: internal (your own/members) or external (tracked). |


### `social.refresh_connection`

**Refresh Social Connection Metrics**

Kick off a background metrics refresh for one tracked social account: ask amdahl-data to sync its source now and return immediately with a syncing acknowledgement (it does not wait). Call when the user wants fresh numbers on demand. Identify the connection by its id; poll the connection until is_syncing clears. Ingestion is owned by amdahl-data, not an in-app fetch.

- HTTP: `POST /social/connections/:id/refresh`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `social`, `metrics`, `refresh`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to refresh. |


### `social.unlink_handle`

**Unlink Handle from Subject**

Ungroup a social connection from its subject by clearing the link - the reverse of linking a handle. Use when a handle was grouped under the wrong subject, or to detach it. The connection itself is kept (handle data and metrics untouched); it just stops belonging to any subject. A non-existent or non-social connection id is a clean not-found.

- HTTP: `POST /social/subjects/:subject_id/unlink`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `social`, `subject`, `unlink`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `connection_id` | `string` | yes | UUID of the social connection (data source) to ungroup. |
| `subject_id` | `string` | no | Subject the connection is being detached from (path context; not required). |


### `social.update_connection`

**Update Social Connection**

Update a tracked social account (social connection) in place: set or clear its nickname, pause or resume tracking, and/or set the per-connection lookback window (how far back posts are snapshotted). Use when the user wants to rename a connection, toggle whether it syncs, or change its history window. Identify the connection by its id; only the fields you pass are changed. Returns the updated connection, or a not-found error if it is not in the tenant.

- HTTP: `PATCH /social/connections/:id`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `social`, `update`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the connection to update. |
| `nickname` | `string | null` | no | Tenant-defined label for this account (e.g. 'Founder - personal'), distinct from the provider display_name. Pass null to clear it. |
| `status` | `enum("active" | "paused")` | no | Tracking status: active (syncing) or paused (kept but not syncing). |
| `lookback_days` | `integer | null` | no | Per-connection sweep window in days (how far back posts are snapshotted). Pass null to use the platform default; values are clamped to 1-3650. |


### `social.update_subject`

**Update Tracked Subject**

Patch a tracked subject - rename it, retag internal/external, set or clear the workspace member it represents, edit notes or the CRM link. Use when subject details change. Only the fields you pass are touched. Rejects a change that would leave an external subject owning a member, or a member who is not in this workspace. A non-existent id is a clean not-found.

- HTTP: `PATCH /social/subjects/:id`
- Required scopes: `data:write`
- Required role: `editor`
- Stability: `stable`
- Version: `1.0`
- Tags: `write`, `social`, `subject`, `identity`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes | UUID of the subject to update. |
| `relationship` | `enum("internal" | "external")` | no | Retag the subject internal/external. |
| `display_name` | `string | null` | no | New display label, or null to clear. |
| `member_user_id` | `string | null` | no | The workspace member this subject represents (internal only; must be a member). Pass null to clear. Cannot be set on an external subject. |
| `crm_contact_ref` | `object | null` | no | CRM linkage (schema only in v1): { contact_id (required, non-empty), source?, label? }, or null to clear. Extra keys are dropped. |
| `notes` | `string | null` | no | Freeform notes, or null to clear. |


### `social.verify_connection`

**Verify Social Connection**

Resolve a social handle to its live account plus most recent post, or a typed reason it couldn't be confirmed (not_found / no_posts / private / provider_error). Use when a tenant is hooking up a profile for social tracking and you must confirm the handle points at the right account before anything is saved. Read-only probe against the channel provider; writes nothing.

- HTTP: `POST /social/verify`
- Required scopes: `data:read`
- Required role: `viewer`
- Stability: `stable`
- Version: `1.0`
- Tags: `read`, `social`, `verify`

**Input**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `channel` | `enum("twitter" | "linkedin")` | yes | Social channel to verify against. Supported: twitter, linkedin. |
| `handle` | `string` | yes | Handle to verify: a bare name, an @name, or a full profile URL. |
