conduyt-mcp 4.22.0 → 4.23.1
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/dist/tools/automations.js +20 -4
- package/dist/tools/call-flows.js +16 -1
- package/dist/tools/custom-fields.js +33 -1
- package/dist/tools/lifecycle.js +22 -9
- package/dist/tools/saved-filters.js +1 -1
- package/dist/tools/workflow-sandbox.js +3 -1
- package/package.json +1 -1
- package/scripts/coverage-manifest.json +6 -1
- package/scripts/coverage-matrix.json +61 -8
|
@@ -57,13 +57,29 @@ export function registerAutomationTools(server, client) {
|
|
|
57
57
|
.optional()
|
|
58
58
|
.describe("What a NEW trigger does when the contact is already mid-sequence: 'keep' (default — skip, existing run keeps its schedule) or 'restart' (cancel the parked run and re-enroll from step 1)"),
|
|
59
59
|
}, async ({ id, ...updates }) => {
|
|
60
|
-
|
|
60
|
+
// (Conduyt Sentry r58–r60 parity) a save that replaces the actions of a LIVE workflow must carry the
|
|
61
|
+
// configRevision it was based on — fetch it first so the server can refuse a stale overwrite (409)
|
|
62
|
+
let body = updates;
|
|
63
|
+
if (updates.actions !== undefined && body.expectedConfigRevision === undefined) {
|
|
64
|
+
const current = (await client.get(`/api/v1/automations/${id}`));
|
|
65
|
+
if (typeof current?.configRevision === "number")
|
|
66
|
+
body = { ...body, expectedConfigRevision: current.configRevision };
|
|
67
|
+
}
|
|
68
|
+
const result = await client.patch(`/api/v1/automations/${id}`, body);
|
|
61
69
|
return formatResult(result);
|
|
62
70
|
});
|
|
63
|
-
server.tool("conduyt_publish_automation", "Publish an automation — copies the draft actions to the live published version. The automation must have actions defined.", {
|
|
71
|
+
server.tool("conduyt_publish_automation", "Publish an automation — copies the draft actions to the live published version. The automation must have actions defined. The publish is bound to the draft revision you reviewed (fetched automatically unless you pass expectedConfigRevision).", {
|
|
64
72
|
id: z.string().describe("Automation UUID"),
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
expectedConfigRevision: z.number().int().nonnegative().optional().describe("The configRevision of the draft you reviewed (from conduyt_get_automation); fetched automatically when omitted"),
|
|
74
|
+
}, async ({ id, expectedConfigRevision }) => {
|
|
75
|
+
// (Conduyt Sentry r59 parity) POST /publish requires the reviewed revision — an edit that landed after the
|
|
76
|
+
// review is refused (409) instead of going live unreviewed
|
|
77
|
+
let revision = expectedConfigRevision;
|
|
78
|
+
if (revision === undefined) {
|
|
79
|
+
const current = (await client.get(`/api/v1/automations/${id}`));
|
|
80
|
+
revision = typeof current?.configRevision === "number" ? current.configRevision : undefined;
|
|
81
|
+
}
|
|
82
|
+
const result = await client.post(`/api/v1/automations/${id}/publish`, { expectedConfigRevision: revision });
|
|
67
83
|
return formatResult(result);
|
|
68
84
|
});
|
|
69
85
|
server.tool("conduyt_resolve_names", "Resolve human-readable names to UUIDs. Use this before creating automations — converts tag names, pipeline names, user names, and custom field labels to their UUIDs.", {
|
package/dist/tools/call-flows.js
CHANGED
|
@@ -32,7 +32,7 @@ export function registerCallFlowTools(server, client) {
|
|
|
32
32
|
server.tool("conduyt_create_call_flow", "Create a call flow. Starts as a DRAFT — edit the graph, validate, then publish to make it live on its number.", {
|
|
33
33
|
name: z.string().describe("Flow name"),
|
|
34
34
|
phoneNumber: z.string().optional().describe("E.164 number this flow answers (must belong to the account)"),
|
|
35
|
-
draftGraph: flowGraphSchema.optional().describe("Initial flow graph — MUST have a string entryId and a nodes array (anything else is silently replaced by a default voicemail flow server-side). Node kinds: entry, greeting, agent, group (queue: holdSeconds/pressOneVoicemail/repeatRing = queue mode), menu (options[{digit,label,next}], next = no-choice path), timing (hours[{day,start,end}], timezone, next = during hours, closedNext = outside hours), voicemail, forward, hangup. Every branch must reach an ending — publish refuses otherwise."),
|
|
35
|
+
draftGraph: flowGraphSchema.optional().describe("Initial flow graph — MUST have a string entryId and a nodes array (anything else is silently replaced by a default voicemail flow server-side). Node kinds: entry, greeting, agent, group (queue: holdSeconds/pressOneVoicemail/repeatRing = queue mode), menu (options[{digit,label,next}], next = no-choice path), timing (hours[{day,start,end}], timezone, next = during hours, closedNext = outside hours), function (functionId = a registered function endpoint id, timeoutSeconds 2-8, next = success, failNext = failure; the JSON response's top-level scalars become call attributes), set_attributes (assignments[{key,value}] — value may use {{contact.firstName}} / {{call.from}} / {{attributes.x}}), branch (attribute, op equals|not_equals|contains|exists|not_exists, value, next = match, elseNext = otherwise), voicemail, forward, hangup. Spoken text in greeting/menu/voicemail/hangup may use {{contact.firstName|there}}, {{call.from}}, {{attributes.key}} (publish refuses unknown references). Every branch must reach an ending — publish refuses otherwise."),
|
|
36
36
|
}, async (params) => {
|
|
37
37
|
const result = await client.post("/api/v1/call-flows", params);
|
|
38
38
|
return formatResult(result);
|
|
@@ -184,4 +184,19 @@ export function registerCallFlowTools(server, client) {
|
|
|
184
184
|
return formatToolError(err.message);
|
|
185
185
|
}
|
|
186
186
|
});
|
|
187
|
+
// ── #32 Call Flows function endpoints (Amazon Connect "Invoke Lambda" parity) ──
|
|
188
|
+
server.tool("conduyt_list_call_flow_functions", "List the account's call-flow function endpoints — the HTTPS endpoints a flow's \"Call a function\" step may invoke mid-call (settings scope).", {}, async () => formatResult(await client.get("/api/v1/call-flows/functions")));
|
|
189
|
+
server.tool("conduyt_create_call_flow_function", "Register a call-flow function endpoint (admin). Conduyt POSTs a signed JSON snapshot of the call (X-Conduyt-Signature = sha256 HMAC of `${timestamp}.${body}` with the secret, X-Conduyt-Timestamp, Idempotency-Key callSid:version:nodeId); the endpoint replies 200 with a JSON object whose top-level strings/numbers/booleans become call attributes. https only, public hosts only, no redirects, 2-8 s timeout, 64 KB response cap. The secret is write-only.", {
|
|
190
|
+
name: z.string().min(1).max(60).describe("Display name (unique per account)"),
|
|
191
|
+
url: z.string().url().describe("HTTPS endpoint URL (ports 443/8443 only, public host)"),
|
|
192
|
+
secret: z.string().min(16).max(200).describe("HMAC signing secret the endpoint will verify with"),
|
|
193
|
+
}, async (params) => formatResult(await client.post("/api/v1/call-flows/functions", params)));
|
|
194
|
+
server.tool("conduyt_update_call_flow_function", "Rename a call-flow function endpoint, change its URL, or rotate its secret (admin).", {
|
|
195
|
+
id: z.string().describe("Function UUID"),
|
|
196
|
+
name: z.string().min(1).max(60).optional(),
|
|
197
|
+
url: z.string().url().optional(),
|
|
198
|
+
secret: z.string().min(16).max(200).optional().describe("New signing secret (rotation)"),
|
|
199
|
+
}, async ({ id, ...body }) => formatResult(await client.patch(`/api/v1/call-flows/functions/${id}`, body)));
|
|
200
|
+
server.tool("conduyt_delete_call_flow_function", "Remove a call-flow function endpoint (admin). Refused while a PUBLISHED flow still calls it — edit and republish that flow first.", { id: z.string().describe("Function UUID") }, async ({ id }) => formatResult(await client.del(`/api/v1/call-flows/functions/${id}`)));
|
|
201
|
+
server.tool("conduyt_test_call_flow_function", "Send a real signed test call (callSid TEST, a sample caller) to a function endpoint and report the status, latency and the attributes it would save.", { id: z.string().describe("Function UUID") }, async ({ id }) => formatResult(await client.post(`/api/v1/call-flows/functions/${id}/test`, {})));
|
|
187
202
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatResult } from "../client.js";
|
|
2
|
+
import { ConduytApiError, formatResult } from "../client.js";
|
|
3
3
|
export function registerCustomFieldTools(server, client) {
|
|
4
4
|
server.tool("conduyt_list_custom_fields", "List custom field definitions. Filter by entity type (contact, deal, or company) to see available fields.", {
|
|
5
5
|
entityType: z.enum(["contact", "deal", "company"]).optional().describe("Filter by entity type"),
|
|
@@ -45,4 +45,36 @@ export function registerCustomFieldTools(server, client) {
|
|
|
45
45
|
const result = await client.del(`/api/v1/custom-fields/${id}`);
|
|
46
46
|
return formatResult(result);
|
|
47
47
|
});
|
|
48
|
+
// ─── account default columns for the contacts list (admin-managed; applied to every user on their next visit) ───
|
|
49
|
+
server.tool("conduyt_get_contact_table_defaults", "Read the account-level DEFAULT COLUMNS for the Contacts list: the column ids every user gets merged into their own layout on their next visit (each version once; users can rearrange afterwards). null = no account default (users see the built-in layout). Reads /api/v1/settings (settings:view).", {}, async () => {
|
|
50
|
+
const result = (await client.get("/api/v1/settings"));
|
|
51
|
+
const raw = result?.data?.contactTableDefaults;
|
|
52
|
+
return formatResult({ data: raw && typeof raw === "object" ? raw : null, revision: raw && typeof raw === "object" && typeof raw.revision === "string" ? raw.revision : "0", columnIds: ["name", "email", "phone", "company", "source", "tags", "score", "intent", "stage", "createdAt", "lastActivity", "assignedTo", "address", "city", "state", "zip", "custom:<fieldKey>"] });
|
|
53
|
+
});
|
|
54
|
+
server.tool("conduyt_set_contact_table_defaults", "Set the account-level DEFAULT COLUMNS for the Contacts list (settings:edit). Every user's saved layout gains the columns it lacks on their next visit (appended; nothing they already show moves); at most 8 ids; custom fields as 'custom:<fieldKey>' (the key must exist). The write is revision-fenced: by default the tool reads the current row and names its `revision` (version@epoch); pass expectedRevision to fence on a row you read earlier. A concurrent change answers { conflict: true, current } — re-read and decide, nothing was overwritten.", {
|
|
55
|
+
columns: z
|
|
56
|
+
.array(z.string().min(1).max(120))
|
|
57
|
+
.max(8)
|
|
58
|
+
.describe("Column ids in display order, e.g. ['createdAt','name','email','phone','company','source','tags','stage','lastActivity']; custom fields as 'custom:<fieldKey>'"),
|
|
59
|
+
expectedRevision: z.string().min(1).max(80).optional().describe("The row `revision` this set was derived from (version@epoch, or '0' when never configured). Omitted: the tool reads the current row and uses its revision."),
|
|
60
|
+
}, async (params) => {
|
|
61
|
+
// (default columns r10, Codex r9 m1) tri-surface parity with the browser: the PATCH contract REQUIRES the server revision
|
|
62
|
+
const readRevision = async () => {
|
|
63
|
+
const result = (await client.get("/api/v1/settings"));
|
|
64
|
+
const raw = result?.data?.contactTableDefaults;
|
|
65
|
+
return raw && typeof raw === "object" && typeof raw.revision === "string" && raw.revision ? raw.revision : "0";
|
|
66
|
+
};
|
|
67
|
+
const expectedRevision = params.expectedRevision ?? (await readRevision());
|
|
68
|
+
try {
|
|
69
|
+
const result = await client.patch("/api/v1/settings", { contactTableDefaults: { columns: params.columns, expectedRevision } });
|
|
70
|
+
return formatResult(result);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (err instanceof ConduytApiError && err.status === 409) {
|
|
74
|
+
const current = (await client.get("/api/v1/settings"));
|
|
75
|
+
return formatResult({ conflict: true, message: err.message, expectedRevision, current: current?.data?.contactTableDefaults ?? null, hint: "the account defaults changed since that revision was read; nothing was overwritten — re-read `current`, merge your intent, and call again with its revision" });
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
48
80
|
}
|
package/dist/tools/lifecycle.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatResult } from "../client.js";
|
|
2
|
+
import { ConduytApiError, formatResult } from "../client.js";
|
|
3
|
+
/** (default columns r18, Codex r17 h1) every general-settings PATCH names the revision it loaded: read it, save, and on a 409 hand
|
|
4
|
+
* the conflict back with the current settings so the caller re-derives its change (tri-surface parity with the browser) */
|
|
5
|
+
async function patchGeneralSettings(client, body) {
|
|
6
|
+
const read = async () => {
|
|
7
|
+
const result = (await client.get("/api/v1/settings"));
|
|
8
|
+
return typeof result?.data?.settingsRevision === "string" ? result.data.settingsRevision : null;
|
|
9
|
+
};
|
|
10
|
+
const expectedSettingsRevision = await read();
|
|
11
|
+
try {
|
|
12
|
+
return formatResult(await client.patch("/api/v1/settings", { ...body, ...(expectedSettingsRevision ? { expectedSettingsRevision } : {}) }));
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
if (err instanceof ConduytApiError && err.status === 409) {
|
|
16
|
+
const current = (await client.get("/api/v1/settings"));
|
|
17
|
+
return formatResult({ conflict: true, message: err.message, expectedSettingsRevision, current: current?.data ?? null, hint: "the account settings changed since that revision was read — re-derive the change from `current` and call again" });
|
|
18
|
+
}
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
3
22
|
/**
|
|
4
23
|
* Lifecycle v2 settings (master lead status + intake deals). The master lead
|
|
5
24
|
* is the acquisition truth (first-write-wins source, one lifecycle status);
|
|
@@ -48,17 +67,11 @@ export function registerLifecycleTools(server, client) {
|
|
|
48
67
|
.enum(["always", "if_no_open", "never"])
|
|
49
68
|
.optional()
|
|
50
69
|
.describe("Policy for contacts that already exist (default 'if_no_open')"),
|
|
51
|
-
}, async (params) => {
|
|
52
|
-
const result = await client.patch("/api/v1/settings", { intakeDeals: params });
|
|
53
|
-
return formatResult(result);
|
|
54
|
-
});
|
|
70
|
+
}, async (params) => patchGeneralSettings(client, { intakeDeals: params }));
|
|
55
71
|
server.tool("conduyt_update_master_statuses", "Set the account's custom lead lifecycle statuses. The five defaults (open, won, lost, abandoned, disqualified) always exist and cannot be removed; this REPLACES the custom list on top of them (max 20 names, 1-40 chars each, letters/numbers/spaces/-/_ only). Terminal statuses stop all outbound automation for a contact. Requires settings:edit.", {
|
|
56
72
|
statuses: z
|
|
57
73
|
.array(masterStatusName)
|
|
58
74
|
.max(20)
|
|
59
75
|
.describe("Custom status names to make available alongside the defaults, e.g. ['nurturing', 'in underwriting']"),
|
|
60
|
-
}, async ({ statuses }) => {
|
|
61
|
-
const result = await client.patch("/api/v1/settings", { masterStatuses: statuses });
|
|
62
|
-
return formatResult(result);
|
|
63
|
-
});
|
|
76
|
+
}, async ({ statuses }) => patchGeneralSettings(client, { masterStatuses: statuses }));
|
|
64
77
|
}
|
|
@@ -14,7 +14,7 @@ import { formatResult } from "../client.js";
|
|
|
14
14
|
// limits (10 groups × 20 rules) so oversized payloads fail here instead of
|
|
15
15
|
// round-tripping to a 400.
|
|
16
16
|
const ruleSchema = z.object({
|
|
17
|
-
field: z.string().describe("Filter field, e.g. assignedTo, source, tags, pipelineStage, dealStatus, createdAt"),
|
|
17
|
+
field: z.string().describe("Filter field, e.g. assignedTo, source, tags, pipeline (the whole pipeline), pipelineStage, dealStatus, createdAt"),
|
|
18
18
|
operator: z.string().describe("Operator valid for the field, e.g. equals, is_any_of, contains, is_empty"),
|
|
19
19
|
value: z.union([z.string(), z.array(z.string())]).optional(),
|
|
20
20
|
});
|
|
@@ -77,7 +77,9 @@ export function registerWorkflowSandboxTools(server, client) {
|
|
|
77
77
|
message: "Workflow has warnings and allowWarnings is false. Fix warnings or set allowWarnings to true.",
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
// (Conduyt Sentry r59 parity) the publish carries the revision of the draft that was just validated
|
|
81
|
+
const current = (await client.get(`/api/v1/automations/${automationId}`));
|
|
82
|
+
const published = await client.post(`/api/v1/automations/${automationId}/publish`, { expectedConfigRevision: typeof current?.configRevision === "number" ? current.configRevision : undefined });
|
|
81
83
|
return formatResult({
|
|
82
84
|
promoted: true,
|
|
83
85
|
warnings: hasWarnings ? validation.warnings : [],
|
package/package.json
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
"planned": "callable in principle; a typed tool is wanted but not built yet"
|
|
6
6
|
},
|
|
7
7
|
"rules": [
|
|
8
|
+
{
|
|
9
|
+
"match": "GET /api/v1/call-flows/functions/:id",
|
|
10
|
+
"status": "planned",
|
|
11
|
+
"reason": "single-function read; the list tool plus update/delete/test exist, a typed get is wanted but not built (4.23.0)"
|
|
12
|
+
},
|
|
8
13
|
{
|
|
9
14
|
"match": "/api/health",
|
|
10
15
|
"status": "excluded",
|
|
@@ -516,4 +521,4 @@
|
|
|
516
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"
|
|
517
522
|
}
|
|
518
523
|
]
|
|
519
|
-
}
|
|
524
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
3
|
-
"toolCount":
|
|
4
|
-
"totalEndpoints":
|
|
2
|
+
"generatedAt": "2026-09-04T21:17:21.600Z",
|
|
3
|
+
"toolCount": 201,
|
|
4
|
+
"totalEndpoints": 893,
|
|
5
5
|
"counts": {
|
|
6
|
-
"covered":
|
|
7
|
-
"excluded":
|
|
8
|
-
"planned":
|
|
6
|
+
"covered": 186,
|
|
7
|
+
"excluded": 263,
|
|
8
|
+
"planned": 444,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
11
11
|
"matrix": {
|
|
@@ -569,6 +569,8 @@
|
|
|
569
569
|
"status": "covered",
|
|
570
570
|
"tools": [
|
|
571
571
|
"conduyt_get_automation",
|
|
572
|
+
"conduyt_update_automation",
|
|
573
|
+
"conduyt_publish_automation",
|
|
572
574
|
"conduyt_workflow_promote"
|
|
573
575
|
]
|
|
574
576
|
},
|
|
@@ -1060,6 +1062,40 @@
|
|
|
1060
1062
|
"status": "excluded",
|
|
1061
1063
|
"reason": "multipart file upload (IVR audio) - not expressible as a text MCP tool; agents reference existing audio by id from conduyt_get_call_flow"
|
|
1062
1064
|
},
|
|
1065
|
+
"GET /api/v1/call-flows/functions": {
|
|
1066
|
+
"status": "covered",
|
|
1067
|
+
"tools": [
|
|
1068
|
+
"conduyt_list_call_flow_functions"
|
|
1069
|
+
]
|
|
1070
|
+
},
|
|
1071
|
+
"POST /api/v1/call-flows/functions": {
|
|
1072
|
+
"status": "covered",
|
|
1073
|
+
"tools": [
|
|
1074
|
+
"conduyt_create_call_flow_function"
|
|
1075
|
+
]
|
|
1076
|
+
},
|
|
1077
|
+
"GET /api/v1/call-flows/functions/:id": {
|
|
1078
|
+
"status": "planned",
|
|
1079
|
+
"reason": "single-function read; the list tool plus update/delete/test exist, a typed get is wanted but not built (4.23.0)"
|
|
1080
|
+
},
|
|
1081
|
+
"PATCH /api/v1/call-flows/functions/:id": {
|
|
1082
|
+
"status": "covered",
|
|
1083
|
+
"tools": [
|
|
1084
|
+
"conduyt_update_call_flow_function"
|
|
1085
|
+
]
|
|
1086
|
+
},
|
|
1087
|
+
"DELETE /api/v1/call-flows/functions/:id": {
|
|
1088
|
+
"status": "covered",
|
|
1089
|
+
"tools": [
|
|
1090
|
+
"conduyt_delete_call_flow_function"
|
|
1091
|
+
]
|
|
1092
|
+
},
|
|
1093
|
+
"POST /api/v1/call-flows/functions/:id/test": {
|
|
1094
|
+
"status": "covered",
|
|
1095
|
+
"tools": [
|
|
1096
|
+
"conduyt_test_call_flow_function"
|
|
1097
|
+
]
|
|
1098
|
+
},
|
|
1063
1099
|
"GET /api/v1/call-flows/numbers": {
|
|
1064
1100
|
"status": "covered",
|
|
1065
1101
|
"tools": [
|
|
@@ -3252,14 +3288,15 @@
|
|
|
3252
3288
|
"GET /api/v1/settings": {
|
|
3253
3289
|
"status": "covered",
|
|
3254
3290
|
"tools": [
|
|
3291
|
+
"conduyt_get_contact_table_defaults",
|
|
3292
|
+
"conduyt_set_contact_table_defaults",
|
|
3255
3293
|
"conduyt_get_lifecycle_settings"
|
|
3256
3294
|
]
|
|
3257
3295
|
},
|
|
3258
3296
|
"PATCH /api/v1/settings": {
|
|
3259
3297
|
"status": "covered",
|
|
3260
3298
|
"tools": [
|
|
3261
|
-
"
|
|
3262
|
-
"conduyt_update_master_statuses"
|
|
3299
|
+
"conduyt_set_contact_table_defaults"
|
|
3263
3300
|
]
|
|
3264
3301
|
},
|
|
3265
3302
|
"GET /api/v1/settings/ai": {
|
|
@@ -3314,6 +3351,14 @@
|
|
|
3314
3351
|
"status": "planned",
|
|
3315
3352
|
"reason": "account settings read/update surfaces"
|
|
3316
3353
|
},
|
|
3354
|
+
"GET /api/v1/settings/operations": {
|
|
3355
|
+
"status": "planned",
|
|
3356
|
+
"reason": "account settings read/update surfaces"
|
|
3357
|
+
},
|
|
3358
|
+
"POST /api/v1/settings/operations": {
|
|
3359
|
+
"status": "planned",
|
|
3360
|
+
"reason": "account settings read/update surfaces"
|
|
3361
|
+
},
|
|
3317
3362
|
"GET /api/v1/settings/scheduled-export": {
|
|
3318
3363
|
"status": "covered",
|
|
3319
3364
|
"tools": [
|
|
@@ -3839,6 +3884,14 @@
|
|
|
3839
3884
|
"status": "excluded",
|
|
3840
3885
|
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|
|
3841
3886
|
},
|
|
3887
|
+
"GET /api/v1/webhooks/voice/fallback": {
|
|
3888
|
+
"status": "excluded",
|
|
3889
|
+
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|
|
3890
|
+
},
|
|
3891
|
+
"POST /api/v1/webhooks/voice/fallback": {
|
|
3892
|
+
"status": "excluded",
|
|
3893
|
+
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|
|
3894
|
+
},
|
|
3842
3895
|
"POST /api/v1/webhooks/voice/flow-step": {
|
|
3843
3896
|
"status": "excluded",
|
|
3844
3897
|
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|