conduyt-mcp 4.69.0 → 4.71.0

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.
@@ -24,9 +24,51 @@ const pageFields = {
24
24
  leadSource: z.string().max(100).nullable().optional().describe("lead source stamped on contacts the page creates"),
25
25
  brandName: z.string().max(100).nullable().optional().describe("white-label name shown on the page and in Zoom meeting names"),
26
26
  embedAllowedOrigins: z.array(z.string().url()).nullable().optional().describe("origins allowed to embed/call the page; null or empty = any"),
27
+ documentsEnabled: z.boolean().optional().describe("the documents step: the public upload-url and documents routes answer for this page's bookings (bank statements and funding agreements attached to the lead)"),
28
+ documentsPageUrl: z.string().url().nullable().optional().describe("the site page the confirmation email links the visitor back to for the upload (https; the page reads ?appointment=<id>); null = no link"),
27
29
  availabilityRules: z.array(availabilityWindow).nullable().optional().describe("availability override; null = the host's own rules"),
28
30
  isActive: z.boolean().optional(),
29
31
  };
32
+ /** (Codex rb-r9 m3) every other field PATCH /booking-pages/{id} accepts (the OpenAPI BookingPagePatch schema): with pageFields
33
+ * the update tool covers the route's whole body, typed as the route normalises it */
34
+ const updateOnlyFields = {
35
+ durationOptions: z.array(z.object({ label: z.string().min(1), minutes: z.number().int().min(5).max(480) })).nullable().optional().describe("selectable durations; empty or null clears"),
36
+ bufferBefore: z.number().int().min(0).nullable().optional().describe("minutes blocked before each booking"),
37
+ bufferAfter: z.number().int().min(0).nullable().optional().describe("minutes blocked after each booking"),
38
+ maxBookingsPerDay: z.number().int().min(1).nullable().optional(),
39
+ color: z.string().regex(/^#[0-9a-fA-F]{3,8}$/).nullable().optional().describe("hex colour"),
40
+ thankYouMessage: z.string().max(2000).nullable().optional(),
41
+ redirectUrl: z.string().url().nullable().optional().describe("where the visitor goes after booking"),
42
+ requirePhone: z.boolean().optional(),
43
+ embedEnabled: z.boolean().optional(),
44
+ // (Codex rb-r10 m3) the shapes the route's sanitizers keep (an entry outside them is dropped; a list with none clears)
45
+ customQuestions: z.array(z.object({ id: z.string().max(57).optional(), label: z.string().min(1).max(500), type: z.enum(["short_text", "long_text", "email", "phone", "select", "checkbox", "date", "number", "hidden"]), required: z.boolean().optional(), options: z.array(z.string().max(500)).max(50).optional(), defaultValue: z.string().max(500).optional() })).max(50).optional(),
46
+ formFields: z.array(z.object({ id: z.string().min(1).max(64), label: z.string().min(1).max(120), type: z.enum(["text", "email", "tel", "textarea"]), show: z.boolean(), required: z.boolean(), locked: z.boolean().optional() })).max(20).nullable().optional(),
47
+ fieldMappings: z.array(z.object({ formFieldId: z.string().min(1).max(64), contactField: z.string().regex(/^(firstName|lastName|email|phone|company|jobTitle|address|city|state|zip|country|source|notes|website|customFields\.[a-zA-Z_][a-zA-Z0-9_]*)$/) })).max(20).nullable().optional(),
48
+ reminders: z.array(z.object({ type: z.enum(["email", "sms"]), minutesBefore: z.number().int().min(1).max(10080) })).max(10).optional(),
49
+ logoUrl: z.string().url().nullable().optional(),
50
+ hideBranding: z.boolean().optional(),
51
+ template: z.enum(["bda"]).nullable().optional().describe("null = the default page, 'bda' = the BDA template"),
52
+ reviews: z.object({ badges: z.array(z.record(z.string(), z.unknown())).optional(), testimonials: z.array(z.record(z.string(), z.unknown())).optional() }).nullable().optional(),
53
+ campaign: z.string().nullable().optional(),
54
+ hyrosTag: z.string().nullable().optional(),
55
+ eventType: z.enum(["one_on_one", "round_robin", "collective", "group"]).optional(),
56
+ maxAttendees: z.number().int().min(1).nullable().optional(),
57
+ hostUserIds: z.array(z.string().uuid()).optional().describe("users on this account who can host"),
58
+ hostGroupId: z.string().uuid().nullable().optional(),
59
+ hostPriorities: z.record(z.string(), z.enum(["highest", "high", "low", "lowest"])).nullable().optional().describe("host user id to priority"),
60
+ distributionMethod: z.enum(["maximize", "equal"]).optional(),
61
+ paymentEnabled: z.boolean().optional().describe("accepted, but checkout is not implemented yet: the page stays unpaid and the answer carries a warning"),
62
+ paymentAmount: z.number().int().min(1).nullable().optional().describe("cents"),
63
+ paymentCurrency: z.enum(["usd", "eur", "gbp", "cad", "aud", "mxn"]).nullable().optional(),
64
+ stripeProductId: z.string().nullable().optional(),
65
+ stripePriceId: z.string().nullable().optional(),
66
+ gdprEnabled: z.boolean().optional(),
67
+ gdprText: z.string().max(2000).nullable().optional(),
68
+ gdprLinkUrl: z.string().url().nullable().optional(),
69
+ waitlistEnabled: z.boolean().optional(),
70
+ enableIcalFeed: z.boolean().optional().describe("true issues a new iCal feed token, false revokes it"),
71
+ };
30
72
  export function registerBookingPageTools(server, client) {
31
73
  server.tool("conduyt_list_booking_pages", "List the account's booking pages (GET /api/v1/booking-pages): slug, title, host, duration, active, video option. Requires calendar:view.", { activeOnly: z.boolean().optional().describe("only pages accepting bookings") }, async ({ activeOnly }) => {
32
74
  const result = await client.get(`/api/v1/booking-pages?${activeOnly ? "is_active=true" : ""}`);
@@ -40,7 +82,7 @@ export function registerBookingPageTools(server, client) {
40
82
  const result = await client.post("/api/v1/booking-pages", params);
41
83
  return formatResult(result);
42
84
  });
43
- server.tool("conduyt_update_booking_page", "Update a booking page (PATCH /api/v1/booking-pages/{id}): only the fields given change. videoProvider 'zoom' turns on one Zoom meeting per booking (null turns it off and clears the host); videoHostEmail must be a user of the connected Zoom account (null = the app's own user). Requires calendar:edit (admin/owner).", { id: z.string().uuid().describe("booking page UUID"), ...pageFields }, async ({ id, ...updates }) => {
85
+ server.tool("conduyt_update_booking_page", "Update a booking page (PATCH /api/v1/booking-pages/{id}): only the fields given change. videoProvider 'zoom' turns on one Zoom meeting per booking (null turns it off and clears the host); videoHostEmail must be a user of the connected Zoom account (null = the app's own user). Requires calendar:edit (admin/owner).", { id: z.string().uuid().describe("booking page UUID"), ...pageFields, ...updateOnlyFields }, async ({ id, ...updates }) => {
44
86
  const result = await client.patch(`/api/v1/booking-pages/${id}`, updates);
45
87
  return formatResult(result);
46
88
  });
@@ -1,7 +1,28 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { ConduytClient } from "../client.js";
3
+ type DisclosureOn = "inbound" | "outbound" | "both";
4
+ type RecordingPolicy = {
5
+ inbound: boolean;
6
+ outbound: boolean;
7
+ disclosure: {
8
+ enabled: boolean;
9
+ on: DisclosureOn;
10
+ };
11
+ consentGreeting: string;
12
+ enabled: boolean;
13
+ };
14
+ /**
15
+ * What a stored policy MEANS, read the way the server reads it (Codex r5): a workspace that never saved the new shape
16
+ * still stores the old one, `{ enabled, consentGreeting }`, which means outbound recording with the disclosure on
17
+ * whenever a wording is set. Handed back raw, an agent auditing consent would read no disclosure at all. `enabled`
18
+ * stays beside `outbound` as the old name of the same switch. null for a value that is not a policy.
19
+ */
20
+ export declare function effectiveRecordingPolicy(raw: unknown): RecordingPolicy | null;
21
+ /** the iPhone line has no inbound calls: what its policy does there, the way the server applies it */
22
+ export declare function iphoneDomain(policy: RecordingPolicy): RecordingPolicy;
3
23
  /**
4
24
  * Smart Dialing policy (GET/PATCH /api/v1/dialer/settings): the account-wide rules every rep's Smart Dialing run
5
25
  * follows. Read needs the dialer scope; update needs the settings scope (admin). (#95/#96, 2026-09-16)
6
26
  */
7
27
  export declare function registerDialerSettingsTools(server: McpServer, client: ConduytClient): void;
28
+ export {};
@@ -1,5 +1,34 @@
1
1
  import { z } from "zod";
2
2
  import { formatResult } from "../client.js";
3
+ import { patchGeneralSettings } from "./lifecycle.js";
4
+ /** the two lines a workspace records calls on: its Twilio lines (settings.recording) and its iPhone line (settings.projectBlueRecording) */
5
+ const RECORDING_KEY = { twilio: "recording", iphone: "projectBlueRecording" };
6
+ const isOn = (v) => v === "inbound" || v === "outbound" || v === "both";
7
+ /**
8
+ * What a stored policy MEANS, read the way the server reads it (Codex r5): a workspace that never saved the new shape
9
+ * still stores the old one, `{ enabled, consentGreeting }`, which means outbound recording with the disclosure on
10
+ * whenever a wording is set. Handed back raw, an agent auditing consent would read no disclosure at all. `enabled`
11
+ * stays beside `outbound` as the old name of the same switch. null for a value that is not a policy.
12
+ */
13
+ export function effectiveRecordingPolicy(raw) {
14
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
15
+ return null;
16
+ const r = raw;
17
+ const greeting = typeof r.consentGreeting === "string" ? r.consentGreeting.trim() : "";
18
+ const newShape = typeof r.inbound === "boolean" || typeof r.outbound === "boolean" || (r.disclosure !== undefined && r.disclosure !== null);
19
+ if (newShape) {
20
+ const d = (r.disclosure && typeof r.disclosure === "object" ? r.disclosure : {});
21
+ const outbound = typeof r.outbound === "boolean" ? r.outbound : r.enabled === true;
22
+ return { inbound: r.inbound === true, outbound, disclosure: { enabled: d.enabled === true, on: isOn(d.on) ? d.on : "outbound" }, consentGreeting: greeting, enabled: outbound };
23
+ }
24
+ const outbound = r.enabled === true;
25
+ return { inbound: false, outbound, disclosure: { enabled: outbound && greeting.length > 0, on: "outbound" }, consentGreeting: greeting, enabled: outbound };
26
+ }
27
+ /** the iPhone line has no inbound calls: what its policy does there, the way the server applies it */
28
+ export function iphoneDomain(policy) {
29
+ const on = policy.disclosure.on;
30
+ return { ...policy, inbound: false, disclosure: on === "inbound" ? { enabled: false, on: "outbound" } : { enabled: policy.disclosure.enabled, on: "outbound" } };
31
+ }
3
32
  /**
4
33
  * Smart Dialing policy (GET/PATCH /api/v1/dialer/settings): the account-wide rules every rep's Smart Dialing run
5
34
  * follows. Read needs the dialer scope; update needs the settings scope (admin). (#95/#96, 2026-09-16)
@@ -20,4 +49,56 @@ export function registerDialerSettingsTools(server, client) {
20
49
  const result = await client.patch("/api/v1/dialer/settings", { autoAdvanceEnabled, pauseSeconds, advanceOn, openLeadOnDial, smartDialCallerId });
21
50
  return formatResult(result);
22
51
  });
52
+ // Call recording policy (the Call recording card of Settings, released 2026-09-30): which calls are recorded and
53
+ // whether callers hear a disclosure. Stored in the general settings (GET/PATCH /api/v1/settings).
54
+ server.tool("conduyt_get_call_recording_settings", "Read the workspace's call recording policy for both kinds of line. twilio = the workspace's Twilio lines: always an object, with everything off when the workspace never set it. Needs a key whose creator may view settings; without it the tool answers with an error instead of a policy. iphone = the iPhone line's OWN policy (outbound only); null = none was written, and the iPhone line then follows the Twilio lines' policy without its inbound part. Each object is the policy as it is APPLIED: inbound, outbound, disclosure { enabled, on: 'inbound' | 'outbound' | 'both' } (whether a recording disclosure is played and on which calls), consentGreeting (what is said; empty = the default sentence) and enabled (the OLD name of the outbound switch: the same value as outbound). A workspace that never saved the new shape is read the way the server reads its old one: outbound recording with the disclosure on whenever a wording is set. The workspace is responsible for consent where it operates.", {}, async () => {
55
+ const result = (await client.get("/api/v1/settings"));
56
+ const raw = result?.data ?? {};
57
+ const twilio = effectiveRecordingPolicy(raw[RECORDING_KEY.twilio]);
58
+ // a key whose creator may not view settings gets a short answer WITHOUT the policy: that is "not visible",
59
+ // never "not configured" (the full answer always carries the Twilio policy)
60
+ if (!twilio) {
61
+ return { content: [{ type: "text", text: "The call recording policy is not visible to this API key: its creator needs permission to view settings. Nothing can be said about whether calls are recorded." }], isError: true };
62
+ }
63
+ const iphone = effectiveRecordingPolicy(raw[RECORDING_KEY.iphone]);
64
+ return formatResult({ twilio, iphone: iphone ? iphoneDomain(iphone) : null });
65
+ });
66
+ server.tool("conduyt_update_call_recording_settings", "Change the workspace's call recording policy (admin: settings scope). line: 'twilio' (default) or 'iphone'. Send only what changes; what is not sent stays as it is. inbound / outbound: record calls the team receives / makes. disclosureEnabled + disclosureOn ('inbound' | 'outbound' | 'both'): play a recording disclosure and on which calls (send both together). On the iPhone line the disclosure plays on outbound calls only: 'inbound' is refused and 'both' is saved as 'outbound'. consentGreeting: the sentence that is said (500 characters at most). enabled: the OLD name of the outbound switch, accepted for older callers and sent as outbound (when both are sent, outbound decides); nothing else changes with it. Read conduyt_get_call_recording_settings first. The workspace is responsible for consent where it operates.", {
67
+ line: z.enum(["twilio", "iphone"]).optional().describe("Which line's policy: the Twilio lines (default) or the iPhone line"),
68
+ inbound: z.boolean().optional().describe("Record calls the team receives (Twilio lines only)"),
69
+ outbound: z.boolean().optional().describe("Record calls the team makes, including Smart Dialing"),
70
+ enabled: z.boolean().optional().describe("The OLD name of the outbound switch (older callers); prefer outbound"),
71
+ disclosureEnabled: z.boolean().optional().describe("Play a recording disclosure (send together with disclosureOn)"),
72
+ disclosureOn: z.enum(["inbound", "outbound", "both"]).optional().describe("Which calls the disclosure is played on (send together with disclosureEnabled)"),
73
+ consentGreeting: z.string().trim().max(500).optional().describe("What is said; empty string = the default sentence"),
74
+ }, async ({ line, inbound, outbound, enabled, disclosureEnabled, disclosureOn, consentGreeting }) => {
75
+ const fail = (text) => ({ content: [{ type: "text", text }], isError: true });
76
+ if ([inbound, outbound, enabled, disclosureEnabled, disclosureOn, consentGreeting].every((v) => v === undefined))
77
+ return fail("Nothing to change: pass at least one of inbound, outbound, disclosureEnabled + disclosureOn, consentGreeting.");
78
+ if ((disclosureEnabled === undefined) !== (disclosureOn === undefined))
79
+ return fail("disclosureEnabled and disclosureOn are sent together: the disclosure is one setting (whether it plays, and on which calls).");
80
+ const which = line ?? "twilio";
81
+ if (which === "iphone" && inbound !== undefined)
82
+ return fail("The iPhone line records outbound calls only: inbound is not a setting of that line.");
83
+ // the iPhone line has no inbound calls: a disclosure on inbound only would never play, so it is refused here
84
+ // instead of saved as if it were on; 'both' is what the line does on its outbound calls
85
+ if (which === "iphone" && disclosureOn === "inbound")
86
+ return fail("The iPhone line has outbound calls only: a disclosure on inbound calls would never play. Send disclosureOn 'outbound'.");
87
+ const on = which === "iphone" && disclosureOn === "both" ? "outbound" : disclosureOn;
88
+ const policy = {};
89
+ if (inbound !== undefined)
90
+ policy.inbound = inbound;
91
+ // (Codex r4) the old name is the outbound switch and nothing else: sent AS outbound, never as enabled, because
92
+ // the server gives an enabled-only write over a policy saved in the old shape its old whole meaning (the
93
+ // disclosure recomputed from it), which this tool promises not to touch
94
+ if (outbound !== undefined)
95
+ policy.outbound = outbound;
96
+ else if (enabled !== undefined)
97
+ policy.outbound = enabled;
98
+ if (disclosureEnabled !== undefined && on !== undefined)
99
+ policy.disclosure = { enabled: disclosureEnabled, on };
100
+ if (consentGreeting !== undefined)
101
+ policy.consentGreeting = consentGreeting;
102
+ return patchGeneralSettings(client, { [RECORDING_KEY[which]]: policy });
103
+ });
23
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt-mcp",
3
- "version": "4.69.0",
3
+ "version": "4.71.0",
4
4
  "description": "MCP server for Conduyt CRM — expose CRM operations as AI-accessible tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,7 +1,7 @@
1
1
  {
2
- "description": "SOL-016: the INTENTIONAL REST->MCP coverage policy. Every catalog endpoint must be either covered by a tool (derived automatically), matched by a rule here, or the coverage gate fails \u2014 silent drift between 'discoverable API' and 'callable MCP' is the failure mode this prevents. Rules are first-match-wins prefix matches on 'METHOD /path' keys (a bare path prefix matches every method).",
2
+ "description": "SOL-016: the INTENTIONAL REST->MCP coverage policy. Every catalog endpoint must be either covered by a tool (derived automatically), matched by a rule here, or the coverage gate fails — silent drift between 'discoverable API' and 'callable MCP' is the failure mode this prevents. Rules are first-match-wins prefix matches on 'METHOD /path' keys (a bare path prefix matches every method).",
3
3
  "statuses": {
4
- "excluded": "never MCP-callable by design \u2014 the reason says why",
4
+ "excluded": "never MCP-callable by design — the reason says why",
5
5
  "planned": "callable in principle; a typed tool is wanted but not built yet"
6
6
  },
7
7
  "rules": [
@@ -43,22 +43,22 @@
43
43
  {
44
44
  "match": "/api/public/",
45
45
  "status": "excluded",
46
- "reason": "public unauthenticated surfaces (forms, booking, unsubscribe) \u2014 no key semantics"
46
+ "reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
47
47
  },
48
48
  {
49
49
  "match": "/api/v1/public/",
50
50
  "status": "excluded",
51
- "reason": "public unauthenticated surfaces \u2014 no key semantics"
51
+ "reason": "public unauthenticated surfaces — no key semantics"
52
52
  },
53
53
  {
54
54
  "match": "/api/v1/webhooks/manage",
55
55
  "status": "planned",
56
- "reason": "OUTBOUND webhook subscription management (CRUD/test/deliveries) \u2014 agent-relevant, unlike the inbound receivers below"
56
+ "reason": "OUTBOUND webhook subscription management (CRUD/test/deliveries) — agent-relevant, unlike the inbound receivers below"
57
57
  },
58
58
  {
59
59
  "match": "/api/v1/webhooks/replay",
60
60
  "status": "planned",
61
- "reason": "outbound webhook delivery replay \u2014 agent-relevant"
61
+ "reason": "outbound webhook delivery replay — agent-relevant"
62
62
  },
63
63
  {
64
64
  "match": "/api/v1/webhooks/",
@@ -78,7 +78,7 @@
78
78
  {
79
79
  "match": "/api/v1/dialer/",
80
80
  "status": "excluded",
81
- "reason": "realtime browser dialer (WebRTC tokens, live call control) \u2014 needs a live audio session"
81
+ "reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
82
82
  },
83
83
  {
84
84
  "match": "/api/v1/screen-share",
@@ -208,7 +208,7 @@
208
208
  {
209
209
  "match": "/api/v1/email-domains",
210
210
  "status": "planned",
211
- "reason": "sending-domain management (reply-capture subpaths ARE covered \u2014 see the reply_capture_* tools)"
211
+ "reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
212
212
  },
213
213
  {
214
214
  "match": "/api/v1/mailbox",
@@ -403,7 +403,7 @@
403
403
  {
404
404
  "match": "/api/track/",
405
405
  "status": "excluded",
406
- "reason": "email open/click tracking pixels and redirects \u2014 recipient-facing"
406
+ "reason": "email open/click tracking pixels and redirects — recipient-facing"
407
407
  },
408
408
  {
409
409
  "match": "/api/webhooks/",
@@ -508,7 +508,7 @@
508
508
  {
509
509
  "match": "/api/v1/automation-folders",
510
510
  "status": "planned",
511
- "reason": "automation-folder organization (shipped 2026-08) \u2014 agent-relevant workflow housekeeping; typed tools wanted"
511
+ "reason": "automation-folder organization (shipped 2026-08) — agent-relevant workflow housekeeping; typed tools wanted"
512
512
  },
513
513
  {
514
514
  "match": "POST /api/v1/call-flows/audio",
@@ -518,7 +518,37 @@
518
518
  {
519
519
  "match": "GET /api/v1/ops/capacity",
520
520
  "status": "planned",
521
- "reason": "operator capacity dashboard (#51, shipped 2026-09) \u2014 read-only queue/park telemetry; a typed conduyt_capacity_snapshot tool is agent-relevant for throughput questions"
521
+ "reason": "operator capacity dashboard (#51, shipped 2026-09) — read-only queue/park telemetry; a typed conduyt_capacity_snapshot tool is agent-relevant for throughput questions"
522
+ },
523
+ {
524
+ "match": "GET /api/v1/help/guides",
525
+ "status": "planned",
526
+ "reason": "the Help center's guides (2026-09-30): a typed read tool is next, right after the API is live"
527
+ },
528
+ {
529
+ "match": "GET /api/v1/help/guides/:slug",
530
+ "status": "planned",
531
+ "reason": "one guide's text (2026-09-30): a typed read tool is next, right after the API is live"
532
+ },
533
+ {
534
+ "match": "/api/v1/help/guides/:slug/feedback",
535
+ "status": "excluded",
536
+ "reason": "a person's own answer to Was this helpful: session only (assertSessionOnly), never an API key's"
537
+ },
538
+ {
539
+ "match": "POST /api/v1/help/guides/:slug/open",
540
+ "status": "excluded",
541
+ "reason": "a person's own reading of a guide, for Most read: session only (assertSessionOnly)"
542
+ },
543
+ {
544
+ "match": "POST /api/v1/help/suggestions",
545
+ "status": "excluded",
546
+ "reason": "a person's own suggestion, mailed to support: session only (assertSessionOnly)"
547
+ },
548
+ {
549
+ "match": "/api/v1/support/",
550
+ "status": "excluded",
551
+ "reason": "the Contact support form and its attachment upload: a signed-in person's request, session only (assertSessionOnly)"
522
552
  }
523
553
  ]
524
- }
554
+ }
@@ -1,11 +1,11 @@
1
1
  {
2
- "generatedAt": "2026-09-27T00:07:38.757Z",
3
- "toolCount": 290,
4
- "totalEndpoints": 1011,
2
+ "generatedAt": "2026-09-30T16:40:04.197Z",
3
+ "toolCount": 292,
4
+ "totalEndpoints": 1031,
5
5
  "counts": {
6
6
  "covered": 266,
7
- "excluded": 295,
8
- "planned": 450,
7
+ "excluded": 311,
8
+ "planned": 454,
9
9
  "uncategorized": 0
10
10
  },
11
11
  "matrix": {
@@ -1657,6 +1657,10 @@
1657
1657
  "status": "excluded",
1658
1658
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
1659
1659
  },
1660
+ "GET /api/v1/cron/lead-pool-push": {
1661
+ "status": "excluded",
1662
+ "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
1663
+ },
1660
1664
  "GET /api/v1/cron/lead-redistribution": {
1661
1665
  "status": "excluded",
1662
1666
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
@@ -1797,6 +1801,10 @@
1797
1801
  "status": "excluded",
1798
1802
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
1799
1803
  },
1804
+ "GET /api/v1/cron/process-subscriber-deals": {
1805
+ "status": "excluded",
1806
+ "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
1807
+ },
1800
1808
  "GET /api/v1/cron/process-trial-signup-alerts": {
1801
1809
  "status": "excluded",
1802
1810
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
@@ -1805,6 +1813,10 @@
1805
1813
  "status": "excluded",
1806
1814
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
1807
1815
  },
1816
+ "GET /api/v1/cron/process-unsubscribe-intents": {
1817
+ "status": "excluded",
1818
+ "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
1819
+ },
1808
1820
  "GET /api/v1/cron/process-wake-jobs": {
1809
1821
  "status": "excluded",
1810
1822
  "reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
@@ -2205,6 +2217,10 @@
2205
2217
  "status": "excluded",
2206
2218
  "reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
2207
2219
  },
2220
+ "GET /api/v1/dialer/call/:id": {
2221
+ "status": "excluded",
2222
+ "reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
2223
+ },
2208
2224
  "PATCH /api/v1/dialer/call/:id": {
2209
2225
  "status": "excluded",
2210
2226
  "reason": "realtime browser dialer (WebRTC tokens, live call control) — needs a live audio session"
@@ -2771,6 +2787,30 @@
2771
2787
  "status": "excluded",
2772
2788
  "reason": "unauthenticated liveness probe"
2773
2789
  },
2790
+ "GET /api/v1/help/guides": {
2791
+ "status": "planned",
2792
+ "reason": "the Help center's guides (2026-09-30): a typed read tool is next, right after the API is live"
2793
+ },
2794
+ "GET /api/v1/help/guides/:slug": {
2795
+ "status": "planned",
2796
+ "reason": "the Help center's guides (2026-09-30): a typed read tool is next, right after the API is live"
2797
+ },
2798
+ "GET /api/v1/help/guides/:slug/feedback": {
2799
+ "status": "planned",
2800
+ "reason": "the Help center's guides (2026-09-30): a typed read tool is next, right after the API is live"
2801
+ },
2802
+ "POST /api/v1/help/guides/:slug/feedback": {
2803
+ "status": "excluded",
2804
+ "reason": "a person's own answer to Was this helpful: session only (assertSessionOnly), never an API key's"
2805
+ },
2806
+ "POST /api/v1/help/guides/:slug/open": {
2807
+ "status": "excluded",
2808
+ "reason": "a person's own reading of a guide, for Most read: session only (assertSessionOnly)"
2809
+ },
2810
+ "POST /api/v1/help/suggestions": {
2811
+ "status": "excluded",
2812
+ "reason": "a person's own suggestion, mailed to support: session only (assertSessionOnly)"
2813
+ },
2774
2814
  "GET /api/v1/imports": {
2775
2815
  "status": "planned",
2776
2816
  "reason": "import job monitoring/resubmission (ai-import tool covers creation)"
@@ -3239,6 +3279,14 @@
3239
3279
  "status": "excluded",
3240
3280
  "reason": "public unauthenticated surfaces — no key semantics"
3241
3281
  },
3282
+ "POST /api/v1/public/booking/:slug/documents": {
3283
+ "status": "excluded",
3284
+ "reason": "public unauthenticated surfaces — no key semantics"
3285
+ },
3286
+ "OPTIONS /api/v1/public/booking/:slug/documents": {
3287
+ "status": "excluded",
3288
+ "reason": "public unauthenticated surfaces — no key semantics"
3289
+ },
3242
3290
  "GET /api/v1/public/booking/:slug/embed": {
3243
3291
  "status": "excluded",
3244
3292
  "reason": "public unauthenticated surfaces — no key semantics"
@@ -3267,6 +3315,14 @@
3267
3315
  "status": "excluded",
3268
3316
  "reason": "public unauthenticated surfaces — no key semantics"
3269
3317
  },
3318
+ "POST /api/v1/public/booking/:slug/upload-url": {
3319
+ "status": "excluded",
3320
+ "reason": "public unauthenticated surfaces — no key semantics"
3321
+ },
3322
+ "OPTIONS /api/v1/public/booking/:slug/upload-url": {
3323
+ "status": "excluded",
3324
+ "reason": "public unauthenticated surfaces — no key semantics"
3325
+ },
3270
3326
  "POST /api/v1/public/booking/:slug/waitlist": {
3271
3327
  "status": "excluded",
3272
3328
  "reason": "public unauthenticated surfaces — no key semantics"
@@ -3295,6 +3351,10 @@
3295
3351
  "status": "excluded",
3296
3352
  "reason": "public unauthenticated surfaces — no key semantics"
3297
3353
  },
3354
+ "POST /api/v1/public/screen-share/:code/state": {
3355
+ "status": "excluded",
3356
+ "reason": "public unauthenticated surfaces — no key semantics"
3357
+ },
3298
3358
  "POST /api/v1/public/subscribe": {
3299
3359
  "status": "excluded",
3300
3360
  "reason": "public unauthenticated surfaces — no key semantics"
@@ -3711,6 +3771,10 @@
3711
3771
  "status": "excluded",
3712
3772
  "reason": "realtime browser RTC surface"
3713
3773
  },
3774
+ "GET /api/v1/screen-share/session/:code": {
3775
+ "status": "excluded",
3776
+ "reason": "realtime browser RTC surface"
3777
+ },
3714
3778
  "DELETE /api/v1/screen-share/session/:code": {
3715
3779
  "status": "excluded",
3716
3780
  "reason": "realtime browser RTC surface"
@@ -3726,6 +3790,7 @@
3726
3790
  "tools": [
3727
3791
  "conduyt_get_contact_table_defaults",
3728
3792
  "conduyt_set_contact_table_defaults",
3793
+ "conduyt_get_call_recording_settings",
3729
3794
  "conduyt_get_lead_pool_settings",
3730
3795
  "conduyt_update_lead_pool_settings",
3731
3796
  "conduyt_get_lifecycle_settings",
@@ -4179,6 +4244,14 @@
4179
4244
  "status": "excluded",
4180
4245
  "reason": "UI enum/metadata hydration for dashboard pickers; tools embed their enums"
4181
4246
  },
4247
+ "POST /api/v1/support/requests": {
4248
+ "status": "excluded",
4249
+ "reason": "the Contact support form and its attachment upload: a signed-in person's request, session only (assertSessionOnly)"
4250
+ },
4251
+ "POST /api/v1/support/upload-url": {
4252
+ "status": "excluded",
4253
+ "reason": "the Contact support form and its attachment upload: a signed-in person's request, session only (assertSessionOnly)"
4254
+ },
4182
4255
  "GET /api/v1/tags": {
4183
4256
  "status": "covered",
4184
4257
  "tools": [
@@ -4384,7 +4457,11 @@
4384
4457
  "status": "planned",
4385
4458
  "reason": "team management subroutes beyond list/invite"
4386
4459
  },
4387
- "POST /api/v1/users/me/welcomed": {
4460
+ "GET /api/v1/users/me/tour": {
4461
+ "status": "planned",
4462
+ "reason": "team management subroutes beyond list/invite"
4463
+ },
4464
+ "POST /api/v1/users/me/tour": {
4388
4465
  "status": "planned",
4389
4466
  "reason": "team management subroutes beyond list/invite"
4390
4467
  },
@@ -4542,6 +4619,10 @@
4542
4619
  "status": "excluded",
4543
4620
  "reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
4544
4621
  },
4622
+ "POST /api/v1/webhooks/voice/vendor-greeting": {
4623
+ "status": "excluded",
4624
+ "reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
4625
+ },
4545
4626
  "POST /api/v1/webhooks/voice/vendor-recording": {
4546
4627
  "status": "excluded",
4547
4628
  "reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"