diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 1f4a4a6..dce1788 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -24,7 +24,7 @@ "interface": { "displayName": "Zapier SDK", "shortDescription": "Programmatic access to Zapier's 9,000+ apps via the TypeScript SDK.", - "longDescription": "Let your agent connect to anything. Zapier handles the keys. Ships the zapier-sdk skill, the zapier-sdk-explorer subagent, and a verified examples corpus so your code uses real action keys the first time.", + "longDescription": "Let your agent connect to anything. Zapier handles the keys. Ships the zapier-sdk skill, the zapier-workflows skill for deploying durable automations, the zapier-sdk-explorer subagent, and a verified examples corpus so your code uses real action keys the first time.", "developerName": "Zapier", "category": "Developer Tools", "capabilities": [], @@ -34,7 +34,8 @@ "defaultPrompt": [ "Connect Notion via Zapier and search my pages", "Send an email through Gmail using the Zapier SDK", - "Look up a Google Sheets row with the Zapier SDK" + "Look up a Google Sheets row with the Zapier SDK", + "Build a Zapier workflow that posts new GitHub issues to Slack" ], "brandColor": "#FF4F00", "logo": "./assets/logo.svg", diff --git a/AGENTS.md b/AGENTS.md index eebcead..4ba9203 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,7 @@ Each file below is authoritative for a slice of the repo. Read them; don't re-de - **[`.github/scripts/README.md`](./.github/scripts/README.md)**: CI checks (structural + live-catalog audit). - **[`skills/zapier-sdk/SKILL.md`](./skills/zapier-sdk/SKILL.md)**: installable skill manifest (cross-platform). - **[`skills/zapier-sdk/references/cli-commands.md`](./skills/zapier-sdk/references/cli-commands.md)**: full inventory of `zapier-sdk` CLI commands, generated from `zapier-sdk --help`. +- **[`skills/zapier-workflows/SKILL.md`](./skills/zapier-workflows/SKILL.md)**: installable skill manifest for building, deploying, listing, and modifying durable workflows. - **[`agents/README.md`](./agents/README.md)**: optional Claude Code subagents that pair with the skill. ## When to use this SDK vs. alternatives @@ -51,7 +52,8 @@ No build, lint, or test step lives in this repo. `package.json` has no `scripts` ├── AGENTS.md ← you are here ├── CONTRIBUTING.md ├── skills/ -│ └── zapier-sdk/ ← SKILL.md manifest for agent runtimes +│ ├── zapier-sdk/ ← SKILL.md manifest for agent runtimes +│ └── zapier-workflows/ ← SKILL.md manifest for durable workflows └── examples/ ├── by-app/ ← plain single-action SDK scripts ├── by-domain/ ← filesystem symlinks into by-pattern, grouped by audience diff --git a/README.md b/README.md index 39a3c46..2073856 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ The [`examples/`](./examples) directory is the heart of this repo — indexed th Drop every best practice for writing Zapier SDK code straight into your agent's context. Your agent picks up: - **The `zapier-sdk` skill**: how to authenticate, discover apps and actions at runtime, run actions without inventing method names, and reach for the right escape hatch when there's no first-class action. +- **The `zapier-workflows` skill**: build, test, deploy, list, and modify durable workflows — code that runs on Zapier's infrastructure on a trigger, instead of in your own process. - **The `zapier-sdk-explorer` subagent**: a read-only investigator that resolves exact app / action / field IDs against the live Zapier catalog before your agent writes a line of code. - **The verified examples corpus**: every action key checked against `listActions` on the way in, so your agent has canonical, copy-paste-ready patterns to grep. @@ -107,7 +108,7 @@ copilot plugin install sdk@zapier ### Cursor and other assistants -The [skill](./skills/zapier-sdk/) conforms to [agentskills.io](https://agentskills.io) so any conformant runtime can load it directly. For tools without native agentskills.io support, clone the repo and reference `skills/zapier-sdk/SKILL.md` from your assistant's rules or instructions file (`.cursorrules`, `AGENTS.md`, `.github/copilot-instructions.md`, etc.): +The [`zapier-sdk`](./skills/zapier-sdk/) and [`zapier-workflows`](./skills/zapier-workflows/) skills conform to [agentskills.io](https://agentskills.io) so any conformant runtime can load them directly. For tools without native agentskills.io support, clone the repo and reference the relevant `SKILL.md` from your assistant's rules or instructions file (`.cursorrules`, `AGENTS.md`, `.github/copilot-instructions.md`, etc.): ``` git clone https://github.com/zapier/sdk.git @@ -120,6 +121,7 @@ git clone https://github.com/zapier/sdk.git - **[docs.zapier.com/sdk/reference](https://docs.zapier.com/sdk/reference)** — full method reference. - **[docs.zapier.com/sdk/cli-reference](https://docs.zapier.com/sdk/cli-reference)** — CLI reference for `zapier-sdk`. - **[`skills/zapier-sdk/references/cli-commands.md`](./skills/zapier-sdk/references/cli-commands.md)** — full CLI command inventory in-repo, generated from `zapier-sdk --help`. +- **[`skills/zapier-workflows/`](./skills/zapier-workflows/)** — the durable workflows skill: build, test, deploy, list, and modify code that runs on Zapier's infrastructure. - **[`@zapier/zapier-sdk`](https://www.npmjs.com/package/@zapier/zapier-sdk)** — runtime SDK on npm. - **[`@zapier/zapier-sdk-cli`](https://www.npmjs.com/package/@zapier/zapier-sdk-cli)** — CLI companion on npm. - **[Zapier MCP](https://github.com/zapier/zapier-mcp)** — the MCP-server alternative for tool-calling from Cursor, Claude Desktop, or Codex. diff --git a/llms.txt b/llms.txt index 835470d..4063414 100644 --- a/llms.txt +++ b/llms.txt @@ -16,6 +16,7 @@ For the broader Zapier developer docs index (MCP, embedding, integration builder - [`skills/zapier-sdk/SKILL.md`](https://github.com/zapier/sdk/blob/main/skills/zapier-sdk/SKILL.md): the installable [agentskills.io](https://agentskills.io) manifest covering authentication, discovery, running actions, and escape hatches. - [`skills/zapier-sdk/references/cli-commands.md`](https://github.com/zapier/sdk/blob/main/skills/zapier-sdk/references/cli-commands.md): full inventory of `zapier-sdk` CLI commands, generated from `zapier-sdk --help`. +- [`skills/zapier-workflows/SKILL.md`](https://github.com/zapier/sdk/blob/main/skills/zapier-workflows/SKILL.md): the installable manifest for building, testing, deploying, listing, and modifying durable Zapier workflows. - [`agents/README.md`](https://github.com/zapier/sdk/blob/main/agents/README.md): optional Claude Code subagents that pair with the skill. - [`agents/zapier-sdk-explorer.md`](https://github.com/zapier/sdk/blob/main/agents/zapier-sdk-explorer.md): read-only investigator that resolves exact app, action, and field IDs against the live Zapier catalog. diff --git a/skills.sh.json b/skills.sh.json index 29f44d2..7ececfe 100644 --- a/skills.sh.json +++ b/skills.sh.json @@ -5,6 +5,11 @@ "title": "Zapier SDK", "description": "Write Zapier SDK code that works the first time — authenticate, discover apps and actions, and run them without inventing method names.", "skills": ["zapier-sdk"] + }, + { + "title": "Zapier Workflows", + "description": "Build, test, deploy, list, and modify durable Zapier workflows — code that runs on Zapier's infrastructure on a trigger.", + "skills": ["zapier-workflows"] } ] } diff --git a/skills/zapier-sdk/SKILL.md b/skills/zapier-sdk/SKILL.md index abb211b..1d15ed6 100644 --- a/skills/zapier-sdk/SKILL.md +++ b/skills/zapier-sdk/SKILL.md @@ -29,7 +29,7 @@ Pick the SDK when the deliverable is code. If the user wants ad-hoc tool calls i **Two SDK shapes to distinguish:** - **Plain SDK script** (this file): the user's own code calls the SDK. Runs wherever their code runs (Node script, Next.js route, Lambda, CLI). -- **Durable workflow** (`@zapier/zapier-durable`): the user *deploys* a workflow that Zapier's infrastructure runs on a trigger (webhook, poll, schedule). Retries stay idempotent via `ctx.step`. Read [`references/workflows.md`](references/workflows.md) when the user wants deployment on Zapier's infrastructure instead of running the code themselves. +- **Durable workflow** (`@zapier/zapier-durable`): the user *deploys* a workflow that Zapier's infrastructure runs on a trigger (webhook, poll, schedule). Retries stay idempotent via `ctx.step`. Use the `zapier-workflows` skill when the user wants deployment on Zapier's infrastructure instead of running the code themselves. ## Prerequisites @@ -217,7 +217,7 @@ Same auth and audit trail as `runAction`. Use this when the app's Zapier action ## Examples -For fill-in-the-blank skeletons (plain script, Zapier Table, durable workflow), see [`references/examples.md`](references/examples.md). These are shape-only; the real, action-key-verified corpus is at https://github.com/zapier/sdk/tree/main/examples. Grep the corpus by app or pattern when you need a working reference. +For fill-in-the-blank skeletons (plain script, Zapier Table), see [`references/examples.md`](references/examples.md). These are shape-only; the real, action-key-verified corpus is at https://github.com/zapier/sdk/tree/main/examples. Grep the corpus by app or pattern when you need a working reference. For durable workflows, use the `zapier-workflows` skill instead. ## Full documentation diff --git a/skills/zapier-sdk/references/examples.md b/skills/zapier-sdk/references/examples.md index 49bbba7..4c1843f 100644 --- a/skills/zapier-sdk/references/examples.md +++ b/skills/zapier-sdk/references/examples.md @@ -84,61 +84,9 @@ async function main() { main().catch(console.error); ``` -## 3. Durable workflow (notify-on-event shape) +## 3. Durable workflow (deploy to Zapier's infrastructure) -Deployable to Zapier's infrastructure. Runs on a trigger (webhook, poll, schedule). Every side-effect goes through `ctx.step` with the trigger's primary id in the step name so retries are idempotent. - -Matches `examples/by-pattern/notify-on-event//workflow.ts`. - -```typescript -// workflow.ts -import { defineDurable } from "@zapier/zapier-durable"; -import { createZapierSdk } from "@zapier/zapier-sdk"; -import { z } from "zod"; - -const sdk = createZapierSdk(); - -// Constants get pulled out and documented in the leaf directory's README. -const DESTINATION_CONNECTION = ""; // e.g. "gmail_primary" -const DESTINATION_APP_KEY = ""; // e.g. "GoogleMailV2CLIAPI" - -const InputSchema = z.object({ - primaryId: z.string(), // whatever id the trigger emits (charge id, response id, ...) - // ...other fields the trigger provides -}); -type Input = z.infer; - -export default defineDurable( - "", - async (ctx, rawInput) => { - const input = InputSchema.parse(rawInput); - - // Step name MUST include `${input.primaryId}`. Without it, a retry - // would double-write. This is the whole reason `defineDurable` exists. - await ctx.step(`-${input.primaryId}`, async () => - sdk.runAction({ - appKey: DESTINATION_APP_KEY, - actionType: "", - actionKey: "", - connection: DESTINATION_CONNECTION, - inputs: { - // ... - }, - }), - ); - - return { done: true }; - }, -); -``` - -Deploy loop: - -```bash -npm install -npx tsc --noEmit workflow.ts -npx zapier-sdk publish-workflow-version --file workflow.ts -``` +Runs on a trigger (webhook, poll, schedule) instead of in your own process. This is a different skill, not a third skeleton here — see the `zapier-workflows` skill for the full build/test/deploy flow, the idempotency invariant, and the four workflow shapes. ## What these skeletons deliberately leave out diff --git a/skills/zapier-sdk/references/workflows.md b/skills/zapier-sdk/references/workflows.md deleted file mode 100644 index 23553fb..0000000 --- a/skills/zapier-sdk/references/workflows.md +++ /dev/null @@ -1,205 +0,0 @@ -# Durable workflows - -Read this when the user wants to **deploy** something that runs on Zapier's infrastructure (webhook, poll, or schedule) rather than call the SDK from their own process. - -## The invariant - -Every side-effecting call goes through `ctx.step("-${primaryId}", async () => ...)`. The suffix is the primary id from the payload (charge id, response id, `triggered_at` timestamp). That's what makes a retry safe: replaying the same trigger reuses the same step names and skips work that already succeeded. A workflow without the `${primaryId}` suffix can double-write on retry. - -## Plain script vs. durable workflow - -| Situation | Use | -|---|---| -| One-off call from a script, Next.js route, CLI, or Lambda you already run | Plain SDK script (`examples/by-app/`) | -| Runs on Zapier's infrastructure on a trigger (webhook, poll, schedule) | Durable workflow (`examples/by-pattern/`) | -| Side effects that must be retry-safe | Durable workflow (`ctx.step` is why the shape exists) | -| Multi-step orchestration across several apps | Durable workflow, one `ctx.step` per side effect | - -## The four shapes - -Every deployable workflow fits one. Pick the closest match and put the file under `examples/by-pattern///`. Read the shape's `README.md` for full belongs-here rules. - -| Shape | Description | -|---|---| -| **notify-on-event** | Trigger to message. Fan-out lives here too. | -| **data-sync** | Read to write, keep two systems consistent. | -| **lead-routing** | Read to decide to write. Has a decision step before the write. | -| **scheduled-report** | Cron to read to summarize. Aggregate output on a timer. | - -## Naming - -Directory and `defineDurable` slug must match. Form: `--to--`, kebab-case, specific on both ends (the repo, the channel, the table, not just the app). Example: `github-marketplace-to-slack-team-agent-discovery-feed`. - -## Workflow spine - -Copy an existing entry as a template. Every workflow follows this shape: - -```typescript -import { defineDurable } from "@zapier/zapier-durable"; -import { createZapierSdk } from "@zapier/zapier-sdk"; -import { z } from "zod"; - -const sdk = createZapierSdk(); - -// Deploy-time constants. Every one gets a row in the README's parameters table. -const GMAIL_CONNECTION = "gmail_primary"; -const RECIPIENTS = ["team@example.com"]; - -const InputSchema = z.object({ - responseId: z.string(), - respondentEmail: z.string().email(), -}); -type Input = z.infer; - -export default defineDurable( - "typeform-submission-to-gmail", // must match leaf directory name - async (ctx, rawInput) => { - const { responseId, respondentEmail } = InputSchema.parse(rawInput); - - await ctx.step(`send-confirmation-${responseId}`, async () => - sdk.runAction({ - appKey: "GoogleMailV2CLIAPI", - actionType: "write", - actionKey: "message", - connection: GMAIL_CONNECTION, - inputs: { to: [respondentEmail], subject: "Thanks", body: "..." }, - }), - ); - - return { sent: true }; - }, -); -``` - -- `defineDurable("", handler)` exported as default. Slug matches leaf directory name. -- `const sdk = createZapierSdk()` at module scope. Runtime injects credentials. -- Top-of-file constants for every value a deployer edits. Each one gets a README parameters-table row. -- Zod `InputSchema` parsed as the handler's first line. This is the contract for what the trigger delivers. -- One `ctx.step` per side effect, keyed by `${primaryId}`. - -## Idempotency keys by trigger type - -| Trigger | Idempotency key | Example | -|---|---|---| -| Webhook / poll (record-shaped) | Record's primary id | `${responseId}`, `${chargeId}`, `${leadgen_id}` | -| Schedule (Zapier's Schedule app) | Invocation timestamp | `const runId = triggered_at; ctx.step(\`aggregate-${runId}\`, ...)` | -| Per-item loop inside a step | Item's primary id, never the array index | `for (const c of contacts) { await ctx.step(\`upsert-${c.id}\`, ...) }` | - -Using an array index as the suffix is a bug. If the upstream reorders on a retry, the wrong step name matches. - -## Connections at deploy time - -Workflows never store user tokens. Reference each connection by a string alias declared as a top-of-file constant: - -```typescript -const HUBSPOT_CONNECTION = "hubspot_primary"; -const DISCORD_CONNECTION = "discord_primary"; -``` - -The alias is resolved at deploy time via the `--connections` flag (see [Deploy](#deploy)). A **runtime** SDK script would pass `connection.id` (a UUID) directly. Don't mix the two shapes. - -## Dependency pinning (24-hour rule) - -Zapier's workflow sandbox installs deps with `pnpm install --config.minimumReleaseAge=1440`. **It rejects any direct dependency published less than 24 hours ago.** Three packages always pin: - -| Package | Passed to | -|---|---| -| `@zapier/zapier-sdk` | `--dependencies` | -| `@zapier/zapier-durable` | `--zapier-durable-version` (its own flag) | -| `zod` | `--dependencies` | - -Pin exact versions in `package.json` (no `^`, no `~`). To find the newest aged-eligible version: - -```bash -npm view @zapier/zapier-sdk time --json -# Take the newest X.Y.Z stable version whose timestamp is >=24h old. No pre-releases. -``` - -If no stable version >=24h old exists, wait. Do not fall back to a pre-release. - -## Deploy - -The publish flow uses the CLI's `--experimental` surface. Two commands, in order, and a verify step. Don't skip the verify. - -```bash -# 1. Create the workflow container (skip if you already have a workflow_id). -zapier-sdk --experimental create-workflow "" \ - --description "" --private --json - -# 2. Publish a version with the trigger claim. -SOURCE_FILES=$(jq -n --rawfile w workflow.ts '{"workflow.ts": $w}') -zapier-sdk --experimental publish-workflow-version "$SOURCE_FILES" \ - --dependencies '{"@zapier/zapier-sdk":"","zod":""}' \ - --zapier-durable-version '' \ - --connections '' \ - --trigger '{"selected_api":"@","action":"","authentication_id":"","params":{...}}' \ - --enabled --json - -# 3. VERIFY enabled:true. Trigger claims fail silently. -zapier-sdk --experimental get-workflow --json -``` - -Three things silently break a trigger claim: - -- `selected_api` not versioned (`GitHubCLIAPI` fails; `GitHubCLIAPI@2.5.0` works). -- `params` value types don't match the trigger schema (arrays as arrays, strings as strings). Look them up with `list-action-input-fields`. -- Missing `--enabled`. Workflow deploys but the trigger isn't claimed. - -If `get-workflow` returns `enabled: false`, the trigger claim failed. Diagnose against the three causes above. Do NOT report success. - -## Catch Hook triggers (`WebHookCLIAPI`) - -For `hook_v2` and `hook_raw` triggers, `params` must include two static hex codes generated once at scaffold time: - -```typescript -_zap_static_hook_code: "<12-char hex>", // e.g. openssl rand -hex 6 -_zap_static_hook_seed: "<12-char hex>", -``` - -Bake both into the README so re-publishes reuse them. **Rotating either value mints a new public URL and breaks external callers.** - -The public URL external systems POST to is `https://hooks.zapier.com/hooks/catch/<_zap_static_hook_code>/`. That's what goes in the workflow's README as the endpoint. The `code-substrate-workflows.zapier.com/api/v0/workflows/trigger/...` URL requires a Zapier JWT and is only for editor testing. Never surface it externally. - -## Test synthetically before deploying - -`run-durable` executes the workflow against real connections without publishing. Real side effects. Before running, tell the user which channels/emails/records the workflow will actually touch and get explicit confirmation. - -```bash -SOURCE_FILES=$(jq -n --rawfile w workflow.ts '{"workflow.ts": $w}') -zapier-sdk --experimental run-durable "$SOURCE_FILES" \ - --dependencies '{"@zapier/zapier-sdk":"","zod":""}' \ - --zapier-durable-version '' \ - --connections '' \ - --input '' \ - --private -# Capture the run_id, then poll: -zapier-sdk --experimental get-durable-run --json -# Terminal statuses are "finished" or "failed". The initial response usually shows "initialized"; that is not success. -``` - -## Escape hatches - -- **No first-class action?** Use `sdk.fetch(url, { connection, method })` inside a `ctx.step`. Same Zapier auth and audit trail. Examples: bulk HubSpot pull, Stripe charges over a window, Clearbit enrichment (all under `examples/by-pattern/`). -- **Dynamic inputs?** Mark them `// dynamic` in the workflow and tell deployers to run `zapier-sdk list-action-input-fields ` against their live connection before setting values. - -## Gotchas - -- **Never paraphrase action keys.** If `list-actions` returns `channel_message`, write `channel_message`, not `send_channel_message`. Same for app keys (`GoogleMailV2CLIAPI`, not `gmail`) and trigger keys. -- **Step name uniqueness.** Each `ctx.step` name must be unique within one invocation and stable across retries. Two `ctx.step("send-email", ...)` calls in the same run collide. -- **`runAction` always returns `{ data: T[] }`.** Even search-style actions that logically return one record wrap it in an array. Cast at the call site. -- **`is_hidden: true` actions are not stable.** They may work at runtime but aren't guaranteed to keep working. Filter them out before picking an action key. -- **Retries don't roll back committed steps.** If step 3 fails, retry replays steps 1 and 2 by name (which short-circuit) and re-attempts step 3. If step 1 wrote externally, that side effect stays. Design writes to be safe to retry. -- **Deploy-time constants vs. runtime inputs.** Values a deployer sets once (connection aliases, database ids, channel ids) → top-of-file constants. Values that vary per invocation (record ids, timestamps, form fields) → `InputSchema`. -- **Don't overwrite existing workflow directories.** Refuse to scaffold into a directory that already exists. - -## Where to look next - -- Minimal workflow: [`examples/by-pattern/notify-on-event/typeform-submission-to-gmail/workflow.ts`](../../../examples/by-pattern/notify-on-event/typeform-submission-to-gmail/workflow.ts) -- Multi-step with branching + `sdk.fetch`: [`examples/by-pattern/lead-routing/inbound-lead-orchestration/workflow.ts`](../../../examples/by-pattern/lead-routing/inbound-lead-orchestration/workflow.ts) -- Scheduled trigger with a computed window: [`examples/by-pattern/scheduled-report/daily-revenue-summary/workflow.ts`](../../../examples/by-pattern/scheduled-report/daily-revenue-summary/workflow.ts) -- Corpus conventions: [`examples/by-pattern/README.md`](../../../examples/by-pattern/README.md) -- Shape belongs-here rules: `examples/by-pattern/*/README.md` - -## Investigation before authoring (Claude Code only) - -Before writing a new `workflow.ts`, offload the "what are the exact keys and IDs" question to the [`zapier-sdk-explorer`](../../../agents/zapier-sdk-explorer.md) subagent. It runs read-only against the live catalog, returns a structured build plan with citations for every identifier, and keeps the raw JSON dumps out of your main context. Only available in Claude Code; ignore this section elsewhere. diff --git a/skills/zapier-workflows/SKILL.md b/skills/zapier-workflows/SKILL.md new file mode 100644 index 0000000..1c48327 --- /dev/null +++ b/skills/zapier-workflows/SKILL.md @@ -0,0 +1,77 @@ +--- +name: zapier-workflows +description: | + Build, test, deploy, list, inspect, and modify durable Zapier workflows (Code Workflows) — code that Zapier's + infrastructure runs on a trigger (webhook, poll, schedule), not in your own process. Use when the user wants to + build a Zapier workflow, create an automation, write a durable workflow, "build me a Zap that...", deploy code + to Zapier, list their workflows or Zaps, check workflow run history, or fix/modify/republish an existing workflow. +license: MIT +metadata: + author: zapier +--- + +# Zapier Workflows + +Durable workflows are code that runs **on Zapier's infrastructure**, triggered by a webhook, a poll, or a schedule — not in the user's own process. If the user just wants to make one authenticated API call from their own script, Next.js route, or Lambda, that's the plain SDK — use the `zapier-sdk` skill instead. + +## Prerequisites + +```bash +zapier-sdk --version +zapier-sdk get-profile --json +zapier-sdk --experimental --help +``` + +If any of these fail — CLI missing, not logged in, or no Code Workflows commands in the `--experimental` help output — read [`references/setup.md`](references/setup.md) for the full install/auth/access-check flow before continuing. + +Zapier Workflows is currently a closed beta (EA). `list-workflows` returning an allowlist/permission/JWT error means the account isn't in the beta yet — `references/setup.md` has the exact message to give the user. + +## The core invariant + +Every side-effecting call goes through `ctx.step("-${primaryId}", async () => ...)`. The suffix is the primary id from the trigger payload (charge id, response id, or a timestamp for scheduled runs). That's what makes a retry safe: replaying the same trigger reuses the same step names and skips work that already succeeded. Omit the suffix and a retry can double-write. + +## Plain script vs. durable workflow + +| Situation | Use | +|---|---| +| One-off call from a script, Next.js route, CLI, or Lambda the user already runs | Plain SDK script — `zapier-sdk` skill | +| Runs on Zapier's infrastructure on a trigger (webhook, poll, schedule) | Durable workflow (this skill) | +| Side effects that must be retry-safe | Durable workflow — `ctx.step` is why the shape exists | +| Multi-step orchestration across several apps | Durable workflow, one `ctx.step` per side effect | + +## The four shapes + +Every deployable workflow fits one. Pick the closest match — `references/build-and-deploy.md` has the belongs-here rules and naming convention for each. + +| Shape | Description | +|---|---| +| **notify-on-event** | Trigger to message. Fan-out lives here too. | +| **data-sync** | Read to write, keep two systems consistent. | +| **lead-routing** | Read to decide to write. Has a decision step before the write. | +| **scheduled-report** | Cron to read to summarize. Aggregate output on a timer. | + +## What to do next + +- Building, testing, or deploying a new workflow → [`references/build-and-deploy.md`](references/build-and-deploy.md) +- Listing workflows or checking run history → [`references/list-and-history.md`](references/list-and-history.md) +- Fixing, editing, or republishing an existing workflow → [`references/modify.md`](references/modify.md) +- SDK CLI missing, not authenticated, or Workflows EA access is unclear → [`references/setup.md`](references/setup.md) + +## Cross-cutting gotchas + +These apply no matter which task above you're doing. + +- **The SDK CLI ships continuously; this skill does not pin a compatible version.** If a command or flag documented in the references is missing from `zapier-sdk --experimental --help`, trust the live `--help` output over these instructions and adapt. +- **Trigger claims fail silently.** Publishing a trigger-backed workflow version can return success while the workflow stays disabled. Always re-read `get-workflow` after publish and confirm `enabled: true` before reporting success — see `references/build-and-deploy.md` for the specific causes. +- **Connection bindings are a nested object, never a bare string.** `{"slack_work": {"connectionId": "123"}}`, not `{"slack_work": "123"}`. Same shape for `run-durable`, `publish-workflow-version`, and any republish. +- **`runAction` always returns `{ data: T[] }`**, even for search-style actions that logically return one record. Cast/destructure at the call site. +- **Never paraphrase action, app, or trigger keys.** If `list-actions` returns `channel_message`, write `channel_message`, not `send_channel_message`. +- **`is_hidden: true` actions are not on the stable surface.** They may run, but Zapier makes no promise they keep working — filter them out before picking an action key. +- **Do not use `zapier-sdk-code-substrate`.** That's the legacy private CLI path. The public `zapier-sdk --experimental` surface is the only supported route for everything in this skill. + +## Full documentation + +- Quickstart: https://docs.zapier.com/sdk/quickstart.md +- CLI Reference: https://docs.zapier.com/sdk/cli-reference.md +- All docs index: https://docs.zapier.com/llms.txt +- Example workflows: https://github.com/zapier/sdk/tree/main/examples/by-pattern diff --git a/skills/zapier-workflows/references/build-and-deploy.md b/skills/zapier-workflows/references/build-and-deploy.md new file mode 100644 index 0000000..09e0714 --- /dev/null +++ b/skills/zapier-workflows/references/build-and-deploy.md @@ -0,0 +1,547 @@ +# Build, Test, and Deploy a Workflow + +Read this when the user wants to build a new durable workflow, or test/deploy one that's already written. Use the public `zapier-sdk --experimental` surface — do not use `zapier-sdk-code-substrate`. + +## Naming + +Directory and `defineDurable` slug must match. Form: `--to--`, kebab-case, specific on both ends (the repo, the channel, the table — not just the app). Example: `github-marketplace-to-slack-team-agent-discovery-feed`. + +Refuse to scaffold into a directory that already exists. + +## Dependency pinning (24-hour rule) + +Zapier's workflow sandbox installs dependencies with `pnpm install --config.minimumReleaseAge=1440` — it **rejects any direct dependency published less than 24 hours ago**. Three packages always need an aged-eligible pin: + +| Package | Passed to | +|---|---| +| `@zapier/zapier-sdk` | `--dependencies` | +| `@zapier/zapier-durable` | `--zapier-durable-version` (its own flag) | +| `zod` | `--dependencies` (imported by every generated `workflow.ts`) | + +`@zapier/zapier-sdk` publishes several times a day, so its npm-latest is regularly younger than 24h. Find the newest aged-eligible version of each with this script — Node only, no `jq` needed: + +```bash +SELECT_AGED_VERSION=' +const cp = require("child_process"); +const pkg = process.argv[1]; +const times = JSON.parse(cp.execSync("npm view " + pkg + " time --json", { encoding: "utf8" })); +const cutoff = Date.now() - 24 * 60 * 60 * 1000; +const eligible = Object.keys(times) + .filter((v) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(v)) + .map((v) => ({ v, t: new Date(times[v]).getTime() })) + .filter((x) => x.t <= cutoff) + .sort((a, b) => a.t - b.t); +if (!eligible.length) { + console.error("No " + pkg + " stable version published >=24h ago"); + process.exit(1); +} +console.log(eligible[eligible.length - 1].v); +' +SDK_VERSION="$(node -e "$SELECT_AGED_VERSION" @zapier/zapier-sdk)" +DURABLE_VERSION="$(node -e "$SELECT_AGED_VERSION" @zapier/zapier-durable)" +ZOD_VERSION="$(node -e "$SELECT_AGED_VERSION" zod)" +echo "SDK_VERSION=$SDK_VERSION DURABLE_VERSION=$DURABLE_VERSION ZOD_VERSION=$ZOD_VERSION" +``` + +Use exact versions everywhere — never `latest` — in `package.json` and every CLI flag below. **Every package the generated `workflow.ts` imports must appear in `--dependencies`**, aged-pinned: the sandbox installs from `--dependencies`, not your local `package.json`, so a missing import (such as `zod`) fails the run with `Cannot find package`. If no stable version ≥24h old exists, wait — don't fall back to a pre-release. + +The user must also have app connections configured at https://zapier.com/app/assets/connections for any app actions the workflow will run. + +## Phase 1: Understand the intent + +Read the user's natural language request and extract: + +1. Steps and ordering. +2. Apps involved. +3. Data passed between steps. +4. Manual input fields or trigger input fields. +5. Conditional logic. +6. Waits, callbacks, or human approval gates. + +Summarize the proposed workflow back to the user before discovery. Ask focused clarifying questions for missing details like target channels, folders, recipients, or whether to stop when a search returns no results. + +Do not generate code until the user agrees on the workflow shape. + +## Phase 2: Discover apps, connections, actions, triggers, and fields + +Use the standard Zapier SDK CLI for app/action discovery: + +```bash +zapier-sdk list-apps --search "" --json +zapier-sdk list-connections --owner me --json +zapier-sdk list-actions --action-type --json +zapier-sdk list-action-input-fields --connection --json +zapier-sdk list-action-input-field-choices --connection --json +``` + +For workflows that should subscribe to a Zapier app trigger, use the experimental trigger discovery commands: + +```bash +zapier-sdk --experimental list-triggers --json +zapier-sdk --experimental list-trigger-input-fields --connection --json +zapier-sdk --experimental list-trigger-input-field-choices --connection --json +``` + +If several apps, connections, actions, triggers, or field choices are plausible, show the candidates and ask the user to choose. + +Assign a short snake_case connection alias for each chosen connection, such as `slack_work` or `gmail_primary`. Track alias to connection ID. The alias goes in workflow code; the connection ID is passed to test/deploy commands through the `--connections` JSON. + +For output mapping between steps, run a safe action test only after user confirmation. Use the current SDK command shape: + +```bash +zapier-sdk run-action \ + --connection \ + --inputs '<{"key":"value"}>' \ + --json +``` + +For trigger-backed workflows, capture the trigger configuration for publish: + +```json +{ + "selected_api": "GoogleSheetsAPI@2.3.0", + "action": "new_row", + "authentication_id": "connection-id-or-null", + "params": {} +} +``` + +Use the version-pinned app/API identifier for `selected_api`, the trigger action key for `action`, the trigger source connection ID for `authentication_id` when the trigger requires auth, and trigger input values for `params`. Omit optional fields only when the trigger does not need them. + +For `selected_api`, use the **version-pinned implementation identifier** — the `implementation_id` returned by SDK discovery (`list-apps`/`get-app`), such as `GoogleSheetsAPI@2.3.0`. Do not use the bare app key (`GoogleSheetsAPI`) and do not substitute a display name. A bare, unversioned `selected_api` makes the trigger claim **fail silently at publish**: the publish call returns success with no errors, but the workflow stays disabled and nothing surfaces the cause. If discovery only exposes a bare app slug and not a versioned `implementation_id`, treat that as a blocker and record it in the build plan before publishing — do not publish a trigger with an unversioned identifier. + +For `params`, match each field's `value_type` from `list-trigger-input-fields `. ARRAY fields must be JSON arrays (for example `"dow": ["1"]`); STRING fields must be plain strings (for example `"hod": "9:00 AM"`). Passing a scalar where an array is expected (or vice versa) fails the trigger claim the same silent way. + +Capture app implementation/version information from SDK discovery output when available, such as `list-apps`, `get-app`, `list-actions`, or trigger/action result metadata. Do not invent app versions. If no implementation/version binding is exposed, omit `--app_versions` rather than guessing. + +## Phase 3: Confirm the build plan + +Before writing code, present: + +```text +Workflow: +Input: { field1, field2 } +Connections: + alias = connectionId (connection title) +Trigger: + selected_api.action with params, or none for webhook/manual-only workflow +Steps: + 1. - .. + 2. - .. +Return: +``` + +Ask the user to confirm before generating files. + +## Phase 4: Generate the workflow project + +Create a workflow directory: + +```text +/ + / + package.json + workflow.ts +``` + +`package.json` should include exact dependencies: + +```json +{ + "type": "module", + "dependencies": { + "@zapier/zapier-sdk": "", + "@zapier/zapier-durable": "", + "zod": "" + }, + "devDependencies": { + "typescript": "latest" + } +} +``` + +If you add a build script, use `--skipLibCheck` for now to avoid type-check failures from SDK/durable transitive type declarations: + +```json +{ + "scripts": { + "build": "tsc --target es2022 --module nodenext --moduleResolution nodenext --skipLibCheck --outDir dist workflow.ts" + } +} +``` + +`workflow.ts` should: + +- Import `defineDurable` from `@zapier/zapier-durable`. +- Import `createZapierSdk` from `@zapier/zapier-sdk`. +- Create the SDK client once at module level: `const sdk = createZapierSdk()` above `defineDurable`. +- Use Zod for input validation when the workflow has input. +- Keep external side effects (app actions, fetches) inside `ctx.step` calls. +- Make each app action exactly **one** `ctx.step` whose body is a single `return sdk.runAction({...})` call — one `runAction` per step. +- Group validation, input normalization, simple guards, data shaping into steps as needed. +- Use connection aliases, not raw connection IDs, inside workflow code. +- Reference a prior step's output with `stepVar.data[0].field` for the first result, or `stepVar.data` for the whole array. +- Normalize manual input before Zod validation. In the current `run-durable` path, input may arrive as a JSON string rather than an already-parsed object. +- Pull deploy-time constants (connection aliases, database ids, channel ids) to top-of-file `const`s; keep per-invocation values (record ids, timestamps, form fields) in the input schema instead. + +Use this helper pattern for workflows with input: + +```typescript +function normalizeInput(rawInput: unknown): unknown { + if (typeof rawInput === "string") { + return JSON.parse(rawInput); + } + return rawInput; +} +``` + +Then parse the normalized value: + +```typescript +const input = InputSchema.parse(normalizeInput(rawInput)); +``` + +### Visualizer-friendly structure + +Generate durable source that can be turned into a meaningful step graph. Avoid overly dynamic construction. + +**`defineDurable` call shape — every call must resolve `run` to a function.** Use either the bare form `defineDurable("workflow-name", async (ctx, input) => { ... })` or the object form `defineDurable({ name: "workflow-name", inputSchema, outputSchema, description, run: async (ctx, input) => { ... } })`. `ctx` is always the first parameter of `run`; `input` is the optional second parameter, so `async (ctx) => { ... }` is also valid. These shapes are invalid and make the workflow fail on its first run with `durable.run is not a function`: + +- `defineDurable(async (ctx, input) => { ... })` — a bare function with no name. The function is treated as an options object, so `run` is never set. This is the most common mistake. +- `defineDurable({ name: "workflow-name" })` — object missing `run`. +- `defineDurable({ name: "workflow-name", run: someNonFunction })` — `run` is not a function. + +`durable.run is not a function` is a code-shape defect in your `defineDurable` call, not a version mismatch. Do not change the pinned `@zapier/zapier-durable` or `@zapier/zapier-sdk` versions to fix it — correct the call so it passes a `name` and a `run` function. + +Default to this parser-friendly shape — module-level `sdk`, hoisted app-key/connection constants, and a bare `runAction` body for each app action: + +```typescript +import { defineDurable } from "@zapier/zapier-durable"; +import { createZapierSdk } from "@zapier/zapier-sdk"; +import { z } from "zod"; + +const sdk = createZapierSdk(); + +const InputSchema = z.object({ reaction: z.string() }); +type Input = z.infer; + +const TODOIST_APP_KEY = "TodoistV2CLIAPI"; +const TODOIST_CONNECTION = "todoist_primary"; + +const workflow = defineDurable( + "example-workflow", + async (ctx, input) => { + // Plain code: guard outside any step. + if (input.reaction !== "todo") { + return { skipped: true }; + } + + // Plain code: shape the action input outside the step. + const taskInput = buildTaskInput(input); + + // App action: one runAction, object literal, module-level sdk. + const createdTask = await ctx.step("create-todoist-task", async () => + sdk.runAction({ + appKey: TODOIST_APP_KEY, + actionType: "write", + actionKey: "new_task", + connection: TODOIST_CONNECTION, + inputs: taskInput, + }), + ); + + return { createdTask }; + }, +); +``` + +### App-action step shape (editor recognition) + +The editor renders a `ctx.step` as an **app-action step** (with the app icon) when its body is a single `sdk.runAction({...})` call with `appKey`, `actionType`, and `actionKey` (object literal, or a `const` that resolves to one; the `app` / `action` spellings also work). A string-literal step id (`ctx.step("create-todoist-task", ...)`) and an inline `async () => ...` callback are the recognized form; object form `ctx.step({ name, run })` works too. + +Other steps render as plain **code steps** — for example a step with no `runAction`, or with more than one, or one created in a loop with a dynamic id (`` `process-item-${index}` ``). That is expected, not a regression; loops and fan-out legitimately need dynamic ids. + +## Idempotency keys by trigger type + +| Trigger | Idempotency key | Example | +|---|---|---| +| Webhook / poll (record-shaped) | Record's primary id | `${responseId}`, `${chargeId}`, `${leadgen_id}` | +| Schedule (Zapier's Schedule app) | Invocation timestamp | `const runId = triggered_at; ctx.step(\`aggregate-${runId}\`, ...)` | +| Per-item loop inside a step | Item's primary id, never the array index | `for (const c of contacts) { await ctx.step(\`upsert-${c.id}\`, ...) }` | + +Using an array index as the suffix is a bug. If the upstream reorders on a retry, the wrong step name matches. + +## Phase 5: Test the workflow + +Build `source_files` from `workflow.ts`: + +```bash +SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')" +``` + +Build the `connections` JSON from the selected aliases. It's a nested object — each alias maps to an object holding a `connectionId` (never a bare string): + +```json +{ + "slack_work": { "connectionId": "12345678" }, + "gmail_primary": { "connectionId": "87654321" } +} +``` + +Before running, tell the user what actions may happen in connected apps and wait for confirmation if there are side effects. + +Run the durable: + +```bash +zapier-sdk --experimental run-durable "$SOURCE_FILES" \ + --dependencies '{"@zapier/zapier-sdk":"","zod":""}' \ + --zapier-durable-version '' \ + --connections '' \ + --input '' \ + --private +``` + +`run-durable` returns a run immediately, often before the workflow is complete. Capture the returned run ID, then poll until terminal status. Do not assume the first response contains final output. + +```bash +zapier-sdk --experimental get-durable-run --json +``` + +Terminal success means the run has `status: "finished"`, an expected `output`, `error: null`, and top-level `errors: []`. Terminal failure means `status: "failed"` or a non-null `error`. Continue polling while the run is initialized or started. + +Fix code and retest until the behavior matches the confirmed plan. + +## Phase 6: Deploy the workflow + +Decide whether the workflow should be private before creating it. For EA users, default to private unless the user explicitly wants an account-visible workflow. + +Create a private workflow container: + +```bash +zapier-sdk --experimental create-workflow "" \ + --description "" \ + --private \ + --json +``` + +Omit `--private` only if the user explicitly wants the workflow visible to the broader account. + +Capture the returned workflow ID. Then publish the version. The current SDK CLI expects `source_files` as a JSON object, not a path to `workflow.ts`. + +For publish, use the same nested `connections` shape as `run-durable` — each alias maps to an object holding a `connectionId`: + +```json +{ + "slack_work": { "connectionId": "123-or-uuid" }, + "gmail_primary": { "connectionId": "456-or-uuid" } +} +``` + +If app implementation/version information is known, build `app_versions`: + +```json +{ + "slack": { "implementation_name": "SlackCLIAPI", "version": "optional" } +} +``` + +Omit the entire `--app_versions` flag when no app implementation/version binding is needed. Likewise, omit `--connections` when the workflow has no connection bindings. Do not pass placeholder text like "if needed" to the CLI. + +For trigger-backed workflows, build the `trigger` JSON from Phase 2. Keep `selected_api` version-pinned to the `implementation_id` (for example `GoogleSheetsAPI@2.3.0`) and keep each `params` field shaped to its `value_type` (see Phase 2) — a bare app key or a wrong param shape makes the trigger claim fail silently at publish: + +```json +{ + "selected_api": "GoogleSheetsAPI@2.3.0", + "action": "new_row", + "authentication_id": "connection-id-or-null", + "params": {} +} +``` + +Publish a webhook/manual-only workflow by omitting `--trigger`: + +```bash +SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')" + +zapier-sdk --experimental publish-workflow-version "$SOURCE_FILES" \ + --dependencies '{"@zapier/zapier-sdk":"","zod":""}' \ + --zapier-durable-version '' \ + --connections '' \ + --app_versions '' \ + --enabled \ + --json +``` + +Publish a trigger-backed workflow by adding `--trigger`: + +```bash +zapier-sdk --experimental publish-workflow-version "$SOURCE_FILES" \ + --dependencies '{"@zapier/zapier-sdk":"","zod":""}' \ + --zapier-durable-version '' \ + --connections '' \ + --app_versions '' \ + --trigger '' \ + --enabled \ + --json +``` + +Do not use the old `--trigger-app`, `--trigger-action`, `--trigger-auth`, or `--trigger-params` flags. The current trigger publish path is the single JSON `--trigger` object. + +## Catch Hook triggers (`WebHookCLIAPI`) + +For `hook_v2` and `hook_raw` triggers, `params` must include two static hex codes generated once at scaffold time: + +```typescript +_zap_static_hook_code: "<12-char hex>", // e.g. openssl rand -hex 6 +_zap_static_hook_seed: "<12-char hex>", +``` + +Bake both into the workflow's notes so re-publishes reuse them. **Rotating either value mints a new public URL and breaks external callers.** + +The public URL external systems POST to is `https://hooks.zapier.com/hooks/catch/<_zap_static_hook_code>/`. That's what to document as the endpoint. The `code-substrate-workflows.zapier.com/api/v0/workflows/trigger/...` URL requires a Zapier JWT and is only for editor testing — never surface it externally. + +## Phase 7: Verify deployment + +Read back the workflow and versions: + +```bash +zapier-sdk --experimental get-workflow --json +zapier-sdk --experimental list-workflow-versions --json +zapier-sdk --experimental get-workflow-version --json +``` + +For trigger-backed workflows, verify the trigger actually claimed. The claim is asynchronous and can fail silently, so re-read the workflow (allow a few seconds; poll if needed) and confirm it is enabled: + +```bash +zapier-sdk --experimental get-workflow --json +``` + +If `enabled` is `false` even though you published with `--enabled`, the trigger claim failed. The most common cause is a `selected_api` that is not version-pinned to the `implementation_id`, or a `params` field with the wrong shape (see Phase 2). Re-publish with a corrected `--trigger` and re-check. Do not report the workflow as deployed until `get-workflow` shows `enabled: true`. + +If manual triggering is supported for the workflow, test it only after confirming side effects with the user: + +```bash +zapier-sdk --experimental trigger-workflow --input '' --json +``` + +If `trigger-workflow` returns a trigger ID before a workflow run ID is available, bridge from trigger to run: + +```bash +zapier-sdk --experimental get-trigger-run --json +``` + +Then inspect run history and, if needed, a deployed workflow run: + +```bash +zapier-sdk --experimental list-workflow-runs --json +zapier-sdk --experimental get-workflow-run --json +``` + +Finish by reporting: + +- Workflow name and ID. +- Where `workflow.ts` lives locally. +- Whether testing passed. +- Whether the deployed workflow is enabled. +- Whether the workflow is private or account-visible. +- Whether the workflow uses a Zapier app trigger or webhook/manual triggering. +- The Zapier editor link: `https://zapier.com/durables-editor/`. + +## Durable patterns + +### Waits + +```typescript +await ctx.wait("wait-before-followup", 3600); +``` + +Place waits at top-level workflow scope, not inside `ctx.step`. + +### Callbacks + +```typescript +const [approvalPromise, callbackUrl] = await ctx.createCallback({ + name: "wait-for-approval", + payloadSchema: z.object({ approved: z.boolean() }), + timeoutSeconds: 86400, +}); + +await ctx.step("send-approval-request", async () => + sdk.runAction({ + appKey: "ExampleCLIAPI", + actionType: "write", + actionKey: "send_message", + connection: "example_connection", + inputs: { callbackUrl }, + }), +); + +const approval = await approvalPromise; +if (!approval.approved) { + throw new Error("Approval denied"); +} +``` + +### Parallel or repeated work + +Use `Promise.all()` outside `ctx.step`; each iteration creates its own step: + +```typescript +const results = await Promise.all( + items.map((item, index) => + ctx.step(`process-item-${index}`, async () => + sdk.runAction({ + appKey: "ExampleCLIAPI", + actionType: "write", + actionKey: "do_something", + connection: "example_connection", + inputs: { item }, + }), + ), + ), +); +``` + +Loop/fan-out steps use a dynamic id (`` `process-item-${index}` ``), so the editor renders them as code steps — expected for this pattern (see "App-action step shape" above). + +### Error handling + +Use step-level retries for flaky external calls: + +```typescript +const result = await ctx.step({ + name: "flaky-api-call", + maxAttempts: 3, + retryDelaySeconds: 5, + run: async () => + sdk.runAction({ + appKey: "ExampleCLIAPI", + actionType: "write", + actionKey: "do_something", + connection: "example_connection", + inputs: {}, + }), +}); +``` + +Prefer `sdk.runAction` when a Zapier action exists. Use `sdk.fetch` only when the app action cannot provide the needed behavior or data. + +Retries don't roll back committed steps: if step 3 fails, retry replays steps 1 and 2 by name (which short-circuit) and re-attempts step 3. If step 1 wrote externally, that side effect stays. Design writes to be safe to retry. + +## Escape hatches + +**No first-class action?** Use `sdk.fetch(url, { connection, method })` inside a `ctx.step`. Same Zapier auth and audit trail. Examples: bulk HubSpot pull, Stripe charges over a window, Clearbit enrichment (all under `examples/by-pattern/`). + +**Dynamic inputs?** Mark them `// dynamic` in the workflow and tell deployers to run `zapier-sdk list-action-input-fields ` against their live connection before setting values. + +## Investigation before authoring (Claude Code only) + +Before writing a new `workflow.ts`, offload the "what are the exact keys and IDs" question to the [`zapier-sdk-explorer`](../../../agents/zapier-sdk-explorer.md) subagent. It runs read-only against the live catalog, returns a structured build plan with citations for every identifier, and keeps the raw JSON dumps out of your main context. Only available in Claude Code; ignore this section elsewhere. + +## Where to look next + +- Minimal workflow: [`examples/by-pattern/notify-on-event/typeform-submission-to-gmail/workflow.ts`](../../../examples/by-pattern/notify-on-event/typeform-submission-to-gmail/workflow.ts) +- Multi-step with branching + `sdk.fetch`: [`examples/by-pattern/lead-routing/inbound-lead-orchestration/workflow.ts`](../../../examples/by-pattern/lead-routing/inbound-lead-orchestration/workflow.ts) +- Scheduled trigger with a computed window: [`examples/by-pattern/scheduled-report/daily-revenue-summary/workflow.ts`](../../../examples/by-pattern/scheduled-report/daily-revenue-summary/workflow.ts) +- Corpus conventions: [`examples/by-pattern/README.md`](../../../examples/by-pattern/README.md) +- Shape belongs-here rules: `examples/by-pattern/*/README.md` diff --git a/skills/zapier-workflows/references/list-and-history.md b/skills/zapier-workflows/references/list-and-history.md new file mode 100644 index 0000000..ed843dd --- /dev/null +++ b/skills/zapier-workflows/references/list-and-history.md @@ -0,0 +1,77 @@ +# List Workflows and Run History + +Read this when the user asks to list workflows, or asks about run history for a specific one. Use the public `zapier-sdk --experimental` surface — do not use `zapier-sdk-code-substrate`. + +## Check prerequisites + +```bash +zapier-sdk --version +zapier-sdk get-profile --json +zapier-sdk --experimental --help +``` + +If auth fails, ask the user to run `zapier-sdk login` in an interactive terminal and retry. + +## List workflows + +```bash +zapier-sdk --experimental list-workflows --json +``` + +Parse the JSON output and format what the user asked for. Common useful fields may include `id`, `name`, `enabled`, `is_private`, `created_by_user_id`, `created_at`, `updated_at`, `description`, `current_version`, and trigger-related metadata if present. + +For each workflow with an `id`, include the Zapier editor link: + +```text +https://zapier.com/durables-editor/ +``` + +Treat `trigger_url` as sensitive because it contains a secret token. Do not print `trigger_url` unless the user explicitly asks for it. + +## Ownership scoping + +`list-workflows` may return every workflow the authenticated user can see, including team workflows. If the user asks for "my workflows," first show the likely matches and explain any uncertainty rather than silently filtering by the wrong ID. + +Known quirk: `zapier-sdk get-profile` may return a UUID that does not match `list-workflows[].created_by_user_id`, which may be a separate numeric user ID. If you cannot confidently map those IDs, say so and present the unfiltered list with enough context for the user to choose. + +## Identify a specific workflow + +If the user provides a workflow ID, use it directly. + +If the user refers to the workflow by name or description, list workflows first and find the matching ID (same `list-workflows --json` call as above). If multiple workflows match, show the candidates and ask the user which one they mean. + +## Recent activity and run history + +Fetch runs for the relevant workflow(s): + +```bash +zapier-sdk --experimental list-workflow-runs --json +``` + +Be mindful of API volume for large accounts when fetching runs across many workflows just to find the most recent activity. + +Parse the JSON output. Useful fields may include `id`, `status`, `started_at`, `finished_at`, `input`, and `output`. + +When the workflow ID is known, include the Zapier editor link: + +```text +https://zapier.com/durables-editor/ +``` + +## Drill into a run + +If a single deployed workflow run failed or the user wants step-level detail: + +```bash +zapier-sdk --experimental get-workflow-run --json +``` + +Use `get-durable-run ` only for one-off synthetic runs created by `zapier-sdk --experimental run-durable` (see [`build-and-deploy.md`](build-and-deploy.md)), not for deployed workflow runs returned by `list-workflow-runs`. + +If a manual trigger response returns a trigger ID before a workflow run ID is available, bridge from trigger to run: + +```bash +zapier-sdk --experimental get-trigger-run --json +``` + +Summarize the failure, status, timing, input, output, and any step error details that appear in the response. Avoid dumping raw JSON unless the user asks for it. diff --git a/skills/zapier-workflows/references/modify.md b/skills/zapier-workflows/references/modify.md new file mode 100644 index 0000000..790edb0 --- /dev/null +++ b/skills/zapier-workflows/references/modify.md @@ -0,0 +1,133 @@ +# Modify an Existing Workflow + +Read this when the user asks to fix, update, edit, or republish an existing deployed workflow. Use the public `zapier-sdk --experimental` surface — do not use `zapier-sdk-code-substrate`. + +Modifying a deployed workflow follows a discover → fetch → edit → republish → verify pattern. Publishing a workflow version writes to the user's Zapier account, so get explicit confirmation before publishing. + +## Step 1: Identify the workflow + +If the user provides a workflow ID, use it directly. Otherwise list workflows and find the matching one by name or description: + +```bash +zapier-sdk --experimental list-workflows --json +``` + +If multiple workflows match, show candidates and ask the user which one to modify. + +## Step 2: Fetch current metadata and version + +Run these reads, then preserve the current metadata before changing anything: + +```bash +zapier-sdk --experimental get-workflow --json +zapier-sdk --experimental list-workflow-versions --json +``` + +From the versions list, pick the current or newest version ID, then fetch it: + +```bash +zapier-sdk --experimental get-workflow-version --json +``` + +Capture: + +- `source_files`, especially `source_files["workflow.ts"]`. +- `dependencies`. +- `zapier_durable_version`. +- `enabled`. +- Any `connections`, `app_versions`, `trigger`, or workflow metadata present in the workflow or version response. + +The current SDK publish command takes `source_files` as a JSON object. Do not pass a raw `workflow.ts` path to `publish-workflow-version`. + +## Step 3: Make the edit + +Prefer editing an existing local workflow file if one exists. Otherwise, write `source_files["workflow.ts"]` into a local `workflow.ts` in a workflow-specific directory and edit that copy. + +Apply the requested change narrowly. Preserve existing Zod schemas, `ctx.step` boundaries, connection aliases, dependency pins, durable runtime version, publish connection bindings, app-version bindings, trigger configuration, and visibility/enabled state unless there is a reason to change them. + +## Step 4: Optional synthetic test + +For non-trivial changes, propose a test run before publishing. This may run real downstream actions, so summarize side effects and wait for confirmation. + +Build `source_files` from the local file: + +```bash +SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')" +``` + +Run the workflow: + +```bash +zapier-sdk --experimental run-durable "$SOURCE_FILES" \ + --dependencies '' \ + --zapier_durable_version '' \ + --connections '' \ + --input '' \ + --private +``` + +For synthetic `run-durable` tests, reuse the fetched version's connection bindings as-is — they're already the nested object shape `{ "alias": { "connectionId": "..." } }` that both `run-durable` and `publish-workflow-version` accept. Do not flatten to a bare string like `{ "alias": "id" }`; that fails with `expected object, received string`. + +If the run returns a run ID, inspect it when needed: + +```bash +zapier-sdk --experimental get-durable-run --json +``` + +## Step 5: Confirm, then republish + +Before publishing, summarize for the user: + +1. The diagnosis. +2. The code or config change. +3. The workflow ID being updated. +4. The publish command shape and values that will be preserved from the old version, including dependencies, durable version, enabled state, connections, app versions, and trigger configuration. + +Wait for explicit confirmation before publishing. + +Build `source_files`: + +```bash +SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')" +``` + +Publish: + +```bash +zapier-sdk --experimental publish-workflow-version "$SOURCE_FILES" \ + --dependencies '' \ + --zapier_durable_version '' \ + --connections '' \ + --app_versions '' \ + --trigger '' \ + --json +``` + +Use the fetched workflow's enabled state when publishing. If the workflow was enabled before the edit, either omit `--enabled` or pass bare `--enabled` because publish defaults to enabled. If the workflow was disabled before the edit, add `--enabled false`; do not use `--enabled=false` or `--no-enabled`. Do not accidentally re-enable a disabled workflow. + +Omit `--connections`, `--app_versions`, or `--trigger` only when the fetched metadata confirms the workflow version does not use that field. If the fetched metadata includes trigger, connection, or app-version configuration but the shape cannot be mapped to the current publish flags, stop before publishing and tell the user the workflow needs SDK confirmation rather than silently dropping metadata. + +Do not use the old trigger republish flags (`--trigger-app`, `--trigger-action`, `--trigger-auth`, `--trigger-params`). The current trigger publish path is the single JSON `--trigger` object. + +## Step 6: Verify + +Read back the workflow and versions: + +```bash +zapier-sdk --experimental get-workflow --json +zapier-sdk --experimental list-workflow-versions --json +``` + +Confirm the newest version reflects the publish, the workflow is still enabled if it should be, and trigger/connection/app-version metadata was preserved. If the change is hard to validate without a live trigger fire, tell the user exactly what test event to send and what result to expect. + +Finish by reporting: + +- Workflow name and ID. +- Whether the requested change was published. +- Whether trigger, connection, and app-version metadata were preserved. +- Whether the workflow is enabled. +- The Zapier editor link: `https://zapier.com/durables-editor/`. + +## Reverting + +Previous versions remain available. To revert, fetch the prior version's source (Step 2) and republish it with the same `publish-workflow-version` pattern above (Step 5), preserving dependency, durable version, connection, app-version, trigger, and enabled-state metadata. diff --git a/skills/zapier-workflows/references/setup.md b/skills/zapier-workflows/references/setup.md new file mode 100644 index 0000000..3743bdc --- /dev/null +++ b/skills/zapier-workflows/references/setup.md @@ -0,0 +1,254 @@ +# Workflows Setup + +Read this when the SDK CLI isn't installed, isn't authenticated, or Zapier Workflows EA access hasn't been confirmed. Imperative recipe — each step gates the next. Do not skip a step that failed. + +This is the public-first EA path. It uses the Zapier SDK CLI and does not install the legacy `@zapier/zapier-sdk-code-substrate` package. + +## Flow + +```mermaid +flowchart TD + probe["1. Probe environment"] --> envOk{"Node 18+, npm, git OK?"} + envOk -->|no| stopEnv["STOP: tell user what to install"] + envOk -->|yes| installCli["2. Install or update SDK CLI"] + installCli --> verifyExperimental["3. Verify experimental commands"] + verifyExperimental --> commandsOk{"Code Workflows commands visible?"} + commandsOk -->|no| stopCli["STOP: diagnose SDK CLI install"] + commandsOk -->|yes| checkAuth["4. Check Zapier auth"] + checkAuth --> authOk{"JSON has data and no errors?"} + authOk -->|no| login["4b. Ask user to run interactive login"] + login --> checkAuth + authOk -->|yes| checkAccess["5. Check Zapier Workflows EA access"] + checkAccess --> accessOk{"Read-only workflow list succeeds?"} + accessOk -->|no| stopAccess["STOP: closed beta access required"] + accessOk -->|yes| report["6. Report success"] +``` + +## What this installs + +- `@zapier/zapier-sdk-cli@latest` — public npm package that provides `zapier-sdk`, `zapier-sdk-cli`, and `zapier-sdk-experimental`. Installed globally. + +What this does not install: + +- `@zapier/zapier-sdk-code-substrate` — old private CLI path. Do not install it for EA. +- `@zapier/zapier-durable` globally. [`build-and-deploy.md`](build-and-deploy.md) installs or pins it inside individual workflow projects when needed. + +## Step 1: Probe environment + +Run each check. If any fails, stop and tell the user how to fix it. + +```bash +node --version +npm --version +git --version +``` + +Expected output: + +```bash +v18.0.0 # or higher +10.x.x # npm version; any current version is fine +git version 2.x.x +``` + +Requirements: + +| Tool | Minimum version | Install if missing | +|---|---|---| +| Node | 18 | `brew install node` or use nvm | +| npm | any current version | bundled with Node | +| git | any | usually preinstalled on macOS; otherwise `brew install git` | + +If Node or npm is missing, explain that Node includes npm and the user needs a normal Node install before continuing. For macOS users, suggest either the Node LTS installer from `nodejs.org`, Homebrew (`brew install node`), or nvm if they already use it. Do not continue until `node --version` and `npm --version` work. + +If git is missing, explain that git is needed to fetch things like the verified examples corpus. For macOS users, suggest installing Apple Command Line Tools or Homebrew git. Do not continue until `git --version` works. + +## Step 2: Install or update the Zapier SDK CLI + +Check for an existing binary and the latest published CLI version: + +```bash +which zapier-sdk +zapier-sdk --version +npm view @zapier/zapier-sdk-cli version +``` + +If `zapier-sdk` is missing, install the CLI globally: + +```bash +npm install -g @zapier/zapier-sdk-cli@latest +``` + +If `zapier-sdk` already exists, compare the installed version from `zapier-sdk --version` with the latest version from `npm view @zapier/zapier-sdk-cli version`. If they differ, update the CLI: + +```bash +npm install -g @zapier/zapier-sdk-cli@latest +``` + +After updating, rerun: + +```bash +zapier-sdk --version +npm view @zapier/zapier-sdk-cli version +``` + +Continue only when the installed CLI version matches the latest published `@zapier/zapier-sdk-cli` version. + +Verify the binary is on PATH: + +```bash +which zapier-sdk +zapier-sdk --version +``` + +If global npm installs fail because of permissions, tell the user to fix their Node/npm setup before retrying. Prefer a user-owned Node install through nvm or Homebrew over `sudo npm install -g`. + +## Step 3: Verify Code Workflows experimental commands + +```bash +zapier-sdk --experimental --help +zapier-sdk --experimental create-workflow --help +zapier-sdk --experimental publish-workflow-version --help +zapier-sdk --experimental run-durable --help +zapier-sdk --experimental list-triggers --help +zapier-sdk --experimental get-workflow-run --help +zapier-sdk --experimental trigger-workflow --help +``` + +Expected output includes the Code Workflows command group, including commands such as: + +```text +create-workflow +list-workflows +run-durable +publish-workflow-version +list-workflow-runs +get-workflow-run +``` + +The command-specific help must expose the flags the rest of this skill depends on: + +- `create-workflow --help` includes `--private`. +- `publish-workflow-version --help` includes `--connections`, `--app_versions`, and `--trigger`. +- `run-durable --help` includes `--connections` and `--private`. +- `list-triggers --help` succeeds. +- `get-workflow-run --help` succeeds. +- `trigger-workflow --help` includes `--input`. + +The equivalent binary may also work: + +```bash +zapier-sdk-experimental --help +``` + +If neither form exposes Code Workflows commands, stop and diagnose the SDK CLI install. Do not fall back to `@zapier/zapier-sdk-code-substrate`. + +If `zapier-sdk` exists but the Code Workflows command group or required command-specific flags are missing, the user likely has an older SDK CLI. Run: + +```bash +npm install -g @zapier/zapier-sdk-cli@latest +zapier-sdk --experimental --help +zapier-sdk --experimental publish-workflow-version --help +``` + +Retry the command-specific help checks once after updating. Proceed only after the Code Workflows command group and required flags are visible. If the required flags are still missing after updating, stop and report the installed CLI version and latest npm version; do not attempt to build or deploy a workflow that the installed CLI can't support. + +## Step 4: Authenticate to Zapier + +Check auth state first: + +```bash +zapier-sdk get-profile --json +``` + +Treat auth as successful only if the JSON has a non-null `data` object with an email and the `errors` array is empty. Do not rely on exit code alone; some SDK CLI auth failures return exit code 0 with errors in the JSON body. + +Expected successful output includes the user's email: + +```json +{ + "data": { + "email": "user@example.com" + }, + "errors": [] +} +``` + +If `data` is null, `errors` is non-empty, or the error message says authentication is required, stop and ask the user to run the interactive login command in a real terminal: + +```bash +zapier-sdk login +``` + +This opens a browser. The CLI error text may suggest `npx zapier-sdk login`, but after the global install above the preferred command is `zapier-sdk login`. Do not run browser login inside a non-interactive shell or background process unless the user explicitly asks you to manage the interactive login. After the user finishes login, rerun `zapier-sdk get-profile --json` and inspect the JSON again. + +For Zapier employees, the normal path is to log in with their Zapier work account. For external-user testing, use the account that has been allowlisted for Zapier Workflows EA. + +Do not ask the user for a Zapier password, API key, npm token, or copied auth token. Authentication should happen through the browser-based `zapier-sdk login` flow unless the user explicitly says they are using client credentials for automation. + +If the user wants non-interactive auth for automation, note that the CLI error message may mention `ZAPIER_CREDENTIALS` or client credential environment variables. For this EA install path, prefer browser login unless the user already has client credentials. + +## Step 5: Check Zapier Workflows EA access + +After SDK profile auth succeeds, confirm the authenticated account has Zapier Workflows EA access with a read-only Code Workflows call: + +```bash +zapier-sdk --experimental list-workflows --json +``` + +Expected output is JSON containing workflow data or an empty list, with no errors. This command should not create or modify cloud state. + +Treat the access check as successful only if the JSON has workflow data or an empty workflow list and `errors` is empty. Do not rely on exit code alone; this command may return exit code 0 while the JSON body contains errors. + +If the response says authentication is required, return to Step 4 and diagnose SDK auth. + +If the response includes any of the following, treat it as a Zapier Workflows EA access failure and stop: + +- `None of the security schemes (userJwt) successfully authenticated this request` +- `allowlist`, `not allowlisted`, or `not whitelisted` +- `forbidden`, `permission`, `unauthorized`, or `access denied` + +When EA access fails, tell the user: + +```text +You're logged in to Zapier as , and the Zapier SDK CLI is installed, but this account does not currently have Zapier Workflows EA access. + +Zapier Workflows is currently only available to members of our closed beta. + +To request access, fill out the beta sign-up form: + +https://next-gen-zaps.zapier.app/ + +Submitting the form does not grant access immediately. The Zapier team will review your request and let you know once access has been granted. + +After your account is allowlisted, retry this setup. Reinstalling Node, npm, git, or the SDK CLI will not fix this access check. +``` + +Use the email from `zapier-sdk get-profile --json` in place of ``. + +## Step 6: Report success + +Tell the user: + +- Zapier SDK CLI is installed and on PATH, confirmed via `which zapier-sdk`. +- Code Workflows experimental commands are available. +- The authenticated Zapier account email from `zapier-sdk get-profile --json`. +- Zapier Workflows EA access was confirmed with a read-only workflow listing. +- This confirms SDK CLI install, login, and Zapier Workflows EA access. It does not yet prove that building, publishing, triggering, or running a full workflow works. + +Next steps for the user: + +- Configure app connections at https://zapier.com/app/assets/connections before attempting to build workflows. +- Ask the agent to create a workflow, for example: "Create a Zapier workflow that takes a manual input and sends a Slack message." See [`build-and-deploy.md`](build-and-deploy.md). + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---|---|---| +| `node --version` prints less than `v18` | Old Node | `brew upgrade node` or use nvm to install a current LTS | +| `npm install -g` fails with permissions errors | Global npm prefix is not user-writable | Use nvm or Homebrew Node; avoid `sudo npm install -g` unless the user explicitly accepts that system-level change | +| `zapier-sdk --experimental --help` lacks Code Workflows commands | Old CLI or wrong package installed | Install `@zapier/zapier-sdk-cli@latest`, then rerun `zapier-sdk --version` and the help command | +| `zapier-sdk get-profile` says not logged in | User has not authenticated the CLI | Run `zapier-sdk login` in an interactive terminal, then retry | +| `get-profile` succeeds but `list-workflows` returns an access, permission, allowlist, or JWT/security-scheme error | The Zapier account is authenticated but does not have Zapier Workflows EA access | Stop. Tell the user Zapier Workflows is currently only available to members of our closed beta, include the authenticated email, and ask them to retry after allowlisting | +| `zapier-sdk login` does not open a browser | No default browser configured, or remote/SSH session | Try `zapier-sdk login --no-browser` if supported by the installed CLI, or run from a local terminal | +| `zapier-sdk login` hangs in a non-interactive shell | `login` is browser-interactive; cannot run unattended | Ask the user to run it manually in an actual terminal |