master-zapier-plan-draft/research/zapier/scripts/ingest-function-reference.py
George Lambert b4150c8250 Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
2026-09-09 02:37:36 -04:00

1152 lines
54 KiB
Python

#!/usr/bin/env python3
"""Document every Zapier CLI / core / SDK function in MCP-reference format.
Writes kind=cli_function, core_function, sdk_function (and upgrades the
legacy cli_command / sdk_command rows) plus FUNCTIONS-REFERENCE.md and a
guide:zapier-functions playbook.
"""
from __future__ import annotations
import json
import os
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(os.environ.get("ZAPIER_RESEARCH_ROOT") or Path(__file__).resolve().parents[1])
REPOS = ROOT / "repos"
NOW = datetime.now(timezone.utc).isoformat()
PLATFORM_CLI_MD = (
REPOS / "zapier-platform" / "packages" / "cli" / "docs" / "cli.md"
)
if not PLATFORM_CLI_MD.exists():
PLATFORM_CLI_MD = Path("/Users/marchon/research/zapier-platform/packages/cli/docs/cli.md")
SDK_CLI_MD = REPOS / "sdk" / "skills" / "zapier-sdk" / "references" / "cli-commands.md"
def kebab_to_camel(name: str) -> str:
parts = name.replace(":", "-").split("-")
if not parts:
return name
return parts[0] + "".join(p[:1].upper() + p[1:] if p else "" for p in parts[1:])
# ---------------------------------------------------------------------------
# Core runtime (z.*) — hand-authored, MCP-quality
# ---------------------------------------------------------------------------
CORE = [
{
"key": "z.request",
"category": "http",
"title": "Authenticated HTTP to the partner API",
"signature": "z.request(url, options?) → Promise<HttpResponse>\nz.request(optionsWithUrl) → Promise<HttpResponse>",
"high_level": "The only HTTP client you should use inside a perform. Adds auth middleware, logging, and status checks.",
"internals": (
"Runs the beforeRequest chain (basic/digest/oauth1 headers, query merge, prepare-request), "
"then Node fetch, then afterResponse (throw-for-status, stale-auth → RefreshAuthError, "
"throttling, log-response). `json` serializes the body; `form` sends urlencoded; `params` "
"are query string; `raw:true` returns a stream (RawHttpResponse). Non-2xx throws "
"ResponseError unless skipThrowForStatus. Same path Zapier uses in production."
),
"inputs": """```ts
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
type Input =
| [url: string, options?: HttpRequestOptions]
| [options: HttpRequestOptions & { url: string }];
interface HttpRequestOptions {
method?: HttpMethod;
headers?: Record<string, string>;
body?: string | Buffer | NodeJS.ReadableStream | Record<string, unknown>;
json?: unknown; // JSON body
form?: unknown; // application/x-www-form-urlencoded
params?: Record<string, unknown>;
raw?: boolean;
skipThrowForStatus?: boolean;
removeMissingValuesFrom?: { params?: boolean; body?: boolean };
skipEncodingChars?: string;
middlewareData?: Record<string, unknown>;
timeout?: number;
redirect?: "manual" | "error" | "follow";
}
```""",
"outputs": """```ts
interface HttpResponse<T = any> {
status: number;
headers: Headers;
content: string;
data: T; // parsed JSON when content-type is JSON
throwForStatus(): void;
getHeader(key: string): string | undefined;
request: HttpRequestOptions;
}
// raw:true → RawHttpResponse with .body stream, .buffer(), .json(), .text()
```""",
"related": ["BeforeRequestMiddleware", "AfterResponseMiddleware", "z.errors.RefreshAuthError", "z.errors.ResponseError", "z.console"],
"examples": [
{"title": "GET with auth middleware", "code": "const resp = await z.request('https://api.example.com/v1/contacts');\nreturn resp.data;"},
{"title": "POST JSON", "code": "const resp = await z.request({\n url: 'https://api.example.com/v1/contacts',\n method: 'POST',\n json: { name: bundle.inputData.name },\n});\nreturn resp.data;"},
],
"mcp_twin": "execute_zapier_read_action / execute_zapier_write_action (hosted)",
"sdk_twin": "zapier.fetch(url, { connection, method }) / zapier-sdk curl",
},
{
"key": "z.console",
"category": "debug",
"title": "Integration logger",
"signature": "z.console.log|info|warn|error(...args) → void",
"high_level": "Logs that show up in zapier-platform logs and Zap history. Prefer this over console.log.",
"internals": "Forwards to Zapier's log pipeline (type=console). Local `zapier-platform test` prints to stdout; production is queried via `zapier-platform logs --type console`.",
"inputs": "```ts\n(...args: unknown[]) => void\n```",
"outputs": "```ts\nvoid\n```",
"related": ["zapier-platform logs"],
"examples": [{"title": "Debug a payload", "code": "z.console.log('input', bundle.inputData);"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.dehydrate",
"category": "hydration",
"title": "Lazy pointer to an expensive object",
"signature": "z.dehydrate(func, inputData?, cacheExpiration?) → string",
"high_level": "Returns a pointer string. Zapier later calls func(z, bundle) when a downstream step needs the data.",
"internals": "Serializes {method path, inputData} into a hydrate token. At hydrate time the runtime resolves the function from App.hydrators / the original module and invokes it. Use for large payloads you do not want in every trigger item.",
"inputs": "```ts\n(func: (z: ZObject, bundle: Bundle<T>) => any, inputData?: T, cacheExpiration?: number) => string\n```",
"outputs": "```ts\nstring // hydrate pointer, not the payload\n```",
"related": ["z.dehydrateFile", "z.stashFile", "HydratorsSchema"],
"examples": [{"title": "Defer a contact fetch", "code": "return {\n id: contact.id,\n name: contact.name,\n extra: z.dehydrate(getFullContact, { id: contact.id }),\n};"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.dehydrateFile",
"category": "hydration",
"title": "Lazy pointer to a file",
"signature": "z.dehydrateFile(func, inputData?, cacheExpiration?) → string",
"high_level": "File-specific dehydrator. Zapier fetches bytes only when a later step needs the file.",
"internals": "Same pointer mechanism as z.dehydrate but marked as a file so the editor treats it as a file field. See example-apps/files.",
"inputs": "```ts\n(func: (z: ZObject, bundle: Bundle<T>) => any, inputData?: T, cacheExpiration?: number) => string\n```",
"outputs": "```ts\nstring\n```",
"related": ["z.dehydrate", "z.stashFile"],
"examples": [{"title": "Trigger item with a file", "code": "return { id: file.id, file: z.dehydrateFile(downloadFile, { id: file.id }) };"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.stashFile",
"category": "files",
"title": "Upload bytes and get a public Zapier URL",
"signature": "z.stashFile(input, knownLength?, filename?, contentType?) → string",
"high_level": "Turns a Buffer, stream, URL string, or Promise<RawHttpResponse> into a short-lived public URL.",
"internals": "Uploads to Zapier's file stash. Used by hydrators and file creates. Length/filename/contentType help the stash; streams should pass knownLength when possible.",
"inputs": "```ts\n(input: string | Buffer | NodeJS.ReadableStream | Promise<RawHttpResponse> | Promise<string>,\n knownLength?: number, filename?: string, contentType?: string) => string\n```",
"outputs": "```ts\nstring // https://zapier-dev-files.s3… style URL\n```",
"related": ["z.dehydrateFile", "z.request"],
"examples": [{"title": "Stash a download", "code": "const raw = await z.request({ url: fileUrl, raw: true });\nreturn { file: z.stashFile(raw, undefined, 'report.pdf', 'application/pdf') };"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.cursor.get",
"category": "polling",
"title": "Read the polling cursor",
"signature": "z.cursor.get() → Promise<string>",
"high_level": "Per-subscription opaque cursor so a poll can resume from last-seen id/timestamp.",
"internals": "Zapier stores one cursor string per user/zap/trigger. Empty string if never set. Pair with z.cursor.set at the end of perform.",
"inputs": "```ts\n() => Promise<string>\n```",
"outputs": "```ts\nPromise<string>\n```",
"related": ["z.cursor.set", "PollingTriggerPerform"],
"examples": [{"title": "Incremental poll", "code": "const since = await z.cursor.get();\nconst rows = await fetchSince(since);\nawait z.cursor.set(rows[0]?.updated_at || since);\nreturn rows;"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.cursor.set",
"category": "polling",
"title": "Write the polling cursor",
"signature": "z.cursor.set(cursor) → Promise<null>",
"high_level": "Persist an opaque cursor for the next poll.",
"internals": "Replaces the stored cursor. Keep it small (id or ISO timestamp).",
"inputs": "```ts\n(cursor: string) => Promise<null>\n```",
"outputs": "```ts\nPromise<null>\n```",
"related": ["z.cursor.get"],
"examples": [{"title": "Save newest id", "code": "await z.cursor.set(String(newest.id));"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.generateCallbackUrl",
"category": "callback",
"title": "Resume URL for long-running creates",
"signature": "z.generateCallbackUrl() → string",
"high_level": "Partner API can POST here to resume a create via performResume.",
"internals": "Mints a one-shot Zapier URL. Original perform returns quickly; later performResume gets bundle.cleanedRequest + bundle.outputData. See example-apps/callback.",
"inputs": "```ts\n() => string\n```",
"outputs": "```ts\nstring // https://hooks.zapier.com/… callback\n```",
"related": ["CreatePerformResume"],
"examples": [{"title": "Kick off async job", "code": "const hook = z.generateCallbackUrl();\nawait z.request({ url: '.../jobs', method: 'POST', json: { callback: hook } });\nreturn { pending: true };"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.hash",
"category": "crypto",
"title": "Hash helper (Node crypto)",
"signature": "z.hash(algorithm, data, encoding?, input_encoding?) → string",
"high_level": "Convenience around crypto.createHash. Default hex / binary.",
"internals": "algorithm typically 'sha256'. Used for cache keys and HMAC-style signatures you compute yourself (OAuth1 signing is automatic).",
"inputs": "```ts\n(algorithm: string, data: string, encoding?: string, input_encoding?: string) => string\n```",
"outputs": "```ts\nstring\n```",
"related": [],
"examples": [{"title": "SHA-256 hex", "code": "const digest = z.hash('sha256', bundle.inputData.raw);"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.JSON.parse",
"category": "json",
"title": "JSON.parse with a clean Zapier error",
"signature": "z.JSON.parse(text) → any",
"high_level": "Prefer over JSON.parse so users see a friendly error on bad JSON.",
"internals": "Wraps JSON.parse and rethrows as a user-visible AppError.",
"inputs": "```ts\n(text: string) => any\n```",
"outputs": "```ts\nany\n```",
"related": ["z.JSON.stringify", "z.errors.Error"],
"examples": [{"title": "Parse partner body", "code": "const data = z.JSON.parse(resp.content);"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.JSON.stringify",
"category": "json",
"title": "JSON.stringify on z",
"signature": "z.JSON.stringify(value) → string",
"high_level": "Same as built-in JSON.stringify; kept for symmetry with z.JSON.parse.",
"internals": "Delegates to JSON.stringify.",
"inputs": "```ts\n(value: unknown) => string\n```",
"outputs": "```ts\nstring\n```",
"related": ["z.JSON.parse"],
"examples": [{"title": "Serialize", "code": "const body = z.JSON.stringify({ ok: true });"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.cache.get",
"category": "cache",
"title": "Read per-auth cache",
"signature": "z.cache.get(key) → Promise<any>",
"high_level": "JSON-encodable values scoped to the connected account.",
"internals": "Zapier-hosted key/value. Use for account-id lookups you would otherwise repeat every poll.",
"inputs": "```ts\n(key: string) => Promise<any>\n```",
"outputs": "```ts\nPromise<any> // undefined/null if missing\n```",
"related": ["z.cache.set", "z.cache.delete"],
"examples": [{"title": "Memoize org id", "code": "let org = await z.cache.get('orgId');\nif (!org) { org = (await fetchOrg()).id; await z.cache.set('orgId', org, 3600); }"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.cache.set",
"category": "cache",
"title": "Write per-auth cache",
"signature": "z.cache.set(key, value, ttl?, scope?, nx?) → Promise<boolean|null>",
"high_level": "Store JSON-encodable data. ttl in seconds. nx=true sets only if missing.",
"internals": "scope further namespaces the key. Returns whether the write landed.",
"inputs": "```ts\n(key: string, value: any, ttl?: number, scope?: string[], nx?: boolean) => Promise<boolean | null>\n```",
"outputs": "```ts\nPromise<boolean | null>\n```",
"related": ["z.cache.get", "z.cache.delete"],
"examples": [{"title": "TTL 1h", "code": "await z.cache.set('orgId', org.id, 3600);"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.cache.delete",
"category": "cache",
"title": "Delete a cache key",
"signature": "z.cache.delete(key) → Promise<boolean>",
"high_level": "Drop stale metadata (e.g. after auth refresh).",
"internals": "Removes one key from the per-auth cache.",
"inputs": "```ts\n(key: string) => Promise<boolean>\n```",
"outputs": "```ts\nPromise<boolean>\n```",
"related": ["z.cache.get", "z.cache.set"],
"examples": [{"title": "Invalidate", "code": "await z.cache.delete('orgId');"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.errors.Error",
"category": "errors",
"title": "User-visible app error",
"signature": "throw new z.errors.Error(message, code?, status?)",
"high_level": "Primary error to throw from perform. Message is shown to the user.",
"internals": "Serializes {message, code, status} as AppError. code/status help Zapier classify retries vs user-fixable failures.",
"inputs": "```ts\n(message: string, code?: string, status?: number)\n```",
"outputs": "```ts\nnever // throws\n```",
"related": ["z.errors.HaltedError", "z.errors.RefreshAuthError", "z.errors.ThrottledError"],
"examples": [{"title": "Validation", "code": "throw new z.errors.Error('Name is required', 'InvalidInput', 400);"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.errors.HaltedError",
"category": "errors",
"title": "Stop this run without failing the Zap",
"signature": "throw new z.errors.HaltedError(message?)",
"high_level": "Filter-out. Use when the payload should be ignored.",
"internals": "Marks the run halted, not errored. Zaps stay on. Typical for 'this webhook is not the event we care about'.",
"inputs": "```ts\n(message?: string)\n```",
"outputs": "```ts\nnever\n```",
"related": ["z.errors.Error"],
"examples": [{"title": "Ignore other event types", "code": "if (bundle.cleanedRequest.type !== 'invoice.paid') {\n throw new z.errors.HaltedError('not an invoice.paid event');\n}"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.errors.ExpiredAuthError",
"category": "errors",
"title": "Dead connection — user must reconnect",
"signature": "throw new z.errors.ExpiredAuthError(message?)",
"high_level": "Revoked tokens that cannot be refreshed.",
"internals": "Pauses the Zap and emails the user to reconnect. Do not use for a refreshable 401 — that is RefreshAuthError.",
"inputs": "```ts\n(message?: string)\n```",
"outputs": "```ts\nnever\n```",
"related": ["z.errors.RefreshAuthError", "manage_zapier_connections"],
"examples": [{"title": "Revoked", "code": "if (resp.status === 403 && resp.data.error === 'revoked') {\n throw new z.errors.ExpiredAuthError('Reconnect this account');\n}"}],
"mcp_twin": "manage_zapier_connections",
"sdk_twin": "create-connection / wait-for-new-connection",
},
{
"key": "z.errors.RefreshAuthError",
"category": "errors",
"title": "Refresh OAuth/session and retry",
"signature": "throw new z.errors.RefreshAuthError(message?)",
"high_level": "Typical 401 handler. Zapier calls refreshAccessToken / sessionConfig.perform then retries.",
"internals": "Thrown automatically by afterResponse when it sees stale auth; you can also throw it yourself. Only works for oauth2/session with refresh configured.",
"inputs": "```ts\n(message?: string)\n```",
"outputs": "```ts\nnever\n```",
"related": ["z.errors.ExpiredAuthError", "AfterResponseMiddleware", "AuthenticationOAuth2ConfigSchema"],
"examples": [{"title": "401", "code": "if (resp.status === 401) throw new z.errors.RefreshAuthError();"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.errors.ThrottledError",
"category": "errors",
"title": "Rate limit — retry after delay",
"signature": "throw new z.errors.ThrottledError(message, delaySeconds?)",
"high_level": "Signals 429. Pass delay from Retry-After.",
"internals": "Zapier backs off for delaySeconds then retries the perform. Also thrown by afterResponse throw-for-throttling middleware.",
"inputs": "```ts\n(message: string, delaySeconds?: number)\n```",
"outputs": "```ts\nnever\n```",
"related": ["z.request"],
"examples": [{"title": "Honor Retry-After", "code": "throw new z.errors.ThrottledError('slow down', Number(resp.getHeader('retry-after') || 60));"}],
"mcp_twin": None,
"sdk_twin": "ZAPIER_ACTION_ERROR … N) Retry-After on run-action",
},
{
"key": "z.errors.ResponseError",
"category": "errors",
"title": "Wrap an HTTP response as an error",
"signature": "throw new z.errors.ResponseError(response)",
"high_level": "Usually auto-thrown by z.request on non-2xx.",
"internals": "JSON-stringifies status, content-type, retry-after, content, request URL.",
"inputs": "```ts\n(response: HttpResponse)\n```",
"outputs": "```ts\nnever\n```",
"related": ["z.request"],
"examples": [{"title": "Manual throw", "code": "if (resp.status >= 500) throw new z.errors.ResponseError(resp);"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.errors.CheckError",
"category": "errors",
"title": "Platform check failed",
"signature": "throw new z.errors.CheckError(message?)",
"high_level": "Raised by runtime checks (trigger not array, create not object, missing id).",
"internals": "See packages/core/src/checks/*. You rarely throw this yourself; fix the return shape instead.",
"inputs": "```ts\n(message?: string)\n```",
"outputs": "```ts\nnever\n```",
"related": ["PollingTriggerPerform", "CreatePerform"],
"examples": [],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.errors.StopRequestError",
"category": "errors",
"title": "Abort an outbound HTTP request from middleware",
"signature": "throw new z.errors.StopRequestError(message?)",
"high_level": "Throw from beforeRequest to cancel z.request without treating it as an app failure.",
"internals": "Short-circuits the HTTP stack. Used when middleware decides the call should not go out.",
"inputs": "```ts\n(message?: string)\n```",
"outputs": "```ts\nnever\n```",
"related": ["BeforeRequestMiddleware", "z.request"],
"examples": [{"title": "Skip empty search", "code": "if (!request.params.q) throw new z.errors.StopRequestError('no query');"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "z.errors.DehydrateError",
"category": "errors",
"title": "Hydration pointer failed",
"signature": "throw new z.errors.DehydrateError(message?)",
"high_level": "Bad dehydrate token or hydrator threw.",
"internals": "Runtime-raised when a hydrate pointer cannot be resolved.",
"inputs": "```ts\n(message?: string)\n```",
"outputs": "```ts\nnever\n```",
"related": ["z.dehydrate"],
"examples": [],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "createAppTester",
"category": "testing",
"title": "Unit-test a perform or request template",
"signature": "createAppTester(appRaw, options?) → (func|request, bundle?) => Promise",
"high_level": "From zapier-platform-core. Invokes a perform with a partial bundle.",
"internals": "Builds a local z + bundle, runs the same execute path as production (minus some hook/file limits). Used by `zapier-platform test`.",
"inputs": "```ts\n(appRaw: object, options?: { customStoreKey?: string }) => AppTester\n```",
"outputs": "```ts\n(func | Request, bundle?: DeepPartial<Bundle>) => Promise<T>\n```",
"related": ["zapier.tools.env.inject", "zapier-platform test", "zapier-platform invoke"],
"examples": [{"title": "Jest", "code": "const tester = createAppTester(App);\nconst rows = await tester(App.triggers.new_contact.operation.perform, {\n authData: { api_key: process.env.API_KEY },\n inputData: {},\n});"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "zapier.tools.env.inject",
"category": "testing",
"title": "Load .env into process.env",
"signature": "zapier.tools.env.inject(filename?)",
"high_level": "Called automatically by the test runner. Useful in custom setup.",
"internals": "Parses KEY=VAL lines. Auth fields for invoke live as authData_* in .env.",
"inputs": "```ts\n(filename?: string) => void\n```",
"outputs": "```ts\nvoid\n```",
"related": ["createAppTester", "zapier-platform invoke"],
"examples": [{"title": "Manual", "code": "require('zapier-platform-core').tools.env.inject();"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "BeforeRequestMiddleware",
"category": "middleware",
"title": "Mutate every outbound z.request",
"signature": "(request, z, bundle) => request | Promise<request>",
"high_level": "App.beforeRequest. Typical: attach Bearer from bundle.authData.",
"internals": "Runs in order before fetch. Must return the request object (with url).",
"inputs": "```ts\n(request: HttpRequestOptions & { url: string }, z: ZObject, bundle: Bundle) => request | Promise<request>\n```",
"outputs": "```ts\nHttpRequestOptions & { url: string }\n```",
"related": ["AfterResponseMiddleware", "z.request"],
"examples": [{"title": "Bearer", "code": "const addAuth = (req, z, bundle) => {\n req.headers = req.headers || {};\n req.headers.Authorization = `Bearer ${bundle.authData.access_token}`;\n return req;\n};\nmodule.exports = { beforeRequest: [addAuth] };"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "AfterResponseMiddleware",
"category": "middleware",
"title": "Inspect every HTTP response",
"signature": "(response, z, bundle) => response | Promise<response>",
"high_level": "App.afterResponse. Throw RefreshAuthError on 401; remap error envelopes.",
"internals": "Runs after fetch, before the perform sees the response. Can mutate response.data.",
"inputs": "```ts\n(response: HttpResponse, z: ZObject, bundle: Bundle) => response | Promise<response>\n```",
"outputs": "```ts\nHttpResponse\n```",
"related": ["BeforeRequestMiddleware", "z.errors.RefreshAuthError"],
"examples": [{"title": "401 → refresh", "code": "const maybeRefresh = (resp, z) => {\n if (resp.status === 401) throw new z.errors.RefreshAuthError();\n return resp;\n};"}],
"mcp_twin": None,
"sdk_twin": None,
},
{
"key": "PollingTriggerPerform",
"category": "perform",
"title": "Polling trigger perform",
"signature": "(z, bundle) => object[] | Promise<object[]>",
"high_level": "Must return an array of objects with id (or primary output fields).",
"internals": "Zapier dedupes by id. Newest-first recommended. meta.isPopulatingDedupe / isLoadingSample / isFillingDynamicDropdown change how many you should fetch.",
"inputs": "```ts\n(z: ZObject, bundle: Bundle) => object[] | Promise<object[]>\n```",
"outputs": "```ts\nArray<{ id: string } & Record<string, unknown>>\n```",
"related": ["z.cursor.get", "z.request", "TriggerSchema"],
"examples": [{"title": "List contacts", "code": "const r = await z.request('https://api.example.com/contacts');\nreturn r.data.map((c) => ({ id: c.id, name: c.name }));"}],
"mcp_twin": None,
"sdk_twin": "list-triggers / create-trigger-inbox",
},
{
"key": "WebhookTriggerPerform",
"category": "perform",
"title": "REST Hook perform",
"signature": "(z, bundle) => object[] | Promise<object[]>",
"high_level": "Parse bundle.cleanedRequest into an array of objects.",
"internals": "Also implement performSubscribe / performUnsubscribe / performList. subscribe stores bundle.subscribeData; unsubscribe uses it. targetUrl is the Zapier hook URL to register with the partner.",
"inputs": "```ts\n(z: ZObject, bundle: Bundle) => object[] | Promise<object[]>\n```",
"outputs": "```ts\nobject[]\n```",
"related": ["WebhookTriggerPerformSubscribe", "BasicHookOperationSchema"],
"examples": [{"title": "Unwrap", "code": "const evt = bundle.cleanedRequest;\nreturn [{ id: evt.id, ...evt.data }];"}],
"mcp_twin": None,
"sdk_twin": "create-trigger-inbox (hosted subscription)",
},
{
"key": "CreatePerform",
"category": "perform",
"title": "Create action perform",
"signature": "(z, bundle) => object | Promise<object>",
"high_level": "Must return one object, not an array.",
"internals": "Returning a non-object fails 'non-object from create'. Use performResume + z.generateCallbackUrl for long jobs. performBuffer for bulk.",
"inputs": "```ts\n(z: ZObject, bundle: Bundle) => object | Promise<object>\n```",
"outputs": "```ts\nobject\n```",
"related": ["z.request", "z.generateCallbackUrl", "performBuffer"],
"examples": [{"title": "Create contact", "code": "const r = await z.request({ url: '.../contacts', method: 'POST', json: bundle.inputData });\nreturn r.data;"}],
"mcp_twin": "execute_zapier_write_action",
"sdk_twin": "run-action <app> write <key>",
},
{
"key": "SearchPerform",
"category": "perform",
"title": "Search action perform",
"signature": "(z, bundle) => object[] | Promise<object[]>",
"high_level": "Must return an array (possibly empty). Empty = not found (needed for search-or-create).",
"internals": "May return { results, paging_token } envelope. meta.paging_token continues a previous page.",
"inputs": "```ts\n(z: ZObject, bundle: Bundle) => object[] | Promise<object[]>\n```",
"outputs": "```ts\nobject[] | { results: object[]; paging_token?: string }\n```",
"related": ["CreatePerform", "SearchOrCreateSchema"],
"examples": [{"title": "Find by email", "code": "const r = await z.request({ url: '.../contacts', params: { email: bundle.inputData.email } });\nreturn r.data.items || [];"}],
"mcp_twin": "execute_zapier_read_action",
"sdk_twin": "run-action <app> search <key>",
},
{
"key": "performBuffer",
"category": "perform",
"title": "Buffered / bulk create",
"signature": "(z, BufferedBundle) => Promise<{[id]: {outputData?, error?}}>",
"high_level": "Process bundle.buffer items in one API call. Return a map keyed by each item's meta.id.",
"internals": "Zapier groups creates and sends them together. Each result must be success (outputData) or error string.",
"inputs": "```ts\n(z: ZObject, bundle: { authData; buffer: { inputData; meta: { id: string } }[]; groupedBy }) => Promise<PerformBufferResult>\n```",
"outputs": "```ts\nRecord<string, { outputData?: object; error?: string }>\n```",
"related": ["CreatePerform"],
"examples": [{"title": "Bulk insert", "code": "const created = await postAll(bundle.buffer.map((i) => i.inputData));\nreturn Object.fromEntries(bundle.buffer.map((i, n) => [i.meta.id, { outputData: created[n] }]));"}],
"mcp_twin": None,
"sdk_twin": None,
},
]
# ---------------------------------------------------------------------------
# CLI / SDK parsers
# ---------------------------------------------------------------------------
CLI_INTERNALS = {
"init": "Copies an official example-apps template (oauth2, session-auth, …) into PATH. Does not call Zapier until register/push.",
"scaffold": "AST-edits index.js/ts to register a new trigger/search/create/resource file generated from packages/cli/scaffold templates.",
"convert": "Downloads a Visual Builder definition (or --json) and emits CLI source. Existing files are not clobbered.",
"validate": "Runs zapier-platform-schema JSON Schema plus optional live style checks against Zapier's servers.",
"build": "Temp copy → zapierwrapper.js entry → esbuild dep detection → build/build.zip + source.zip.",
"upload": "POSTs build.zip + source.zip for the version in package.json. Versions must be sequential.",
"push": "build then upload. --snapshot makes 0.0.0-LABEL for dev.",
"invoke": "Local (default, .env), relay (-a auth id, traffic via Zapier), or remote (-r, production). Emulates (z, bundle).",
"test": "Wrapper around npm/yarn/pnpm test after validate + env inject.",
"register": "Creates the integration on developer.zapier.com and writes .zapierapprc.",
"link": "Writes .zapierapprc pointing at an existing integration id.",
"login": "Stores a deploy key in ~/.zapierrc (SSO via --sso).",
"promote": "Marks a pushed version as the public default. Does not migrate users.",
"migrate": "Moves users FROM→TO (optional percent). Non-breaking only. Track with jobs.",
"deprecate": "Schedules removal (DATE ≥ 3 weeks). Users emailed at T-14d.",
"canary:create": "Temporary traffic split FROM→TO for duration seconds. Reverts when expired.",
}
SDK_CATEGORY_INTERNALS = {
"Accounts": "Browser OAuth to Zapier itself (not a third-party app). Writes SDK credentials used by createZapierSdk().",
"Apps": "Directory of 9,000+ integrations. Same catalog as MCP discover_zapier_actions.",
"Connections": "OAuth grants Zapier holds per app+user. MCP twins: list/manage_zapier_connections.",
"Actions": "Discover and run partner actions. MCP twins: inspect + execute_zapier_*_action.",
"Triggers": "Trigger Inbox API — subscribe to partner events, lease/ack messages.",
"Tables": "Zapier Tables CRUD (not partner apps).",
"HTTP Requests": "Authenticated raw HTTP through a connection (SDK fetch / curl).",
"Code Workflows": "Experimental durable workflows on Zapier infrastructure.",
"Client Credentials": "Deploy SDK in CI without a browser login.",
"Utilities": "Project init, type generation, local MCP server for the SDK.",
}
def parse_platform_cli(text: str) -> list[dict]:
parts = re.split(r"\n## ", text)
rows = []
for part in parts[1:]:
lines = part.splitlines()
name = lines[0].strip()
block = "\n".join(lines[1:]).strip()
usage = ""
m = re.search(r"\*\*Usage\*\*:\s*`([^`]+)`", block)
if m:
usage = m.group(1)
summary = ""
qm = re.search(r"^>\s*(.+)$", block, re.M)
if qm:
summary = qm.group(1).strip()
aliases = []
am = re.search(r"\*\*Aliases\*\*\s*((?:\n\* .*)+)", block)
if am:
aliases = re.findall(r"`([^`]+)`", am.group(1))
flags = []
for fl in re.findall(r"^\* (.+)$", block, re.M):
if "`" in fl or fl.startswith("(required)") or "--" in fl:
flags.append(fl.strip())
examples = re.findall(r"^\* (`[^`]+`)", block, re.M)
examples = [e.strip("`") for e in examples]
args = []
in_args = False
for line in lines:
if line.startswith("**Arguments**"):
in_args = True
continue
if in_args:
if line.startswith("**"):
break
if line.startswith("* "):
args.append(line[2:].strip())
rows.append(
{
"name": name,
"usage": usage or f"zapier-platform {name}",
"summary": summary,
"block": block,
"flags": flags,
"args": args,
"examples": examples,
"aliases": aliases + [f"zapier {name}"],
}
)
return rows
def parse_sdk_cli(text: str) -> list[dict]:
rows = []
category = "Utilities"
# headings: ### `get-profile` or #### `get-profile`
chunks = re.split(r"\n#{3,4} `([^`]+)`", text)
# chunks[0] preamble, then name, body, name, body...
intro = chunks[0]
cats = re.findall(r"^### ([A-Za-z ]+)$", intro, re.M)
for i in range(1, len(chunks), 2):
name = chunks[i].strip()
body = chunks[i + 1] if i + 1 < len(chunks) else ""
# category: last ### Title before this that is NOT a command
# infer from inventory or from nearby ### without backticks
cat_m = re.search(r"experimental", body[:80], re.I)
usage = ""
um = re.search(r"Usage:\s*zapier-sdk ([^\n]+)", body)
if um:
usage = "zapier-sdk " + um.group(1).strip()
summary = ""
# first non-empty prose line after heading extras
for line in body.splitlines():
s = line.strip()
if not s or s.startswith("Usage:") or s.startswith("```") or s.startswith("_(requires"):
continue
if s.startswith("Arguments:") or s.startswith("Options:"):
break
if s.startswith("(") and "experimental" in s:
continue
summary = s
break
args = []
opts = []
section = None
for line in body.splitlines():
if line.startswith("Arguments:"):
section = "args"
continue
if line.startswith("Options:"):
section = "opts"
continue
if section and line.strip() and not line.startswith(" ") and not line.startswith("-") and line[0].isalpha() and ":" not in line[:20]:
# new section heading
if line.strip() in {"Arguments:", "Options:"}:
continue
if section == "args" and re.match(r"^\s{2}\S", line):
args.append(line.strip())
if section == "opts" and re.match(r"^\s{2}-", line):
opts.append(line.strip())
# category from earlier inventory headings in file
rows.append(
{
"name": name,
"usage": usage or f"zapier-sdk {name}",
"summary": summary,
"block": body.strip(),
"args": args,
"flags": opts,
"examples": re.findall(r"zapier-sdk [^\n`]+", body)[:6],
"experimental": "experimental" in body[:400].lower() or "requires `--experimental`" in body,
"ts": kebab_to_camel(name),
}
)
# assign categories from the inventory section
cat_map = {}
current = "Utilities"
for line in text.splitlines():
m = re.match(r"^### ([A-Za-z ]+)$", line)
if m and not line.startswith("### `"):
current = m.group(1).strip()
cm = re.match(r"^- `([a-z0-9.:-]+)", line)
if cm:
cat_map[cm.group(1)] = current
for r in rows:
r["category"] = cat_map.get(r["name"], "Utilities")
return rows
def render_body(kind_label: str, rec: dict, internals: str, related: list[str], twins: dict, typed: str, outputs: str, examples_md: str) -> str:
parts = [
f"# `{rec['name']}`",
"",
f"> {rec.get('summary') or rec['name']}",
"",
"## High-level description",
"",
rec.get("summary") or rec["name"],
"",
"## Internals",
"",
internals,
"",
"## Typed inputs",
"",
typed,
"",
"## Outputs",
"",
outputs,
"",
f"## Surface: {kind_label}",
"",
f"- Usage: `{rec.get('usage') or rec['name']}`",
]
if rec.get("aliases"):
parts.append("- Aliases: " + ", ".join(f"`{a}`" for a in rec["aliases"]))
if rec.get("ts"):
parts.append(f"- TypeScript: `zapier.{rec['ts']}(...)`")
if rec.get("experimental"):
parts.append("- Experimental: requires `--experimental`")
parts += ["", "## Related functions", ""]
for rel in related:
parts.append(f"- `{rel}`")
if not related:
parts.append("- _(see same category)_")
parts += ["", "## Twins (MCP / other CLI)", ""]
for k, v in twins.items():
if v:
parts.append(f"- **{k}:** `{v}`")
if rec.get("block"):
parts += ["", "## Official text", "", rec["block"][:8000]]
if examples_md:
parts += ["", "## Examples", "", examples_md]
return "\n".join(parts)
def typed_from_cli(rec: dict) -> str:
lines = ["```ts", "type Input = {"]
for a in rec.get("args") or []:
name = a.split("|")[0].strip().split()[0].strip("`()")
req = "(required)" in a or a.lower().startswith("(required)")
desc = a.split("|", 1)[-1].strip() if "|" in a else a
lines.append(f" {name}{'?' if not req else ''}: string; // {desc[:140]}")
for fl in rec.get("flags") or []:
names = re.findall(r"`([^`]+)`", fl)
flag = names[0] if names else fl.split()[0]
flag = flag.lstrip("-")
if not flag or flag in {"d", "h", "help"}:
# still include long flags
longf = next((n.lstrip("-") for n in names if n.startswith("--")), flag)
flag = longf or flag
lines.append(f" {flag.replace('-', '_') }?: string | boolean; // {fl[:140]}")
if len(lines) == 2:
lines.append(" // see official flags")
lines += ["};", "```"]
return "\n".join(lines)
def examples_md(examples: list[str]) -> str:
if not examples:
return ""
return "\n".join(f"```bash\n{e}\n```" for e in examples[:8])
def core_row(item: dict) -> dict:
examples_md_s = "\n\n".join(
f"### {e['title']}\n\n```ts\n{e['code']}\n```" for e in item.get("examples") or []
)
body = "\n".join(
[
f"# `{item['key']}`",
"",
f"> {item['high_level']}",
"",
"## High-level description",
"",
item["high_level"],
"",
"## Internals",
"",
item["internals"],
"",
"## Typed inputs",
"",
item["inputs"],
"",
"## Outputs",
"",
item["outputs"],
"",
f"- Signature: `{item['signature']}`",
f"- Category: `{item['category']}`",
"",
"## Related functions",
"",
*([f"- `{r}`" for r in item.get("related") or []] or ["- —"]),
"",
"## Twins (MCP / SDK)",
"",
f"- MCP: `{item.get('mcp_twin') or ''}`",
f"- SDK: `{item.get('sdk_twin') or ''}`",
"",
"## Examples",
"",
examples_md_s or "_see signature_",
]
)
return {
"_id": f"core_function:{item['key'].lower()}",
"kind": "core_function",
"key": item["key"],
"title": f"{item['key']}{item['title']}",
"summary": item["high_level"],
"body": body,
"usage": item["signature"].split("\n")[0],
"signature": item["signature"].split("\n")[0],
"aliases": [],
"flags": [],
"args": [],
"examples": item.get("examples") or [],
"source_url": "https://docs.zapier.com/integrations/build-cli/core",
"source_repo": "zapier/zapier-platform",
"source_path": "packages/core/types/custom.d.ts",
"section": "platform-core",
"tags": ["core", "function", item["category"], "zapier-platform-core"],
"related": item.get("related") or [],
"meta": {
"surface": "platform_core",
"category": item["category"],
"mcp_twin": item.get("mcp_twin"),
"sdk_twin": item.get("sdk_twin"),
"internals": item["internals"],
},
"ingested_at": NOW,
}
def main() -> int:
rows: list[dict] = []
# core
for item in CORE:
rows.append(core_row(item))
# platform CLI
if PLATFORM_CLI_MD.exists():
cli = parse_platform_cli(PLATFORM_CLI_MD.read_text(encoding="utf-8", errors="replace"))
else:
print("missing platform cli.md", file=sys.stderr)
cli = []
for rec in cli:
internals = CLI_INTERNALS.get(
rec["name"],
"Platform CLI talks to developer.zapier.com with the deploy key in ~/.zapierrc. "
"Implementation: zapier-platform/packages/cli/src/oclif/commands/.",
)
related = []
if rec["name"] in ("push", "upload", "build"):
related = ["build", "upload", "push", "validate"]
elif rec["name"] in ("promote", "migrate", "deprecate", "canary:create", "jobs"):
related = ["promote", "migrate", "deprecate", "jobs", "versions"]
elif rec["name"].startswith("invoke"):
related = ["invoke", "test", "createAppTester"]
elif rec["name"] in ("init", "scaffold", "convert"):
related = ["init", "scaffold", "convert", "register"]
twins = {
"deprecated alias": f"zapier {rec['name']}",
"MCP": "n/a (this builds integrations, MCP consumes them)",
}
body = render_body(
"zapier-platform CLI (build integrations)",
rec,
internals,
related,
twins,
typed_from_cli(rec),
"```ts\n// stdout (human table by default). --format json|raw|table|row|plain\n```",
examples_md(rec.get("examples") or []),
)
doc = {
"_id": f"cli_function:{rec['name']}",
"kind": "cli_function",
"key": rec["name"],
"title": f"zapier-platform {rec['name']}",
"summary": rec.get("summary") or rec["name"],
"body": body,
"usage": rec.get("usage"),
"signature": rec.get("usage"),
"aliases": rec.get("aliases") or [],
"flags": rec.get("flags") or [],
"args": rec.get("args") or [],
"examples": rec.get("examples") or [],
"source_url": "https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md",
"source_repo": "zapier/zapier-platform",
"source_path": "packages/cli/docs/cli.md",
"section": "platform-cli",
"tags": ["cli", "function", "zapier-platform-cli", rec["name"].split(":")[0]],
"related": related,
"meta": {"surface": "platform_cli", "internals": internals},
"ingested_at": NOW,
}
rows.append(doc)
# keep legacy kind in sync
legacy = dict(doc)
legacy["_id"] = f"cli_command:{rec['name']}"
legacy["kind"] = "cli_command"
rows.append(legacy)
# SDK CLI + TS methods
if SDK_CLI_MD.exists():
sdk = parse_sdk_cli(SDK_CLI_MD.read_text(encoding="utf-8", errors="replace"))
else:
print("missing sdk cli-commands.md", file=sys.stderr)
sdk = []
MCP_TWINS = {
"list-apps": "discover_zapier_actions",
"get-app": "discover_zapier_actions",
"list-actions": "inspect_zapier_actions / discover_zapier_actions",
"get-action": "inspect_zapier_actions",
"list-action-input-fields": "inspect_zapier_actions",
"get-action-input-fields-schema": "inspect_zapier_actions",
"list-action-input-field-choices": "inspect_zapier_actions (enum_property)",
"run-action": "execute_zapier_read_action / execute_zapier_write_action",
"list-connections": "list_zapier_connections",
"find-first-connection": "list_zapier_connections",
"find-unique-connection": "list_zapier_connections",
"create-connection": "manage_zapier_connections",
"get-connection-start-url": "manage_zapier_connections",
"wait-for-new-connection": "manage_zapier_connections (after auth_url)",
"curl": "write_code_action / z.request analog",
"mcp": "hosted Zapier MCP (different server)",
"feedback": "send_feedback",
}
for rec in sdk:
cat = rec.get("category") or "Utilities"
internals = SDK_CATEGORY_INTERNALS.get(cat, "Zapier SDK talks to api.zapier.com with SDK credentials.")
internals += f" TypeScript method: `zapier.{rec['ts']}`. CLI: `{rec['usage']}`."
related = [x["name"] for x in sdk if x.get("category") == cat and x["name"] != rec["name"]][:8]
twins = {
"TypeScript": f"zapier.{rec['ts']}()",
"MCP": MCP_TWINS.get(rec["name"], ""),
"Platform CLI": "n/a (SDK consumes apps; Platform CLI publishes them)",
}
typed = typed_from_cli(rec)
# better TS blurb
typed = f"```ts\n// CLI: {rec['usage']}\n// TS: const {{ data }} = await zapier.{rec['ts']}({{ ... }})\n```\n" + typed
body = render_body(
f"Zapier SDK CLI / TypeScript ({cat})",
rec,
internals,
related,
twins,
typed,
"```ts\n{ data: T } | paginated { data: T[]; next_cursor?: string }\n// --json for raw. Always prefer live discovery over hardcoded keys.\n```",
examples_md(rec.get("examples") or [rec["usage"]]),
)
doc = {
"_id": f"sdk_function:{rec['name']}",
"kind": "sdk_function",
"key": rec["name"],
"title": f"zapier-sdk {rec['name']} / zapier.{rec['ts']}",
"summary": rec.get("summary") or rec["name"],
"body": body,
"usage": rec.get("usage"),
"signature": f"zapier.{rec['ts']}()",
"aliases": [rec["ts"], f"zapier.{rec['ts']}"],
"flags": rec.get("flags") or [],
"args": rec.get("args") or [],
"examples": rec.get("examples") or [],
"source_url": "https://docs.zapier.com/sdk/cli-reference",
"source_repo": "zapier/sdk",
"source_path": "skills/zapier-sdk/references/cli-commands.md",
"section": "sdk",
"tags": ["sdk", "function", "zapier-sdk", cat.lower().replace(" ", "-")],
"related": related,
"meta": {
"surface": "sdk",
"category": cat,
"typescript": rec["ts"],
"experimental": rec.get("experimental"),
"mcp_twin": MCP_TWINS.get(rec["name"]),
"internals": internals,
},
"ingested_at": NOW,
}
rows.append(doc)
# also write camelCase lookup aliases as thin pointers? skip — aliases field is enough
guide_body = f"""# Zapier functions — complete reference (CLI, core, SDK)
**Start here first:**
`db.platform_reference.findOne({{ kind: "guide", key: "build-new-connector" }})`
or [PLATFORM-REFERENCE.md](PLATFORM-REFERENCE.md).
This guide is the **function catalog**. MCP tools are documented separately as
`kind: "mcp_function"` and [MCP-REFERENCE.md](MCP-REFERENCE.md).
## Surfaces (do not mix)
| Surface | kind | Count | Package |
|---------|------|------:|---------|
| Hosted MCP meta-tools | `mcp_function` | 17 | mcp.zapier.com |
| Platform CLI (publish an integration) | `cli_function` | {sum(1 for r in rows if r['kind']=='cli_function')} | `zapier-platform-cli` |
| Platform core (`z`, perform, middleware) | `core_function` | {sum(1 for r in rows if r['kind']=='core_function')} | `zapier-platform-core` |
| SDK CLI + TypeScript (consume apps) | `sdk_function` | {sum(1 for r in rows if r['kind']=='sdk_function')} | `@zapier/zapier-sdk` / `@zapier/zapier-sdk-cli` |
```js
db.platform_reference.find({{ kind: "cli_function" }}).sort({{ key: 1 }})
db.platform_reference.find({{ kind: "core_function" }}).sort({{ key: 1 }})
db.platform_reference.find({{ kind: "sdk_function" }}).sort({{ key: 1 }})
db.platform_reference.findOne({{ kind: "sdk_function", key: "run-action" }})
db.platform_reference.findOne({{ kind: "core_function", key: "z.request" }})
```
Human index: [FUNCTIONS-REFERENCE.md](FUNCTIONS-REFERENCE.md).
## Build-an-integration call graph
```
zapier-platform login
→ init --template oauth2
→ scaffold trigger|create|search
→ implement (z, bundle) with z.request / z.errors.*
→ invoke auth start|test / test / validate
→ register → push → promote → migrate
```
## Consume-an-app call graph (SDK)
```
zapier-sdk login
→ list-apps → list-actions → list-action-input-fields
→ find-first-connection | create-connection
→ run-action
(events) list-triggers → create-trigger-inbox → lease/ack
(raw HTTP) curl --connection <id>
```
Same methods exist on `createZapierSdk()` as camelCase (`runAction`, `listApps`, …).
"""
rows.append(
{
"_id": "guide:zapier-functions",
"kind": "guide",
"key": "zapier-functions",
"title": "Zapier functions — CLI, core, SDK catalog",
"summary": "Complete function reference. Start from build-new-connector, then query cli_function / core_function / sdk_function.",
"body": guide_body,
"usage": 'db.platform_reference.findOne({kind:"guide", key:"zapier-functions"})',
"signature": "",
"aliases": ["functions playbook"],
"flags": [],
"args": [],
"examples": [],
"source_url": "",
"source_repo": "",
"source_path": "scripts/ingest-function-reference.py",
"section": "guide",
"tags": ["guide", "functions", "cli", "sdk", "core"],
"related": [],
"meta": {
"cli": sum(1 for r in rows if r["kind"] == "cli_function"),
"core": sum(1 for r in rows if r["kind"] == "core_function"),
"sdk": sum(1 for r in rows if r["kind"] == "sdk_function"),
},
"ingested_at": NOW,
}
)
# markdown index
md = [
"# Zapier function reference (CLI, core, SDK)",
"",
"**Grok start:** `db.platform_reference.findOne({ kind: \"guide\", key: \"build-new-connector\" })` ",
"MCP tools: [MCP-REFERENCE.md](MCP-REFERENCE.md). This file is everything else.",
"",
"Each function has a Mongo document with the same sections as MCP: high-level, internals, typed inputs, outputs, related, twins, examples.",
"",
"## Core (`kind: \"core_function\"`)",
"",
"| Function | Signature | Category |",
"|----------|-----------|----------|",
]
for r in sorted((x for x in rows if x["kind"] == "core_function"), key=lambda x: x["key"]):
md.append(f"| `{r['key']}` | `{r['signature']}` | {r['meta'].get('category','')} |")
md += ["", "## Platform CLI (`kind: \"cli_function\"`)", "", "| Command | Usage |", "|---------|-------|"]
for r in sorted((x for x in rows if x["kind"] == "cli_function"), key=lambda x: x["key"]):
md.append(f"| `{r['key']}` | `{r['usage']}` |")
md += ["", "## SDK (`kind: \"sdk_function\"`)", "", "| CLI | TypeScript | Category | MCP twin |", "|-----|------------|----------|----------|"]
for r in sorted((x for x in rows if x["kind"] == "sdk_function"), key=lambda x: (x["meta"].get("category",""), x["key"])):
md.append(
f"| `{r['key']}` | `zapier.{r['meta'].get('typescript')}` | {r['meta'].get('category')} | {r['meta'].get('mcp_twin') or ''} |"
)
md += [
"",
"## Query",
"",
"```js",
'db.platform_reference.findOne({ kind: "guide", key: "zapier-functions" })',
'db.platform_reference.find({ kind: "core_function", key: "z.request" })',
'db.platform_reference.find({ kind: "sdk_function", "meta.category": "Actions" })',
"```",
"",
]
md_path = ROOT / "FUNCTIONS-REFERENCE.md"
md_path.write_text("\n".join(md) + "\n")
print(f"wrote {md_path}")
out = ROOT / "raw" / "function-reference.jsonl"
out.parent.mkdir(parents=True, exist_ok=True)
with out.open("w") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
counts: dict[str, int] = {}
for r in rows:
counts[r["kind"]] = counts.get(r["kind"], 0) + 1
print("wrote", len(rows), "", out, counts)
uri = os.environ.get("ZAPIER_MONGO_URI") or os.environ.get("MDB_MCP_CONNECTION_STRING")
if not uri:
print("ZAPIER_MONGO_URI unset; skipped mongo")
return 0
from pymongo import MongoClient, ReplaceOne
col = MongoClient(uri).get_database("zapier")["platform_reference"]
BATCH = 200
ops = [ReplaceOne({"_id": r["_id"]}, r, upsert=True) for r in rows]
for i in range(0, len(ops), BATCH):
col.bulk_write(ops[i : i + BATCH], ordered=False)
col.create_index([("kind", 1), ("key", 1)])
print(
"mongo",
{k: col.count_documents({"kind": k}) for k in ("cli_function", "core_function", "sdk_function", "mcp_function", "guide")},
)
return 0
if __name__ == "__main__":
raise SystemExit(main())