conduyt-mcp 4.105.0 → 4.105.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-accessible tools. Covers the core CRM surface (contacts, deals, pipelines, custom fields, automations, messaging, and more); use `conduyt_api_catalog` to discover the full set of 600+ REST endpoints.
4
4
 
5
- ## 378 Tools
5
+ ## 383 Tools
6
6
 
7
7
  ### Discovery
8
8
  | Tool | Description |
@@ -91,6 +91,13 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
91
91
  | `conduyt_publish_automation` | Push draft actions to live |
92
92
  | `conduyt_resolve_names` | Convert tag/pipeline/user names to UUIDs |
93
93
 
94
+ ### Lead distribution (read-only)
95
+ | Tool | Description |
96
+ |------|-------------|
97
+ | `conduyt_get_distribution_step` | One Distribution step's live table: team, Receiving, tiers, caps, this step's counts, the queue and the next lead (or preview an unsaved table) |
98
+ | `conduyt_list_distribution_steps` | Every workflow step in Distribution mode (Settings > Lead Routing) |
99
+ | `conduyt_list_assignment_reasons` | The assignment reasons, resolved: by deal, contact, step, person, outcome, days |
100
+
94
101
  ### Workflow Sandbox
95
102
  | Tool | Description |
96
103
  |------|-------------|
@@ -10,6 +10,7 @@ import { registerAutomationTools } from "./tools/automations.js";
10
10
  import { registerSequenceTools } from "./tools/sequences.js";
11
11
  import { registerMessagingTools } from "./tools/messaging.js";
12
12
  import { registerMessageCheckSettingsTools } from "./tools/message-check-settings.js";
13
+ import { registerAiLocalFilesTools } from "./tools/ai-local-files.js";
13
14
  import { registerCallTools } from "./tools/calls.js";
14
15
  import { registerDialerSettingsTools, registerCallingHoursTools } from "./tools/dialer-settings.js";
15
16
  import { registerCompanyTools } from "./tools/companies.js";
@@ -48,6 +49,7 @@ import { registerSmartViewTools } from "./tools/smart-views.js";
48
49
  import { registerAccountExportTools } from "./tools/account-exports.js";
49
50
  import { registerLifecycleTools } from "./tools/lifecycle.js";
50
51
  import { registerLeadPoolTools } from "./tools/lead-pool.js";
52
+ import { registerLeadDistributionTools } from "./tools/lead-distribution.js";
51
53
  import { registerReplyCaptureTools } from "./tools/reply-capture.js";
52
54
  import { registerCallFlowTools } from "./tools/call-flows.js";
53
55
  import { registerProjectBlueTools } from "./tools/project-blue.js";
@@ -71,6 +73,7 @@ export function registerAllTools(server, client) {
71
73
  registerSequenceTools(server, client);
72
74
  registerMessagingTools(server, client);
73
75
  registerMessageCheckSettingsTools(server, client);
76
+ registerAiLocalFilesTools(server, client);
74
77
  registerCallTools(server, client);
75
78
  registerDialerSettingsTools(server, client);
76
79
  registerCallingHoursTools(server, client);
@@ -110,6 +113,7 @@ export function registerAllTools(server, client) {
110
113
  registerAccountExportTools(server, client);
111
114
  registerLifecycleTools(server, client);
112
115
  registerLeadPoolTools(server, client);
116
+ registerLeadDistributionTools(server, client);
113
117
  registerReplyCaptureTools(server, client);
114
118
  registerCallFlowTools(server, client);
115
119
  registerProjectBlueTools(server, client);
@@ -9,6 +9,10 @@ export const TOOL_ANNOTATIONS = {
9
9
  conduyt_api_catalog: ro("Get API Catalog"),
10
10
  conduyt_check_permissions: ro("Check Permissions"),
11
11
  conduyt_search: ro("Search CRM Records"),
12
+ // (#996 UI) lead distribution reads (the step table, the Settings overview, the reasons); the draft preview POST writes nothing
13
+ conduyt_get_distribution_step: ro("Get Distribution Step"),
14
+ conduyt_list_distribution_steps: ro("List Distribution Steps"),
15
+ conduyt_list_assignment_reasons: ro("List Assignment Reasons"),
12
16
  conduyt_mcp_coverage: ro("Get MCP Coverage Matrix"),
13
17
  conduyt_list_contacts: ro("List Contacts"),
14
18
  conduyt_count_contacts: ro("Count Contacts"),
@@ -96,6 +100,8 @@ export const TOOL_ANNOTATIONS = {
96
100
  conduyt_appointment_report: ro("Get Appointment Report"),
97
101
  conduyt_funnel_report: ro("Get Funnel Report"),
98
102
  conduyt_get_message_check_minimum_grade: ro("Get Message Check Minimum Grade"),
103
+ conduyt_get_ai_local_files: ro("Get AI Local Files Settings"),
104
+ conduyt_set_ai_local_files: update("Set AI Local Files Settings"),
99
105
  conduyt_update_message_check_minimum_grade: update("Update Message Check Minimum Grade"),
100
106
  conduyt_list_calls: ro("List Calls"),
101
107
  conduyt_search_dialer_history: ro("Search Dialer History"),
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ConduytClient } from "../client.js";
3
+ export declare function registerAiLocalFilesTools(server: McpServer, client: ConduytClient): void;
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import { formatResult } from "../client.js";
3
+ import { strictInput } from "./strict.js";
4
+ /**
5
+ * (#655 phase 2) Settings › AI › Local files (GET / PATCH /api/v1/settings/ai-local-files): may AI teammates use their OWNER's local
6
+ * files (read on the owner's computer by the owner's own AI, in folders the owner allows there; Conduyt never reads a disk), and the
7
+ * sensitive-number screen on those posts. Both default on. Changing either is owners and admins only, never an AI teammate's key;
8
+ * switching the screen off needs acknowledgeUnscreened: true and is audited. Refusals are surfaced verbatim, never retried.
9
+ */
10
+ const STATE = "{ data: { enabled, sensitiveScreen, aiTeammatesEnabled, canManage } }";
11
+ export function registerAiLocalFilesTools(server, client) {
12
+ strictInput(server.tool("conduyt_get_ai_local_files", `Read Settings › AI › Local files (GET /api/v1/settings/ai-local-files): ${STATE}. enabled: AI teammates may use their owner's local files (read on the owner's computer, in folders the owner allows there, only when the owner's own message asks; default true). sensitiveScreen: a post from local files is refused 422 sensitive_numbers_in_local_post when it contains a full card, Social Security or bank routing + account number (default true). aiTeammatesEnabled: the workspace's AI teammates switch. canManage: this key may change it. Requires the settings scope and settings.view.`, {}, async () => formatResult(await client.get("/api/v1/settings/ai-local-files"))));
13
+ strictInput(server.tool("conduyt_set_ai_local_files", `Change Settings › AI › Local files (PATCH /api/v1/settings/ai-local-files): pass enabled and/or sensitiveScreen; answers ${STATE}. Owners and admins only (403 otherwise; an AI teammate's own key is always 403 agent_key_refused). Switching sensitiveScreen off needs acknowledgeUnscreened: true (400 local_files_screen_acknowledgement_required otherwise): the team accepts that posts from local files are no longer checked for full card, Social Security or bank account numbers; the acknowledgement is not stored, the change is audited (ai_local_files.changed, who, when, old -> new). Switching local files off needs no acknowledgement. Refusals are surfaced verbatim, never retried.`, {
14
+ enabled: z.boolean().optional().describe("AI teammates may use their owner's local files (true) or not (false)"),
15
+ sensitiveScreen: z.boolean().optional().describe("the sensitive-number screen on posts from local files; false needs acknowledgeUnscreened: true"),
16
+ acknowledgeUnscreened: z.boolean().optional().describe("required true with sensitiveScreen: false"),
17
+ }, async ({ enabled, sensitiveScreen, acknowledgeUnscreened }) => {
18
+ if (enabled === undefined && sensitiveScreen === undefined)
19
+ throw new Error("nothing to change: pass enabled and/or sensitiveScreen");
20
+ const body = {};
21
+ if (enabled !== undefined)
22
+ body.enabled = enabled;
23
+ if (sensitiveScreen !== undefined)
24
+ body.sensitiveScreen = sensitiveScreen;
25
+ if (acknowledgeUnscreened !== undefined)
26
+ body.acknowledgeUnscreened = acknowledgeUnscreened;
27
+ return formatResult(await client.patch("/api/v1/settings/ai-local-files", body));
28
+ }));
29
+ }
@@ -0,0 +1,28 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ConduytClient } from "../client.js";
3
+ /**
4
+ * (#996 UI, tri-surface parity) the READ side of lead distribution (the assign_to_user step's "distribution" mode: tiers, caps
5
+ * per hour / day / week / month, an overflow), the same reads the step editor, Settings > Lead Routing and the assignment
6
+ * history use. All three write nothing; the table itself is edited through the workflow (conduyt_update_automation, the
7
+ * step's config) exactly as before.
8
+ * conduyt_get_distribution_step GET /automations/{id}/distribution?node_id= (the step as the runner executes it), or with
9
+ * `config` POST /automations/{id}/distribution (an unsaved draft table, still read-only)
10
+ * conduyt_list_distribution_steps GET /automations/distribution-steps
11
+ * conduyt_list_assignment_reasons GET /activities/assignment-reasons
12
+ */
13
+ export declare const ASSIGNMENT_OUTCOMES: readonly ["assigned", "catch_all", "contact_owner", "kept", "pool", "nobody"];
14
+ /** the query string the reasons route reads (repeated user_id / outcome, the rest once) */
15
+ export declare function assignmentReasonsQuery(p: {
16
+ deal_id?: string;
17
+ contact_id?: string;
18
+ automation_id?: string;
19
+ node_id?: string;
20
+ user_ids?: string[];
21
+ outcomes?: string[];
22
+ activity_ids?: string[];
23
+ from?: string;
24
+ to?: string;
25
+ per_page?: number;
26
+ cursor?: string;
27
+ }): string;
28
+ export declare function registerLeadDistributionTools(server: McpServer, client: ConduytClient): void;
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ import { formatResult } from "../client.js";
3
+ /**
4
+ * (#996 UI, tri-surface parity) the READ side of lead distribution (the assign_to_user step's "distribution" mode: tiers, caps
5
+ * per hour / day / week / month, an overflow), the same reads the step editor, Settings > Lead Routing and the assignment
6
+ * history use. All three write nothing; the table itself is edited through the workflow (conduyt_update_automation, the
7
+ * step's config) exactly as before.
8
+ * conduyt_get_distribution_step GET /automations/{id}/distribution?node_id= (the step as the runner executes it), or with
9
+ * `config` POST /automations/{id}/distribution (an unsaved draft table, still read-only)
10
+ * conduyt_list_distribution_steps GET /automations/distribution-steps
11
+ * conduyt_list_assignment_reasons GET /activities/assignment-reasons
12
+ */
13
+ export const ASSIGNMENT_OUTCOMES = ["assigned", "catch_all", "contact_owner", "kept", "pool", "nobody"];
14
+ const day = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "a calendar day, YYYY-MM-DD");
15
+ /** the query string the reasons route reads (repeated user_id / outcome, the rest once) */
16
+ export function assignmentReasonsQuery(p) {
17
+ const q = new URLSearchParams();
18
+ for (const k of ["deal_id", "contact_id", "automation_id", "node_id", "from", "to", "cursor"])
19
+ if (p[k])
20
+ q.set(k, p[k]);
21
+ for (const a of [...new Set(p.activity_ids ?? [])])
22
+ q.append("activity_id", a);
23
+ for (const u of [...new Set(p.user_ids ?? [])])
24
+ q.append("user_id", u);
25
+ for (const o of [...new Set(p.outcomes ?? [])])
26
+ q.append("outcome", o);
27
+ if (p.per_page !== undefined)
28
+ q.set("per_page", String(p.per_page));
29
+ return q.toString();
30
+ }
31
+ export function registerLeadDistributionTools(server, client) {
32
+ server.tool("conduyt_get_distribution_step", "Read ONE Distribution step's live table (assign_to_user with mode \"distribution\"), read-only: the team (a member group's active members, or the listed people, flagged when someone left the account), each person's Receiving status, tier (highest / high / low / lowest, absent = high), own caps (a period present overrides the team default; null = own unlimited; absent = inherits) and effective caps per hour / day / week / month, THIS step's assignment counts for the current periods (the account timezone, the week from Monday), the ordered queue (`order` 1 = next; `skipped` = away, not_on_account or the period whose cap is reached; `zeroCap` = a cap of 0, never gets leads), the next lead from the workflow dry run's own plan (`next.reason` is the exact reason line the step would write; `next.dryRun` the dry run's line), `problems` in plain English (a person who left, the catch-all removed). Without `config` it reads the step as the runner executes it (the published graph, else the draft; 404 when the workflow has no Distribution step with that node_id; 422 when the stored table does not read). With `config` (the step's whole assign_to_user config, mode \"distribution\") it previews that unsaved table instead and writes nothing (400 when it does not read). Permission automations.view; a deal-less API key gets 404 (every Distribution workflow assigns deals).", {
33
+ automation_id: z.string().uuid().describe("The workflow (automation) id"),
34
+ node_id: z.string().min(1).max(200).describe("The step's node id in the graph (a linear workflow's action id, else #<index>)"),
35
+ config: z.record(z.unknown()).optional().describe("Optional: an unsaved assign_to_user config to preview (mode \"distribution\", groupId OR userIds, tiers, caps, overflow, applyTo deal | both)"),
36
+ }, async ({ automation_id, node_id, config }) => formatResult(config
37
+ ? await client.post(`/api/v1/automations/${automation_id}/distribution`, { nodeId: node_id, config })
38
+ : await client.get(`/api/v1/automations/${automation_id}/distribution?node_id=${encodeURIComponent(node_id)}`)));
39
+ server.tool("conduyt_list_distribution_steps", "Settings > Lead Routing, \"Distribution in workflows\" (read-only): every workflow step running the assign_to_user Distribution mode, from the graph the runner executes, with its team (group name, or the count of picked people), receivingNow / total, who gets the next lead (`next`, the dry run's plan), the overflow (pool, none, or the catch-all person), assignedToday (this step's assignments since the account's midnight) and `problem` when the stored table would fail at run time. An aggregate: at most 200 steps (meta.max_steps) from at most 200 workflows (meta.max_workflows), newest changed first; meta.truncated says more exist (either cap). This is NOT the older account-wide Lead Routing Rules router (/api/v1/lead-routing). Permission automations.view; a deal-less API key is served an empty list.", {}, async () => formatResult(await client.get("/api/v1/automations/distribution-steps")));
40
+ server.tool("conduyt_list_assignment_reasons", "The lead distribution reasons (activities of type assignment_reason), newest first, read-only, each resolved: `reason` (the exact line, e.g. \"Assigned to Heather Henson: tier highest, 3 of 10 today\" or \"Sent to the pool: everyone was off or at cap\"), `outcome` (assigned, catch_all, contact_owner, kept, pool, nobody), the person, the deal and contact, the workflow and step (`automation`, `nodeId`, `stepLabel`), tier and today's count, `skipped` (who was passed over: a cap period, or away = not receiving leads) and `ledgerId` (the assignment_ledger row the assignment counted toward the caps; null for the overflow outcomes that assign nobody). Every reason sits on BOTH the deal and its contact, so contact_id gives the contact's whole routing history across its deals. Filters: deal_id, contact_id, automation_id + node_id (one step's history; node_id needs automation_id), user_ids (up to 50 distinct), outcomes, activity_ids (up to 100 distinct: the reasons of activities you already hold), from / to (days in the account timezone). A kept outcome's person is the owner the card stayed with. per_page 1-100 (default 25); pass meta.nextCursor as cursor for the next page. Permission activities.view, the caller's visible contacts and deals only.", {
41
+ deal_id: z.string().uuid().optional(),
42
+ contact_id: z.string().uuid().optional(),
43
+ automation_id: z.string().uuid().optional().describe("The workflow; with node_id, one step's history"),
44
+ node_id: z.string().min(1).max(200).optional().describe("The step's node id (needs automation_id)"),
45
+ // (r2, Codex r1 P3) repeats are removed BEFORE the caps, as the route does (50 people, 100 activities)
46
+ user_ids: z.array(z.string().uuid()).optional().describe("Only reasons that assigned (or kept) one of these people; up to 50 distinct"),
47
+ outcomes: z.array(z.enum(ASSIGNMENT_OUTCOMES)).optional(),
48
+ activity_ids: z.array(z.string().uuid()).optional().describe("The reasons of these assignment_reason activities (a feed resolving the rows it loaded); up to 100 distinct"),
49
+ from: day.optional().describe("First day, YYYY-MM-DD, in the account timezone"),
50
+ to: day.optional().describe("Last day, YYYY-MM-DD, in the account timezone (inclusive)"),
51
+ per_page: z.number().int().min(1).max(100).optional(),
52
+ cursor: z.string().min(1).optional().describe("meta.nextCursor of the previous page"),
53
+ }, async (p) => {
54
+ if (p.node_id && !p.automation_id)
55
+ throw new Error("node_id needs automation_id (a step belongs to one workflow)");
56
+ if (new Set(p.user_ids ?? []).size > 50)
57
+ throw new Error("user_ids takes at most 50 distinct people");
58
+ if (new Set(p.activity_ids ?? []).size > 100)
59
+ throw new Error("activity_ids takes at most 100 distinct activities");
60
+ return formatResult(await client.get(`/api/v1/activities/assignment-reasons?${assignmentReasonsQuery(p)}`));
61
+ });
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.105.0",
3
+ "version": "4.105.2",
4
4
  "mcpName": "com.conduyt/mcp",
5
5
  "description": "MCP server for Conduyt CRM — expose CRM operations as AI-accessible tools",
6
6
  "type": "module",
@@ -25,6 +25,11 @@
25
25
  "status": "excluded",
26
26
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
27
27
  },
28
+ {
29
+ "match": "POST /api/v1/account-exports",
30
+ "status": "planned",
31
+ "reason": "(#618) starts a background Activity export run (owners and admins; the CSV past 100,000 rows); a typed tool is wanted but not built (the #655 phase 2 parity release only refreshed the snapshot to app f3455749d)"
32
+ },
28
33
  {
29
34
  "match": "GET /api/v1/account-exports/:id/files/:entity/download",
30
35
  "status": "excluded",
@@ -1,11 +1,11 @@
1
1
  {
2
- "generatedAt": "2026-10-30T13:37:28.694Z",
3
- "toolCount": 378,
4
- "totalEndpoints": 1134,
2
+ "generatedAt": "2026-11-05T14:27:30.253Z",
3
+ "toolCount": 383,
4
+ "totalEndpoints": 1150,
5
5
  "counts": {
6
- "covered": 331,
7
- "excluded": 350,
8
- "planned": 453,
6
+ "covered": 337,
7
+ "excluded": 354,
8
+ "planned": 459,
9
9
  "uncategorized": 0
10
10
  },
11
11
  "matrix": {
@@ -265,6 +265,10 @@
265
265
  "conduyt_account_exports_list"
266
266
  ]
267
267
  },
268
+ "POST /api/v1/account-exports": {
269
+ "status": "planned",
270
+ "reason": "(#618) starts a background Activity export run (owners and admins; the CSV past 100,000 rows); a typed tool is wanted but not built (the #655 phase 2 parity release only refreshed the snapshot to app f3455749d)"
271
+ },
268
272
  "GET /api/v1/account-exports/:id/files/:entity/download": {
269
273
  "status": "excluded",
270
274
  "reason": "raw CSV bytes (up to 50k rows) do not belong in a tool result; conduyt_account_exports_list returns the authed downloadPath for API/CLI retrieval"
@@ -301,6 +305,12 @@
301
305
  "status": "planned",
302
306
  "reason": "activity detail reads beyond the log tools"
303
307
  },
308
+ "GET /api/v1/activities/assignment-reasons": {
309
+ "status": "covered",
310
+ "tools": [
311
+ "conduyt_list_assignment_reasons"
312
+ ]
313
+ },
304
314
  "GET /api/v1/activity/export.csv": {
305
315
  "status": "covered",
306
316
  "tools": [
@@ -351,6 +361,14 @@
351
361
  "status": "excluded",
352
362
  "reason": "super-admin platform operations (Conduyt staff only)"
353
363
  },
364
+ "GET /api/v1/admin/external-deletions": {
365
+ "status": "excluded",
366
+ "reason": "super-admin platform operations (Conduyt staff only)"
367
+ },
368
+ "POST /api/v1/admin/external-deletions/:id/replay": {
369
+ "status": "excluded",
370
+ "reason": "super-admin platform operations (Conduyt staff only)"
371
+ },
354
372
  "POST /api/v1/admin/impersonate": {
355
373
  "status": "excluded",
356
374
  "reason": "super-admin platform operations (Conduyt staff only)"
@@ -697,6 +715,18 @@
697
715
  "status": "planned",
698
716
  "reason": "automation subroutes beyond the workflow-builder tools (step-logs detail, node ops)"
699
717
  },
718
+ "GET /api/v1/automations/:id/distribution": {
719
+ "status": "covered",
720
+ "tools": [
721
+ "conduyt_get_distribution_step"
722
+ ]
723
+ },
724
+ "POST /api/v1/automations/:id/distribution": {
725
+ "status": "covered",
726
+ "tools": [
727
+ "conduyt_get_distribution_step"
728
+ ]
729
+ },
700
730
  "POST /api/v1/automations/:id/dry-run": {
701
731
  "status": "covered",
702
732
  "tools": [
@@ -746,6 +776,12 @@
746
776
  "status": "planned",
747
777
  "reason": "automation subroutes beyond the workflow-builder tools (step-logs detail, node ops)"
748
778
  },
779
+ "GET /api/v1/automations/distribution-steps": {
780
+ "status": "covered",
781
+ "tools": [
782
+ "conduyt_list_distribution_steps"
783
+ ]
784
+ },
749
785
  "GET /api/v1/automations/events": {
750
786
  "status": "planned",
751
787
  "reason": "automation subroutes beyond the workflow-builder tools (step-logs detail, node ops)"
@@ -1337,6 +1373,10 @@
1337
1373
  "status": "excluded",
1338
1374
  "reason": "(#599) session-only (assertSessionOnly): a channel's agents-respond mode is an admin setting; every API key is refused"
1339
1375
  },
1376
+ "GET /api/v1/chat/channels/:id/linked-record": {
1377
+ "status": "planned",
1378
+ "reason": "team chat read/post for agents"
1379
+ },
1340
1380
  "POST /api/v1/chat/channels/:id/members": {
1341
1381
  "status": "planned",
1342
1382
  "reason": "team chat read/post for agents"
@@ -1640,6 +1680,10 @@
1640
1680
  "conduyt_forget_contact"
1641
1681
  ]
1642
1682
  },
1683
+ "GET /api/v1/contacts/:id/phone-reachability": {
1684
+ "status": "planned",
1685
+ "reason": "contact subroutes beyond the core CRUD/search tools"
1686
+ },
1643
1687
  "GET /api/v1/contacts/:id/score": {
1644
1688
  "status": "planned",
1645
1689
  "reason": "contact subroutes beyond the core CRUD/search tools"
@@ -2056,6 +2100,10 @@
2056
2100
  "status": "excluded",
2057
2101
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
2058
2102
  },
2103
+ "GET /api/v1/cron/reap-expired-sessions": {
2104
+ "status": "excluded",
2105
+ "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
2106
+ },
2059
2107
  "GET /api/v1/cron/reap-orphaned-executions": {
2060
2108
  "status": "excluded",
2061
2109
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
@@ -2685,6 +2733,10 @@
2685
2733
  "status": "excluded",
2686
2734
  "reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
2687
2735
  },
2736
+ "GET /api/v1/dialer/status-log/timeline": {
2737
+ "status": "excluded",
2738
+ "reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
2739
+ },
2688
2740
  "POST /api/v1/dialer/token": {
2689
2741
  "status": "excluded",
2690
2742
  "reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
@@ -2885,6 +2937,10 @@
2885
2937
  "conduyt_add_email_opt_out"
2886
2938
  ]
2887
2939
  },
2940
+ "POST /api/v1/email/opt-out-list/check": {
2941
+ "status": "planned",
2942
+ "reason": "bulk email send surfaces"
2943
+ },
2888
2944
  "POST /api/v1/email/opt-out-list/resubscribe": {
2889
2945
  "status": "covered",
2890
2946
  "tools": [
@@ -3169,6 +3225,14 @@
3169
3225
  "status": "planned",
3170
3226
  "reason": "import job monitoring/resubmission (ai-import tool covers creation)"
3171
3227
  },
3228
+ "GET /api/v1/imports/:id/restore": {
3229
+ "status": "planned",
3230
+ "reason": "import job monitoring/resubmission (ai-import tool covers creation)"
3231
+ },
3232
+ "POST /api/v1/imports/:id/restore": {
3233
+ "status": "planned",
3234
+ "reason": "import job monitoring/resubmission (ai-import tool covers creation)"
3235
+ },
3172
3236
  "POST /api/v1/imports/:id/resubmit": {
3173
3237
  "status": "planned",
3174
3238
  "reason": "import job monitoring/resubmission (ai-import tool covers creation)"
@@ -4305,6 +4369,18 @@
4305
4369
  "status": "planned",
4306
4370
  "reason": "account settings read/update surfaces"
4307
4371
  },
4372
+ "GET /api/v1/settings/ai-local-files": {
4373
+ "status": "covered",
4374
+ "tools": [
4375
+ "conduyt_get_ai_local_files"
4376
+ ]
4377
+ },
4378
+ "PATCH /api/v1/settings/ai-local-files": {
4379
+ "status": "covered",
4380
+ "tools": [
4381
+ "conduyt_set_ai_local_files"
4382
+ ]
4383
+ },
4308
4384
  "GET /api/v1/settings/ai-teammates": {
4309
4385
  "status": "excluded",
4310
4386
  "reason": "(#614) session-only (assertSessionOnly): an admin turns AI teammates on or off in Settings > AI (Professional and above, the trial, comp); every API key is refused"