@usethrottle/mcp 0.2.0 → 0.2.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 +9 -2
- package/dist/contract-index.json +1 -1
- package/dist/docs-index.json +1 -1
- package/dist/lib.cjs +1784 -153
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +58 -4
- package/dist/lib.d.ts +58 -4
- package/dist/lib.js +1784 -153
- package/dist/lib.js.map +1 -1
- package/dist/mcp.js +1784 -153
- package/dist/mcp.js.map +1 -1
- package/package.json +3 -3
package/dist/mcp.js
CHANGED
|
@@ -9,6 +9,7 @@ var ThrottleError = class extends Error {
|
|
|
9
9
|
statusCode;
|
|
10
10
|
details;
|
|
11
11
|
body;
|
|
12
|
+
requestId;
|
|
12
13
|
constructor(args) {
|
|
13
14
|
super(args.message);
|
|
14
15
|
this.name = "ThrottleError";
|
|
@@ -16,14 +17,17 @@ var ThrottleError = class extends Error {
|
|
|
16
17
|
this.statusCode = args.statusCode;
|
|
17
18
|
this.details = args.details;
|
|
18
19
|
this.body = args.body;
|
|
20
|
+
const fromBody = args.body?.meta?.requestId;
|
|
21
|
+
const fromHeader = typeof args.responseHeaders?.get === "function" ? args.responseHeaders.get("x-request-id") : args.responseHeaders?.["x-request-id"];
|
|
22
|
+
this.requestId = args.requestId ?? (typeof fromBody === "string" ? fromBody : void 0) ?? (typeof fromHeader === "string" ? fromHeader : void 0);
|
|
19
23
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
20
24
|
}
|
|
21
25
|
};
|
|
22
26
|
|
|
23
27
|
// src/client.ts
|
|
24
28
|
var ThrottleApiError = class extends ThrottleError {
|
|
25
|
-
constructor(code, message, statusCode, details) {
|
|
26
|
-
super({ code, message, statusCode, details });
|
|
29
|
+
constructor(code, message, statusCode, details, responseHeaders) {
|
|
30
|
+
super({ code, message, statusCode, details, responseHeaders });
|
|
27
31
|
this.name = "ThrottleApiError";
|
|
28
32
|
}
|
|
29
33
|
};
|
|
@@ -37,18 +41,43 @@ function buildQuery(query) {
|
|
|
37
41
|
const qs = params.toString();
|
|
38
42
|
return qs ? `?${qs}` : "";
|
|
39
43
|
}
|
|
44
|
+
var API_KEY_PREFIX_RE = /^(sk_|pk_)/;
|
|
40
45
|
var ThrottleClient = class {
|
|
41
46
|
constructor(auth, fetchImpl = fetch) {
|
|
42
47
|
this.auth = auth;
|
|
43
48
|
this.fetchImpl = fetchImpl;
|
|
44
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Swap the credential every later request is sent with. The hosted remote
|
|
52
|
+
* server builds one client per MCP session at `initialize`, but an OAuth
|
|
53
|
+
* access token lives one hour and the MCP client refreshes it underneath
|
|
54
|
+
* the session — each request then arrives with a newer bearer than the one
|
|
55
|
+
* the session was opened with. Without this, the session keeps calling the
|
|
56
|
+
* REST API with the token it was born with and every tool fails with a 401
|
|
57
|
+
* the moment that first token expires, even though the caller is still
|
|
58
|
+
* perfectly authenticated.
|
|
59
|
+
*/
|
|
60
|
+
setApiKey(apiKey) {
|
|
61
|
+
this.auth = { ...this.auth, apiKey };
|
|
62
|
+
}
|
|
45
63
|
async requestJson(method, path, options = {}) {
|
|
46
64
|
const url = `${this.auth.apiBaseUrl.replace(/\/$/, "")}${path}${buildQuery(options.query)}`;
|
|
47
65
|
const headers = {
|
|
48
66
|
"content-type": "application/json",
|
|
49
|
-
"x-api-key": this.auth.apiKey
|
|
67
|
+
"x-api-key": this.auth.apiKey,
|
|
68
|
+
// Also sent as Authorization: Bearer — required for an OAuth access
|
|
69
|
+
// token (the remote server's oauth_token sessions), which the auth
|
|
70
|
+
// middleware only ever recognizes via this header (peeked by issuer),
|
|
71
|
+
// never via x-api-key. For an sk_/pk_ key this is a no-op: the
|
|
72
|
+
// middleware already treats `Authorization: Bearer sk_.../pk_...`
|
|
73
|
+
// identically to `X-API-Key`, so existing stdio-CLI callers are
|
|
74
|
+
// unaffected.
|
|
75
|
+
Authorization: `Bearer ${this.auth.apiKey}`
|
|
50
76
|
};
|
|
51
77
|
if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
|
|
78
|
+
if (options.applicationId && !API_KEY_PREFIX_RE.test(this.auth.apiKey)) {
|
|
79
|
+
headers["x-throttle-application-id"] = options.applicationId;
|
|
80
|
+
}
|
|
52
81
|
const sendsBody = method === "POST" || method === "PATCH" || method === "PUT";
|
|
53
82
|
const body = options.body !== void 0 ? JSON.stringify(options.body) : sendsBody ? "{}" : void 0;
|
|
54
83
|
const res = await this.fetchImpl(url, { method, headers, body });
|
|
@@ -64,18 +93,19 @@ var ThrottleClient = class {
|
|
|
64
93
|
err?.code ?? `http_${res.status}`,
|
|
65
94
|
err?.message ?? `Request failed (${res.status}): ${text.slice(0, 200)}`,
|
|
66
95
|
res.status,
|
|
67
|
-
err?.details
|
|
96
|
+
err?.details,
|
|
97
|
+
res.headers
|
|
68
98
|
);
|
|
69
99
|
}
|
|
70
100
|
return parsed;
|
|
71
101
|
}
|
|
72
102
|
/** Unwraps the `{ data }` envelope. Key presence, not `??` — `{data: null}` unwraps to null. */
|
|
73
|
-
async get(path, query) {
|
|
74
|
-
const body = await this.requestJson("GET", path, { query });
|
|
103
|
+
async get(path, query, options) {
|
|
104
|
+
const body = await this.requestJson("GET", path, { query, applicationId: options?.applicationId });
|
|
75
105
|
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
76
106
|
}
|
|
77
|
-
async list(path, query) {
|
|
78
|
-
const body = await this.requestJson("GET", path, { query });
|
|
107
|
+
async list(path, query, options) {
|
|
108
|
+
const body = await this.requestJson("GET", path, { query, applicationId: options?.applicationId });
|
|
79
109
|
const data = "data" in body ? body.data : body;
|
|
80
110
|
const meta = body.meta;
|
|
81
111
|
return { data: Array.isArray(data) ? data : [], pagination: meta?.pagination ?? null };
|
|
@@ -84,6 +114,10 @@ var ThrottleClient = class {
|
|
|
84
114
|
const body = await this.requestJson("POST", path, options);
|
|
85
115
|
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
86
116
|
}
|
|
117
|
+
async patch(path, options = {}) {
|
|
118
|
+
const body = await this.requestJson("PATCH", path, options);
|
|
119
|
+
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
120
|
+
}
|
|
87
121
|
};
|
|
88
122
|
async function optional(load) {
|
|
89
123
|
try {
|
|
@@ -98,6 +132,7 @@ async function optional(load) {
|
|
|
98
132
|
|
|
99
133
|
// src/server.ts
|
|
100
134
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
135
|
+
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
101
136
|
|
|
102
137
|
// ../core-scopes/src/scopes.ts
|
|
103
138
|
function hasScope(granted, required) {
|
|
@@ -126,6 +161,8 @@ function add(group, resource, actions, labels, opts = {}) {
|
|
|
126
161
|
staffOnly: opts.staffOnly ?? false,
|
|
127
162
|
// publishable defaults false; only the curated set below is true
|
|
128
163
|
publishableAllowed: opts.publishableAllowed ?? false,
|
|
164
|
+
// publishableOnly defaults false; only customer_accounts:write sets it
|
|
165
|
+
publishableOnly: opts.publishableOnly ?? false,
|
|
129
166
|
// extension default true for commerce scopes; Taxonomy B passes false
|
|
130
167
|
extensionAllowed: opts.extensionAllowed ?? true,
|
|
131
168
|
implies
|
|
@@ -136,19 +173,36 @@ add("Orders", "orders", ["read", "write"], { read: "View orders", write: "Manage
|
|
|
136
173
|
add("Quotes", "quotes", ["read", "write"], { read: "View quotes", write: "Manage quotes" });
|
|
137
174
|
add("Orders", "order_cancellations", ["write"], { write: "Cancel orders" });
|
|
138
175
|
add("Orders", "order_returns", ["read", "write"], { read: "View returns", write: "Process returns" });
|
|
176
|
+
add("Orders", "order_comps", ["write"], { write: "Comp orders (write off the balance)" }, { sensitive: true });
|
|
177
|
+
add("Orders", "order_holds", ["write"], { write: "Freeze and unfreeze orders" });
|
|
178
|
+
add(
|
|
179
|
+
"Imports",
|
|
180
|
+
"imports",
|
|
181
|
+
["read", "write"],
|
|
182
|
+
{ read: "View data imports", write: "Run and undo data imports" },
|
|
183
|
+
{ extensionAllowed: false }
|
|
184
|
+
);
|
|
139
185
|
add("Payments", "payments", ["read", "write"], { read: "View payments", write: "Create payments" });
|
|
140
186
|
add("Payments", "payment_authorizations", ["write"], { write: "Authorize payments" });
|
|
141
187
|
add("Payments", "payment_captures", ["write"], { write: "Capture payments" });
|
|
142
188
|
add("Payments", "payment_refunds", ["read", "write"], { read: "View refunds", write: "Refund payments" }, { sensitive: true });
|
|
143
189
|
add("Payments", "payment_voids", ["write"], { write: "Void payments" }, { sensitive: true });
|
|
190
|
+
add("Payments", "payment_records", ["write"], { write: "Record payments received outside Throttle" }, { sensitive: true });
|
|
144
191
|
add("Payments", "payment_disputes", ["read", "write"], { read: "View disputes", write: "Handle disputes" }, { sensitive: true });
|
|
145
192
|
add("Payments", "payment_methods", ["read", "write"], { read: "View payment methods", write: "Tokenize payment methods" }, { sensitive: true });
|
|
146
193
|
add("Customers", "customers", ["read", "write"], { read: "View customers", write: "Manage customers" });
|
|
147
194
|
add("Customers", "customer_pii", ["read"], { read: "View customer PII" }, { sensitive: true });
|
|
148
195
|
add("Customers", "customer_addresses", ["read", "write"], { read: "View addresses", write: "Manage addresses" });
|
|
149
196
|
add("Customers", "customer_payment_methods", ["read", "write"], { read: "View stored cards", write: "Manage stored cards" }, { sensitive: true });
|
|
197
|
+
add(
|
|
198
|
+
"Customer accounts",
|
|
199
|
+
"customer_accounts",
|
|
200
|
+
["write"],
|
|
201
|
+
{ write: "Let shoppers register, sign in and manage their account" },
|
|
202
|
+
{ publishableAllowed: true, publishableOnly: true, extensionAllowed: false }
|
|
203
|
+
);
|
|
150
204
|
add("Carts", "carts", ["read", "write"], { read: "View carts", write: "Manage carts" });
|
|
151
|
-
add("Carts", "cart_items", ["
|
|
205
|
+
add("Carts", "cart_items", ["write"], { write: "Manage line items" });
|
|
152
206
|
add("Carts", "checkout_sessions", ["read", "write"], { read: "View checkout sessions", write: "Manage checkout sessions" });
|
|
153
207
|
add("Discounts", "discounts", ["read", "write"], { read: "View discounts", write: "Manage discounts" });
|
|
154
208
|
add("Subscriptions", "subscriptions", ["read", "write"], { read: "View subscriptions", write: "Manage subscriptions" });
|
|
@@ -169,8 +223,32 @@ add("Shipping & Tax", "shipping_quotes", ["write"], { write: "Request shipping q
|
|
|
169
223
|
add("Shipping & Tax", "tax_calculations", ["write"], { write: "Calculate tax" }, { publishableAllowed: true });
|
|
170
224
|
add("Shipping & Tax", "shipping_tax_quote_tokens", ["read", "write"], { read: "View quote tokens", write: "Manage quote tokens" });
|
|
171
225
|
add("Shipping & Tax", "shipping_tax_snapshots", ["write"], { write: "Accept external snapshots" });
|
|
226
|
+
add(
|
|
227
|
+
"Scripts",
|
|
228
|
+
"storefront_scripts",
|
|
229
|
+
["read", "write"],
|
|
230
|
+
{
|
|
231
|
+
read: "Load storefront scripts",
|
|
232
|
+
// `write` covers exactly one thing: reporting a script's own load outcome
|
|
233
|
+
// from the merchant's storefront, so the health panel stops being
|
|
234
|
+
// hosted-checkout-only. On checkout the session id in the URL is the
|
|
235
|
+
// capability that scopes that write; a storefront page has no session, so
|
|
236
|
+
// the pk it already holds is what scopes it instead. It writes nothing but
|
|
237
|
+
// load telemetry — authoring stays on `application_scripts:write`, which
|
|
238
|
+
// is not publishable-allowed.
|
|
239
|
+
write: "Report storefront script load outcomes"
|
|
240
|
+
},
|
|
241
|
+
{ publishableAllowed: true }
|
|
242
|
+
);
|
|
243
|
+
add("Scripts", "application_scripts", ["read", "write"], {
|
|
244
|
+
read: "View merchant-authored scripts",
|
|
245
|
+
write: "Create, edit and disable merchant-authored scripts"
|
|
246
|
+
}, { extensionAllowed: false });
|
|
247
|
+
add("Scripts", "extension_scripts", ["read", "write"], {
|
|
248
|
+
read: "View an extension's buyer-facing scripts",
|
|
249
|
+
write: "Create, edit and delete an extension's buyer-facing scripts"
|
|
250
|
+
}, { extensionAllowed: false });
|
|
172
251
|
add("Connectors", "connectors", ["read", "write"], { read: "View connectors", write: "Manage connectors" });
|
|
173
|
-
add("Connectors", "connector_activations", ["write"], { write: "Activate/deactivate connectors" });
|
|
174
252
|
add("Webhooks & Events", "webhooks", ["read", "write"], { read: "View webhooks", write: "Manage webhooks" });
|
|
175
253
|
add("Webhooks & Events", "webhook_deliveries", ["read", "write"], { read: "View deliveries", write: "Retry deliveries" });
|
|
176
254
|
add("Webhooks & Events", "events", ["read"], { read: "View events" });
|
|
@@ -186,7 +264,7 @@ add("Platform billing", "billing", ["read", "write"], { read: "View billing", wr
|
|
|
186
264
|
add("Platform billing", "billing_payment_methods", ["read", "write"], { read: "View billing cards", write: "Manage billing cards" }, HUMAN);
|
|
187
265
|
add("Email", "email_templates", ["read", "write"], { read: "View email templates", write: "Edit email templates" }, HUMAN);
|
|
188
266
|
add("Email", "email_settings", ["read", "write"], { read: "View email settings", write: "Edit email settings" }, HUMAN);
|
|
189
|
-
add("Go-live", "go_live", ["
|
|
267
|
+
add("Go-live", "go_live", ["write"], { write: "Submit go-live" }, HUMAN);
|
|
190
268
|
add("Partner", "partner", ["read", "write"], { read: "View partner profile", write: "Edit partner profile" }, HUMAN);
|
|
191
269
|
add("Partner", "affiliates", ["read", "write"], { read: "View affiliates", write: "Manage affiliates" }, HUMAN);
|
|
192
270
|
add("Notifications", "notifications", ["write"], { write: "Send notifications" }, HUMAN);
|
|
@@ -210,6 +288,7 @@ REG["extensions:install"] = {
|
|
|
210
288
|
sensitive: false,
|
|
211
289
|
staffOnly: false,
|
|
212
290
|
publishableAllowed: false,
|
|
291
|
+
publishableOnly: false,
|
|
213
292
|
extensionAllowed: false,
|
|
214
293
|
implies: []
|
|
215
294
|
};
|
|
@@ -222,6 +301,7 @@ REG["*"] = {
|
|
|
222
301
|
sensitive: true,
|
|
223
302
|
staffOnly: false,
|
|
224
303
|
publishableAllowed: false,
|
|
304
|
+
publishableOnly: false,
|
|
225
305
|
extensionAllowed: false,
|
|
226
306
|
implies: []
|
|
227
307
|
};
|
|
@@ -241,6 +321,9 @@ var EVENT_SCOPE_OVERRIDES = {
|
|
|
241
321
|
"payment.refunded": "payment_refunds:read",
|
|
242
322
|
"payment.partially_refunded": "payment_refunds:read",
|
|
243
323
|
"payment.refund_failed": "payment_refunds:read",
|
|
324
|
+
// Money returned, so it belongs with the other refund events rather than
|
|
325
|
+
// behind the broad subscriptions:read the `subscription.` prefix would give.
|
|
326
|
+
"subscription.invoice_refunded": "payment_refunds:read",
|
|
244
327
|
"payment.disputed": "payment_disputes:read",
|
|
245
328
|
"payment.dispute_cleared": "payment_disputes:read",
|
|
246
329
|
"fulfillment.digital.delivered": "fulfillment_digital:read"
|
|
@@ -257,9 +340,17 @@ var PREFIX_RULES = [
|
|
|
257
340
|
["fulfillment.", "fulfillments:read"],
|
|
258
341
|
["discount.", "discounts:read"],
|
|
259
342
|
["invoice.", "invoices:read"],
|
|
343
|
+
["import.", "imports:read"],
|
|
260
344
|
["shipping_tax.", "shipping_tax:read"],
|
|
261
345
|
["workspace.", "billing:read"],
|
|
262
|
-
|
|
346
|
+
// `extensions:read` is a human scope (extensions must never watch other
|
|
347
|
+
// extensions), so only a workspace endpoint can SUBSCRIBE to this family.
|
|
348
|
+
// The uninstalled installation's own endpoint is addressed directly by the
|
|
349
|
+
// uninstall path and needs no subscription at all.
|
|
350
|
+
["extension.", "extensions:read"],
|
|
351
|
+
// Subscribing to script outcomes needs script read — the payload names the
|
|
352
|
+
// script key and its hash, which is the merchant's script inventory.
|
|
353
|
+
["script.", "application_scripts:read"]
|
|
263
354
|
];
|
|
264
355
|
function eventReadScope(type) {
|
|
265
356
|
if (EVENT_SCOPE_OVERRIDES[type]) return EVENT_SCOPE_OVERRIDES[type];
|
|
@@ -271,8 +362,90 @@ function eventReadScope(type) {
|
|
|
271
362
|
|
|
272
363
|
// ../core-scopes/src/issuance.ts
|
|
273
364
|
var PUBLISHABLE_SCOPES = Object.values(SCOPE_REGISTRY).filter((d) => d.publishableAllowed).map((d) => d.id);
|
|
365
|
+
var CONNECTOR_GRANTABLE_SENSITIVE_SCOPES = ["customer_pii:read"];
|
|
366
|
+
function validateScopesForOAuth(scopes, opts = {}) {
|
|
367
|
+
const invalid = [];
|
|
368
|
+
for (const id of scopes) {
|
|
369
|
+
if (id === "*") {
|
|
370
|
+
invalid.push(id);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const def = SCOPE_REGISTRY[id];
|
|
374
|
+
if (!def || def.staffOnly) {
|
|
375
|
+
invalid.push(id);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (def.sensitive && !(opts.connector && CONNECTOR_GRANTABLE_SENSITIVE_SCOPES.includes(id))) invalid.push(id);
|
|
379
|
+
}
|
|
380
|
+
return { ok: invalid.length === 0, invalid };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ../core-scopes/src/shopper-pii.ts
|
|
384
|
+
var PERSON_KEYS = /* @__PURE__ */ new Set([
|
|
385
|
+
"email",
|
|
386
|
+
"customerEmail",
|
|
387
|
+
"billingEmail",
|
|
388
|
+
"buyerEmail",
|
|
389
|
+
"authorEmail",
|
|
390
|
+
"authorName",
|
|
391
|
+
"phone",
|
|
392
|
+
"firstName",
|
|
393
|
+
"lastName",
|
|
394
|
+
"addressLine1",
|
|
395
|
+
"addressLine2",
|
|
396
|
+
"billingAddressLines",
|
|
397
|
+
"processorResponse",
|
|
398
|
+
"shareToken",
|
|
399
|
+
"company",
|
|
400
|
+
"externalId",
|
|
401
|
+
"externalCustomerId",
|
|
402
|
+
// Free-form: contents cannot be inspected for a person, so the field goes.
|
|
403
|
+
"metadata",
|
|
404
|
+
"notes",
|
|
405
|
+
"internalNotes",
|
|
406
|
+
"buyerNote",
|
|
407
|
+
"buyerReference",
|
|
408
|
+
"changeSummary",
|
|
409
|
+
"declinedReason",
|
|
410
|
+
"compReason",
|
|
411
|
+
"holdReason",
|
|
412
|
+
"body",
|
|
413
|
+
"acceptance",
|
|
414
|
+
"acceptanceSelections",
|
|
415
|
+
"clientContext",
|
|
416
|
+
"ipAddress",
|
|
417
|
+
"userAgent",
|
|
418
|
+
"vatNumber",
|
|
419
|
+
"buyerVatNumber",
|
|
420
|
+
"taxExemptionNumber"
|
|
421
|
+
]);
|
|
422
|
+
var PERSON_CONTAINERS = /* @__PURE__ */ new Set(["customer", "buyer"]);
|
|
423
|
+
function isPlainObject(value) {
|
|
424
|
+
if (value === null || typeof value !== "object") return false;
|
|
425
|
+
const proto = Object.getPrototypeOf(value);
|
|
426
|
+
return proto === Object.prototype || proto === null;
|
|
427
|
+
}
|
|
428
|
+
function walk(value, parentKey, depth) {
|
|
429
|
+
if (depth > 32) return value;
|
|
430
|
+
if (Array.isArray(value)) return value.map((item) => walk(item, parentKey, depth + 1));
|
|
431
|
+
if (!isPlainObject(value)) return value;
|
|
432
|
+
const out = {};
|
|
433
|
+
const isPerson = parentKey !== null && PERSON_CONTAINERS.has(parentKey);
|
|
434
|
+
for (const [key, child] of Object.entries(value)) {
|
|
435
|
+
if (PERSON_KEYS.has(key) || isPerson && key === "name") {
|
|
436
|
+
out[key] = child === void 0 ? child : null;
|
|
437
|
+
} else {
|
|
438
|
+
out[key] = walk(child, key, depth + 1);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
function redactShopperPii(value) {
|
|
444
|
+
return walk(value, null, 0);
|
|
445
|
+
}
|
|
274
446
|
|
|
275
447
|
// ../action-catalog/src/actions.ts
|
|
448
|
+
var OAUTH_WITHHELD = "Over an OAuth connection (ChatGPT, Claude) these are withheld, because a buyer can type them: line-item and quote-item names, SKUs and reference ids; company; every address line except the country; the merchant's external ids; notes, messages, reasons and metadata. Describe items by quantity and amount, and say the rest is withheld rather than missing.";
|
|
276
449
|
var ACTIONS = [
|
|
277
450
|
// ---------------------------------------------------------------- identity
|
|
278
451
|
{
|
|
@@ -289,7 +462,7 @@ var ACTIONS = [
|
|
|
289
462
|
{
|
|
290
463
|
id: "search_docs",
|
|
291
464
|
title: "Search the Throttle developer docs",
|
|
292
|
-
description: "Full-text search over the Throttle developer documentation. Returns titles, canonical
|
|
465
|
+
description: "Full-text search over the Throttle developer documentation. Returns titles, canonical usethrottle.dev/docs URLs, and matching excerpts. Prefer this over recalling API details from memory.",
|
|
293
466
|
kind: "read",
|
|
294
467
|
scopes: [],
|
|
295
468
|
input: [
|
|
@@ -326,7 +499,7 @@ var ACTIONS = [
|
|
|
326
499
|
{
|
|
327
500
|
id: "validate_payload",
|
|
328
501
|
title: "Validate a request body against the API contract",
|
|
329
|
-
description: "Check a request body against the published contract before sending it. Names every unsupported field and gives its canonical replacement \u2014 Throttle is strict camelCase and rejects retired aliases such as line_items, unit_price, customer_id and storeId.",
|
|
502
|
+
description: "Check a request body against the published contract before sending it. Names every unsupported field and gives its canonical replacement \u2014 Throttle is strict camelCase and rejects retired aliases such as line_items, unit_price, customer_id and storeId. Full reference: https://usethrottle.dev/docs/developers/api-reference.",
|
|
330
503
|
kind: "read",
|
|
331
504
|
scopes: [],
|
|
332
505
|
input: [
|
|
@@ -341,15 +514,22 @@ var ACTIONS = [
|
|
|
341
514
|
{
|
|
342
515
|
id: "api_get",
|
|
343
516
|
title: "Read any Throttle API endpoint",
|
|
344
|
-
description: "Escape hatch for reads the curated tools do not cover: performs a GET against any /api/v1 path and returns the response. Cannot write. Confirm the path with describe_endpoint first if unsure.",
|
|
517
|
+
description: "Escape hatch for reads the curated tools do not cover: performs a GET against any /api/v1 path and returns the response. Cannot write. Confirm the path with describe_endpoint first if unsure. Full reference: https://usethrottle.dev/docs/developers/api-reference.",
|
|
345
518
|
kind: "read",
|
|
346
519
|
scopes: [],
|
|
520
|
+
credentialScoped: true,
|
|
347
521
|
input: [
|
|
348
522
|
{
|
|
349
523
|
name: "path",
|
|
350
524
|
type: "string",
|
|
351
525
|
required: true,
|
|
352
526
|
description: "Path beginning with /api/v1, query string included."
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: "applicationId",
|
|
530
|
+
type: "string",
|
|
531
|
+
required: false,
|
|
532
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
353
533
|
}
|
|
354
534
|
],
|
|
355
535
|
requests: [],
|
|
@@ -371,11 +551,33 @@ var ACTIONS = [
|
|
|
371
551
|
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
372
552
|
{ name: "status", type: "string", required: false, description: "Order status filter." },
|
|
373
553
|
{ name: "paymentStatus", type: "string", required: false, description: "Payment status filter." },
|
|
554
|
+
{
|
|
555
|
+
name: "createdAfter",
|
|
556
|
+
type: "string",
|
|
557
|
+
required: false,
|
|
558
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: "createdBefore",
|
|
562
|
+
type: "string",
|
|
563
|
+
required: false,
|
|
564
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
565
|
+
},
|
|
374
566
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
375
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
567
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
568
|
+
{
|
|
569
|
+
name: "applicationId",
|
|
570
|
+
type: "string",
|
|
571
|
+
required: false,
|
|
572
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
573
|
+
}
|
|
376
574
|
],
|
|
377
575
|
requests: [{ method: "GET", path: "/api/v1/orders" }],
|
|
378
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
576
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
577
|
+
guardrails: [
|
|
578
|
+
OAUTH_WITHHELD,
|
|
579
|
+
"Over an OAuth connection, q matches the order id, order number and status fields only."
|
|
580
|
+
]
|
|
379
581
|
},
|
|
380
582
|
{
|
|
381
583
|
id: "get_order",
|
|
@@ -383,7 +585,16 @@ var ACTIONS = [
|
|
|
383
585
|
description: "Full picture of a single order: the order itself plus its payments, returns and fulfillments in one response.",
|
|
384
586
|
kind: "read",
|
|
385
587
|
scopes: ["orders:read"],
|
|
386
|
-
|
|
588
|
+
sectionScopes: ["payments:read", "order_returns:read", "fulfillments:read"],
|
|
589
|
+
input: [
|
|
590
|
+
{ name: "id", type: "string", required: true, description: "Order id." },
|
|
591
|
+
{
|
|
592
|
+
name: "applicationId",
|
|
593
|
+
type: "string",
|
|
594
|
+
required: false,
|
|
595
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
596
|
+
}
|
|
597
|
+
],
|
|
387
598
|
requests: [
|
|
388
599
|
{ method: "GET", path: "/api/v1/orders/:id" },
|
|
389
600
|
{ method: "GET", path: "/api/v1/orders/:id/payments" },
|
|
@@ -392,23 +603,45 @@ var ACTIONS = [
|
|
|
392
603
|
],
|
|
393
604
|
channels: ["mcp"],
|
|
394
605
|
guardrails: [
|
|
395
|
-
"Sub-reads need their own scopes (payments:read, order_returns:read, fulfillments:read). Skip a section the credential cannot read and say so in the result rather than failing the whole call."
|
|
606
|
+
"Sub-reads need their own scopes (payments:read, order_returns:read, fulfillments:read). Skip a section the credential cannot read and say so in the result rather than failing the whole call.",
|
|
607
|
+
OAUTH_WITHHELD
|
|
396
608
|
]
|
|
397
609
|
},
|
|
398
610
|
// ---------------------------------------------------------------- customers
|
|
399
611
|
{
|
|
400
612
|
id: "search_customers",
|
|
401
613
|
title: "Search customers",
|
|
402
|
-
description: "Find customers by name, email or external id.",
|
|
614
|
+
description: "Find customers by name, email or external id, or list them by creation date. Over an OAuth connection (ChatGPT, Claude) text search is not available \u2014 list by date instead \u2014 because every field it could match is withheld there.",
|
|
403
615
|
kind: "read",
|
|
404
616
|
scopes: ["customers:read"],
|
|
405
617
|
input: [
|
|
406
618
|
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
619
|
+
{
|
|
620
|
+
name: "createdAfter",
|
|
621
|
+
type: "string",
|
|
622
|
+
required: false,
|
|
623
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
name: "createdBefore",
|
|
627
|
+
type: "string",
|
|
628
|
+
required: false,
|
|
629
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
630
|
+
},
|
|
407
631
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
408
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
632
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
633
|
+
{
|
|
634
|
+
name: "applicationId",
|
|
635
|
+
type: "string",
|
|
636
|
+
required: false,
|
|
637
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
638
|
+
}
|
|
409
639
|
],
|
|
410
640
|
requests: [{ method: "GET", path: "/api/v1/customers" }],
|
|
411
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
641
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
642
|
+
guardrails: [
|
|
643
|
+
'email, phone, firstName, lastName, name and metadata are withheld (null, or absent from the result) unless the credential holds customer_pii:read, which OAuth connections cannot hold. Over an OAuth connection company, externalId and externalCustomerId are withheld too, and q is refused. Do not describe a missing name/email as "no name on file"; say it is withheld.'
|
|
644
|
+
]
|
|
412
645
|
},
|
|
413
646
|
{
|
|
414
647
|
id: "get_customer",
|
|
@@ -416,7 +649,16 @@ var ACTIONS = [
|
|
|
416
649
|
description: "Full picture of a single customer: the record plus their subscriptions and addresses in one response.",
|
|
417
650
|
kind: "read",
|
|
418
651
|
scopes: ["customers:read"],
|
|
419
|
-
|
|
652
|
+
sectionScopes: ["customer_addresses:read", "subscriptions:read"],
|
|
653
|
+
input: [
|
|
654
|
+
{ name: "id", type: "string", required: true, description: "Customer id." },
|
|
655
|
+
{
|
|
656
|
+
name: "applicationId",
|
|
657
|
+
type: "string",
|
|
658
|
+
required: false,
|
|
659
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
660
|
+
}
|
|
661
|
+
],
|
|
420
662
|
requests: [
|
|
421
663
|
{ method: "GET", path: "/api/v1/customers/:id" },
|
|
422
664
|
{ method: "GET", path: "/api/v1/customers/:id/addresses" },
|
|
@@ -424,7 +666,9 @@ var ACTIONS = [
|
|
|
424
666
|
],
|
|
425
667
|
channels: ["mcp"],
|
|
426
668
|
guardrails: [
|
|
427
|
-
"Sub-reads need customer_addresses:read / subscriptions:read. Omit a section the credential cannot read instead of failing."
|
|
669
|
+
"Sub-reads need customer_addresses:read / subscriptions:read. Omit a section the credential cannot read instead of failing.",
|
|
670
|
+
"email, phone, firstName, lastName, name and metadata on the customer record are withheld (null, or absent from the result) unless the credential holds customer_pii:read, which OAuth connections cannot hold. Say PII is redacted rather than reporting the buyer as anonymous.",
|
|
671
|
+
OAUTH_WITHHELD
|
|
428
672
|
]
|
|
429
673
|
},
|
|
430
674
|
// ------------------------------------------------------------- subscriptions
|
|
@@ -437,8 +681,26 @@ var ACTIONS = [
|
|
|
437
681
|
input: [
|
|
438
682
|
{ name: "customerId", type: "string", required: false, description: "Filter by customer." },
|
|
439
683
|
{ name: "status", type: "string", required: false, description: "Filter by status." },
|
|
684
|
+
{
|
|
685
|
+
name: "createdAfter",
|
|
686
|
+
type: "string",
|
|
687
|
+
required: false,
|
|
688
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
name: "createdBefore",
|
|
692
|
+
type: "string",
|
|
693
|
+
required: false,
|
|
694
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
695
|
+
},
|
|
440
696
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
441
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
697
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
698
|
+
{
|
|
699
|
+
name: "applicationId",
|
|
700
|
+
type: "string",
|
|
701
|
+
required: false,
|
|
702
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
703
|
+
}
|
|
442
704
|
],
|
|
443
705
|
requests: [{ method: "GET", path: "/api/v1/subscriptions" }],
|
|
444
706
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -449,7 +711,15 @@ var ACTIONS = [
|
|
|
449
711
|
description: "Read a single subscription including its current period and pending changes.",
|
|
450
712
|
kind: "read",
|
|
451
713
|
scopes: ["subscriptions:read"],
|
|
452
|
-
input: [
|
|
714
|
+
input: [
|
|
715
|
+
{ name: "id", type: "string", required: true, description: "Subscription id." },
|
|
716
|
+
{
|
|
717
|
+
name: "applicationId",
|
|
718
|
+
type: "string",
|
|
719
|
+
required: false,
|
|
720
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
721
|
+
}
|
|
722
|
+
],
|
|
453
723
|
requests: [{ method: "GET", path: "/api/v1/subscriptions/:id" }],
|
|
454
724
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
455
725
|
},
|
|
@@ -462,8 +732,26 @@ var ACTIONS = [
|
|
|
462
732
|
scopes: ["quotes:read"],
|
|
463
733
|
input: [
|
|
464
734
|
{ name: "status", type: "string", required: false, description: "Filter by quote status." },
|
|
735
|
+
{
|
|
736
|
+
name: "createdAfter",
|
|
737
|
+
type: "string",
|
|
738
|
+
required: false,
|
|
739
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
name: "createdBefore",
|
|
743
|
+
type: "string",
|
|
744
|
+
required: false,
|
|
745
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
746
|
+
},
|
|
465
747
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
466
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
748
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
749
|
+
{
|
|
750
|
+
name: "applicationId",
|
|
751
|
+
type: "string",
|
|
752
|
+
required: false,
|
|
753
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
754
|
+
}
|
|
467
755
|
],
|
|
468
756
|
requests: [{ method: "GET", path: "/api/v1/quotes" }],
|
|
469
757
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -471,12 +759,21 @@ var ACTIONS = [
|
|
|
471
759
|
{
|
|
472
760
|
id: "get_quote",
|
|
473
761
|
title: "Get one quote",
|
|
474
|
-
description: "Read a single quote with its line items
|
|
762
|
+
description: "Read a single quote: status, totals, and each revision with its line items and payment terms.",
|
|
475
763
|
kind: "read",
|
|
476
764
|
scopes: ["quotes:read"],
|
|
477
|
-
input: [
|
|
765
|
+
input: [
|
|
766
|
+
{ name: "id", type: "string", required: true, description: "Quote id." },
|
|
767
|
+
{
|
|
768
|
+
name: "applicationId",
|
|
769
|
+
type: "string",
|
|
770
|
+
required: false,
|
|
771
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
772
|
+
}
|
|
773
|
+
],
|
|
478
774
|
requests: [{ method: "GET", path: "/api/v1/quotes/:id" }],
|
|
479
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
775
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
776
|
+
guardrails: [OAUTH_WITHHELD]
|
|
480
777
|
},
|
|
481
778
|
// ----------------------------------------------------------------- invoices
|
|
482
779
|
{
|
|
@@ -488,10 +785,17 @@ var ACTIONS = [
|
|
|
488
785
|
input: [
|
|
489
786
|
{ name: "status", type: "string", required: false, description: "Filter by invoice status." },
|
|
490
787
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
491
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
788
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
789
|
+
{
|
|
790
|
+
name: "applicationId",
|
|
791
|
+
type: "string",
|
|
792
|
+
required: false,
|
|
793
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
794
|
+
}
|
|
492
795
|
],
|
|
493
796
|
requests: [{ method: "GET", path: "/api/v1/invoices" }],
|
|
494
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
797
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
798
|
+
guardrails: [OAUTH_WITHHELD]
|
|
495
799
|
},
|
|
496
800
|
{
|
|
497
801
|
id: "receivables_aging",
|
|
@@ -499,7 +803,14 @@ var ACTIONS = [
|
|
|
499
803
|
description: 'Outstanding receivables bucketed by age \u2014 answers "what is overdue and by how long".',
|
|
500
804
|
kind: "read",
|
|
501
805
|
scopes: ["invoices:read"],
|
|
502
|
-
input: [
|
|
806
|
+
input: [
|
|
807
|
+
{
|
|
808
|
+
name: "applicationId",
|
|
809
|
+
type: "string",
|
|
810
|
+
required: false,
|
|
811
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
812
|
+
}
|
|
813
|
+
],
|
|
503
814
|
requests: [{ method: "GET", path: "/api/v1/invoices/aging" }],
|
|
504
815
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
505
816
|
},
|
|
@@ -524,7 +835,13 @@ var ACTIONS = [
|
|
|
524
835
|
{ name: "createdAfter", type: "string", required: false, description: "ISO 8601 date lower bound." },
|
|
525
836
|
{ name: "createdBefore", type: "string", required: false, description: "ISO 8601 date upper bound." },
|
|
526
837
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
527
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
838
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
839
|
+
{
|
|
840
|
+
name: "applicationId",
|
|
841
|
+
type: "string",
|
|
842
|
+
required: false,
|
|
843
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
844
|
+
}
|
|
528
845
|
],
|
|
529
846
|
requests: [{ method: "GET", path: "/api/v1/discounts" }],
|
|
530
847
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -535,7 +852,15 @@ var ACTIONS = [
|
|
|
535
852
|
description: "Read a single discount code: value, conditions, usage limits and redemption count.",
|
|
536
853
|
kind: "read",
|
|
537
854
|
scopes: ["discounts:read"],
|
|
538
|
-
input: [
|
|
855
|
+
input: [
|
|
856
|
+
{ name: "id", type: "string", required: true, description: "Discount id." },
|
|
857
|
+
{
|
|
858
|
+
name: "applicationId",
|
|
859
|
+
type: "string",
|
|
860
|
+
required: false,
|
|
861
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
862
|
+
}
|
|
863
|
+
],
|
|
539
864
|
requests: [{ method: "GET", path: "/api/v1/discounts/:id" }],
|
|
540
865
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
541
866
|
},
|
|
@@ -562,7 +887,13 @@ var ACTIONS = [
|
|
|
562
887
|
{ name: "amountMin", type: "number", required: false, description: "Minimum amount, minor units." },
|
|
563
888
|
{ name: "amountMax", type: "number", required: false, description: "Maximum amount, minor units." },
|
|
564
889
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
565
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
890
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
891
|
+
{
|
|
892
|
+
name: "applicationId",
|
|
893
|
+
type: "string",
|
|
894
|
+
required: false,
|
|
895
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
896
|
+
}
|
|
566
897
|
],
|
|
567
898
|
requests: [{ method: "GET", path: "/api/v1/payments" }],
|
|
568
899
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -573,7 +904,15 @@ var ACTIONS = [
|
|
|
573
904
|
description: "Read a single payment plus its full transaction history (authorize/capture/refund/void attempts).",
|
|
574
905
|
kind: "read",
|
|
575
906
|
scopes: ["payments:read"],
|
|
576
|
-
input: [
|
|
907
|
+
input: [
|
|
908
|
+
{ name: "id", type: "string", required: true, description: "Payment id." },
|
|
909
|
+
{
|
|
910
|
+
name: "applicationId",
|
|
911
|
+
type: "string",
|
|
912
|
+
required: false,
|
|
913
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
914
|
+
}
|
|
915
|
+
],
|
|
577
916
|
requests: [
|
|
578
917
|
{ method: "GET", path: "/api/v1/payments/:id" },
|
|
579
918
|
{ method: "GET", path: "/api/v1/payments/:id/transactions" }
|
|
@@ -590,9 +929,18 @@ var ACTIONS = [
|
|
|
590
929
|
description: "Read a single issued invoice: line items, totals, status and the billing snapshot at issue time.",
|
|
591
930
|
kind: "read",
|
|
592
931
|
scopes: ["invoices:read"],
|
|
593
|
-
input: [
|
|
932
|
+
input: [
|
|
933
|
+
{ name: "id", type: "string", required: true, description: "Invoice id." },
|
|
934
|
+
{
|
|
935
|
+
name: "applicationId",
|
|
936
|
+
type: "string",
|
|
937
|
+
required: false,
|
|
938
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
939
|
+
}
|
|
940
|
+
],
|
|
594
941
|
requests: [{ method: "GET", path: "/api/v1/invoices/:id" }],
|
|
595
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
942
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
943
|
+
guardrails: [OAUTH_WITHHELD]
|
|
596
944
|
},
|
|
597
945
|
// ------------------------------------------------------------------ carts
|
|
598
946
|
{
|
|
@@ -604,7 +952,13 @@ var ACTIONS = [
|
|
|
604
952
|
input: [
|
|
605
953
|
{ name: "sinceDays", type: "number", required: false, description: "How many days back to look." },
|
|
606
954
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
607
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
955
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
956
|
+
{
|
|
957
|
+
name: "applicationId",
|
|
958
|
+
type: "string",
|
|
959
|
+
required: false,
|
|
960
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
961
|
+
}
|
|
608
962
|
],
|
|
609
963
|
requests: [
|
|
610
964
|
{ method: "GET", path: "/api/v1/abandoned-carts" },
|
|
@@ -625,7 +979,13 @@ var ACTIONS = [
|
|
|
625
979
|
{ name: "entityType", type: "string", required: false, description: "Entity type filter." },
|
|
626
980
|
{ name: "entityId", type: "string", required: false, description: "Entity id filter." },
|
|
627
981
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
628
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
982
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
983
|
+
{
|
|
984
|
+
name: "applicationId",
|
|
985
|
+
type: "string",
|
|
986
|
+
required: false,
|
|
987
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
988
|
+
}
|
|
629
989
|
],
|
|
630
990
|
requests: [{ method: "GET", path: "/api/v1/events" }],
|
|
631
991
|
channels: ["mcp", "n8n"]
|
|
@@ -633,13 +993,30 @@ var ACTIONS = [
|
|
|
633
993
|
{
|
|
634
994
|
id: "list_webhook_deliveries",
|
|
635
995
|
title: "List webhook deliveries",
|
|
636
|
-
description: "Recent outbound webhook deliveries, filterable by endpoint and
|
|
996
|
+
description: "Recent outbound webhook deliveries, filterable by endpoint, status and event type.",
|
|
637
997
|
kind: "read",
|
|
638
998
|
scopes: ["webhook_deliveries:read"],
|
|
639
999
|
input: [
|
|
640
1000
|
{ name: "endpointId", type: "string", required: false, description: "Filter by endpoint." },
|
|
641
|
-
{
|
|
642
|
-
|
|
1001
|
+
{
|
|
1002
|
+
name: "status",
|
|
1003
|
+
type: "string",
|
|
1004
|
+
required: false,
|
|
1005
|
+
description: "Filter by delivery status: pending, delivered, failed or dead_letter."
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
name: "eventType",
|
|
1009
|
+
type: "string",
|
|
1010
|
+
required: false,
|
|
1011
|
+
description: "Filter by exact event type, e.g. payment.failed."
|
|
1012
|
+
},
|
|
1013
|
+
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
1014
|
+
{
|
|
1015
|
+
name: "applicationId",
|
|
1016
|
+
type: "string",
|
|
1017
|
+
required: false,
|
|
1018
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1019
|
+
}
|
|
643
1020
|
],
|
|
644
1021
|
requests: [{ method: "GET", path: "/api/v1/webhook-deliveries" }],
|
|
645
1022
|
channels: ["mcp", "n8n"]
|
|
@@ -650,7 +1027,15 @@ var ACTIONS = [
|
|
|
650
1027
|
description: "Full delivery record: the signed payload we sent, the response we got back, and every attempt.",
|
|
651
1028
|
kind: "read",
|
|
652
1029
|
scopes: ["webhook_deliveries:read"],
|
|
653
|
-
input: [
|
|
1030
|
+
input: [
|
|
1031
|
+
{ name: "id", type: "string", required: true, description: "Delivery id." },
|
|
1032
|
+
{
|
|
1033
|
+
name: "applicationId",
|
|
1034
|
+
type: "string",
|
|
1035
|
+
required: false,
|
|
1036
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1037
|
+
}
|
|
1038
|
+
],
|
|
654
1039
|
requests: [{ method: "GET", path: "/api/v1/webhook-deliveries/:id" }],
|
|
655
1040
|
channels: ["mcp", "n8n"]
|
|
656
1041
|
},
|
|
@@ -660,12 +1045,19 @@ var ACTIONS = [
|
|
|
660
1045
|
description: "Diagnose a failing checkout in one call: session state, its cart, the events it emitted, and any webhook deliveries that followed. Use this before asking the user to reproduce anything.",
|
|
661
1046
|
kind: "read",
|
|
662
1047
|
scopes: ["checkout_sessions:read"],
|
|
1048
|
+
sectionScopes: ["events:read", "webhook_deliveries:read"],
|
|
663
1049
|
input: [
|
|
664
1050
|
{
|
|
665
1051
|
name: "id",
|
|
666
1052
|
type: "string",
|
|
667
1053
|
required: true,
|
|
668
1054
|
description: "Checkout session id (or cart id \u2014 the tool resolves the session)."
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
name: "applicationId",
|
|
1058
|
+
type: "string",
|
|
1059
|
+
required: false,
|
|
1060
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
669
1061
|
}
|
|
670
1062
|
],
|
|
671
1063
|
requests: [
|
|
@@ -676,7 +1068,8 @@ var ACTIONS = [
|
|
|
676
1068
|
],
|
|
677
1069
|
channels: ["mcp"],
|
|
678
1070
|
guardrails: [
|
|
679
|
-
"Report which sections were unavailable rather than failing the call when a sub-read is unscoped."
|
|
1071
|
+
"Report which sections were unavailable rather than failing the call when a sub-read is unscoped.",
|
|
1072
|
+
OAUTH_WITHHELD
|
|
680
1073
|
]
|
|
681
1074
|
},
|
|
682
1075
|
// ------------------------------------------------------------- extensions
|
|
@@ -695,7 +1088,13 @@ var ACTIONS = [
|
|
|
695
1088
|
enum: ["active", "suspended", "uninstalled"]
|
|
696
1089
|
},
|
|
697
1090
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
698
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
1091
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
1092
|
+
{
|
|
1093
|
+
name: "applicationId",
|
|
1094
|
+
type: "string",
|
|
1095
|
+
required: false,
|
|
1096
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1097
|
+
}
|
|
699
1098
|
],
|
|
700
1099
|
requests: [{ method: "GET", path: "/api/v1/installations" }],
|
|
701
1100
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -707,7 +1106,14 @@ var ACTIONS = [
|
|
|
707
1106
|
description: "Read the payment-routing cascade, active routing connectors, and card/other decline rules \u2014 what determines which processor handles a payment and what happens on a decline.",
|
|
708
1107
|
kind: "read",
|
|
709
1108
|
scopes: ["connectors:read"],
|
|
710
|
-
input: [
|
|
1109
|
+
input: [
|
|
1110
|
+
{
|
|
1111
|
+
name: "applicationId",
|
|
1112
|
+
type: "string",
|
|
1113
|
+
required: false,
|
|
1114
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1115
|
+
}
|
|
1116
|
+
],
|
|
711
1117
|
requests: [
|
|
712
1118
|
{ method: "GET", path: "/api/v1/payment-routing" },
|
|
713
1119
|
{ method: "GET", path: "/api/v1/routing-connectors" },
|
|
@@ -725,7 +1131,15 @@ var ACTIONS = [
|
|
|
725
1131
|
description: 'Answers "am I ready to go live?" \u2014 active connectors, webhook endpoints and their recent delivery health, and the environment this credential is pinned to.',
|
|
726
1132
|
kind: "read",
|
|
727
1133
|
scopes: [],
|
|
728
|
-
|
|
1134
|
+
sectionScopes: ["connectors:read", "webhooks:read"],
|
|
1135
|
+
input: [
|
|
1136
|
+
{
|
|
1137
|
+
name: "applicationId",
|
|
1138
|
+
type: "string",
|
|
1139
|
+
required: false,
|
|
1140
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1141
|
+
}
|
|
1142
|
+
],
|
|
729
1143
|
requests: [
|
|
730
1144
|
{ method: "GET", path: "/api/v1/whoami" },
|
|
731
1145
|
{ method: "GET", path: "/api/v1/connectors" },
|
|
@@ -741,7 +1155,7 @@ var ACTIONS = [
|
|
|
741
1155
|
{
|
|
742
1156
|
id: "get_workspace_settings",
|
|
743
1157
|
title: "Get workspace settings",
|
|
744
|
-
description: "Read this workspace's identity
|
|
1158
|
+
description: "Read this workspace's identity: name, slug, branding and onboarding state. Payment terms are set per application and environment, not here.",
|
|
745
1159
|
kind: "read",
|
|
746
1160
|
scopes: ["merchant:read"],
|
|
747
1161
|
input: [],
|
|
@@ -760,12 +1174,48 @@ var ACTIONS = [
|
|
|
760
1174
|
requests: [{ method: "GET", path: "/api/v1/audit-log" }],
|
|
761
1175
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
762
1176
|
},
|
|
1177
|
+
{
|
|
1178
|
+
id: "list_applications",
|
|
1179
|
+
title: "List applications in this workspace",
|
|
1180
|
+
description: 'List every application in this workspace you have access to, so you can pass one of their ids as applicationId on other read and analytics tools. Note: the underlying route is the legacy-named /v1/stores (kept for contract stability) \u2014 the resource it returns is applications, not a separate "stores" concept.',
|
|
1181
|
+
kind: "read",
|
|
1182
|
+
scopes: ["applications:read"],
|
|
1183
|
+
input: [],
|
|
1184
|
+
requests: [{ method: "GET", path: "/api/v1/stores" }],
|
|
1185
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
id: "get_sales_timeseries",
|
|
1189
|
+
title: "Sales over time",
|
|
1190
|
+
description: `Order count and gross revenue bucketed by day, week or month over any date range, in the merchant's timezone, optionally compared to the previous period or the same range last year. Use this for "how much did we sell on/last/between\u2026" instead of paging search_orders and adding it up.`,
|
|
1191
|
+
kind: "read",
|
|
1192
|
+
scopes: ["orders:read"],
|
|
1193
|
+
input: [
|
|
1194
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1195
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1196
|
+
{ name: "groupBy", type: "string", required: false, description: "Bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1197
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries, e.g. America/New_York. Defaults to UTC \u2014 ask the user rather than assuming UTC when the answer depends on day boundaries." },
|
|
1198
|
+
{ name: "compareTo", type: "string", required: false, description: "Add a comparison total.", enum: ["previous_period", "previous_year", "none"], default: "none" },
|
|
1199
|
+
{
|
|
1200
|
+
name: "applicationId",
|
|
1201
|
+
type: "string",
|
|
1202
|
+
required: false,
|
|
1203
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1204
|
+
}
|
|
1205
|
+
],
|
|
1206
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/orders-timeseries" }],
|
|
1207
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1208
|
+
guardrails: [
|
|
1209
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net."
|
|
1210
|
+
]
|
|
1211
|
+
},
|
|
763
1212
|
{
|
|
764
1213
|
id: "get_business_summary",
|
|
765
1214
|
title: "Business summary",
|
|
766
|
-
description: "How are we doing? Orders/revenue, subscription MRR, receivables and quotes pipeline
|
|
1215
|
+
description: "How are we doing? Orders/revenue, subscription MRR, receivables and quotes pipeline over a date range, compared to the immediately preceding window of equal length. Note: period=week|month|quarter means the TRAILING 7/30/90 days, not a calendar period \u2014 pass from/to for calendar months.",
|
|
767
1216
|
kind: "read",
|
|
768
1217
|
scopes: [],
|
|
1218
|
+
sectionScopes: ["orders:read", "subscriptions:read", "invoices:read", "quotes:read"],
|
|
769
1219
|
input: [
|
|
770
1220
|
{
|
|
771
1221
|
name: "period",
|
|
@@ -774,6 +1224,15 @@ var ACTIONS = [
|
|
|
774
1224
|
description: "Reporting window.",
|
|
775
1225
|
enum: ["week", "month", "quarter"],
|
|
776
1226
|
default: "month"
|
|
1227
|
+
},
|
|
1228
|
+
{ name: "from", type: "string", required: false, description: "ISO 8601 start of range. Overrides period." },
|
|
1229
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range. Defaults to now." },
|
|
1230
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for period boundaries. Defaults to UTC." },
|
|
1231
|
+
{
|
|
1232
|
+
name: "applicationId",
|
|
1233
|
+
type: "string",
|
|
1234
|
+
required: false,
|
|
1235
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
777
1236
|
}
|
|
778
1237
|
],
|
|
779
1238
|
requests: [{ method: "GET", path: "/api/v1/analytics/business-summary" }],
|
|
@@ -782,19 +1241,189 @@ var ACTIONS = [
|
|
|
782
1241
|
"Each section needs its own scope (orders:read, subscriptions:read, invoices:read, quotes:read). A section without its scope is reported as omitted rather than failing the whole call."
|
|
783
1242
|
]
|
|
784
1243
|
},
|
|
1244
|
+
{
|
|
1245
|
+
id: "get_top_products",
|
|
1246
|
+
title: "Top products",
|
|
1247
|
+
description: "Best-selling products over a date range, by revenue or units sold. Line items come from the merchant's own cart payload, so a product is identified by its external reference id when present and by name otherwise. The response carries both the top-N rows (by the chosen sort) and range-wide totals across every matching product, so you can compute what share of total revenue/units the top N represent.",
|
|
1248
|
+
kind: "read",
|
|
1249
|
+
scopes: ["orders:read"],
|
|
1250
|
+
input: [
|
|
1251
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1252
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1253
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1254
|
+
{ name: "by", type: "string", required: false, description: "Sort key.", enum: ["revenue", "units"], default: "revenue" },
|
|
1255
|
+
{ name: "limit", type: "number", required: false, description: "Max rows to return (1-100). Defaults to 20." },
|
|
1256
|
+
{
|
|
1257
|
+
name: "applicationId",
|
|
1258
|
+
type: "string",
|
|
1259
|
+
required: false,
|
|
1260
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1261
|
+
}
|
|
1262
|
+
],
|
|
1263
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/products" }],
|
|
1264
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1265
|
+
guardrails: [
|
|
1266
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net."
|
|
1267
|
+
]
|
|
1268
|
+
},
|
|
785
1269
|
{
|
|
786
1270
|
id: "get_attention_digest",
|
|
787
1271
|
title: "Attention digest",
|
|
788
1272
|
description: "What needs attention right now? Open disputes, subscriptions in dunning, overdue Net-N invoices, and webhooks with degraded delivery health \u2014 a point-in-time snapshot, not a time range.",
|
|
789
1273
|
kind: "read",
|
|
790
1274
|
scopes: [],
|
|
791
|
-
|
|
1275
|
+
sectionScopes: ["payment_disputes:read", "subscriptions:read", "invoices:read", "webhook_deliveries:read"],
|
|
1276
|
+
input: [
|
|
1277
|
+
{
|
|
1278
|
+
name: "applicationId",
|
|
1279
|
+
type: "string",
|
|
1280
|
+
required: false,
|
|
1281
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1282
|
+
}
|
|
1283
|
+
],
|
|
792
1284
|
requests: [{ method: "GET", path: "/api/v1/analytics/attention-digest" }],
|
|
793
1285
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
794
1286
|
guardrails: [
|
|
795
1287
|
"Each section needs its own scope (payment_disputes:read, subscriptions:read, invoices:read, webhook_deliveries:read). A section without its scope is reported as omitted rather than failing the whole call."
|
|
796
1288
|
]
|
|
797
1289
|
},
|
|
1290
|
+
{
|
|
1291
|
+
id: "get_customer_growth",
|
|
1292
|
+
title: "Customer growth",
|
|
1293
|
+
description: "Customer growth over a date range: signups per day/week/month, how many orders came from first-time versus returning customers, and the highest-value customers in the window.",
|
|
1294
|
+
kind: "read",
|
|
1295
|
+
scopes: ["customers:read"],
|
|
1296
|
+
input: [
|
|
1297
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1298
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1299
|
+
{ name: "groupBy", type: "string", required: false, description: "Signup series bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1300
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries. Defaults to UTC." },
|
|
1301
|
+
{ name: "topBy", type: "string", required: false, description: "Sort key for top customers.", enum: ["revenue", "orders"], default: "revenue" },
|
|
1302
|
+
{ name: "topLimit", type: "number", required: false, description: "Max top-customers rows to return (1-50). Defaults to 10." },
|
|
1303
|
+
{
|
|
1304
|
+
name: "applicationId",
|
|
1305
|
+
type: "string",
|
|
1306
|
+
required: false,
|
|
1307
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1308
|
+
}
|
|
1309
|
+
],
|
|
1310
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/customers" }],
|
|
1311
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1312
|
+
guardrails: [
|
|
1313
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net.",
|
|
1314
|
+
"topCustomers is a top-N list; use the accompanying totals for range-wide customer/order/revenue counts instead of summing the returned rows.",
|
|
1315
|
+
"topCustomers[].email and .name come back null unless the credential holds customer_pii:read, which OAuth connections cannot hold. Identify a top customer by id, not by a null name."
|
|
1316
|
+
]
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
id: "get_revenue_breakdown",
|
|
1320
|
+
title: "Revenue breakdown",
|
|
1321
|
+
description: "Revenue split by one dimension over a date range \u2014 payment method, order source, or currency. Amounts are gross; refunds are not deducted.",
|
|
1322
|
+
kind: "read",
|
|
1323
|
+
scopes: ["orders:read"],
|
|
1324
|
+
input: [
|
|
1325
|
+
{
|
|
1326
|
+
name: "dimension",
|
|
1327
|
+
type: "string",
|
|
1328
|
+
required: true,
|
|
1329
|
+
description: "Breakdown dimension.",
|
|
1330
|
+
enum: ["paymentMethod", "source", "currency"]
|
|
1331
|
+
},
|
|
1332
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1333
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1334
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1335
|
+
{
|
|
1336
|
+
name: "applicationId",
|
|
1337
|
+
type: "string",
|
|
1338
|
+
required: false,
|
|
1339
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1340
|
+
}
|
|
1341
|
+
],
|
|
1342
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/revenue-breakdown" }],
|
|
1343
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1344
|
+
guardrails: [
|
|
1345
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net.",
|
|
1346
|
+
"dimension is required; it is never defaulted, so omitting it fails the call instead of silently picking a dimension.",
|
|
1347
|
+
"For dimension=paymentMethod, an order paid via more than one method can appear in more than one row, so rows can sum to more than totals (a separate whole-range aggregate over orders alone)."
|
|
1348
|
+
]
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
id: "get_funnel_conversion",
|
|
1352
|
+
title: "Checkout funnel",
|
|
1353
|
+
description: "Checkout funnel over a date range: carts created, checkout sessions started, orders completed, with the drop-off between each stage.",
|
|
1354
|
+
kind: "read",
|
|
1355
|
+
scopes: ["carts:read"],
|
|
1356
|
+
input: [
|
|
1357
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1358
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1359
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1360
|
+
{
|
|
1361
|
+
name: "applicationId",
|
|
1362
|
+
type: "string",
|
|
1363
|
+
required: false,
|
|
1364
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1365
|
+
}
|
|
1366
|
+
],
|
|
1367
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/funnel" }],
|
|
1368
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1369
|
+
guardrails: [
|
|
1370
|
+
"Each stage needs its own scope (carts:read, checkout_sessions:read, orders:read), checked independently \u2014 an OAuth grant carries literal scopes with no legacy expansion, so a credential can hold carts:read without the other two. An omitted stage is reported as such rather than failing the whole call.",
|
|
1371
|
+
"conversionRate and dropOff[].ratePct are null (unknown) when a stage they depend on is omitted, and 0 only when the stage was readable with a true zero count. Never treat a null here as 0.",
|
|
1372
|
+
'dropOff[].ratePct is the percentage of "from" that did NOT reach "to" (loss), not a conversion rate. The step-conversion rate is 100 - ratePct.'
|
|
1373
|
+
]
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
id: "get_subscription_movement",
|
|
1377
|
+
title: "Subscription movement",
|
|
1378
|
+
description: "Subscription movement over a date range: new subscriptions, cancellations and net MRR change per day/week/month. Figures are reconstructed from subscription timestamps, not a stored historical snapshot.",
|
|
1379
|
+
kind: "read",
|
|
1380
|
+
scopes: ["subscriptions:read"],
|
|
1381
|
+
input: [
|
|
1382
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1383
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1384
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries. Defaults to UTC." },
|
|
1385
|
+
{ name: "groupBy", type: "string", required: false, description: "Bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1386
|
+
{
|
|
1387
|
+
name: "applicationId",
|
|
1388
|
+
type: "string",
|
|
1389
|
+
required: false,
|
|
1390
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1391
|
+
}
|
|
1392
|
+
],
|
|
1393
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/subscriptions" }],
|
|
1394
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1395
|
+
guardrails: [
|
|
1396
|
+
"newSubscriptions, churnedSubscriptions and mrrDeltaCents are reconstructed per bucket from subscription created_at/cancelled_at timestamps, not a stored historical MRR snapshot \u2014 never present this series as an audited historical MRR ledger.",
|
|
1397
|
+
"mrrDeltaCents for a bucket only credits subscriptions created in that bucket that are still active right now, and only debits subscriptions cancelled in that bucket that already existed when it started. A subscription created AND cancelled inside the same bucket still counts toward both newSubscriptions and churnedSubscriptions but contributes 0 to mrrDeltaCents.",
|
|
1398
|
+
"totals is the sum over the full series (the series is never truncated), so totals.newSubscriptions/.churnedSubscriptions/.netMrrDeltaCents always equal the corresponding sums of series[]."
|
|
1399
|
+
]
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
id: "get_discount_performance",
|
|
1403
|
+
title: "Discount performance",
|
|
1404
|
+
description: "Discount code performance over a date range: redemption count, revenue on discounted orders, and total discount given per code.",
|
|
1405
|
+
kind: "read",
|
|
1406
|
+
scopes: ["discounts:read"],
|
|
1407
|
+
input: [
|
|
1408
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1409
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1410
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1411
|
+
{ name: "limit", type: "number", required: false, description: "Max rows to return. Defaults to 20." },
|
|
1412
|
+
{
|
|
1413
|
+
name: "applicationId",
|
|
1414
|
+
type: "string",
|
|
1415
|
+
required: false,
|
|
1416
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1417
|
+
}
|
|
1418
|
+
],
|
|
1419
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/discounts" }],
|
|
1420
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1421
|
+
guardrails: [
|
|
1422
|
+
'discountedRevenueCents and discountGivenCents are DIFFERENT measures: discountedRevenueCents is the gross order total on orders that redeemed the code; discountGivenCents is the discount amount taken off those same orders. Never describe discountedRevenueCents as "given away" or discountGivenCents as revenue.',
|
|
1423
|
+
"Only orders with status <> draft and a gross-eligible payment_status are counted; refunds are not deducted from discountedRevenueCents, because that needs payment_refunds:read.",
|
|
1424
|
+
"rows are the top N codes by discount given; totals is a SEPARATE whole-range aggregate (not a reduction over rows), so totals.redemptions / totals.discountGivenCents stay valid even when rows is truncated by limit."
|
|
1425
|
+
]
|
|
1426
|
+
},
|
|
798
1427
|
// ------------------------------------------------------------ sandbox write
|
|
799
1428
|
{
|
|
800
1429
|
id: "seed_test_data",
|
|
@@ -820,6 +1449,151 @@ var ACTIONS = [
|
|
|
820
1449
|
"Seeds a finalized DRAFT order \u2014 it does not capture a payment, which needs a browser-side tokenised payment method."
|
|
821
1450
|
]
|
|
822
1451
|
},
|
|
1452
|
+
// ---------------------------------------------------------- inbound writes
|
|
1453
|
+
// The no-code channels are mostly used in the "something happened elsewhere,
|
|
1454
|
+
// put it into Throttle" direction — a form submission, a new CRM contact, a
|
|
1455
|
+
// spreadsheet row. These three are that direction.
|
|
1456
|
+
{
|
|
1457
|
+
id: "create_customer",
|
|
1458
|
+
title: "Create a customer",
|
|
1459
|
+
description: "Create a customer record. Use search_customers first if the buyer may already exist \u2014 this does not de-duplicate by email.",
|
|
1460
|
+
kind: "write",
|
|
1461
|
+
scopes: ["customers:write"],
|
|
1462
|
+
input: [
|
|
1463
|
+
{ name: "email", type: "string", required: true, description: "The buyer's email address." },
|
|
1464
|
+
{ name: "firstName", type: "string", required: false, description: "Given name." },
|
|
1465
|
+
{ name: "lastName", type: "string", required: false, description: "Family name." },
|
|
1466
|
+
{ name: "phone", type: "string", required: false, description: "Contact phone number." },
|
|
1467
|
+
{ name: "company", type: "string", required: false, description: "The buyer's company." },
|
|
1468
|
+
{
|
|
1469
|
+
name: "externalId",
|
|
1470
|
+
type: "string",
|
|
1471
|
+
required: false,
|
|
1472
|
+
description: "This customer's id in your own system. Resolvable later via get_customer."
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
name: "netN",
|
|
1476
|
+
type: "number",
|
|
1477
|
+
required: false,
|
|
1478
|
+
description: "Invoice terms in days for this customer specifically, 0-365. Omit to use the workspace default; 0 means due on issue."
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
name: "acceptsMarketing",
|
|
1482
|
+
type: "boolean",
|
|
1483
|
+
required: false,
|
|
1484
|
+
description: "Whether the buyer opted into marketing email.",
|
|
1485
|
+
default: false
|
|
1486
|
+
}
|
|
1487
|
+
],
|
|
1488
|
+
requests: [{ method: "POST", path: "/api/v1/customers" }],
|
|
1489
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1490
|
+
idempotent: true,
|
|
1491
|
+
guardrails: [
|
|
1492
|
+
"The created record is echoed back with email, phone, firstName, lastName and name nulled unless the credential holds customer_pii:read, which OAuth connections cannot hold \u2014 the customer is still created with the values sent, only the response is redacted."
|
|
1493
|
+
]
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
id: "update_customer",
|
|
1497
|
+
title: "Update a customer",
|
|
1498
|
+
description: "Change fields on an existing customer. Only the fields supplied are touched; everything else is left alone.",
|
|
1499
|
+
kind: "write",
|
|
1500
|
+
scopes: ["customers:write"],
|
|
1501
|
+
input: [
|
|
1502
|
+
{ name: "id", type: "string", required: true, description: "Customer id." },
|
|
1503
|
+
{ name: "email", type: "string", required: false, description: "The buyer's email address." },
|
|
1504
|
+
{ name: "firstName", type: "string", required: false, description: "Given name." },
|
|
1505
|
+
{ name: "lastName", type: "string", required: false, description: "Family name." },
|
|
1506
|
+
{ name: "phone", type: "string", required: false, description: "Contact phone number." },
|
|
1507
|
+
{ name: "company", type: "string", required: false, description: "The buyer's company." },
|
|
1508
|
+
{
|
|
1509
|
+
name: "externalId",
|
|
1510
|
+
type: "string",
|
|
1511
|
+
required: false,
|
|
1512
|
+
description: "This customer's id in your own system. Pass null to clear it."
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
name: "netN",
|
|
1516
|
+
type: "number",
|
|
1517
|
+
required: false,
|
|
1518
|
+
description: "Invoice terms in days for this customer specifically, 0-365."
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
name: "acceptsMarketing",
|
|
1522
|
+
type: "boolean",
|
|
1523
|
+
required: false,
|
|
1524
|
+
description: "Whether the buyer opted into marketing email."
|
|
1525
|
+
}
|
|
1526
|
+
],
|
|
1527
|
+
requests: [{ method: "PATCH", path: "/api/v1/customers/:id" }],
|
|
1528
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1529
|
+
idempotent: true,
|
|
1530
|
+
guardrails: [
|
|
1531
|
+
"The updated record is echoed back with email, phone, firstName, lastName and name nulled unless the credential holds customer_pii:read, which OAuth connections cannot hold \u2014 the update still applied, only the response is redacted."
|
|
1532
|
+
]
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
id: "create_checkout_link",
|
|
1536
|
+
title: "Create a checkout link",
|
|
1537
|
+
description: "Build a cart from line items and return a hosted checkout URL the buyer can pay at. Prices are in minor units (2500 = $25.00).",
|
|
1538
|
+
kind: "write",
|
|
1539
|
+
scopes: ["carts:write", "cart_items:write", "checkout_sessions:write"],
|
|
1540
|
+
input: [
|
|
1541
|
+
{
|
|
1542
|
+
name: "lineItems",
|
|
1543
|
+
type: "array",
|
|
1544
|
+
required: true,
|
|
1545
|
+
description: "What the buyer is paying for: { name, unitPrice, quantity }. unitPrice is an integer in minor units."
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
name: "returnUrl",
|
|
1549
|
+
type: "string",
|
|
1550
|
+
required: true,
|
|
1551
|
+
description: "Absolute URL the buyer lands on after paying."
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
name: "cancelUrl",
|
|
1555
|
+
type: "string",
|
|
1556
|
+
required: true,
|
|
1557
|
+
description: "Absolute URL the buyer lands on if they abandon checkout."
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
name: "customerId",
|
|
1561
|
+
type: "string",
|
|
1562
|
+
required: false,
|
|
1563
|
+
description: "Attach the cart to an existing customer."
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
name: "customerEmail",
|
|
1567
|
+
type: "string",
|
|
1568
|
+
required: false,
|
|
1569
|
+
description: "Pre-fill the buyer email without needing a customer record."
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
name: "discountCode",
|
|
1573
|
+
type: "string",
|
|
1574
|
+
required: false,
|
|
1575
|
+
description: "Discount code to apply to the session."
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
name: "netN",
|
|
1579
|
+
type: "number",
|
|
1580
|
+
required: false,
|
|
1581
|
+
description: "Invoice terms in days for this cart, 0-365. Omit to pay in full at checkout."
|
|
1582
|
+
}
|
|
1583
|
+
],
|
|
1584
|
+
// Three calls: the API has no single "checkout link" endpoint, and a
|
|
1585
|
+
// checkout session requires a cart that already holds its line items.
|
|
1586
|
+
requests: [
|
|
1587
|
+
{ method: "POST", path: "/api/v1/carts" },
|
|
1588
|
+
{ method: "POST", path: "/api/v1/carts/:id/items" },
|
|
1589
|
+
{ method: "POST", path: "/api/v1/checkout/sessions" }
|
|
1590
|
+
],
|
|
1591
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1592
|
+
idempotent: true,
|
|
1593
|
+
guardrails: [
|
|
1594
|
+
"The returned URL is buyer-facing and takes real payment in a production environment."
|
|
1595
|
+
]
|
|
1596
|
+
},
|
|
823
1597
|
// -------------------------------------------------------------- money/mail
|
|
824
1598
|
{
|
|
825
1599
|
id: "create_quote",
|
|
@@ -848,7 +1622,8 @@ var ACTIONS = [
|
|
|
848
1622
|
requests: [{ method: "POST", path: "/api/v1/quotes/:id/issue" }],
|
|
849
1623
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
850
1624
|
idempotent: true,
|
|
851
|
-
guardrails: ['Sends mail to a real buyer. Never call it speculatively or to "check" a quote.']
|
|
1625
|
+
guardrails: ['Sends mail to a real buyer. Never call it speculatively or to "check" a quote.'],
|
|
1626
|
+
destructive: true
|
|
852
1627
|
},
|
|
853
1628
|
{
|
|
854
1629
|
id: "refund_payment",
|
|
@@ -867,12 +1642,20 @@ var ACTIONS = [
|
|
|
867
1642
|
{ name: "reason", type: "string", required: false, description: "Reason recorded on the refund." }
|
|
868
1643
|
],
|
|
869
1644
|
requests: [{ method: "POST", path: "/api/v1/payments/:id/refund" }],
|
|
870
|
-
|
|
1645
|
+
// NOT zapier/make. `payment_refunds:write` is `sensitive` in the scope
|
|
1646
|
+
// registry, and `validateScopesForOAuth` rejects every sensitive scope — so
|
|
1647
|
+
// an OAuth grant, which is the only credential a public Zapier or Make app
|
|
1648
|
+
// can hold, can never carry it. Listing the action on those channels would
|
|
1649
|
+
// ship a connector action that 403s on every call. MCP and n8n keep it:
|
|
1650
|
+
// both authenticate with an `sk_` key, which CAN hold the scope.
|
|
1651
|
+
// `oauth-grantable-scopes` in the catalog tests enforces this.
|
|
1652
|
+
channels: ["mcp", "n8n"],
|
|
871
1653
|
idempotent: true,
|
|
872
1654
|
guardrails: [
|
|
873
1655
|
"Moves money and is irreversible. Confirm with the user first.",
|
|
874
1656
|
"The API treats an omitted amount as a FULL refund, so this action requires it explicitly."
|
|
875
|
-
]
|
|
1657
|
+
],
|
|
1658
|
+
destructive: true
|
|
876
1659
|
},
|
|
877
1660
|
{
|
|
878
1661
|
id: "manage_subscription",
|
|
@@ -915,7 +1698,8 @@ var ACTIONS = [
|
|
|
915
1698
|
requests: [{ method: "POST", path: "/api/v1/subscriptions/:id/cancel" }],
|
|
916
1699
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
917
1700
|
idempotent: true,
|
|
918
|
-
guardrails: ["Ends billing for a paying customer. Confirm with the user first."]
|
|
1701
|
+
guardrails: ["Ends billing for a paying customer. Confirm with the user first."],
|
|
1702
|
+
destructive: true
|
|
919
1703
|
},
|
|
920
1704
|
{
|
|
921
1705
|
id: "replay_webhook_delivery",
|
|
@@ -927,7 +1711,8 @@ var ACTIONS = [
|
|
|
927
1711
|
requests: [{ method: "POST", path: "/api/v1/webhook-deliveries/:id/replay" }],
|
|
928
1712
|
channels: ["mcp", "n8n"],
|
|
929
1713
|
idempotent: true,
|
|
930
|
-
guardrails: ["The receiving system will process the event again. Confirm it is idempotent first."]
|
|
1714
|
+
guardrails: ["The receiving system will process the event again. Confirm it is idempotent first."],
|
|
1715
|
+
destructive: true
|
|
931
1716
|
},
|
|
932
1717
|
{
|
|
933
1718
|
id: "send_test_webhook",
|
|
@@ -941,7 +1726,8 @@ var ACTIONS = [
|
|
|
941
1726
|
],
|
|
942
1727
|
requests: [{ method: "POST", path: "/api/v1/webhook-endpoints/:id/test" }],
|
|
943
1728
|
channels: ["mcp", "n8n"],
|
|
944
|
-
idempotent: true
|
|
1729
|
+
idempotent: true,
|
|
1730
|
+
destructive: true
|
|
945
1731
|
}
|
|
946
1732
|
];
|
|
947
1733
|
|
|
@@ -1172,7 +1958,7 @@ function validatePayload(index, input) {
|
|
|
1172
1958
|
}
|
|
1173
1959
|
|
|
1174
1960
|
// src/docs.ts
|
|
1175
|
-
var DOCS_ORIGIN = "https://
|
|
1961
|
+
var DOCS_ORIGIN = "https://usethrottle.dev/docs";
|
|
1176
1962
|
function excerptAround(text, needle, radius = 160) {
|
|
1177
1963
|
const at = text.toLowerCase().indexOf(needle.toLowerCase());
|
|
1178
1964
|
if (at < 0) return text.slice(0, radius * 2).trim();
|
|
@@ -1256,27 +2042,35 @@ var HANDLERS = {
|
|
|
1256
2042
|
// ------------------------------------------------------------- generic read
|
|
1257
2043
|
api_get: async (ctx, args) => {
|
|
1258
2044
|
const path = String(args.path ?? "");
|
|
1259
|
-
|
|
2045
|
+
const resolved = new URL(path, "https://throttle.invalid").pathname;
|
|
2046
|
+
if (!path.startsWith("/api/v1/") || !resolved.startsWith("/api/v1/") || /(^|[\/\\])\.(\.)?([\/\\?#]|$)|%2e/i.test(path)) {
|
|
1260
2047
|
throw new Error(
|
|
1261
2048
|
`Refusing to call "${path}". api_get only reads paths beginning with /api/v1/ on the configured Throttle host.`
|
|
1262
2049
|
);
|
|
1263
2050
|
}
|
|
1264
|
-
return ctx.client.get(path);
|
|
2051
|
+
return ctx.client.get(path, void 0, { applicationId: args.applicationId });
|
|
1265
2052
|
},
|
|
1266
2053
|
// ------------------------------------------------------------------ orders
|
|
1267
|
-
search_orders: async (ctx, args) => ctx.client.list(
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
2054
|
+
search_orders: async (ctx, args) => ctx.client.list(
|
|
2055
|
+
"/api/v1/orders",
|
|
2056
|
+
{
|
|
2057
|
+
q: args.q,
|
|
2058
|
+
status: args.status,
|
|
2059
|
+
paymentStatus: args.paymentStatus,
|
|
2060
|
+
createdAfter: args.createdAfter,
|
|
2061
|
+
createdBefore: args.createdBefore,
|
|
2062
|
+
...page(args)
|
|
2063
|
+
},
|
|
2064
|
+
{ applicationId: args.applicationId }
|
|
2065
|
+
),
|
|
1273
2066
|
get_order: async (ctx, args) => {
|
|
1274
2067
|
const id = String(args.id);
|
|
1275
|
-
const
|
|
2068
|
+
const opts = { applicationId: args.applicationId };
|
|
2069
|
+
const order = await ctx.client.get(`/api/v1/orders/${id}`, void 0, opts);
|
|
1276
2070
|
const [payments, returns, fulfillments] = await Promise.all([
|
|
1277
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/payments
|
|
1278
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/returns
|
|
1279
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/fulfillments
|
|
2071
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/payments`, void 0, opts)),
|
|
2072
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/returns`, void 0, opts)),
|
|
2073
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/fulfillments`, void 0, opts))
|
|
1280
2074
|
]);
|
|
1281
2075
|
return {
|
|
1282
2076
|
order,
|
|
@@ -1286,13 +2080,23 @@ var HANDLERS = {
|
|
|
1286
2080
|
};
|
|
1287
2081
|
},
|
|
1288
2082
|
// --------------------------------------------------------------- customers
|
|
1289
|
-
search_customers: async (ctx, args) => ctx.client.list(
|
|
2083
|
+
search_customers: async (ctx, args) => ctx.client.list(
|
|
2084
|
+
"/api/v1/customers",
|
|
2085
|
+
{
|
|
2086
|
+
q: args.q,
|
|
2087
|
+
createdAfter: args.createdAfter,
|
|
2088
|
+
createdBefore: args.createdBefore,
|
|
2089
|
+
...page(args)
|
|
2090
|
+
},
|
|
2091
|
+
{ applicationId: args.applicationId }
|
|
2092
|
+
),
|
|
1290
2093
|
get_customer: async (ctx, args) => {
|
|
1291
2094
|
const id = String(args.id);
|
|
1292
|
-
const
|
|
2095
|
+
const opts = { applicationId: args.applicationId };
|
|
2096
|
+
const customer = await ctx.client.get(`/api/v1/customers/${id}`, void 0, opts);
|
|
1293
2097
|
const [addresses, subscriptions] = await Promise.all([
|
|
1294
|
-
optional(() => ctx.client.list(`/api/v1/customers/${id}/addresses
|
|
1295
|
-
optional(() => ctx.client.list("/api/v1/subscriptions", { customerId: id }))
|
|
2098
|
+
optional(() => ctx.client.list(`/api/v1/customers/${id}/addresses`, void 0, opts)),
|
|
2099
|
+
optional(() => ctx.client.list("/api/v1/subscriptions", { customerId: id }, opts))
|
|
1296
2100
|
]);
|
|
1297
2101
|
return {
|
|
1298
2102
|
customer,
|
|
@@ -1301,60 +2105,101 @@ var HANDLERS = {
|
|
|
1301
2105
|
};
|
|
1302
2106
|
},
|
|
1303
2107
|
// ----------------------------------------------------------- subscriptions
|
|
1304
|
-
list_subscriptions: async (ctx, args) => ctx.client.list(
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
2108
|
+
list_subscriptions: async (ctx, args) => ctx.client.list(
|
|
2109
|
+
"/api/v1/subscriptions",
|
|
2110
|
+
{
|
|
2111
|
+
customerId: args.customerId,
|
|
2112
|
+
status: args.status,
|
|
2113
|
+
createdAfter: args.createdAfter,
|
|
2114
|
+
createdBefore: args.createdBefore,
|
|
2115
|
+
...page(args)
|
|
2116
|
+
},
|
|
2117
|
+
{ applicationId: args.applicationId }
|
|
2118
|
+
),
|
|
2119
|
+
get_subscription: async (ctx, args) => ctx.client.get(`/api/v1/subscriptions/${String(args.id)}`, void 0, {
|
|
2120
|
+
applicationId: args.applicationId
|
|
1308
2121
|
}),
|
|
1309
|
-
get_subscription: async (ctx, args) => ctx.client.get(`/api/v1/subscriptions/${String(args.id)}`),
|
|
1310
2122
|
// ------------------------------------------------------------------ quotes
|
|
1311
|
-
search_quotes: async (ctx, args) => ctx.client.list(
|
|
1312
|
-
|
|
2123
|
+
search_quotes: async (ctx, args) => ctx.client.list(
|
|
2124
|
+
"/api/v1/quotes",
|
|
2125
|
+
{
|
|
2126
|
+
status: args.status,
|
|
2127
|
+
createdAfter: args.createdAfter,
|
|
2128
|
+
createdBefore: args.createdBefore,
|
|
2129
|
+
...page(args)
|
|
2130
|
+
},
|
|
2131
|
+
{ applicationId: args.applicationId }
|
|
2132
|
+
),
|
|
2133
|
+
get_quote: async (ctx, args) => ctx.client.get(`/api/v1/quotes/${String(args.id)}`, void 0, {
|
|
2134
|
+
applicationId: args.applicationId
|
|
2135
|
+
}),
|
|
1313
2136
|
// ---------------------------------------------------------------- invoices
|
|
1314
|
-
list_invoices: async (ctx, args) => ctx.client.list(
|
|
1315
|
-
|
|
2137
|
+
list_invoices: async (ctx, args) => ctx.client.list(
|
|
2138
|
+
"/api/v1/invoices",
|
|
2139
|
+
{ status: args.status, ...page(args) },
|
|
2140
|
+
{ applicationId: args.applicationId }
|
|
2141
|
+
),
|
|
2142
|
+
receivables_aging: async (ctx, args) => ctx.client.get("/api/v1/invoices/aging", void 0, { applicationId: args.applicationId }),
|
|
1316
2143
|
// --------------------------------------------------------------- discounts
|
|
1317
|
-
search_discounts: async (ctx, args) => ctx.client.list(
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
2144
|
+
search_discounts: async (ctx, args) => ctx.client.list(
|
|
2145
|
+
"/api/v1/discounts",
|
|
2146
|
+
{
|
|
2147
|
+
q: args.q,
|
|
2148
|
+
code: args.code,
|
|
2149
|
+
type: args.type,
|
|
2150
|
+
isActive: args.isActive,
|
|
2151
|
+
createdAfter: args.createdAfter,
|
|
2152
|
+
createdBefore: args.createdBefore,
|
|
2153
|
+
...page(args)
|
|
2154
|
+
},
|
|
2155
|
+
{ applicationId: args.applicationId }
|
|
2156
|
+
),
|
|
2157
|
+
get_discount: async (ctx, args) => ctx.client.get(`/api/v1/discounts/${String(args.id)}`, void 0, {
|
|
2158
|
+
applicationId: args.applicationId
|
|
1325
2159
|
}),
|
|
1326
|
-
get_discount: async (ctx, args) => ctx.client.get(`/api/v1/discounts/${String(args.id)}`),
|
|
1327
2160
|
// ---------------------------------------------------------------- payments
|
|
1328
|
-
search_payments: async (ctx, args) => ctx.client.list(
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
2161
|
+
search_payments: async (ctx, args) => ctx.client.list(
|
|
2162
|
+
"/api/v1/payments",
|
|
2163
|
+
{
|
|
2164
|
+
method: args.method,
|
|
2165
|
+
status: args.status,
|
|
2166
|
+
// The route only accepts these as snake_case query params -- confirmed
|
|
2167
|
+
// by reading its querystring schema directly, not assumed from the
|
|
2168
|
+
// platform's usual camelCase convention.
|
|
2169
|
+
order_id: args.orderId,
|
|
2170
|
+
aging: args.aging,
|
|
2171
|
+
due_from: args.dueFrom,
|
|
2172
|
+
due_to: args.dueTo,
|
|
2173
|
+
amount_min: args.amountMin,
|
|
2174
|
+
amount_max: args.amountMax,
|
|
2175
|
+
...page(args)
|
|
2176
|
+
},
|
|
2177
|
+
{ applicationId: args.applicationId }
|
|
2178
|
+
),
|
|
1342
2179
|
get_payment: async (ctx, args) => {
|
|
1343
2180
|
const id = String(args.id);
|
|
1344
|
-
const
|
|
1345
|
-
const
|
|
2181
|
+
const opts = { applicationId: args.applicationId };
|
|
2182
|
+
const payment = await ctx.client.get(`/api/v1/payments/${id}`, void 0, opts);
|
|
2183
|
+
const transactions = await optional(
|
|
2184
|
+
() => ctx.client.list(`/api/v1/payments/${id}/transactions`, void 0, opts)
|
|
2185
|
+
);
|
|
1346
2186
|
return {
|
|
1347
2187
|
payment,
|
|
1348
2188
|
transactions: transactions.ok ? transactions.value.data : transactions.unavailable
|
|
1349
2189
|
};
|
|
1350
2190
|
},
|
|
1351
2191
|
// ---------------------------------------------------------------- invoices
|
|
1352
|
-
get_invoice: async (ctx, args) => ctx.client.get(`/api/v1/invoices/${String(args.id)}
|
|
2192
|
+
get_invoice: async (ctx, args) => ctx.client.get(`/api/v1/invoices/${String(args.id)}`, void 0, {
|
|
2193
|
+
applicationId: args.applicationId
|
|
2194
|
+
}),
|
|
1353
2195
|
// ------------------------------------------------------------------ carts
|
|
1354
2196
|
list_abandoned_carts: async (ctx, args) => {
|
|
2197
|
+
const opts = { applicationId: args.applicationId };
|
|
1355
2198
|
const [carts, summary] = await Promise.all([
|
|
1356
|
-
optional(() => ctx.client.list("/api/v1/abandoned-carts", { ...page(args) })),
|
|
1357
|
-
optional(
|
|
2199
|
+
optional(() => ctx.client.list("/api/v1/abandoned-carts", { ...page(args) }, opts)),
|
|
2200
|
+
optional(
|
|
2201
|
+
() => ctx.client.get("/api/v1/abandoned-carts/summary", { sinceDays: args.sinceDays }, opts)
|
|
2202
|
+
)
|
|
1358
2203
|
]);
|
|
1359
2204
|
return {
|
|
1360
2205
|
carts: carts.ok ? carts.value.data : carts.unavailable,
|
|
@@ -1362,18 +2207,29 @@ var HANDLERS = {
|
|
|
1362
2207
|
};
|
|
1363
2208
|
},
|
|
1364
2209
|
// --------------------------------------------------------------- debugging
|
|
1365
|
-
search_events: async (ctx, args) => ctx.client.list(
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
2210
|
+
search_events: async (ctx, args) => ctx.client.list(
|
|
2211
|
+
"/api/v1/events",
|
|
2212
|
+
{
|
|
2213
|
+
type: args.type,
|
|
2214
|
+
entityType: args.entityType,
|
|
2215
|
+
entityId: args.entityId,
|
|
2216
|
+
...page(args)
|
|
2217
|
+
},
|
|
2218
|
+
{ applicationId: args.applicationId }
|
|
2219
|
+
),
|
|
2220
|
+
list_webhook_deliveries: async (ctx, args) => ctx.client.list(
|
|
2221
|
+
"/api/v1/webhook-deliveries",
|
|
2222
|
+
{
|
|
2223
|
+
endpointId: args.endpointId,
|
|
2224
|
+
status: args.status,
|
|
2225
|
+
eventType: args.eventType,
|
|
2226
|
+
limit: args.limit
|
|
2227
|
+
},
|
|
2228
|
+
{ applicationId: args.applicationId }
|
|
2229
|
+
),
|
|
2230
|
+
get_webhook_delivery: async (ctx, args) => ctx.client.get(`/api/v1/webhook-deliveries/${String(args.id)}`, void 0, {
|
|
2231
|
+
applicationId: args.applicationId
|
|
1375
2232
|
}),
|
|
1376
|
-
get_webhook_delivery: async (ctx, args) => ctx.client.get(`/api/v1/webhook-deliveries/${String(args.id)}`),
|
|
1377
2233
|
send_test_webhook: async (ctx, args) => ctx.client.post(`/api/v1/webhook-endpoints/${String(args.id)}/test`, {
|
|
1378
2234
|
body: { eventType: args.eventType },
|
|
1379
2235
|
idempotencyKey: `mcp-send-test-webhook-${String(args.id)}-${Date.now()}`
|
|
@@ -1383,18 +2239,21 @@ var HANDLERS = {
|
|
|
1383
2239
|
}),
|
|
1384
2240
|
explain_checkout_failure: async (ctx, args) => {
|
|
1385
2241
|
const id = String(args.id);
|
|
1386
|
-
const
|
|
2242
|
+
const opts = { applicationId: args.applicationId };
|
|
2243
|
+
const session = await optional(
|
|
2244
|
+
() => ctx.client.get(`/api/v1/checkout-sessions/${id}`, void 0, opts)
|
|
2245
|
+
);
|
|
1387
2246
|
if (!session.ok) {
|
|
1388
2247
|
return {
|
|
1389
|
-
note: `No checkout session ${id} (${session.unavailable}). If this is a cart id,
|
|
2248
|
+
note: `No checkout session ${id} (${session.unavailable}). If this is a cart id, use search_events for its activity (or read /api/v1/carts/${id} with api_get, where that tool is available).`
|
|
1390
2249
|
};
|
|
1391
2250
|
}
|
|
1392
2251
|
const cartId = session.value?.cartId ?? void 0;
|
|
1393
2252
|
const [cart, sessionEvents, cartEvents, deliveries] = await Promise.all([
|
|
1394
|
-
optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}/cart
|
|
1395
|
-
optional(() => ctx.client.list("/api/v1/events", { entityId: id, limit: 25 })),
|
|
1396
|
-
cartId ? optional(() => ctx.client.list("/api/v1/events", { entityId: cartId, limit: 25 })) : Promise.resolve({ ok: true, value: { data: [], pagination: null } }),
|
|
1397
|
-
optional(() => ctx.client.list("/api/v1/webhook-deliveries", { limit: 15 }))
|
|
2253
|
+
optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}/cart`, void 0, opts)),
|
|
2254
|
+
optional(() => ctx.client.list("/api/v1/events", { entityId: id, limit: 25 }, opts)),
|
|
2255
|
+
cartId ? optional(() => ctx.client.list("/api/v1/events", { entityId: cartId, limit: 25 }, opts)) : Promise.resolve({ ok: true, value: { data: [], pagination: null } }),
|
|
2256
|
+
optional(() => ctx.client.list("/api/v1/webhook-deliveries", { limit: 15 }, opts))
|
|
1398
2257
|
]);
|
|
1399
2258
|
return {
|
|
1400
2259
|
session: session.value,
|
|
@@ -1454,6 +2313,73 @@ var HANDLERS = {
|
|
|
1454
2313
|
note: "Seeded a finalized DRAFT order. No payment was captured \u2014 that needs a browser-tokenised payment method, which a server-side key cannot produce."
|
|
1455
2314
|
};
|
|
1456
2315
|
},
|
|
2316
|
+
// ---------------------------------------------------------- inbound writes
|
|
2317
|
+
create_customer: async (ctx, args) => ctx.client.post("/api/v1/customers", {
|
|
2318
|
+
// `createCustomerBody` is `.strict()`, so only fields it declares may be
|
|
2319
|
+
// sent; `undefined` values drop out at JSON.stringify.
|
|
2320
|
+
body: {
|
|
2321
|
+
email: args.email,
|
|
2322
|
+
firstName: args.firstName,
|
|
2323
|
+
lastName: args.lastName,
|
|
2324
|
+
phone: args.phone,
|
|
2325
|
+
company: args.company,
|
|
2326
|
+
externalId: args.externalId,
|
|
2327
|
+
netN: args.netN,
|
|
2328
|
+
acceptsMarketing: args.acceptsMarketing
|
|
2329
|
+
},
|
|
2330
|
+
idempotencyKey: `mcp-create-customer-${String(args.email)}-${Date.now()}`
|
|
2331
|
+
}),
|
|
2332
|
+
update_customer: async (ctx, args) => ctx.client.patch(`/api/v1/customers/${String(args.id)}`, {
|
|
2333
|
+
// `id` is the path parameter, not a body field — `updateCustomerBody` is
|
|
2334
|
+
// `.strict()` and would reject it. A null `externalId` is meaningful
|
|
2335
|
+
// (it clears the value), so only `undefined` is dropped.
|
|
2336
|
+
body: {
|
|
2337
|
+
email: args.email,
|
|
2338
|
+
firstName: args.firstName,
|
|
2339
|
+
lastName: args.lastName,
|
|
2340
|
+
phone: args.phone,
|
|
2341
|
+
company: args.company,
|
|
2342
|
+
externalId: args.externalId,
|
|
2343
|
+
netN: args.netN,
|
|
2344
|
+
acceptsMarketing: args.acceptsMarketing
|
|
2345
|
+
},
|
|
2346
|
+
idempotencyKey: `mcp-update-customer-${String(args.id)}-${Date.now()}`
|
|
2347
|
+
}),
|
|
2348
|
+
create_checkout_link: async (ctx, args) => {
|
|
2349
|
+
const lineItems = Array.isArray(args.lineItems) ? args.lineItems : [];
|
|
2350
|
+
if (lineItems.length === 0) {
|
|
2351
|
+
throw new Error("create_checkout_link: lineItems must contain at least one item.");
|
|
2352
|
+
}
|
|
2353
|
+
const stamp = Date.now();
|
|
2354
|
+
const applicationId = ctx.identity.application?.id;
|
|
2355
|
+
const cart = await ctx.client.post("/api/v1/carts", {
|
|
2356
|
+
body: {
|
|
2357
|
+
applicationId,
|
|
2358
|
+
customerId: args.customerId,
|
|
2359
|
+
customerEmail: args.customerEmail,
|
|
2360
|
+
netN: args.netN
|
|
2361
|
+
},
|
|
2362
|
+
idempotencyKey: `mcp-checkout-link-cart-${stamp}`
|
|
2363
|
+
});
|
|
2364
|
+
for (const [index, item] of lineItems.entries()) {
|
|
2365
|
+
await ctx.client.post(`/api/v1/carts/${cart?.id}/items`, {
|
|
2366
|
+
body: item,
|
|
2367
|
+
idempotencyKey: `mcp-checkout-link-item-${stamp}-${index}`
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
const session = await ctx.client.post("/api/v1/checkout/sessions", {
|
|
2371
|
+
body: {
|
|
2372
|
+
applicationId,
|
|
2373
|
+
cartId: cart?.id,
|
|
2374
|
+
returnUrl: args.returnUrl,
|
|
2375
|
+
cancelUrl: args.cancelUrl,
|
|
2376
|
+
customerEmail: args.customerEmail,
|
|
2377
|
+
discountCode: args.discountCode
|
|
2378
|
+
},
|
|
2379
|
+
idempotencyKey: `mcp-checkout-link-session-${stamp}`
|
|
2380
|
+
});
|
|
2381
|
+
return { cartId: cart?.id, ...session };
|
|
2382
|
+
},
|
|
1457
2383
|
// -------------------------------------------------------------- money/mail
|
|
1458
2384
|
manage_subscription: async (ctx, args) => {
|
|
1459
2385
|
const id = String(args.id);
|
|
@@ -1483,17 +2409,118 @@ var HANDLERS = {
|
|
|
1483
2409
|
idempotencyKey: `mcp-create-quote-${String(args.customerId)}-${Date.now()}`
|
|
1484
2410
|
})
|
|
1485
2411
|
),
|
|
1486
|
-
|
|
1487
|
-
|
|
2412
|
+
get_sales_timeseries: async (ctx, args) => ctx.client.get(
|
|
2413
|
+
"/api/v1/analytics/orders-timeseries",
|
|
2414
|
+
{
|
|
2415
|
+
from: args.from,
|
|
2416
|
+
to: args.to,
|
|
2417
|
+
groupBy: args.groupBy,
|
|
2418
|
+
timezone: args.timezone,
|
|
2419
|
+
compareTo: args.compareTo
|
|
2420
|
+
},
|
|
2421
|
+
{ applicationId: args.applicationId }
|
|
2422
|
+
),
|
|
2423
|
+
get_business_summary: async (ctx, args) => ctx.client.get(
|
|
2424
|
+
"/api/v1/analytics/business-summary",
|
|
2425
|
+
{
|
|
2426
|
+
period: args.period,
|
|
2427
|
+
from: args.from,
|
|
2428
|
+
to: args.to,
|
|
2429
|
+
timezone: args.timezone
|
|
2430
|
+
},
|
|
2431
|
+
{ applicationId: args.applicationId }
|
|
2432
|
+
),
|
|
2433
|
+
get_attention_digest: async (ctx, args) => ctx.client.get("/api/v1/analytics/attention-digest", void 0, {
|
|
2434
|
+
applicationId: args.applicationId
|
|
2435
|
+
}),
|
|
2436
|
+
get_top_products: async (ctx, args) => ctx.client.get(
|
|
2437
|
+
"/api/v1/analytics/products",
|
|
2438
|
+
{
|
|
2439
|
+
from: args.from,
|
|
2440
|
+
to: args.to,
|
|
2441
|
+
timezone: args.timezone,
|
|
2442
|
+
by: args.by,
|
|
2443
|
+
limit: args.limit
|
|
2444
|
+
},
|
|
2445
|
+
{ applicationId: args.applicationId }
|
|
2446
|
+
),
|
|
2447
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2448
|
+
// range, signups, ordersByCustomerType, topCustomers, totals } }`), and
|
|
2449
|
+
// `.list()` only unwraps arrays: it would silently return an empty list
|
|
2450
|
+
// instead of the real payload.
|
|
2451
|
+
get_customer_growth: async (ctx, args) => ctx.client.get(
|
|
2452
|
+
"/api/v1/analytics/customers",
|
|
2453
|
+
{
|
|
2454
|
+
from: args.from,
|
|
2455
|
+
to: args.to,
|
|
2456
|
+
groupBy: args.groupBy,
|
|
2457
|
+
timezone: args.timezone,
|
|
2458
|
+
topBy: args.topBy,
|
|
2459
|
+
topLimit: args.topLimit
|
|
2460
|
+
},
|
|
2461
|
+
{ applicationId: args.applicationId }
|
|
2462
|
+
),
|
|
2463
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2464
|
+
// range, dimension, rows, totals, note } }`), not a paginated array.
|
|
2465
|
+
get_revenue_breakdown: async (ctx, args) => ctx.client.get(
|
|
2466
|
+
"/api/v1/analytics/revenue-breakdown",
|
|
2467
|
+
{
|
|
2468
|
+
dimension: args.dimension,
|
|
2469
|
+
from: args.from,
|
|
2470
|
+
to: args.to,
|
|
2471
|
+
timezone: args.timezone
|
|
2472
|
+
},
|
|
2473
|
+
{ applicationId: args.applicationId }
|
|
2474
|
+
),
|
|
2475
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2476
|
+
// range, stages, conversionRate, dropOff, note } }`), not a paginated array.
|
|
2477
|
+
get_funnel_conversion: async (ctx, args) => ctx.client.get(
|
|
2478
|
+
"/api/v1/analytics/funnel",
|
|
2479
|
+
{
|
|
2480
|
+
from: args.from,
|
|
2481
|
+
to: args.to,
|
|
2482
|
+
timezone: args.timezone
|
|
2483
|
+
},
|
|
2484
|
+
{ applicationId: args.applicationId }
|
|
2485
|
+
),
|
|
2486
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2487
|
+
// range, series, totals, note } }`), not a paginated array.
|
|
2488
|
+
get_subscription_movement: async (ctx, args) => ctx.client.get(
|
|
2489
|
+
"/api/v1/analytics/subscriptions",
|
|
2490
|
+
{
|
|
2491
|
+
from: args.from,
|
|
2492
|
+
to: args.to,
|
|
2493
|
+
timezone: args.timezone,
|
|
2494
|
+
groupBy: args.groupBy
|
|
2495
|
+
},
|
|
2496
|
+
{ applicationId: args.applicationId }
|
|
2497
|
+
),
|
|
2498
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2499
|
+
// range, rows, totals, note } }`), not a paginated array.
|
|
2500
|
+
get_discount_performance: async (ctx, args) => ctx.client.get(
|
|
2501
|
+
"/api/v1/analytics/discounts",
|
|
2502
|
+
{
|
|
2503
|
+
from: args.from,
|
|
2504
|
+
to: args.to,
|
|
2505
|
+
timezone: args.timezone,
|
|
2506
|
+
limit: args.limit
|
|
2507
|
+
},
|
|
2508
|
+
{ applicationId: args.applicationId }
|
|
2509
|
+
),
|
|
1488
2510
|
// ------------------------------------------------------------- extensions
|
|
1489
|
-
list_extension_installations: async (ctx, args) => ctx.client.list(
|
|
2511
|
+
list_extension_installations: async (ctx, args) => ctx.client.list(
|
|
2512
|
+
"/api/v1/installations",
|
|
2513
|
+
{ status: args.status, ...page(args) },
|
|
2514
|
+
{ applicationId: args.applicationId }
|
|
2515
|
+
),
|
|
1490
2516
|
// ------------------------------------------------------------- connectors
|
|
1491
|
-
get_connector_routing: async (ctx) => {
|
|
2517
|
+
get_connector_routing: async (ctx, args) => {
|
|
2518
|
+
const opts = { applicationId: args.applicationId };
|
|
1492
2519
|
const [paymentRouting, routingConnectors, cardDecline, otherDecline] = await Promise.all([
|
|
1493
|
-
optional(() => ctx.client.get("/api/v1/payment-routing")),
|
|
1494
|
-
optional(() => ctx.client.get("/api/v1/routing-connectors")),
|
|
1495
|
-
optional(() => ctx.client.get("/api/v1/card-decline")),
|
|
1496
|
-
optional(() => ctx.client.get("/api/v1/other-decline"))
|
|
2520
|
+
optional(() => ctx.client.get("/api/v1/payment-routing", void 0, opts)),
|
|
2521
|
+
optional(() => ctx.client.get("/api/v1/routing-connectors", void 0, opts)),
|
|
2522
|
+
optional(() => ctx.client.get("/api/v1/card-decline", void 0, opts)),
|
|
2523
|
+
optional(() => ctx.client.get("/api/v1/other-decline", void 0, opts))
|
|
1497
2524
|
]);
|
|
1498
2525
|
return {
|
|
1499
2526
|
paymentRouting: paymentRouting.ok ? paymentRouting.value : paymentRouting.unavailable,
|
|
@@ -1502,24 +2529,37 @@ var HANDLERS = {
|
|
|
1502
2529
|
otherDecline: otherDecline.ok ? otherDecline.value : otherDecline.unavailable
|
|
1503
2530
|
};
|
|
1504
2531
|
},
|
|
1505
|
-
get_integration_status: async (ctx) => {
|
|
2532
|
+
get_integration_status: async (ctx, args) => {
|
|
2533
|
+
const opts = { applicationId: args.applicationId };
|
|
1506
2534
|
const [connectors, endpoints, coverage] = await Promise.all([
|
|
1507
|
-
optional(() => ctx.client.list("/api/v1/connectors")),
|
|
1508
|
-
optional(() => ctx.client.list("/api/v1/webhook-endpoints")),
|
|
1509
|
-
optional(() => ctx.client.get("/api/v1/webhook-endpoints/coverage"))
|
|
2535
|
+
optional(() => ctx.client.list("/api/v1/connectors", void 0, opts)),
|
|
2536
|
+
optional(() => ctx.client.list("/api/v1/webhook-endpoints", void 0, opts)),
|
|
2537
|
+
optional(() => ctx.client.get("/api/v1/webhook-endpoints/coverage", void 0, opts))
|
|
1510
2538
|
]);
|
|
2539
|
+
const overridden = Boolean(args.applicationId) && args.applicationId !== ctx.identity.application?.id;
|
|
1511
2540
|
return {
|
|
1512
2541
|
environment: ctx.identity.environment,
|
|
1513
2542
|
workspace: ctx.identity.workspace,
|
|
1514
|
-
|
|
2543
|
+
applicationId: args.applicationId ?? ctx.identity.application?.id ?? null,
|
|
2544
|
+
application: overridden ? void 0 : ctx.identity.application,
|
|
1515
2545
|
connectors: connectors.ok ? connectors.value.data : connectors.unavailable,
|
|
1516
2546
|
webhookEndpoints: endpoints.ok ? endpoints.value.data : endpoints.unavailable,
|
|
1517
2547
|
unsubscribedEvents: coverage.ok ? coverage.value : coverage.unavailable
|
|
1518
2548
|
};
|
|
1519
2549
|
},
|
|
1520
2550
|
// --------------------------------------------------------------- workspace
|
|
2551
|
+
// No applicationId forwarding here: /api/v1/merchants/me is workspace-only
|
|
2552
|
+
// (`WHERE workspace_id = ...`, never reads auth.applicationId) — an
|
|
2553
|
+
// applicationId field on this tool would be a silent no-op affordance.
|
|
1521
2554
|
get_workspace_settings: async (ctx) => ctx.client.get("/api/v1/merchants/me"),
|
|
1522
|
-
|
|
2555
|
+
// No applicationId forwarding here either: /api/v1/audit-log filters by
|
|
2556
|
+
// workspaceId (and optionally environmentId), never applicationId.
|
|
2557
|
+
get_audit_log: async (ctx, args) => ctx.client.list("/api/v1/audit-log", { limit: args.limit }),
|
|
2558
|
+
// --------------------------------------------------------- applications
|
|
2559
|
+
// `.get()`, not `.list()` — the legacy /v1/stores route returns a bare
|
|
2560
|
+
// array with no `meta.pagination`, and the brief calls for `.get()` here
|
|
2561
|
+
// specifically.
|
|
2562
|
+
list_applications: async (ctx) => ctx.client.get("/api/v1/stores")
|
|
1523
2563
|
};
|
|
1524
2564
|
|
|
1525
2565
|
// src/input.ts
|
|
@@ -1554,15 +2594,548 @@ function applyModifiers(field, type) {
|
|
|
1554
2594
|
return field.default === void 0 ? described.optional() : described.default(field.default);
|
|
1555
2595
|
}
|
|
1556
2596
|
|
|
2597
|
+
// src/projection.ts
|
|
2598
|
+
var S = "scalar";
|
|
2599
|
+
var T = "text";
|
|
2600
|
+
var PASS = "pass";
|
|
2601
|
+
var section = (shape) => ({ __section: shape });
|
|
2602
|
+
var oneOf = (values) => ({ __oneOf: new Set(values) });
|
|
2603
|
+
function isPrimitive(value) {
|
|
2604
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
2605
|
+
}
|
|
2606
|
+
var TOKEN = /^[^\s@]{0,200}$/;
|
|
2607
|
+
var OMITTED = { omitted: S, grantable: T, reason: T };
|
|
2608
|
+
var UNAVAILABLE = /^(not readable with this credential \(missing scope\)|not found|unavailable \([^\s@]{0,100}\))$/;
|
|
2609
|
+
function project(value, shape) {
|
|
2610
|
+
if (value === void 0) return void 0;
|
|
2611
|
+
if (shape === PASS) return value;
|
|
2612
|
+
if (shape === S) {
|
|
2613
|
+
if (!isPrimitive(value)) return void 0;
|
|
2614
|
+
return typeof value === "string" && !TOKEN.test(value) ? null : value;
|
|
2615
|
+
}
|
|
2616
|
+
if (shape === T) return isPrimitive(value) ? value : void 0;
|
|
2617
|
+
if ("__oneOf" in shape) {
|
|
2618
|
+
if (!isPrimitive(value)) return void 0;
|
|
2619
|
+
return typeof value === "string" && !shape.__oneOf.has(value) ? null : value;
|
|
2620
|
+
}
|
|
2621
|
+
if (Array.isArray(shape)) {
|
|
2622
|
+
return Array.isArray(value) ? value.map((item) => project(item, shape[0])) : void 0;
|
|
2623
|
+
}
|
|
2624
|
+
if ("__section" in shape) {
|
|
2625
|
+
if (typeof value === "string") return UNAVAILABLE.test(value) ? value : null;
|
|
2626
|
+
if (value && typeof value === "object" && value.omitted === true) {
|
|
2627
|
+
return project(value, OMITTED);
|
|
2628
|
+
}
|
|
2629
|
+
return project(value, shape.__section);
|
|
2630
|
+
}
|
|
2631
|
+
if (value === null) return null;
|
|
2632
|
+
if (typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2633
|
+
const out = {};
|
|
2634
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
2635
|
+
const projected = project(value[key], child);
|
|
2636
|
+
if (projected !== void 0) out[key] = projected;
|
|
2637
|
+
}
|
|
2638
|
+
return out;
|
|
2639
|
+
}
|
|
2640
|
+
var PAGINATION = { cursor: S, nextCursor: S, hasMore: S };
|
|
2641
|
+
var page2 = (row) => ({ data: [row], pagination: PAGINATION });
|
|
2642
|
+
var ADDRESS = { countryCode: S };
|
|
2643
|
+
var CUSTOMER_REF = { id: S };
|
|
2644
|
+
var APPLICATION = { id: S, name: T, slug: S };
|
|
2645
|
+
var LIFECYCLE_LABELS = ["Draft", "Pending", "Processing", "Partially fulfilled", "Fulfilled", "Cancelled", "Closed"];
|
|
2646
|
+
var DISPLAY_QUALIFIERS = [
|
|
2647
|
+
"Comped",
|
|
2648
|
+
"Awaiting payment",
|
|
2649
|
+
"Authorized",
|
|
2650
|
+
"Partially paid",
|
|
2651
|
+
"Partially refunded",
|
|
2652
|
+
"Refunded",
|
|
2653
|
+
"Payment failed",
|
|
2654
|
+
"Voided",
|
|
2655
|
+
"Payment processing",
|
|
2656
|
+
"Disputed",
|
|
2657
|
+
"Authorization expired",
|
|
2658
|
+
"Returned",
|
|
2659
|
+
"Partially returned",
|
|
2660
|
+
"Delivered",
|
|
2661
|
+
"In transit"
|
|
2662
|
+
];
|
|
2663
|
+
var ORDER_DISPLAY_STATUSES = new Set(
|
|
2664
|
+
LIFECYCLE_LABELS.flatMap((l) => [l, ...DISPLAY_QUALIFIERS.map((q) => `${l} \xB7 ${q}`)])
|
|
2665
|
+
);
|
|
2666
|
+
var DISPLAY_STATUS = oneOf(ORDER_DISPLAY_STATUSES);
|
|
2667
|
+
var RANGE = { from: S, to: S, timezone: S, groupBy: S };
|
|
2668
|
+
var LINE_ITEM = {
|
|
2669
|
+
id: S,
|
|
2670
|
+
type: S,
|
|
2671
|
+
quantity: S,
|
|
2672
|
+
unitPrice: S,
|
|
2673
|
+
subtotal: S,
|
|
2674
|
+
discountAmount: S,
|
|
2675
|
+
taxAmount: S,
|
|
2676
|
+
total: S,
|
|
2677
|
+
recurring: S
|
|
2678
|
+
};
|
|
2679
|
+
var ORDER = {
|
|
2680
|
+
id: S,
|
|
2681
|
+
orderNumber: S,
|
|
2682
|
+
status: S,
|
|
2683
|
+
displayStatus: DISPLAY_STATUS,
|
|
2684
|
+
paymentStatus: S,
|
|
2685
|
+
type: S,
|
|
2686
|
+
source: S,
|
|
2687
|
+
currency: S,
|
|
2688
|
+
subtotal: S,
|
|
2689
|
+
discountTotal: S,
|
|
2690
|
+
shippingTotal: S,
|
|
2691
|
+
taxTotal: S,
|
|
2692
|
+
total: S,
|
|
2693
|
+
capturedAmount: S,
|
|
2694
|
+
refundedAmount: S,
|
|
2695
|
+
compAmount: S,
|
|
2696
|
+
onHold: S,
|
|
2697
|
+
taxReverseCharge: S,
|
|
2698
|
+
customerId: S,
|
|
2699
|
+
customer: CUSTOMER_REF,
|
|
2700
|
+
subscriptionId: S,
|
|
2701
|
+
shippingAddress: ADDRESS,
|
|
2702
|
+
billingAddress: ADDRESS,
|
|
2703
|
+
fulfillmentSummary: { itemsFulfilled: S, itemsRemaining: S, itemsTotal: S },
|
|
2704
|
+
lineItems: [LINE_ITEM],
|
|
2705
|
+
createdAt: S,
|
|
2706
|
+
completedAt: S,
|
|
2707
|
+
cancelledAt: S,
|
|
2708
|
+
closedAt: S
|
|
2709
|
+
};
|
|
2710
|
+
var PAYMENT = {
|
|
2711
|
+
id: S,
|
|
2712
|
+
orderId: S,
|
|
2713
|
+
status: S,
|
|
2714
|
+
method: S,
|
|
2715
|
+
processor: S,
|
|
2716
|
+
amount: S,
|
|
2717
|
+
capturedAmount: S,
|
|
2718
|
+
currency: S,
|
|
2719
|
+
cardBrand: S,
|
|
2720
|
+
disputed: S,
|
|
2721
|
+
disputeOpenedAt: S,
|
|
2722
|
+
errorCode: S,
|
|
2723
|
+
netN: S,
|
|
2724
|
+
netNSource: S,
|
|
2725
|
+
authorizedAt: S,
|
|
2726
|
+
capturedAt: S,
|
|
2727
|
+
voidedAt: S,
|
|
2728
|
+
createdAt: S
|
|
2729
|
+
};
|
|
2730
|
+
var RETURN = {
|
|
2731
|
+
id: S,
|
|
2732
|
+
orderId: S,
|
|
2733
|
+
status: S,
|
|
2734
|
+
restock: S,
|
|
2735
|
+
refundAmount: S,
|
|
2736
|
+
refundPaymentId: S,
|
|
2737
|
+
createdAt: S,
|
|
2738
|
+
items: [{ lineItemId: S, quantity: S }]
|
|
2739
|
+
};
|
|
2740
|
+
var FULFILLMENT = {
|
|
2741
|
+
id: S,
|
|
2742
|
+
orderId: S,
|
|
2743
|
+
type: S,
|
|
2744
|
+
status: S,
|
|
2745
|
+
createdAt: S,
|
|
2746
|
+
lineItems: [{ lineItemId: S, quantity: S }],
|
|
2747
|
+
detail: { carrier: T, serviceLevel: T, trackingNumber: T, trackingUrl: T, shippedAt: S, estimatedDelivery: S, actualDelivery: S }
|
|
2748
|
+
};
|
|
2749
|
+
var CUSTOMER = { id: S, netN: S, acceptsMarketing: S, createdAt: S };
|
|
2750
|
+
var SUBSCRIPTION = {
|
|
2751
|
+
id: S,
|
|
2752
|
+
status: S,
|
|
2753
|
+
planName: T,
|
|
2754
|
+
planReference: T,
|
|
2755
|
+
amount: S,
|
|
2756
|
+
currency: S,
|
|
2757
|
+
interval: S,
|
|
2758
|
+
quantity: S,
|
|
2759
|
+
intervalUnit: S,
|
|
2760
|
+
intervalCount: S,
|
|
2761
|
+
totalPayments: S,
|
|
2762
|
+
paymentsMade: S,
|
|
2763
|
+
completedAt: S,
|
|
2764
|
+
customerId: S,
|
|
2765
|
+
customer: CUSTOMER_REF,
|
|
2766
|
+
currentPeriodStart: S,
|
|
2767
|
+
currentPeriodEnd: S,
|
|
2768
|
+
billingAnchorAt: S,
|
|
2769
|
+
cancelAtPeriodEnd: S,
|
|
2770
|
+
trialEnd: S,
|
|
2771
|
+
pausedAt: S,
|
|
2772
|
+
cancelledAt: S,
|
|
2773
|
+
lastPaymentAt: S,
|
|
2774
|
+
failureCount: S,
|
|
2775
|
+
pendingPlanName: T,
|
|
2776
|
+
pendingPlanReference: T,
|
|
2777
|
+
pendingAmount: S,
|
|
2778
|
+
pendingInterval: S,
|
|
2779
|
+
pendingQuantity: S,
|
|
2780
|
+
pendingIntervalUnit: S,
|
|
2781
|
+
pendingIntervalCount: S,
|
|
2782
|
+
createdAt: S
|
|
2783
|
+
};
|
|
2784
|
+
var QUOTE_SUMMARY = {
|
|
2785
|
+
id: S,
|
|
2786
|
+
quoteNumber: S,
|
|
2787
|
+
status: S,
|
|
2788
|
+
source: S,
|
|
2789
|
+
currency: S,
|
|
2790
|
+
total: S,
|
|
2791
|
+
customerId: S,
|
|
2792
|
+
orderId: S,
|
|
2793
|
+
expiresAt: S,
|
|
2794
|
+
acceptedAt: S,
|
|
2795
|
+
declinedAt: S,
|
|
2796
|
+
createdAt: S
|
|
2797
|
+
};
|
|
2798
|
+
var QUOTE = {
|
|
2799
|
+
...QUOTE_SUMMARY,
|
|
2800
|
+
revisions: [{
|
|
2801
|
+
id: S,
|
|
2802
|
+
revisionNumber: S,
|
|
2803
|
+
state: S,
|
|
2804
|
+
subtotal: S,
|
|
2805
|
+
discountTotal: S,
|
|
2806
|
+
shippingTotal: S,
|
|
2807
|
+
taxTotal: S,
|
|
2808
|
+
total: S,
|
|
2809
|
+
depositAmount: S,
|
|
2810
|
+
shippingMode: S,
|
|
2811
|
+
issuedAt: S,
|
|
2812
|
+
expiresAt: S,
|
|
2813
|
+
paymentTerms: { mode: S, netN: S, balanceNetN: S, deposit: { type: S, value: S } },
|
|
2814
|
+
items: [{
|
|
2815
|
+
type: S,
|
|
2816
|
+
quantity: S,
|
|
2817
|
+
unitPrice: S,
|
|
2818
|
+
subtotal: S,
|
|
2819
|
+
discountAmount: S,
|
|
2820
|
+
taxAmount: S,
|
|
2821
|
+
total: S,
|
|
2822
|
+
optional: S,
|
|
2823
|
+
optionalSelected: S
|
|
2824
|
+
}]
|
|
2825
|
+
}],
|
|
2826
|
+
// Who commented and when, never what they wrote.
|
|
2827
|
+
comments: [{ authorType: S, visibility: S, createdAt: S }],
|
|
2828
|
+
timeline: [{ type: S, actorType: S, createdAt: S }]
|
|
2829
|
+
};
|
|
2830
|
+
var INVOICE = {
|
|
2831
|
+
id: S,
|
|
2832
|
+
number: S,
|
|
2833
|
+
status: S,
|
|
2834
|
+
sourceType: S,
|
|
2835
|
+
sourceId: S,
|
|
2836
|
+
customerId: S,
|
|
2837
|
+
currency: S,
|
|
2838
|
+
subtotal: S,
|
|
2839
|
+
discountTotal: S,
|
|
2840
|
+
shippingTotal: S,
|
|
2841
|
+
taxTotal: S,
|
|
2842
|
+
total: S,
|
|
2843
|
+
amountRefunded: S,
|
|
2844
|
+
issuedAt: S,
|
|
2845
|
+
dueAt: S,
|
|
2846
|
+
periodStart: S,
|
|
2847
|
+
periodEnd: S,
|
|
2848
|
+
createdAt: S,
|
|
2849
|
+
billingSnapshot: {
|
|
2850
|
+
planName: T,
|
|
2851
|
+
period: { start: S, end: S },
|
|
2852
|
+
lineItems: [{ quantity: S, unitPrice: S, discountAmount: S, taxAmount: S, total: S }],
|
|
2853
|
+
totals: { currency: S, subtotal: S, discountTotal: S, shippingTotal: S, taxTotal: S, total: S },
|
|
2854
|
+
payment: { method: S, brand: S, status: S }
|
|
2855
|
+
}
|
|
2856
|
+
};
|
|
2857
|
+
var DISCOUNT = {
|
|
2858
|
+
id: S,
|
|
2859
|
+
code: T,
|
|
2860
|
+
name: T,
|
|
2861
|
+
type: S,
|
|
2862
|
+
value: S,
|
|
2863
|
+
isActive: S,
|
|
2864
|
+
startsAt: S,
|
|
2865
|
+
endsAt: S,
|
|
2866
|
+
usageCount: S,
|
|
2867
|
+
usageLimit: S,
|
|
2868
|
+
maxRedemptionsPerCustomer: S,
|
|
2869
|
+
createdAt: S
|
|
2870
|
+
};
|
|
2871
|
+
var EVENT = {
|
|
2872
|
+
id: S,
|
|
2873
|
+
type: S,
|
|
2874
|
+
entityType: S,
|
|
2875
|
+
entityId: S,
|
|
2876
|
+
createdAt: S,
|
|
2877
|
+
data: {
|
|
2878
|
+
status: S,
|
|
2879
|
+
previousStatus: S,
|
|
2880
|
+
action: S,
|
|
2881
|
+
amount: S,
|
|
2882
|
+
currency: S,
|
|
2883
|
+
attempt: S,
|
|
2884
|
+
orderId: S,
|
|
2885
|
+
paymentId: S,
|
|
2886
|
+
order: { id: S, orderNumber: S, status: S, paymentStatus: S, currency: S, total: S },
|
|
2887
|
+
payment: { id: S, orderId: S, status: S, method: S, amount: S, currency: S },
|
|
2888
|
+
subscription: { id: S, status: S, planName: T, amount: S, currency: S, interval: S },
|
|
2889
|
+
fulfillment: { id: S, orderId: S, type: S, status: S }
|
|
2890
|
+
}
|
|
2891
|
+
};
|
|
2892
|
+
var DELIVERY = {
|
|
2893
|
+
id: S,
|
|
2894
|
+
endpointId: S,
|
|
2895
|
+
eventId: S,
|
|
2896
|
+
eventType: S,
|
|
2897
|
+
status: S,
|
|
2898
|
+
attemptCount: S,
|
|
2899
|
+
lastStatus: S,
|
|
2900
|
+
deliveredAt: S,
|
|
2901
|
+
nextRetryAt: S,
|
|
2902
|
+
createdAt: S,
|
|
2903
|
+
replayOf: S
|
|
2904
|
+
};
|
|
2905
|
+
var MONEY_COUNT = { count: S, totalCents: S };
|
|
2906
|
+
var ORDER_PERIOD = { orderCount: S, grossCents: S, netCents: S, refundsCents: S, avgOrderCents: S };
|
|
2907
|
+
var OAUTH_PROJECTIONS = {
|
|
2908
|
+
whoami: {
|
|
2909
|
+
type: S,
|
|
2910
|
+
workspace: { name: T, slug: S },
|
|
2911
|
+
application: APPLICATION,
|
|
2912
|
+
environment: { slug: S, kind: S, providerEnvironment: S },
|
|
2913
|
+
note: T
|
|
2914
|
+
},
|
|
2915
|
+
search_docs: PASS,
|
|
2916
|
+
describe_endpoint: PASS,
|
|
2917
|
+
validate_payload: PASS,
|
|
2918
|
+
search_orders: page2(ORDER),
|
|
2919
|
+
get_order: {
|
|
2920
|
+
order: ORDER,
|
|
2921
|
+
payments: section([PAYMENT]),
|
|
2922
|
+
returns: section([RETURN]),
|
|
2923
|
+
fulfillments: section([FULFILLMENT])
|
|
2924
|
+
},
|
|
2925
|
+
search_customers: page2(CUSTOMER),
|
|
2926
|
+
get_customer: {
|
|
2927
|
+
customer: CUSTOMER,
|
|
2928
|
+
addresses: section([{ id: S, isDefault: S, ...ADDRESS }]),
|
|
2929
|
+
subscriptions: section([SUBSCRIPTION])
|
|
2930
|
+
},
|
|
2931
|
+
list_subscriptions: page2(SUBSCRIPTION),
|
|
2932
|
+
get_subscription: SUBSCRIPTION,
|
|
2933
|
+
search_quotes: page2(QUOTE_SUMMARY),
|
|
2934
|
+
get_quote: QUOTE,
|
|
2935
|
+
list_invoices: page2(INVOICE),
|
|
2936
|
+
get_invoice: INVOICE,
|
|
2937
|
+
receivables_aging: {
|
|
2938
|
+
buckets: [{ key: S, label: T, count: S, total_amount: S }],
|
|
2939
|
+
total_outstanding_amount: S,
|
|
2940
|
+
total_outstanding_count: S
|
|
2941
|
+
},
|
|
2942
|
+
search_discounts: page2(DISCOUNT),
|
|
2943
|
+
get_discount: DISCOUNT,
|
|
2944
|
+
search_payments: page2(PAYMENT),
|
|
2945
|
+
get_payment: {
|
|
2946
|
+
payment: PAYMENT,
|
|
2947
|
+
transactions: section([{ id: S, type: S, status: S, amount: S, currency: S, createdAt: S }])
|
|
2948
|
+
},
|
|
2949
|
+
list_abandoned_carts: {
|
|
2950
|
+
carts: section([{
|
|
2951
|
+
id: S,
|
|
2952
|
+
abandonedAt: S,
|
|
2953
|
+
currency: S,
|
|
2954
|
+
itemCount: S,
|
|
2955
|
+
recoveryStatus: S,
|
|
2956
|
+
total: S,
|
|
2957
|
+
customer: { id: S }
|
|
2958
|
+
}]),
|
|
2959
|
+
summary: section({ abandonedCount: S, abandonedValue: S, recoveryEmailsSent: S, sinceDays: S })
|
|
2960
|
+
},
|
|
2961
|
+
search_events: page2(EVENT),
|
|
2962
|
+
list_webhook_deliveries: page2(DELIVERY),
|
|
2963
|
+
get_webhook_delivery: DELIVERY,
|
|
2964
|
+
explain_checkout_failure: {
|
|
2965
|
+
note: T,
|
|
2966
|
+
hint: T,
|
|
2967
|
+
session: {
|
|
2968
|
+
id: S,
|
|
2969
|
+
status: S,
|
|
2970
|
+
cartId: S,
|
|
2971
|
+
customerId: S,
|
|
2972
|
+
expiresAt: S,
|
|
2973
|
+
completedAt: S,
|
|
2974
|
+
allowedMethods: [S],
|
|
2975
|
+
collect: { shippingAddress: S, billingAddress: S, vatNumber: S },
|
|
2976
|
+
shippingAddress: ADDRESS,
|
|
2977
|
+
billingAddress: ADDRESS,
|
|
2978
|
+
recurringTerms: {
|
|
2979
|
+
interval: S,
|
|
2980
|
+
intervalUnit: S,
|
|
2981
|
+
intervalCount: S,
|
|
2982
|
+
totalPayments: S,
|
|
2983
|
+
taxApplies: S,
|
|
2984
|
+
planChargedToday: S,
|
|
2985
|
+
planName: T,
|
|
2986
|
+
trialDays: S,
|
|
2987
|
+
currency: S,
|
|
2988
|
+
firstChargeAmountMinor: S
|
|
2989
|
+
}
|
|
2990
|
+
},
|
|
2991
|
+
cart: section({
|
|
2992
|
+
id: S,
|
|
2993
|
+
status: S,
|
|
2994
|
+
currency: S,
|
|
2995
|
+
subtotal: S,
|
|
2996
|
+
discountTotal: S,
|
|
2997
|
+
shippingTotal: S,
|
|
2998
|
+
taxTotal: S,
|
|
2999
|
+
total: S,
|
|
3000
|
+
itemCount: S,
|
|
3001
|
+
items: [LINE_ITEM],
|
|
3002
|
+
lineItems: [LINE_ITEM]
|
|
3003
|
+
}),
|
|
3004
|
+
events: [EVENT],
|
|
3005
|
+
eventsUnavailable: T,
|
|
3006
|
+
recentWebhookDeliveries: section([DELIVERY])
|
|
3007
|
+
},
|
|
3008
|
+
list_extension_installations: page2({
|
|
3009
|
+
id: S,
|
|
3010
|
+
extensionId: S,
|
|
3011
|
+
extensionName: T,
|
|
3012
|
+
extensionSlug: S,
|
|
3013
|
+
versionId: S,
|
|
3014
|
+
status: S,
|
|
3015
|
+
environmentSlug: S,
|
|
3016
|
+
installedAt: S,
|
|
3017
|
+
uninstalledAt: S
|
|
3018
|
+
}),
|
|
3019
|
+
get_connector_routing: {
|
|
3020
|
+
paymentRouting: section({
|
|
3021
|
+
defaultCascade: [S],
|
|
3022
|
+
rules: [{ cascade: [S], conditions: [{ type: S, op: S, currency: S, min: S, max: S, values: [S] }] }]
|
|
3023
|
+
}),
|
|
3024
|
+
cardDecline: section({ rules: [{ errorCode: S, conditions: [{ type: S, op: S, values: [S] }] }] }),
|
|
3025
|
+
otherDecline: section({ rules: [{ errorCode: S, conditions: [{ type: S, op: S, values: [S] }] }] }),
|
|
3026
|
+
routingConnectors: section([{ id: S, displayName: T, method: S }])
|
|
3027
|
+
},
|
|
3028
|
+
get_integration_status: {
|
|
3029
|
+
environment: { slug: S, kind: S, providerEnvironment: S },
|
|
3030
|
+
workspace: { name: T, slug: S },
|
|
3031
|
+
applicationId: S,
|
|
3032
|
+
application: APPLICATION,
|
|
3033
|
+
connectors: section([{
|
|
3034
|
+
id: S,
|
|
3035
|
+
displayName: T,
|
|
3036
|
+
method: S,
|
|
3037
|
+
type: S,
|
|
3038
|
+
status: S,
|
|
3039
|
+
active: S,
|
|
3040
|
+
position: S,
|
|
3041
|
+
acceptedCountries: [S],
|
|
3042
|
+
acceptedCurrencies: [S],
|
|
3043
|
+
threeDSecureEnabled: S,
|
|
3044
|
+
createdAt: S
|
|
3045
|
+
}]),
|
|
3046
|
+
webhookEndpoints: section([{ id: S, isActive: S, enabledEvents: [S], createdAt: S }]),
|
|
3047
|
+
unsubscribedEvents: section({ windowDays: S, unsubscribed: [{ type: S, count: S, lastEmittedAt: S }] })
|
|
3048
|
+
},
|
|
3049
|
+
get_workspace_settings: { name: T, slug: S, brandColor: S, logoUrl: T, onboardingCompletedAt: S },
|
|
3050
|
+
// What happened and to which record; not which team member did it.
|
|
3051
|
+
get_audit_log: page2({ id: S, action: S, resourceType: S, resourceId: S, createdAt: S }),
|
|
3052
|
+
list_applications: [{ id: S, name: T, slug: S, currency: S, isActive: S }],
|
|
3053
|
+
// Analytics: computed aggregates, listed field by field all the same.
|
|
3054
|
+
get_business_summary: {
|
|
3055
|
+
period: S,
|
|
3056
|
+
range: RANGE,
|
|
3057
|
+
orders: section({ current: ORDER_PERIOD, previous: ORDER_PERIOD, deltaPct: S }),
|
|
3058
|
+
subscriptions: section({
|
|
3059
|
+
mrr: { currentCents: S, periodStartCents: S, deltaPct: S },
|
|
3060
|
+
newSubscriptions: S,
|
|
3061
|
+
cancelledSubscriptions: S,
|
|
3062
|
+
churnRate: S,
|
|
3063
|
+
note: T
|
|
3064
|
+
}),
|
|
3065
|
+
receivables: section({ outstandingCents: S, outstandingCount: S }),
|
|
3066
|
+
quotes: section({ sent: MONEY_COUNT, accepted: MONEY_COUNT, declined: MONEY_COUNT, expired: MONEY_COUNT })
|
|
3067
|
+
},
|
|
3068
|
+
get_attention_digest: {
|
|
3069
|
+
disputes: section({ open: S, totalCents: S }),
|
|
3070
|
+
subscriptionsInDunning: section(S),
|
|
3071
|
+
overdueInvoices: section(MONEY_COUNT),
|
|
3072
|
+
unhealthyWebhooks: section([{ endpointId: S, failureRate: S, attempts7d: S }])
|
|
3073
|
+
},
|
|
3074
|
+
get_sales_timeseries: {
|
|
3075
|
+
application: APPLICATION,
|
|
3076
|
+
range: RANGE,
|
|
3077
|
+
currency: S,
|
|
3078
|
+
days: S,
|
|
3079
|
+
note: T,
|
|
3080
|
+
series: [{ date: S, grossCents: S, orders: S }],
|
|
3081
|
+
totalGrossCents: S,
|
|
3082
|
+
totalOrders: S
|
|
3083
|
+
},
|
|
3084
|
+
// get_top_products has no entry, so it is not offered over OAuth: it ranks
|
|
3085
|
+
// products by line-item reference id or name, which a buyer can type.
|
|
3086
|
+
get_customer_growth: {
|
|
3087
|
+
application: APPLICATION,
|
|
3088
|
+
range: RANGE,
|
|
3089
|
+
note: T,
|
|
3090
|
+
topBy: S,
|
|
3091
|
+
signups: { total: S, series: [{ date: S, signups: S }] },
|
|
3092
|
+
ordersByCustomerType: { newCustomerOrders: S, returningCustomerOrders: S },
|
|
3093
|
+
// The ranking, by id: a top customer's name and email stay behind.
|
|
3094
|
+
topCustomers: [{ customerId: S, orderCount: S, revenueCents: S }],
|
|
3095
|
+
totals: { customerCount: S, orderCount: S, revenueCents: S }
|
|
3096
|
+
},
|
|
3097
|
+
get_revenue_breakdown: {
|
|
3098
|
+
application: APPLICATION,
|
|
3099
|
+
range: RANGE,
|
|
3100
|
+
dimension: S,
|
|
3101
|
+
note: T,
|
|
3102
|
+
rows: [{ key: S, orderCount: S, grossCents: S }],
|
|
3103
|
+
totals: { orderCount: S, grossCents: S }
|
|
3104
|
+
},
|
|
3105
|
+
get_funnel_conversion: {
|
|
3106
|
+
application: APPLICATION,
|
|
3107
|
+
range: RANGE,
|
|
3108
|
+
note: T,
|
|
3109
|
+
conversionRate: S,
|
|
3110
|
+
stages: [{ name: S, count: S }],
|
|
3111
|
+
dropOff: [{ from: S, to: S, ratePct: S }]
|
|
3112
|
+
},
|
|
3113
|
+
get_subscription_movement: {
|
|
3114
|
+
application: APPLICATION,
|
|
3115
|
+
range: RANGE,
|
|
3116
|
+
note: T,
|
|
3117
|
+
series: [{ date: S, newSubscriptions: S, churnedSubscriptions: S, mrrDeltaCents: S }],
|
|
3118
|
+
totals: { newSubscriptions: S, churnedSubscriptions: S, netMrrDeltaCents: S }
|
|
3119
|
+
},
|
|
3120
|
+
get_discount_performance: {
|
|
3121
|
+
application: APPLICATION,
|
|
3122
|
+
range: RANGE,
|
|
3123
|
+
note: T,
|
|
3124
|
+
rows: [{ discountId: S, code: T, redemptions: S, discountedRevenueCents: S, discountGivenCents: S }],
|
|
3125
|
+
totals: { redemptions: S, discountedRevenueCents: S, discountGivenCents: S }
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
|
|
1557
3129
|
// src/server.ts
|
|
1558
|
-
var VERSION = true ? "0.2.
|
|
3130
|
+
var VERSION = true ? "0.2.2" : "0.0.0-dev";
|
|
1559
3131
|
function registerableActions(policy) {
|
|
1560
3132
|
const isProduction = policy.identity.environment.providerEnvironment === "production";
|
|
1561
3133
|
return actionsForChannel("mcp").filter((action) => {
|
|
1562
3134
|
if (!HANDLERS[action.id]) return false;
|
|
3135
|
+
if (action.credentialScoped && policy.allowCredentialScoped === false) return false;
|
|
1563
3136
|
if (!canRunAction(policy.identity.scopes, action)) return false;
|
|
1564
3137
|
if (action.kind === "read") return true;
|
|
1565
|
-
if (action.sandboxOnly) return !isProduction;
|
|
3138
|
+
if (action.sandboxOnly) return !isProduction && (policy.allowSandboxWrites ?? true);
|
|
1566
3139
|
if (!policy.allowWrites) return false;
|
|
1567
3140
|
return !isProduction || policy.allowLiveWrites;
|
|
1568
3141
|
});
|
|
@@ -1579,11 +3152,14 @@ function buildInstructions(identity, actions, policy) {
|
|
|
1579
3152
|
`Throttle MCP ${VERSION}. Connected to ${where}, environment "${identity.environment.slug}" (${isProduction ? "PRODUCTION \u2014 live data" : "non-production \u2014 not live data"}).`,
|
|
1580
3153
|
"",
|
|
1581
3154
|
"Conventions:",
|
|
1582
|
-
"-
|
|
3155
|
+
"- The workspace and environment are fixed for this session and cannot be switched from inside a tool call. Call whoami if you need to state where you are.",
|
|
3156
|
+
'- The application can vary per call: pass "applicationId" on a read or analytics tool to reach a different application in this workspace, and call list_applications to see which ones this connection can reach. Omitting applicationId uses the application this connection was authorized for.',
|
|
3157
|
+
"- Every analytics result covers exactly ONE application and names it in `application`. Say which application a figure describes. If the user asks about their business as a whole and this connection reaches more than one application, do not answer from the default alone \u2014 call list_applications, then query each and add them up, or ask which application they mean.",
|
|
1583
3158
|
'- List tools return { data, pagination }. When pagination.hasMore is true, pass pagination.cursor back as "cursor" rather than raising "limit".',
|
|
1584
3159
|
"- Amounts are integer minor units (2500 = $25.00).",
|
|
1585
3160
|
"- Throttle rejects snake_case on public request bodies. Before writing integration code, confirm shapes with describe_endpoint and check bodies with validate_payload instead of guessing field names.",
|
|
1586
|
-
"- API errors come back as text with a stable code. On insufficient_scopes, tell the user which scope to add \u2014 do not retry."
|
|
3161
|
+
"- API errors come back as text with a stable code. On insufficient_scopes, tell the user which scope to add \u2014 do not retry.",
|
|
3162
|
+
"- Analytics results may mark a section `omitted`. That means UNKNOWN, never zero. When `grantable` is false the data cannot be read over this connection at all \u2014 say so rather than reporting an absence."
|
|
1587
3163
|
];
|
|
1588
3164
|
if (liveWrites.length === 0) {
|
|
1589
3165
|
lines.push(
|
|
@@ -1601,8 +3177,31 @@ function buildInstructions(identity, actions, policy) {
|
|
|
1601
3177
|
}
|
|
1602
3178
|
return lines.join("\n");
|
|
1603
3179
|
}
|
|
3180
|
+
function declareSecuritySchemes(server, actions) {
|
|
3181
|
+
const inner = server.server;
|
|
3182
|
+
const handlers = inner._requestHandlers;
|
|
3183
|
+
const original = handlers?.get("tools/list");
|
|
3184
|
+
if (!original) return;
|
|
3185
|
+
const schemes = new Map(
|
|
3186
|
+
actions.map((a) => [
|
|
3187
|
+
a.id,
|
|
3188
|
+
[{
|
|
3189
|
+
type: "oauth2",
|
|
3190
|
+
scopes: [.../* @__PURE__ */ new Set([...a.scopes, ...a.sectionScopes ?? []])].filter((scope) => validateScopesForOAuth([scope]).ok)
|
|
3191
|
+
}]
|
|
3192
|
+
])
|
|
3193
|
+
);
|
|
3194
|
+
inner.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
3195
|
+
const result = await original(request, extra);
|
|
3196
|
+
return {
|
|
3197
|
+
...result,
|
|
3198
|
+
tools: result.tools.map((tool) => ({ ...tool, securitySchemes: schemes.get(tool.name) ?? [{ type: "oauth2", scopes: [] }] }))
|
|
3199
|
+
};
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
1604
3202
|
function createServer(options) {
|
|
1605
|
-
const
|
|
3203
|
+
const redactPii = options.identity.type === "oauth_token" && !hasScope(options.identity.scopes, "customer_pii:read");
|
|
3204
|
+
const actions = registerableActions(options).filter((a) => !redactPii || OAUTH_PROJECTIONS[a.id] !== void 0);
|
|
1606
3205
|
const server = new McpServer(
|
|
1607
3206
|
{ name: "throttle", version: VERSION },
|
|
1608
3207
|
{ instructions: buildInstructions(options.identity, actions, options) }
|
|
@@ -1615,25 +3214,57 @@ function createServer(options) {
|
|
|
1615
3214
|
};
|
|
1616
3215
|
for (const action of actions) {
|
|
1617
3216
|
const handler = HANDLERS[action.id];
|
|
1618
|
-
server.
|
|
3217
|
+
server.registerTool(
|
|
1619
3218
|
action.id,
|
|
1620
|
-
|
|
1621
|
-
|
|
3219
|
+
{
|
|
3220
|
+
title: action.title,
|
|
3221
|
+
description: describeForModel(action),
|
|
3222
|
+
inputSchema: fieldsToZodShape(action.input),
|
|
3223
|
+
// Every hint is an explicit boolean: directory reviews (OpenAI's in
|
|
3224
|
+
// particular) read a missing value as "unknown", not as false.
|
|
3225
|
+
annotations: {
|
|
3226
|
+
title: action.title,
|
|
3227
|
+
readOnlyHint: action.kind === "read",
|
|
3228
|
+
destructiveHint: action.kind === "write" && Boolean(action.destructive),
|
|
3229
|
+
idempotentHint: Boolean(action.idempotent),
|
|
3230
|
+
// Every tool is bounded to the connected workspace; none reaches the
|
|
3231
|
+
// public internet or an open-ended set of external entities.
|
|
3232
|
+
openWorldHint: false
|
|
3233
|
+
}
|
|
3234
|
+
},
|
|
1622
3235
|
async (args) => {
|
|
1623
3236
|
try {
|
|
3237
|
+
if (redactPii && action.id === "search_customers" && typeof args?.q === "string" && args.q.trim() !== "") {
|
|
3238
|
+
return {
|
|
3239
|
+
content: [{ type: "text", text: "Customer text search is not available over this connection, because names, emails, phone numbers and external ids are withheld. List customers by creation date (createdAfter/createdBefore), or open one by its Throttle id from an order, quote or subscription." }],
|
|
3240
|
+
isError: true
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
1624
3243
|
const result = await handler(ctx, args ?? {});
|
|
1625
|
-
|
|
3244
|
+
const shown = redactPii ? redactShopperPii(project(result, OAUTH_PROJECTIONS[action.id])) : result;
|
|
3245
|
+
return { content: [{ type: "text", text: formatResult(shown) }] };
|
|
1626
3246
|
} catch (error) {
|
|
1627
3247
|
return {
|
|
1628
3248
|
content: [{ type: "text", text: formatError(error) }],
|
|
1629
|
-
isError: true
|
|
3249
|
+
isError: true,
|
|
3250
|
+
...options.authChallenge && isCredentialRejection(error) ? {
|
|
3251
|
+
_meta: {
|
|
3252
|
+
"mcp/www_authenticate": [
|
|
3253
|
+
`${options.authChallenge}, error="invalid_token", error_description="The connected credential was rejected. Sign in again to continue."`
|
|
3254
|
+
]
|
|
3255
|
+
}
|
|
3256
|
+
} : {}
|
|
1630
3257
|
};
|
|
1631
3258
|
}
|
|
1632
3259
|
}
|
|
1633
3260
|
);
|
|
1634
3261
|
}
|
|
3262
|
+
if (options.authChallenge) declareSecuritySchemes(server, actions);
|
|
1635
3263
|
return server;
|
|
1636
3264
|
}
|
|
3265
|
+
function isCredentialRejection(error) {
|
|
3266
|
+
return error?.statusCode === 401;
|
|
3267
|
+
}
|
|
1637
3268
|
function describeForModel(action) {
|
|
1638
3269
|
const parts = [action.description];
|
|
1639
3270
|
if (action.guardrails && action.guardrails.length > 0) {
|