conduyt-mcp 4.72.1 → 4.74.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.
- package/README.md +10 -3
- package/dist/client.d.ts +4 -0
- package/dist/client.js +7 -0
- package/dist/index.js +2 -0
- package/dist/tools/deals.js +2 -1
- package/dist/tools/form-intakes.d.ts +22 -0
- package/dist/tools/form-intakes.js +72 -0
- package/package.json +1 -1
- package/scripts/coverage-matrix.json +56 -6
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Conduyt MCP Server
|
|
2
2
|
|
|
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
|
|
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
5
|
## 299 Tools
|
|
6
6
|
|
|
7
7
|
### Discovery
|
|
8
8
|
| Tool | Description |
|
|
9
9
|
|------|-------------|
|
|
10
|
-
| `conduyt_api_catalog` | Discover all
|
|
10
|
+
| `conduyt_api_catalog` | Discover all 600+ API endpoints |
|
|
11
11
|
| `conduyt_check_permissions` | Introspect current API key permissions |
|
|
12
12
|
| `conduyt_search` | Global search across contacts, deals, companies, tasks |
|
|
13
13
|
|
|
@@ -26,7 +26,7 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
26
26
|
| `conduyt_list_deals` | List deals by pipeline, stage, or assignee |
|
|
27
27
|
| `conduyt_get_deal` | Get deal details with history, including custom-field values |
|
|
28
28
|
| `conduyt_create_deal` | Create a deal with full attributes — currency (e.g. GBP), probability, priority, expected close date, contact/company, and structured custom-field values |
|
|
29
|
-
| `conduyt_update_deal` | Move stage, change value/currency, set probability or close date, reassign, mark won/lost, or write custom-field values |
|
|
29
|
+
| `conduyt_update_deal` | Move stage, change value/currency, set probability or close date, reassign, mark won/lost, or write custom-field values; optional `expectedUpdatedAt` pins the `updatedAt` you read, and a stale pin returns 409 `deal_changed` with the current `updatedAt` and writes nothing |
|
|
30
30
|
| `conduyt_import_deals` | Bulk-import deals (pipeline/stage by name, contact by email) via the canonical import engine — dedupes, side-effect-free, owner/admin only |
|
|
31
31
|
|
|
32
32
|
### Tasks
|
|
@@ -152,6 +152,13 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
152
152
|
| `conduyt_update_webhook` | Update URL, events, or enable/disable |
|
|
153
153
|
| `conduyt_delete_webhook` | Delete a webhook endpoint |
|
|
154
154
|
|
|
155
|
+
### WordPress forms
|
|
156
|
+
| Tool | Description |
|
|
157
|
+
|------|-------------|
|
|
158
|
+
| `conduyt_list_form_intakes` | List the intake addresses: every active one with its full URL, then the revoked history (newest 200) |
|
|
159
|
+
| `conduyt_create_form_intake` | Create an intake address for one Gravity Forms / WPForms / Elementor form (returns its full URL) |
|
|
160
|
+
| `conduyt_revoke_form_intake` | Revoke an intake address (the row is kept for history) |
|
|
161
|
+
|
|
155
162
|
### Notifications
|
|
156
163
|
| Tool | Description |
|
|
157
164
|
|------|-------------|
|
package/dist/client.d.ts
CHANGED
|
@@ -11,12 +11,16 @@ export declare class ConduytApiError extends Error {
|
|
|
11
11
|
readonly status: number | null;
|
|
12
12
|
readonly code: "http" | "timeout" | "response_too_large" | "network";
|
|
13
13
|
readonly retryable: boolean;
|
|
14
|
+
/** The response's Retry-After, in seconds, when it sent one (a busy 503 / a 429): surfaced to the agent, never used
|
|
15
|
+
* to replay a mutation. */
|
|
16
|
+
readonly retryAfterSeconds: number | null;
|
|
14
17
|
constructor(opts: {
|
|
15
18
|
message: string;
|
|
16
19
|
status?: number | null;
|
|
17
20
|
code: ConduytApiError["code"];
|
|
18
21
|
retryable: boolean;
|
|
19
22
|
cause?: unknown;
|
|
23
|
+
retryAfterSeconds?: number | null;
|
|
20
24
|
});
|
|
21
25
|
}
|
|
22
26
|
/** Per-call overrides for legitimately long/large operations (GDPR export,
|
package/dist/client.js
CHANGED
|
@@ -10,12 +10,16 @@ export class ConduytApiError extends Error {
|
|
|
10
10
|
status;
|
|
11
11
|
code;
|
|
12
12
|
retryable;
|
|
13
|
+
/** The response's Retry-After, in seconds, when it sent one (a busy 503 / a 429): surfaced to the agent, never used
|
|
14
|
+
* to replay a mutation. */
|
|
15
|
+
retryAfterSeconds;
|
|
13
16
|
constructor(opts) {
|
|
14
17
|
super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
|
|
15
18
|
this.name = "ConduytApiError";
|
|
16
19
|
this.status = opts.status ?? null;
|
|
17
20
|
this.code = opts.code;
|
|
18
21
|
this.retryable = opts.retryable;
|
|
22
|
+
this.retryAfterSeconds = opts.retryAfterSeconds ?? null;
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
/** Bounded defaults; each overridable via config / env (see src/index.ts). */
|
|
@@ -163,11 +167,14 @@ export class ConduytClient {
|
|
|
163
167
|
if (text)
|
|
164
168
|
msg = text.slice(0, 500);
|
|
165
169
|
}
|
|
170
|
+
const retryAfterRaw = res.headers.get("retry-after");
|
|
171
|
+
const retryAfter = retryAfterRaw === null || retryAfterRaw.trim() === "" ? NaN : Number(retryAfterRaw);
|
|
166
172
|
return new ConduytApiError({
|
|
167
173
|
message: `Conduyt API ${res.status}: ${msg}`,
|
|
168
174
|
status: res.status,
|
|
169
175
|
code: "http",
|
|
170
176
|
retryable: RETRYABLE_STATUSES.has(res.status),
|
|
177
|
+
retryAfterSeconds: Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null,
|
|
171
178
|
});
|
|
172
179
|
}
|
|
173
180
|
retryDelayMs(res, attemptIndex) {
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { registerCalendarTools } from "./tools/calendar.js";
|
|
|
21
21
|
import { registerFormTools } from "./tools/forms.js";
|
|
22
22
|
import { registerInvoiceTools } from "./tools/invoices.js";
|
|
23
23
|
import { registerWebhookTools } from "./tools/webhooks.js";
|
|
24
|
+
import { registerFormIntakeTools } from "./tools/form-intakes.js";
|
|
24
25
|
import { registerNotificationTools } from "./tools/notifications.js";
|
|
25
26
|
import { registerCustomFieldTools } from "./tools/custom-fields.js";
|
|
26
27
|
import { registerSmartListTools } from "./tools/smart-lists.js";
|
|
@@ -93,6 +94,7 @@ registerCalendarTools(server, client);
|
|
|
93
94
|
registerFormTools(server, client);
|
|
94
95
|
registerInvoiceTools(server, client);
|
|
95
96
|
registerWebhookTools(server, client);
|
|
97
|
+
registerFormIntakeTools(server, client);
|
|
96
98
|
registerNotificationTools(server, client);
|
|
97
99
|
registerCustomFieldTools(server, client);
|
|
98
100
|
registerSmartListTools(server, client);
|
package/dist/tools/deals.js
CHANGED
|
@@ -105,7 +105,8 @@ export function registerDealTools(server, client) {
|
|
|
105
105
|
assignedTo: z.string().optional().describe("Reassign to user (owner) UUID"),
|
|
106
106
|
source: z.string().trim().min(1).max(500).optional().describe("Attribution source for THIS deal (the trigger that produced it) — distinct from the contact's first-touch source. Every deal keeps a source (#129): it can be changed, never cleared (null/blank are rejected). 1-500 chars."),
|
|
107
107
|
lostReason: z.string().optional().describe("Reason for a lost/disqualified deal. Some stages REQUIRE this on entry (the move fails with code STAGE_REQUIREMENTS listing lostReasonOptions when the stage has a configured dropdown — resend with one of those exact values)."),
|
|
108
|
-
customFields: z.record(z.any()).optional().describe("Structured custom-field values to merge, keyed by field key"),
|
|
108
|
+
customFields: z.record(z.any()).optional().describe("Structured custom-field values to merge, keyed by field key (send null for a key to clear it; keys you omit keep their current values)"),
|
|
109
|
+
expectedUpdatedAt: z.string().datetime({ message: "expectedUpdatedAt must be the deal's updatedAt exactly as the API returned it (UTC, trailing Z, e.g. 2026-10-06T23:19:00.000Z)" }).optional().describe("Optional: the deal's updatedAt from your last read, ISO 8601 exactly as returned (e.g. 2026-10-06T23:19:00.000Z). If the deal changed since, the update is refused (409, code deal_changed, with the current updatedAt) and nothing is written — read the deal again and retry. Omit for last-write-wins per field."),
|
|
109
110
|
}, async ({ id, ...updates }) => {
|
|
110
111
|
const result = await client.patch(`/api/v1/deals/${id}`, updates);
|
|
111
112
|
return formatResult(result);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ConduytClient } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* WordPress forms intake addresses (app 10-07, tri-surface parity): a per-form URL that Gravity Forms (Webhooks
|
|
5
|
+
* Add-On), WPForms (Webhooks addon) or Elementor Pro (Webhook action) posts submissions to; each submission becomes a
|
|
6
|
+
* contact. GET / POST /api/v1/integrations/wordpress-forms/intakes, DELETE /api/v1/integrations/wordpress-forms/intakes/{id}.
|
|
7
|
+
* Listing and creating need the "form-intakes" AND "contacts" scopes on a write or admin tier key (the URLs are
|
|
8
|
+
* credentials that create and edit contacts); revoking needs "form-intakes". Create and list answer each active intake's
|
|
9
|
+
* full `url` (the credential, to paste into the form plugin); a revoked intake's `url` is null.
|
|
10
|
+
*/
|
|
11
|
+
/** What each tool's key must hold: the API names only the FIRST missing scope, so a 403 is surfaced with all of it. */
|
|
12
|
+
export declare const LIST_AND_CREATE_REQUIREMENT = "the form-intakes AND contacts scopes on a write or admin tier key (a member: settings.edit, contacts.create and contacts.edit)";
|
|
13
|
+
export declare const REVOKE_REQUIREMENT = "the form-intakes scope on an admin tier key (a member: settings.edit)";
|
|
14
|
+
/** A 403 names the complete requirement; a busy 503 says when to retry (never replayed here: create is a mutation). */
|
|
15
|
+
export declare function intakeToolError(err: unknown, requirement: string): {
|
|
16
|
+
content: Array<{
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}>;
|
|
20
|
+
isError: true;
|
|
21
|
+
};
|
|
22
|
+
export declare function registerFormIntakeTools(server: McpServer, client: ConduytClient): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ConduytApiError, formatResult, formatToolError } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* WordPress forms intake addresses (app 10-07, tri-surface parity): a per-form URL that Gravity Forms (Webhooks
|
|
5
|
+
* Add-On), WPForms (Webhooks addon) or Elementor Pro (Webhook action) posts submissions to; each submission becomes a
|
|
6
|
+
* contact. GET / POST /api/v1/integrations/wordpress-forms/intakes, DELETE /api/v1/integrations/wordpress-forms/intakes/{id}.
|
|
7
|
+
* Listing and creating need the "form-intakes" AND "contacts" scopes on a write or admin tier key (the URLs are
|
|
8
|
+
* credentials that create and edit contacts); revoking needs "form-intakes". Create and list answer each active intake's
|
|
9
|
+
* full `url` (the credential, to paste into the form plugin); a revoked intake's `url` is null.
|
|
10
|
+
*/
|
|
11
|
+
/** What each tool's key must hold: the API names only the FIRST missing scope, so a 403 is surfaced with all of it. */
|
|
12
|
+
export const LIST_AND_CREATE_REQUIREMENT = "the form-intakes AND contacts scopes on a write or admin tier key (a member: settings.edit, contacts.create and contacts.edit)";
|
|
13
|
+
export const REVOKE_REQUIREMENT = "the form-intakes scope on an admin tier key (a member: settings.edit)";
|
|
14
|
+
/** A 403 names the complete requirement; a busy 503 says when to retry (never replayed here: create is a mutation). */
|
|
15
|
+
export function intakeToolError(err, requirement) {
|
|
16
|
+
if (err instanceof ConduytApiError) {
|
|
17
|
+
if (err.status === 403)
|
|
18
|
+
return formatToolError(`${err.message}. This tool needs ${requirement}.`);
|
|
19
|
+
if (err.status === 503 && err.retryAfterSeconds !== null) {
|
|
20
|
+
return formatToolError(`busy; retry after ${err.retryAfterSeconds} s (${err.message})`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
export function registerFormIntakeTools(server, client) {
|
|
26
|
+
server.tool("conduyt_list_form_intakes", "List the account's WordPress form intake addresses: every active intake (newest first), then the revoked history (the newest 200 revocations): name, defaultTag, source, assignee, status, receivedCount, lastReceivedAt and `url`: the full address of every ACTIVE intake (the credential, to paste into the form plugin); null for a revoked one. Requires " +
|
|
27
|
+
LIST_AND_CREATE_REQUIREMENT +
|
|
28
|
+
" (the URLs are credentials that create and edit contacts).", {}, async () => {
|
|
29
|
+
try {
|
|
30
|
+
const result = await client.get("/api/v1/integrations/wordpress-forms/intakes");
|
|
31
|
+
return formatResult(result);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return intakeToolError(err, LIST_AND_CREATE_REQUIREMENT);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
server.tool("conduyt_create_form_intake", "Create a WordPress form intake address for one form. Answers the row with `url`, the full address (the credential; conduyt_list_form_intakes shows it too while the intake is active). Paste it into the form plugin's webhook URL (Gravity Forms Webhooks Add-On, WPForms Webhooks, Elementor Pro Webhook action). Each submission then creates or updates a contact (by email, then phone), applies defaultTag / source / assignedTo, and adds 'Submitted <form name>' with the message and fields to the contact's timeline. At most 100 active addresses per account; a busy answer (503) says how many seconds to wait before retrying. Requires " +
|
|
38
|
+
LIST_AND_CREATE_REQUIREMENT +
|
|
39
|
+
".", {
|
|
40
|
+
name: z.string().trim().min(1).max(120).describe("What the form is, e.g. 'Contact page, Smith Roofing'"),
|
|
41
|
+
defaultTag: z.string().max(100).nullable().optional().describe("Tag NAME applied to every submission unless the submission sends its own tags (created on first use)"),
|
|
42
|
+
source: z.string().max(64).optional().describe("Contact source for submissions that do not send one (default 'wordpress')"),
|
|
43
|
+
assignedTo: z.string().uuid().nullable().optional().describe("User id (an active member) who owns new contacts; omit or null = lead routing decides"),
|
|
44
|
+
}, async ({ name, defaultTag, source, assignedTo }) => {
|
|
45
|
+
try {
|
|
46
|
+
// the body as ONE literal: the contract gate reads the request fields from it
|
|
47
|
+
const result = await client.post("/api/v1/integrations/wordpress-forms/intakes", {
|
|
48
|
+
name,
|
|
49
|
+
...(defaultTag !== undefined ? { defaultTag } : {}),
|
|
50
|
+
...(source !== undefined ? { source } : {}),
|
|
51
|
+
...(assignedTo !== undefined ? { assignedTo } : {}),
|
|
52
|
+
});
|
|
53
|
+
return formatResult(result);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
return intakeToolError(err, LIST_AND_CREATE_REQUIREMENT);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
server.tool("conduyt_revoke_form_intake", "Revoke a WordPress form intake address: submissions to it answer 404 from now on and create nothing. The row is kept (status 'revoked', revokedAt) for history; revoking a revoked address answers it unchanged. Requires " +
|
|
60
|
+
REVOKE_REQUIREMENT +
|
|
61
|
+
".", {
|
|
62
|
+
id: z.string().uuid().describe("Intake UUID (from conduyt_list_form_intakes)"),
|
|
63
|
+
}, async ({ id }) => {
|
|
64
|
+
try {
|
|
65
|
+
const result = await client.del(`/api/v1/integrations/wordpress-forms/intakes/${id}`);
|
|
66
|
+
return formatResult(result);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
return intakeToolError(err, REVOKE_REQUIREMENT);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-10-
|
|
3
|
-
"toolCount":
|
|
4
|
-
"totalEndpoints":
|
|
2
|
+
"generatedAt": "2026-10-07T06:35:55.228Z",
|
|
3
|
+
"toolCount": 302,
|
|
4
|
+
"totalEndpoints": 1047,
|
|
5
5
|
"counts": {
|
|
6
|
-
"covered":
|
|
7
|
-
"excluded":
|
|
8
|
-
"planned":
|
|
6
|
+
"covered": 276,
|
|
7
|
+
"excluded": 318,
|
|
8
|
+
"planned": 453,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
11
11
|
"matrix": {
|
|
@@ -189,6 +189,14 @@
|
|
|
189
189
|
"status": "planned",
|
|
190
190
|
"reason": "invoicing"
|
|
191
191
|
},
|
|
192
|
+
"POST /api/v1/invoices/:id/overpayments/:overpaymentId/manual-refund": {
|
|
193
|
+
"status": "planned",
|
|
194
|
+
"reason": "invoicing"
|
|
195
|
+
},
|
|
196
|
+
"POST /api/v1/invoices/:id/overpayments/:overpaymentId/refund": {
|
|
197
|
+
"status": "planned",
|
|
198
|
+
"reason": "invoicing"
|
|
199
|
+
},
|
|
192
200
|
"POST /api/v1/invoices/:id/payment-link": {
|
|
193
201
|
"status": "planned",
|
|
194
202
|
"reason": "invoicing"
|
|
@@ -235,6 +243,14 @@
|
|
|
235
243
|
"status": "excluded",
|
|
236
244
|
"reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
|
|
237
245
|
},
|
|
246
|
+
"GET /api/public/unsubscribe/confirm/:token": {
|
|
247
|
+
"status": "excluded",
|
|
248
|
+
"reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
|
|
249
|
+
},
|
|
250
|
+
"POST /api/public/unsubscribe/confirm/:token": {
|
|
251
|
+
"status": "excluded",
|
|
252
|
+
"reason": "public unauthenticated surfaces (forms, booking, unsubscribe) — no key semantics"
|
|
253
|
+
},
|
|
238
254
|
"GET /api/track/click/:messageId": {
|
|
239
255
|
"status": "excluded",
|
|
240
256
|
"reason": "email open/click tracking pixels and redirects — recipient-facing"
|
|
@@ -1865,6 +1881,14 @@
|
|
|
1865
1881
|
"status": "excluded",
|
|
1866
1882
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1867
1883
|
},
|
|
1884
|
+
"GET /api/v1/cron/reconcile-overpayment-refunds": {
|
|
1885
|
+
"status": "excluded",
|
|
1886
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1887
|
+
},
|
|
1888
|
+
"POST /api/v1/cron/reconcile-overpayment-refunds": {
|
|
1889
|
+
"status": "excluded",
|
|
1890
|
+
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
1891
|
+
},
|
|
1868
1892
|
"GET /api/v1/cron/reconcile-reply-capture": {
|
|
1869
1893
|
"status": "excluded",
|
|
1870
1894
|
"reason": "CRON_SECRET-gated infrastructure jobs; never key-callable"
|
|
@@ -2623,6 +2647,10 @@
|
|
|
2623
2647
|
"status": "planned",
|
|
2624
2648
|
"reason": "sending-domain management (reply-capture subpaths ARE covered — see the reply_capture_* tools)"
|
|
2625
2649
|
},
|
|
2650
|
+
"POST /api/v1/email/opt-out-list/resubscribe": {
|
|
2651
|
+
"status": "planned",
|
|
2652
|
+
"reason": "bulk email send surfaces"
|
|
2653
|
+
},
|
|
2626
2654
|
"POST /api/v1/email/send": {
|
|
2627
2655
|
"status": "covered",
|
|
2628
2656
|
"tools": [
|
|
@@ -2969,6 +2997,24 @@
|
|
|
2969
2997
|
"status": "planned",
|
|
2970
2998
|
"reason": "integration management"
|
|
2971
2999
|
},
|
|
3000
|
+
"GET /api/v1/integrations/wordpress-forms/intakes": {
|
|
3001
|
+
"status": "covered",
|
|
3002
|
+
"tools": [
|
|
3003
|
+
"conduyt_list_form_intakes"
|
|
3004
|
+
]
|
|
3005
|
+
},
|
|
3006
|
+
"POST /api/v1/integrations/wordpress-forms/intakes": {
|
|
3007
|
+
"status": "covered",
|
|
3008
|
+
"tools": [
|
|
3009
|
+
"conduyt_create_form_intake"
|
|
3010
|
+
]
|
|
3011
|
+
},
|
|
3012
|
+
"DELETE /api/v1/integrations/wordpress-forms/intakes/:id": {
|
|
3013
|
+
"status": "covered",
|
|
3014
|
+
"tools": [
|
|
3015
|
+
"conduyt_revoke_form_intake"
|
|
3016
|
+
]
|
|
3017
|
+
},
|
|
2972
3018
|
"GET /api/v1/integrations/zapier/sample/:event": {
|
|
2973
3019
|
"status": "planned",
|
|
2974
3020
|
"reason": "integration management"
|
|
@@ -4549,6 +4595,10 @@
|
|
|
4549
4595
|
"status": "excluded",
|
|
4550
4596
|
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|
|
4551
4597
|
},
|
|
4598
|
+
"POST /api/v1/webhooks/inbound/forms/:token": {
|
|
4599
|
+
"status": "excluded",
|
|
4600
|
+
"reason": "inbound provider callback receivers (Twilio/Resend/etc.), not client-callable"
|
|
4601
|
+
},
|
|
4552
4602
|
"GET /api/v1/webhooks/manage": {
|
|
4553
4603
|
"status": "covered",
|
|
4554
4604
|
"tools": [
|