workflows-create

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Zapier Workflows Create

创建Zapier工作流

Create a complete durable workflow from natural language, test it when appropriate, and deploy it through the Zapier SDK experimental Code Workflows commands.
Use the public SDK CLI path. Do not use
zapier-sdk-code-substrate
.
通过自然语言创建完整的持久化工作流,在合适的时机进行测试,并通过Zapier SDK实验性Code Workflows命令部署。
使用公开的SDK CLI路径。请勿使用
zapier-sdk-code-substrate

Compatibility Gate

兼容性检查

Before using this skill, run the
workflows-doctor
bundle compatibility check. If
workflows-doctor
is not installed or cannot be loaded, run
workflows-install
or install
workflows-doctor
from
zapier/agent-skills
before continuing. If
workflows-doctor
reports SDK/skill drift, follow its refresh instructions, stop this skill invocation, reload the agent workspace if needed, and ask the user to rerun the original request.
使用本技能前,请运行
workflows-doctor
包兼容性检查。如果
workflows-doctor
未安装或无法加载,请先运行
workflows-install
或从
zapier/agent-skills
安装
workflows-doctor
。如果
workflows-doctor
报告SDK/技能版本不一致,请遵循其刷新说明,停止当前技能调用,必要时重新加载Agent工作区,并请用户重新发起原始请求。

Prerequisites

前置条件

Verify these at the start:
bash
zapier-sdk --version
zapier-sdk get-profile --json
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 trigger-workflow --help
Pass
latest
for versions — the platform resolves it. The sandbox installs with
pnpm install --config.minimumReleaseAge=1440
, so a dependency published less than 24h ago is rejected, and
@zapier/zapier-sdk
publishes several times a day. You do not have to work out which release is old enough:
latest
is resolved for you to the newest release that clears that 24h gate (COSUB-956).
That only applies to
@zapier/zapier-durable
and
@zapier/zapier-sdk
. They are resolved server-side, at publish and at run submit, and the exact version is stored on the workflow version or the run — so a workflow stays on the version it resolved to, however long it runs.
Give every other dependency an exact version:
bash
--zapier-durable-version latest
--dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}'
A spec that isn't resolved server-side is stored as written, and every tick regenerates
package.json
from it with no lockfile. So
"zod":"latest"
would install whatever is newest at each tick, and a run that spans a
zod
major would replay against a different library than it recorded. The install itself would succeed —
pnpm
falls back from the
latest
tag to the newest release outside the 24h gate — which is what makes this worth stating: the failure shows up later, as a replay that doesn't match.
When publishing a workflow version you can omit
zod
entirely and the service injects a pinned version for you.
run-durable
injects nothing, so declare there whatever the source imports.
Ranges (
^1.2.3
,
~1.2
) are rejected with a 400 for the durable runtime version — whether you pass it as
--zapier-durable-version
or as the
@zapier/zapier-durable
entry in
--dependencies
. So
latest
is the only non-exact value it takes. Every other dependency does accept a range and stores it as written; pin those exactly anyway, for the reason above. An exact version is always fine; it must be at least 24h old.
Every package the generated
workflow.ts
imports must still appear in
--dependencies
— the sandbox installs from
--dependencies
, not your local
package.json
, so a missing import (such as
zod
) fails the run with
Cannot find package
.
The user must also have app connections configured at https://zapier.com/app/assets/connections for any app actions the workflow will run.
开始前请验证以下内容:
bash
zapier-sdk --version
zapier-sdk get-profile --json
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 trigger-workflow --help
版本参数传入
latest
即可——平台会自动解析合适版本。沙箱环境通过
pnpm install --config.minimumReleaseAge=1440
安装依赖,因此发布时间不足24小时的依赖会被拒绝,而
@zapier/zapier-sdk
每天会发布多次。无需自行计算符合要求的版本:
latest
会自动解析为满足24小时限制的最新版本(COSUB-956)。
此规则仅适用于
@zapier/zapier-durable
@zapier/zapier-sdk
。它们的版本会在服务端解析,无论是发布还是运行提交时,确切版本会存储在工作流版本或运行记录中——因此工作流会始终使用解析时的版本,无论运行时长多久。
其他所有依赖请指定精确版本:
bash
--zapier-durable-version latest
--dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}'
未在服务端解析的依赖会按原样存储,且每次运行都会重新生成
package.json
,无锁文件。因此
"zod":"latest"
会在每次运行时安装最新版本,如果跨越了zod的大版本更新,重放时使用的库会与记录时不同。安装本身会成功——pnpm会从
latest
标签回退到发布超过24小时的最新版本——这也是需要明确说明的原因:问题会在后续重放不匹配时显现。
发布工作流版本时可以完全省略
zod
,服务会自动注入固定版本。
run-durable
不会注入任何依赖,因此需要声明源代码中导入的所有依赖。
持久化运行时版本不接受范围版本(如
^1.2.3
~1.2
),无论是通过
--zapier-durable-version
还是
--dependencies
中的
@zapier/zapier-durable
条目传入,都会返回400错误。因此
latest
是唯一可使用的非精确值。其他依赖可以接受范围版本并按原样存储,但仍建议指定精确版本,原因如上。精确版本必须发布超过24小时。
生成的
workflow.ts
中导入的每个包都必须出现在
--dependencies
——沙箱环境从
--dependencies
安装依赖,而非本地
package.json
,因此缺少的导入(如
zod
)会导致运行失败,提示
Cannot find package

Phase 1: Understand The Intent

阶段1:理解用户意图

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.
  7. Start mode (required — see below).
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.
阅读用户的自然语言请求,提取以下信息:
  1. 步骤及顺序。
  2. 涉及的应用。
  3. 步骤间传递的数据。
  4. 手动输入字段或触发器输入字段。
  5. 条件逻辑。
  6. 等待、回调或人工审批环节。
  7. 启动模式(必填——见下文)。
在进一步操作前,将拟议的工作流总结给用户。针对缺失的细节(如目标渠道、文件夹、收件人,或搜索无结果时是否停止)提出明确的澄清问题。
在用户确认工作流架构前,请勿生成代码。

Classify The Start Mode (Required)

分类启动模式(必填)

Every workflow has exactly one start mode, and you must decide it here — it is a required output of this phase, not something inferred later from whether a trigger happened to get configured. There are two:
  • trigger
    — the workflow starts on its own, either on a schedule (scheduled) or in response to an external event (event-driven). It is published with
    --trigger
    and runs without anyone invoking it.
  • manual
    — the workflow has no trigger and runs only on-demand via
    trigger-workflow
    . This is a deliberate choice, not the absence of a trigger.
Default to looking for a trigger in the build instruction. Infer from intent cues:
  • Recurring or time words ("every morning", "daily", "each hour", "on a schedule") →
    trigger
    , scheduled.
  • "when X happens in
    <app>
    " / "whenever a new
    <record>
    is created" →
    trigger
    , event-driven.
  • Explicit "manually", "on demand", "when I run it", or a workflow clearly meant to be invoked by hand with input →
    manual
    .
If the start mode is ambiguous — no clear trigger cue and no explicit manual cue — ask the user: run it manually on-demand, or attach a trigger (and which)? Never silently assume
manual
just because no trigger was named.
A triggerless workflow published on an unconfirmed assumption is the exact failure this classification exists to prevent.
This decision is required even though the platform treats its start-mode input as optional — the write-only
manual
flag sent at publish is not a required field, and enforcement is per-account and flag-gated, so the optionality is a staged-rollout artifact, not permission to skip the decision. Never publish without a start mode the user, or an unambiguous build instruction, chose.
Carry the chosen start mode forward: it is confirmed in the Phase 3 build plan, drives the publish in Phase 6, and is the gate for the Phase 7 verification.
每个工作流有且仅有一个启动模式,必须在此阶段确定——这是本阶段的必填输出,而非后续从触发器配置推断得出。启动模式分为两种:
  • trigger
    ——工作流自动启动,要么按计划执行(定时触发),要么响应外部事件(事件驱动)。通过
    --trigger
    参数发布,无需手动调用即可运行。
  • manual
    ——工作流无触发器,仅能通过
    trigger-workflow
    按需运行。这是刻意选择的模式,而非未配置触发器的默认情况。
默认优先查找构建指令中的触发器线索。可从意图提示推断:
  • 重复或时间相关词汇(如“每天早上”、“每日”、“每小时”、“按计划”)→
    trigger
    ,定时触发。
  • “当<应用>中发生X事件时” / “每当创建新<记录>时”→
    trigger
    ,事件驱动。
  • 明确提及“手动”、“按需”、“当我运行时”,或工作流明显需手动输入调用→
    manual
**如果启动模式不明确——无明确触发器线索也无明确手动线索——请询问用户:是按需手动运行,还是添加触发器(以及哪种触发器)?切勿因未提及触发器就默认选择
manual
模式。**基于未确认的假设发布无触发器工作流,正是此分类步骤要避免的错误。
即使平台将启动模式输入视为可选,此决策仍为必填项——发布时传入的仅写
manual
标志不是必填字段,且按账户和标志进行管控,因此可选性是分阶段发布的产物,而非跳过决策的许可。发布前必须确保启动模式是用户选择的,或由明确的构建指令指定。
将选定的启动模式贯穿后续流程:在阶段3的构建计划中确认,驱动阶段6的发布操作,并作为阶段7验证的依据。

Phase 2: Discover Apps, Connections, Actions, Triggers, And Fields

阶段2:发现应用、连接、操作、触发器及字段

Use the standard Zapier SDK CLI for app/action discovery:
bash
zapier-sdk list-apps --search "<app name>" --json
zapier-sdk list-connections <appKey> --owner me --json
zapier-sdk list-actions <appKey> --action-type <write|search|read|read_bulk> --json
zapier-sdk list-action-input-fields <appKey> <actionType> <actionKey> --connection <connectionId> --json
zapier-sdk list-action-input-field-choices <appKey> <actionType> <actionKey> <fieldKey> --connection <connectionId> --json
For workflows that should subscribe to a Zapier app trigger, use the experimental trigger discovery commands:
bash
zapier-sdk --experimental list-triggers <appKey> --json
zapier-sdk --experimental list-trigger-input-fields <appKey> <triggerKey> --connection <connectionId> --json
zapier-sdk --experimental list-trigger-input-field-choices <appKey> <triggerKey> <fieldKey> --connection <connectionId> --json
If several apps, connections, actions, triggers, or field choices are plausible, show the candidates and ask the user to choose.
使用标准Zapier SDK CLI进行应用/操作发现:
bash
zapier-sdk list-apps --search "<应用名称>" --json
zapier-sdk list-connections <appKey> --owner me --json
zapier-sdk list-actions <appKey> --action-type <write|search|read|read_bulk> --json
zapier-sdk list-action-input-fields <appKey> <actionType> <actionKey> --connection <connectionId> --json
zapier-sdk list-action-input-field-choices <appKey> <actionType> <actionKey> <fieldKey> --connection <connectionId> --json
对于需要订阅Zapier应用触发器的工作流,使用实验性触发器发现命令:
bash
zapier-sdk --experimental list-triggers <appKey> --json
zapier-sdk --experimental list-trigger-input-fields <appKey> <triggerKey> --connection <connectionId> --json
zapier-sdk --experimental list-trigger-input-field-choices <appKey> <triggerKey> <fieldKey> --connection <connectionId> --json
如果有多个合理的应用、连接、操作、触发器或字段选项,请列出候选项并请用户选择。

Use "AI by Zapier" For AI Steps

使用“AI by Zapier”处理AI步骤

For any AI / "call an LLM" step — summarize, extract, classify, generate, or analyze text — always use "AI by Zapier" (app key
AICLIAPI
) as the step and select the model inside it: if the user names a provider or model, set that as the
model_id
(see below); otherwise use its default model. It runs on Zapier's built-in AI credentials (no third-party account required) and bills as normal Zapier tasks, so an agent-built workflow does not silently route to a separate raw-provider app the user must connect and pay for. Discover it with
list-apps --search "AI by Zapier"
; its generic completion action is
get_completion
("Analyze and Return Data"), alongside
extract_content
(from a URL) and
search_content
(confirm the current set with
list-actions AICLIAPI --action-type write --json
).
Configuring the
get_completion
step.
Inspect its fields with
list-action-input-fields AICLIAPI write get_completion --json
. The ones that matter for a generated step:
  • instructions
    (required) — the prompt describing what the AI should do.
  • provider_id
    (optional) — the AI provider, needed only when the user names one. Choices are
    openai
    ,
    anthropic
    ,
    google
    ,
    azure-openai
    ,
    amazon-bedrock
    (
    list-action-input-field-choices AICLIAPI write get_completion provider_id --json
    ). Setting it is what makes
    model_id
    's choices resolve.
  • model_id
    (required, default
    "advanced/auto"
    ) — the model. For a generic step, pass the default
    "advanced/auto"
    — auto-pick a model in the Advanced tier (tiers:
    standard
    /
    advanced
    /
    premium
    ) on built-in credentials. When the user names a provider or model, set
    provider_id
    first, then resolve the valid model for it with
    list-action-input-field-choices AICLIAPI write get_completion model_id --inputs '{"provider_id":"<provider>"}' --json
    (the list is empty until
    provider_id
    is set) and pass the matching
    <provider>/<model>
    value (for example
    anthropic/claude-sonnet-5
    ,
    openai/gpt-4o
    ). Do not hardcode a model list — resolve it at build time.
  • authentication_id
    (required, default
    "0"
    ) —
    "0"
    is Zapier's built-in AI credentials (the models shown with a Zap icon). Keep
    "0"
    for the default and any built-in model. A model the user names may not be available on built-in credentials — those require the user's own AI provider account (a custom
    authentication_id
    ); if so, tell the user and use their authentication.
    model_id
    depends on this field.
  • inputFields
    (optional, OBJECT) — extra context fields mapped from earlier steps, merged into the prompt.
So a default AI step needs only a prompt.
model_id
and
authentication_id
are required but have working defaults; pass them explicitly with those defaults (
"advanced/auto"
and
"0"
) so the
runAction
inputs are complete, and no connection alias is needed for the built-in path:
typescript
const summary = await ctx.step("summarize-with-ai", async () =>
  sdk.runAction({
    appKey: "AICLIAPI",
    actionType: "write",
    actionKey: "get_completion",
    inputs: {
      instructions: `Summarize this in one sentence: ${input.text}`,
      model_id: "advanced/auto",
      authentication_id: "0",
    },
  }),
);
Naming a provider or model is not a reason to leave "AI by Zapier" — set it as the
model_id
above. Reach for a raw-provider AI app (Anthropic, OpenAI, Google AI, and so on) only when the user explicitly asks for that standalone app, or needs a capability "AI by Zapier" does not offer. When you do, tell the user the step uses their own provider connection and billing, not "AI by Zapier."
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 <appKey> <actionType> <actionKey> \
  --connection <connectionId> \
  --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 <app> <action>
. 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.
"Webhooks by Zapier" and other apps with a catch-hook trigger (PayPal, Salesforce, Twilio, WordPress, Wufoo, Zillow, and others) are discovered and configured exactly like any other trigger app — nothing about them is special-cased. Search
list-apps --search "webhook"
(or the specific app name) for its
implementation_id
(for example
WebHookCLIAPI@1.1.0
— an illustrative example, not a version to hardcode; confirm the current version via discovery), then
list-triggers <appKey>
for its catch-hook trigger action. "Webhooks by Zapier" itself is no-auth (
authentication_id: null
) with empty
params
, but confirm its action key via
list-triggers WebHookCLIAPI
rather than hardcoding one — as of this writing it exposes both
hook_v2
(parsed payload; the common default) and
hook_raw
(unparsed body and headers, max 2MB), and that pair of action keys is specific to
WebHookCLIAPI
, not a pattern the other apps share. Other catch-hook apps (PayPal, Salesforce, Twilio, ...) commonly require a connection, because claiming their trigger means calling the provider's API to register a subscription. Do not assume no-auth or empty
params
for those — confirm each app's actual action key, auth, and param requirements via
list-triggers
/
list-trigger-input-fields
(see above) rather than generalizing from "Webhooks by Zapier." Configure them through
--trigger
at publish time (Phase 6) like any other trigger — do not treat them as "no trigger" / manual-only workflows.
对于任何AI/“调用LLM”步骤——总结、提取、分类、生成或分析文本——始终使用“AI by Zapier”(应用密钥
AICLIAPI
)作为步骤,并在其中选择模型:如果用户指定了提供商或模型,则将其设置为
model_id
(见下文);否则使用默认模型。它使用Zapier内置的AI凭证(无需第三方账户),并按常规Zapier任务计费,因此Agent构建的工作流不会静默路由到用户必须连接并付费的独立原始提供商应用。通过
list-apps --search "AI by Zapier"
发现它;其通用补全操作是
get_completion
(“分析并返回数据”),还有
extract_content
(从URL提取内容)和
search_content
(通过
list-actions AICLIAPI --action-type write --json
确认当前可用操作)。
配置
get_completion
步骤
。通过
list-action-input-fields AICLIAPI write get_completion --json
查看其字段。对于生成步骤,关键字段如下:
  • instructions
    必填)——描述AI应执行操作的提示词。
  • provider_id
    (可选)——AI提供商,仅当用户指定时需要。可选值为
    openai
    anthropic
    google
    azure-openai
    amazon-bedrock
    (通过
    list-action-input-field-choices AICLIAPI write get_completion provider_id --json
    查看)。设置此字段后,
    model_id
    的可选值才会生效。
  • model_id
    必填,默认值
    "advanced/auto"
    )——模型。对于通用步骤,传递默认值
    "advanced/auto"
    ——在内置凭证上自动选择Advanced tier(层级:
    standard
    /
    advanced
    /
    premium
    )的模型。当用户指定提供商或模型时,先设置
    provider_id
    ,然后通过
    list-action-input-field-choices AICLIAPI write get_completion model_id --inputs '{"provider_id":"<提供商>"}' --json
    解析对应有效模型(未设置
    provider_id
    时列表为空),并传递匹配的
    <提供商>/<模型>
    值(例如
    anthropic/claude-sonnet-5
    openai/gpt-4o
    )。请勿硬编码模型列表——在构建时动态解析。
  • authentication_id
    必填,默认值
    "0"
    )——
    "0"
    是Zapier的内置AI凭证(显示Zap图标的模型)。默认情况及所有内置模型均使用
    "0"
    。用户指定的模型可能无法通过内置凭证使用——此时需要用户自己的AI提供商账户(自定义
    authentication_id
    );若如此,请告知用户并使用其凭证。
    model_id
    依赖此字段。
  • inputFields
    (可选,OBJECT)——从之前步骤映射的额外上下文字段,会合并到提示词中。
因此默认AI步骤仅需要提示词。
model_id
authentication_id
为必填字段,但有可用默认值;请显式传递这些默认值(
"advanced/auto"
"0"
),确保
runAction
输入完整,且内置路径无需连接别名:
typescript
const summary = await ctx.step("summarize-with-ai", async () =>
  sdk.runAction({
    appKey: "AICLIAPI",
    actionType: "write",
    actionKey: "get_completion",
    inputs: {
      instructions: `用一句话总结:${input.text}`,
      model_id: "advanced/auto",
      authentication_id: "0",
    },
  }),
);
指定提供商或模型不是放弃使用“AI by Zapier”的理由——按上述方式设置
model_id
即可。仅当用户明确要求使用独立应用,或需要“AI by Zapier”不具备的功能时,才使用原始提供商AI应用(Anthropic、OpenAI、Google AI等)。此时需告知用户该步骤使用其自己的提供商连接和计费,而非“AI by Zapier”。
为每个选定的连接分配简短的蛇形命名连接别名,如
slack_work
gmail_primary
。记录别名与连接ID的映射。别名用于工作流代码;连接ID通过
--connections
JSON参数传递给测试/部署命令。
对于步骤间的输出映射,仅在用户确认后运行安全的操作测试。使用当前SDK命令格式:
bash
zapier-sdk run-action <appKey> <actionType> <actionKey> \
  --connection <connectionId> \
  --inputs '<{"key":"value"}>' \
  --json
对于基于触发器的工作流,捕获发布所需的触发器配置:
json
{
  "selected_api": "GoogleSheetsAPI@2.3.0",
  "action": "new_row",
  "authentication_id": "connection-id-or-null",
  "params": {}
}
selected_api
使用固定版本的应用/API标识符——SDK发现(
list-apps
/
get-app
)返回的
implementation_id
,例如
GoogleSheetsAPI@2.3.0
。请勿使用裸应用密钥(
GoogleSheetsAPI
)或替换为显示名称。未指定版本的裸
selected_api
会导致触发器声明在发布时静默失败:发布调用返回成功无错误,但工作流保持禁用状态,且无任何提示说明原因。如果发现操作仅暴露裸应用slug而非带版本的
implementation_id
,则视为阻塞问题,在发布前记录到构建计划中——请勿发布使用未版本化标识符的触发器。
params
需匹配
list-trigger-input-fields <应用> <操作>
返回的每个字段的
value_type
。ARRAY字段必须为JSON数组(例如
"dow": ["1"]
);STRING字段必须为纯字符串(例如
"hod": "9:00 AM"
)。在需要数组的位置传入标量(反之亦然)会导致触发器声明同样静默失败。
从SDK发现输出(如
list-apps
get-app
list-actions
或触发器/操作结果元数据)中捕获应用实现/版本信息(如果可用)。请勿编造应用版本。如果未暴露实现/版本绑定,请省略
--app-versions
参数,而非猜测。
“Webhooks by Zapier”及其他带有捕获钩子触发器的应用(PayPal、Salesforce、Twilio、WordPress、Wufoo、Zillow等)的发现和配置方式与其他触发器应用完全相同——无需特殊处理。通过
list-apps --search "webhook"
(或特定应用名称)查找其
implementation_id
(例如
WebHookCLIAPI@1.1.0
——示例版本,请勿硬编码;请通过发现确认当前版本),然后通过
list-triggers <appKey>
查找其捕获钩子触发器操作。“Webhooks by Zapier”本身无需认证(
authentication_id: null
)且
params
为空,但请通过
list-triggers WebHookCLIAPI
确认其操作密钥,而非硬编码——截至本文撰写时,它同时暴露
hook_v2
(解析后的负载;常用默认值)和
hook_raw
(未解析的正文和头信息,最大2MB),这对操作密钥是
WebHookCLIAPI
特有的,并非其他应用的通用模式。其他捕获钩子应用(PayPal、Salesforce、Twilio等)通常需要连接,因为声明其触发器意味着调用提供商API注册订阅。请勿假设它们无需认证或
params
为空——请通过
list-triggers
/
list-trigger-input-fields
(见上文)确认每个应用的实际操作密钥、认证和参数要求,而非基于“Webhooks by Zapier”进行泛化。在阶段6发布时通过
--trigger
参数配置它们,与其他触发器一样——请勿将其视为“无触发器”/仅手动工作流。

Phase 3: Confirm The Build Plan

阶段3:确认构建计划

Before writing code, present:
text
Workflow: <kebab-case-name>
Input: { field1, field2 }
Connections:
  alias = connectionId (connection title)
Start mode: trigger (<selected_api.action with params, including "Webhooks by Zapier" or other catch-hook apps>)
  — or —
Start mode: manual — on-demand only via `trigger-workflow`
Steps:
  1. <step-name> - <AppName>.<actionType>.<actionKey>
  2. <step-name> - <AppName>.<actionType>.<actionKey>
Return: <summary of output>
The Start mode line is required and must state exactly one of the two modes classified in Phase 1.
manual
is a deliberate, user-confirmed selection — never render it as "no trigger" or leave it implied by an absent trigger. If the mode is still ambiguous at this point, resolve it with the user before proceeding (Phase 1).
Ask the user to confirm before generating files, including explicit confirmation of the start mode.
编写代码前,呈现以下内容:
text
工作流:<短横线命名>
输入:{ field1, field2 }
连接:
  别名 = connectionId(连接标题)
启动模式:trigger(<selected_api.action及参数,包括“Webhooks by Zapier”或其他捕获钩子应用>)
  —— 或 ——
启动模式:manual — 仅通过`trigger-workflow`按需运行
步骤:
  1. <步骤名称> - <应用名称>.<actionType>.<actionKey>
  2. <步骤名称> - <应用名称>.<actionType>.<actionKey>
返回:<输出摘要>
启动模式行是必填项,必须明确说明阶段1分类的两种模式之一。
manual
是用户确认的刻意选择——切勿将其显示为“无触发器”或隐含未提及触发器。如果此时模式仍不明确,请与用户解决后再继续(阶段1)。
请用户确认后再生成文件,包括明确确认启动模式。

Phase 4: Generate The Workflow Project

阶段4:生成工作流项目

Create a workflow directory:
text
<working-directory>/
  <kebab-case-workflow-name>/
    package.json
    workflow.ts
package.json
is for local type-checking only — the sandbox installs from
--dependencies
, not from this file — so
latest
is fine here too:
json
{
  "type": "module",
  "dependencies": {
    "@zapier/zapier-sdk": "latest",
    "@zapier/zapier-durable": "latest",
    "zod": "4.3.6"
  },
  "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.
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));
创建工作流目录:
text
<工作目录>/
  <短横线命名的工作流名称>/
    package.json
    workflow.ts
package.json
仅用于本地类型检查——沙箱环境从
--dependencies
安装依赖,而非此文件——因此此处也可以使用
latest
json
{
  "type": "module",
  "dependencies": {
    "@zapier/zapier-sdk": "latest",
    "@zapier/zapier-durable": "latest",
    "zod": "4.3.6"
  },
  "devDependencies": {
    "typescript": "latest"
  }
}
如果添加构建脚本,请暂时使用
--skipLibCheck
以避免SDK/durable传递类型声明导致的类型检查失败:
json
{
  "scripts": {
    "build": "tsc --target es2022 --module nodenext --moduleResolution nodenext --skipLibCheck --outDir dist workflow.ts"
  }
}
workflow.ts
应满足:
  • @zapier/zapier-durable
    导入
    defineDurable
  • @zapier/zapier-sdk
    导入
    createZapierSdk
  • 在模块级别创建一次SDK客户端:
    const sdk = createZapierSdk()
    放在
    defineDurable
    上方
  • 当工作流有输入时,使用Zod进行输入验证。
  • 将外部副作用(应用操作、请求)放在
    ctx.step
    调用内部。
  • 每个应用操作对应一个
    ctx.step
    ,其主体为单个
    return sdk.runAction({...})
    调用——每个步骤一个
    runAction
  • 根据需要将验证、输入规范化、简单守卫、数据格式化放入步骤中。
  • 在工作流代码中使用连接别名,而非原始连接ID。
  • 使用
    stepVar.data[0].field
    引用前一步骤的第一个结果,或
    stepVar.data
    引用整个结果数组。
  • 在Zod验证前规范化手动输入。在当前
    run-durable
    路径中,输入可能以JSON字符串形式传入,而非已解析的对象。
对于有输入的工作流,使用以下辅助模式:
typescript
function normalizeInput(rawInput: unknown): unknown {
  if (typeof rawInput === "string") {
    return JSON.parse(rawInput);
  }
  return rawInput;
}
然后解析规范化后的值:
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<typeof InputSchema>;

const TODOIST_APP_KEY = "TodoistV2CLIAPI";
const TODOIST_CONNECTION = "todoist_primary";

const workflow = defineDurable<Input, unknown>(
  "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 };
  },
);
生成可转换为有意义步骤图的持久化源代码。避免过度动态构造。
defineDurable
调用格式——每次调用必须将
run
解析为函数
。可以使用简洁形式
defineDurable("workflow-name", async (ctx, input) => { ... })
或对象形式
defineDurable({ name: "workflow-name", inputSchema, outputSchema, description, run: async (ctx, input) => { ... } })
ctx
始终是
run
的第一个参数;
input
是可选的第二个参数,因此
async (ctx) => { ... }
也是有效的。以下格式无效,会导致工作流首次运行失败,提示
durable.run is not a function
  • defineDurable(async (ctx, input) => { ... })
    ——无名称的简洁函数。该函数会被视为选项对象,因此
    run
    永远不会被设置。这是最常见的错误。
  • defineDurable({ name: "workflow-name" })
    ——缺少
    run
    的对象。
  • defineDurable({ name: "workflow-name", run: someNonFunction })
    ——
    run
    不是函数。
durable.run is not a function
defineDurable
调用的代码格式缺陷,而非版本不匹配。请勿通过更改固定的
@zapier/zapier-durable
@zapier/zapier-sdk
版本来修复——请修正调用格式,确保传入
name
run
函数。
默认使用以下解析友好的格式——模块级别
sdk
、提升的应用密钥/连接常量,以及每个应用操作的简洁
runAction
主体:
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<typeof InputSchema>;

const TODOIST_APP_KEY = "TodoistV2CLIAPI";
const TODOIST_CONNECTION = "todoist_primary";

const workflow = defineDurable<Input, unknown>(
  "example-workflow",
  async (ctx, input) => {
    // 普通代码:在任何步骤外进行守卫检查。
    if (input.reaction !== "todo") {
      return { skipped: true };
    }

    // 普通代码:在步骤外构造操作输入。
    const taskInput = buildTaskInput(input);

    // 应用操作:一个runAction,对象字面量,模块级别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.
ctx.step
的主体为单个带有
appKey
actionType
actionKey
sdk.runAction({...})
调用(对象字面量或解析为对象的
const
app
/
action
拼写也可)时,编辑器会将其渲染为应用操作步骤(带应用图标)。字符串字面量步骤ID(
ctx.step("create-todoist-task", ...)
)和内联
async () => ...
回调是可识别的格式;对象形式
ctx.step({ name, run })
也可使用。
其他步骤会渲染为普通代码步骤——例如无
runAction
的步骤、包含多个
runAction
的步骤,或通过循环创建的带动态ID的步骤(
`process-item-${index}`
)。这是预期行为,而非退化;循环和扇出确实需要动态ID。

Phase 5: Test The Workflow

阶段5:测试工作流

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). The same shape is used for
publish-workflow-version
in Phase 6:
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":"latest","zod":"4.3.6"}' \
  --zapier-durable-version latest \
  --connections '<connections JSON>' \
  --input '<JSON matching input schema>' \
  --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 <run-id> --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.
workflow.ts
构建
source_files
bash
SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')"
从选定的别名构建
connections
JSON。它是嵌套对象——每个别名映射到包含
connectionId
的对象(切勿使用裸字符串)。此格式与阶段6中
publish-workflow-version
使用的格式相同:
json
{
  "slack_work": { "connectionId": "12345678" },
  "gmail_primary": { "connectionId": "87654321" }
}
运行前,告知用户关联应用中可能发生的操作,如果有副作用请等待用户确认。
运行持久化工作流:
bash
zapier-sdk --experimental run-durable "$SOURCE_FILES" \
  --dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}' \
  --zapier-durable-version latest \
  --connections '<connections JSON>' \
  --input '<匹配输入 schema 的 JSON>' \
  --private
run-durable
会立即返回运行记录,通常在工作流完成前。捕获返回的运行ID,然后轮询直到状态变为终止状态。请勿假设首次响应包含最终输出。
bash
zapier-sdk --experimental get-durable-run <run-id> --json
终止成功意味着运行记录的
status: "finished"
,有预期的
output
error: null
,且顶级
errors: []
。终止失败意味着
status: "failed"
error
非空。当运行记录处于初始化或启动状态时,继续轮询。
修复代码并重新测试,直到行为与确认的计划一致。

Phase 6: Deploy The Workflow

阶段6:部署工作流

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 "<workflow-name>" \
  --description "<brief description>" \
  --private \
  --json
Omit
--private
only if the user explicitly wants the workflow visible to the broader account.
Capture the returned workflow ID. Then decide how to ship the code:
  • Direct publish (the default below): publish the first version straight away with
    publish-workflow-version
    . This is the legitimate no-open-draft case — the container was just created, so no draft exists to publish past.
  • Stage as a draft for review: if the user wants to look the workflow over in the Zapier editor before it goes live, put the generated code in a server draft instead of publishing:
    bash
    zapier-sdk --experimental create-workflow-draft <workflow-id> --json
    zapier-sdk --experimental update-workflow-draft <workflow-id> <draft-id> "$SOURCE_FILES" \
      --draft-revision <draft_revision from the create response> \
      --json
    Pass the same
    --dependencies
    ,
    --zapier-durable-version
    ,
    --connections
    ,
    --app-versions
    , and — for a
    Start mode: trigger
    workflow —
    --trigger
    values Phase 6 would have passed to the publish. Then hand the user the draft's editor link —
    https://zapier.com/durables-editor/<workflow-id>/draft/<draft-slug>/workflow.ts
    , using the
    slug
    from the draft response; the final segment is one of the draft's
    source_files
    keys (
    workflow.ts
    in this skill's flow) — to review and publish, or publish on their go-ahead. Carry the start-mode decision to the draft publish exactly as a direct publish would: a
    Start mode: manual
    workflow publishes with
    --manual
    (
    publish-workflow-draft <workflow-id> <draft-id> --manual --enabled --json
    ); a
    Start mode: trigger
    workflow's draft already holds its
    --trigger
    , so publish without
    --manual
    (
    publish-workflow-draft <workflow-id> <draft-id> --enabled --json
    ). Never pass
    --trigger
    and
    --manual
    together. Publishing consumes the draft. Skip Phase 7's version read-backs if nothing was published.
For a direct publish, 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 the
--app-versions
payload:
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": {}
}
A "Webhooks by Zapier" or other catch-hook trigger is a real trigger — publish it with
--trigger
using the config captured in Phase 2, the same as any other app trigger.
How you publish follows directly from the start mode confirmed in Phase 3 — the two are not co-equal defaults; you commit to the one the user chose.
Before publishing, confirm the payload matches the declared start mode:
Start mode: trigger
→ the publish passes
--trigger
and not
--manual
;
Start mode: manual
→ it passes
--manual
and not
--trigger
. Pass exactly one — the platform contract is a discriminated union (a version is either triggered or
manual: true
, never both) and rejects passing both together as a contradiction; the CLI also guards the both-case client-side. The workflow you publish must carry the trigger you decided on, or be explicitly marked manual. Catch any disagreement here, before the publish call, so a dropped or missing trigger is not discovered only in Phase 7.
Start mode: trigger
— publish with
--trigger
, using the config built above. The trigger is the signal; do not also pass
--manual
(that is the contradiction the gate rejects):
bash
SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')"

zapier-sdk --experimental publish-workflow-version <workflow-id> "$SOURCE_FILES" \
  --dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}' \
  --zapier-durable-version latest \
  --connections '<publish connection bindings JSON>' \
  --app-versions '<app versions JSON if needed>' \
  --trigger '<trigger config JSON>' \
  --enabled \
  --json
Start mode: manual
— and only when Phase 3 confirmed manual — omit
--trigger
and pass
--manual
to declare the on-demand start mode explicitly. Marking manual is the deliberate branch, not a fallback for when a trigger was hard to configure: if the user asked for a trigger, a failure to build its config is a blocker to resolve, never a reason to drop to manual.
bash
zapier-sdk --experimental publish-workflow-version <workflow-id> "$SOURCE_FILES" \
  --dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}' \
  --zapier-durable-version latest \
  --connections '<publish connection bindings JSON>' \
  --app-versions '<app versions JSON if needed>' \
  --manual \
  --enabled \
  --json
--manual
and
--trigger
are mutually exclusive: pass
--manual
here because there is no trigger. Never pass both in one publish.
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.
If the publish is rejected with a conflict about open drafts, someone (likely the user, in the Zapier editor) forked a draft on this workflow mid-flow. An open draft always holds unpublished work, so never publish past it silently. Tell the user and offer the same choices as
workflows-modify
: fold your changes into that draft and publish it (
update-workflow-draft
+
publish-workflow-draft
), or — with their explicit confirmation, since it drops the draft's unpublished work — discard the draft (
discard-workflow-draft
) and retry the direct publish.
在创建工作流前,决定其是否应为私有。对于EA用户,默认设为私有,除非用户明确希望工作流对整个账户可见。
创建私有工作流容器:
bash
zapier-sdk --experimental create-workflow "<工作流名称>" \
  --description "<简短描述>" \
  --private \
  --json
仅当用户明确希望工作流对整个账户可见时,才省略
--private
捕获返回的工作流ID。然后决定代码的交付方式:
  • 直接发布(以下为默认方式):立即使用
    publish-workflow-version
    发布第一个版本。这是合法的无草稿场景——容器刚刚创建,因此不存在需要跳过的草稿。
  • 作为草稿暂存以供审核:如果用户希望在Zapier编辑器中查看工作流后再上线,请将生成的代码放入服务端草稿,而非直接发布:
    bash
    zapier-sdk --experimental create-workflow-draft <workflow-id> --json
    zapier-sdk --experimental update-workflow-draft <workflow-id> <draft-id> "$SOURCE_FILES" \
      --draft-revision <创建响应中的draft_revision> \
      --json
    传递与阶段6直接发布相同的
    --dependencies
    --zapier-durable-version
    --connections
    --app-versions
    ,以及——对于
    启动模式: trigger
    的工作流——
    --trigger
    值。然后将草稿的编辑器链接(
    https://zapier.com/durables-editor/<workflow-id>/draft/<draft-slug>/workflow.ts
    ,使用草稿响应中的
    slug
    ;最后一段是草稿的
    source_files
    键之一(本技能流程中为
    workflow.ts
    ))提供给用户,供其审核和发布,或在用户许可后发布。将启动模式决策完全应用于草稿发布,与直接发布一致:
    启动模式: manual
    的工作流发布时传入
    --manual
    publish-workflow-draft <workflow-id> <draft-id> --manual --enabled --json
    );
    启动模式: trigger
    的工作流草稿已包含
    --trigger
    ,因此发布时无需传入
    --manual
    publish-workflow-draft <workflow-id> <draft-id> --enabled --json
    )。切勿同时传入
    --trigger
    --manual
    。发布后草稿会被消耗。如果未发布任何内容,请跳过阶段7的版本回读。
对于直接发布,当前SDK CLI期望
source_files
为JSON对象,而非
workflow.ts
的路径。
发布时,使用与
run-durable
相同的嵌套
connections
格式——每个别名映射到包含
connectionId
的对象:
json
{
  "slack_work": { "connectionId": "123-or-uuid" },
  "gmail_primary": { "connectionId": "456-or-uuid" }
}
如果已知应用实现/版本信息,构建
--app-versions
负载:
json
{
  "slack": { "implementation_name": "SlackCLIAPI", "version": "optional" }
}
当无需应用实现/版本绑定时,省略整个
--app-versions
标志。同样,当工作流无连接绑定时,省略
--connections
。请勿向CLI传递“如需”之类的占位文本。
对于基于触发器的工作流,从阶段2构建
trigger
JSON。确保
selected_api
固定到
implementation_id
版本(例如
GoogleSheetsAPI@2.3.0
),且每个
params
字段的格式符合其
value_type
(见阶段2)——裸应用密钥或错误的参数格式会导致触发器声明在发布时静默失败:
json
{
  "selected_api": "GoogleSheetsAPI@2.3.0",
  "action": "new_row",
  "authentication_id": "connection-id-or-null",
  "params": {}
}
“Webhooks by Zapier”或其他捕获钩子触发器是真实的触发器——使用阶段2捕获的配置,通过
--trigger
参数发布,与其他应用触发器相同。
发布方式直接由阶段3确认的启动模式决定——两者并非同等默认选项;需严格遵循用户选择的模式。
发布前,请确认负载与声明的启动模式匹配
启动模式: trigger
→发布时传入
--trigger
传入
--manual
启动模式: manual
→发布时传入
--manual
传入
--trigger
仅传入其中一个——平台约定为互斥选项(版本要么是触发式,要么是
manual: true
,绝不能同时满足),如果同时传入会被拒绝;CLI也会在客户端阻止这种情况。发布的工作流必须携带选定的触发器,或明确标记为手动。在此处捕获任何不一致,避免在阶段7才发现触发器缺失或遗漏。
启动模式: trigger
——使用上述构建的配置,通过
--trigger
参数发布。触发器是信号;请勿同时传入
--manual
(这是网关会拒绝的矛盾配置):
bash
SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')"

zapier-sdk --experimental publish-workflow-version <workflow-id> "$SOURCE_FILES" \
  --dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}' \
  --zapier-durable-version latest \
  --connections '<发布连接绑定JSON>' \
  --app-versions '<应用版本JSON(如需)>' \
  --trigger '<触发器配置JSON>' \
  --enabled \
  --json
启动模式: manual
——仅当阶段3确认手动模式时,省略
--trigger
并传入
--manual
以明确声明按需启动模式。标记为手动是刻意选择的分支,而非触发器配置困难时的 fallback:如果用户要求触发器,配置失败是需要解决的阻塞问题,绝不能以此为由切换到手动模式。
bash
zapier-sdk --experimental publish-workflow-version <workflow-id> "$SOURCE_FILES" \
  --dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}' \
  --zapier-durable-version latest \
  --connections '<发布连接绑定JSON>' \
  --app-versions '<应用版本JSON(如需)>' \
  --manual \
  --enabled \
  --json
--manual
--trigger
互斥:此处传入
--manual
是因为没有触发器。切勿在一次发布中同时传入两者。
请勿使用旧的
--trigger-app
--trigger-action
--trigger-auth
--trigger-params
标志。当前触发器发布路径为单个JSON
--trigger
对象。
如果发布因开放草稿冲突被拒绝,可能是有人(通常是用户在Zapier编辑器中)在此流程中途创建了该工作流的草稿。开放草稿始终包含未发布的工作,因此切勿静默跳过它。告知用户并提供与
workflows-modify
相同的选择:将你的更改合并到该草稿并发布(
update-workflow-draft
+
publish-workflow-draft
),或——在用户明确确认(因为这会丢弃草稿中的未发布工作)——丢弃草稿(
discard-workflow-draft
)并重试直接发布。

Phase 7: Verify Deployment

阶段7:验证部署

Read back the workflow and versions:
bash
zapier-sdk --experimental get-workflow <workflow-id> --json
zapier-sdk --experimental list-workflow-versions <workflow-id> --json
zapier-sdk --experimental get-workflow-version <workflow-id> <version-id> --json
回读工作流和版本:
bash
zapier-sdk --experimental get-workflow <workflow-id> --json
zapier-sdk --experimental list-workflow-versions <workflow-id> --json
zapier-sdk --experimental get-workflow-version <workflow-id> <version-id> --json

Gate On The Start Mode (Required)

基于启动模式的验证(必填)

Verification must confirm the deployed workflow matches the start mode confirmed in Phase 3 — not merely that it is
enabled
. A triggerless workflow reads back as
enabled: true
, so an
enabled
check alone silently passes a workflow that was supposed to have a trigger but doesn't. Re-read the workflow (the trigger claim is asynchronous and can fail silently, so allow a few seconds and poll if needed) and gate on the declared mode:
bash
zapier-sdk --experimental get-workflow <workflow-id> --json
  • Start mode: trigger
    → require both
    enabled: true
    and a non-empty
    triggers[]
    . An empty
    triggers[]
    means the trigger was dropped or
    --trigger
    was omitted — the claim failed or was never attempted. Do not report the workflow as done. 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);
    enabled: false
    after publishing with
    --enabled
    is the same failure. Re-publish with a corrected
    --trigger
    and re-check.
  • Start mode: manual
    → require
    triggers[]
    to be empty by design, and confirm this workflow was deliberately classified manual in Phase 3 (never triggered). A manual workflow with a non-empty
    triggers[]
    is also a mismatch — stop and reconcile with the user. It is invoked on-demand via
    trigger-workflow
    ; there is no trigger claim to verify.
triggers[]
is the authority for this gate. The platform's start-mode input (the write-only
manual
flag) is never surfaced on any read-back, so do not look for it on
get-workflow
or a version — read the trigger's actual presence instead.
Regardless of trigger type, check the matching entry in
triggers[]
from the
get-workflow --json
read-back above for
details.webhook_url
(re-run the same command if enough time has passed since that read that the claim state could have changed). If present, it is the catch URL external services call — show it to the user plainly; unlike the workflow-level
trigger_url
, it is meant to be shared. Most triggers have no
webhook_url
, and that is normal — do not flag its absence.
If you configured a catch-hook trigger in Phase 2 (a "Webhooks by Zapier" or similar catch-hook app/action) and
details.webhook_url
is still absent once the trigger is active, the installed
@zapier/zapier-sdk
may predate this field — run
workflows-doctor
to check for an update, and in the meantime tell the user to copy the URL from the trigger step in the Zapier editor (
https://zapier.com/durables-editor/<workflow-id>
).
If manual triggering is supported for the workflow, test it only after confirming side effects with the user:
bash
zapier-sdk --experimental trigger-workflow <workflow-id> --input '<JSON>' --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 <trigger-id> --json
Then inspect run history and, if needed, a deployed workflow run:
bash
zapier-sdk --experimental list-workflow-runs <workflow-id> --json
zapier-sdk --experimental get-workflow-run <run-id> --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.
  • The confirmed start mode:
    trigger
    (a Zapier app trigger or a catch-hook trigger — report its
    webhook_url
    if available) or
    manual
    (on-demand only via
    trigger-workflow
    ), and that the Phase 7 gate confirmed the deployed workflow matches it.
  • The Zapier editor link:
    https://zapier.com/durables-editor/<workflow-id>
    .
验证必须确认部署的工作流与阶段3确认的启动模式匹配——不仅仅是确认它已
enabled
。无触发器工作流的
enabled
会显示为
true
,因此仅检查
enabled
会静默通过本应包含触发器但实际没有的工作流。重新读取工作流(触发器声明是异步的,可能静默失败,因此请等待几秒后重新读取或轮询),并基于声明的模式进行验证:
bash
zapier-sdk --experimental get-workflow <workflow-id> --json
  • 启动模式: trigger
    →要求同时满足
    enabled: true
    和非空
    triggers[]
    。空
    triggers[]
    意味着触发器被丢弃或未传入
    --trigger
    ——声明失败或从未尝试。请勿报告工作流已完成。最常见的原因是
    selected_api
    未固定到
    implementation_id
    版本,或
    params
    字段格式错误(见阶段2);发布时传入
    --enabled
    enabled: false
    也是同样的失败。使用修正后的
    --trigger
    重新发布并重新检查。
  • 启动模式: manual
    →要求
    triggers[]
    为空,并确认此工作流在阶段3被刻意分类为手动模式(从未设置触发器)。带有非空
    triggers[]
    的手动工作流也是不匹配的——请停止并与用户协调。它通过
    trigger-workflow
    按需调用;无需验证触发器声明。
triggers[]
是此验证的权威依据。平台的启动模式输入(仅写
manual
标志)不会在任何回读中显示,因此请勿在
get-workflow
或版本信息中查找它——而是读取触发器的实际存在情况。
无论触发器类型如何,从上述
get-workflow --json
回读的
triggers[]
匹配条目中检查
details.webhook_url
(如果自上次读取以来已过足够时间,声明状态可能已更改,请重新运行同一命令)。如果存在,这是外部服务调用的捕获URL——请明确显示给用户;与工作流级别的
trigger_url
不同,它是 meant to be shared的。大多数触发器没有
webhook_url
,这是正常的——请勿将其缺失标记为问题。
如果在阶段2配置了捕获钩子触发器(“Webhooks by Zapier”或类似捕获钩子应用/操作),且触发器激活后
details.webhook_url
仍缺失,可能是安装的
@zapier/zapier-sdk
版本早于此字段的引入——运行
workflows-doctor
检查更新,同时告知用户从Zapier编辑器的触发器步骤中复制URL(
https://zapier.com/durables-editor/<workflow-id>
)。
如果工作流支持手动触发,请在确认无副作用后进行测试:
bash
zapier-sdk --experimental trigger-workflow <workflow-id> --input '<JSON>' --json
如果
trigger-workflow
在工作流运行ID可用前返回触发器ID,请从触发器关联到运行:
bash
zapier-sdk --experimental get-trigger-run <trigger-id> --json
然后检查运行历史,必要时检查已部署工作流的运行记录:
bash
zapier-sdk --experimental list-workflow-runs <workflow-id> --json
zapier-sdk --experimental get-workflow-run <run-id> --json
最后报告以下内容:
  • 工作流名称和ID。
  • workflow.ts
    在本地的存储位置。
  • 测试是否通过。
  • 部署的工作流是否已启用。
  • 工作流是私有还是对账户可见。
  • 确认的启动模式
    trigger
    (Zapier应用触发器或捕获钩子触发器——如果可用,报告其
    webhook_url
    )或
    manual
    (仅通过
    trigger-workflow
    按需运行),且阶段7验证确认部署的工作流与此模式匹配。
  • Zapier编辑器链接:
    https://zapier.com/durables-editor/<workflow-id>

Durable Patterns

持久化模式

Waits

等待

typescript
await ctx.wait("wait-before-followup", 3600);
Place waits at top-level workflow scope, not inside
ctx.step
.
typescript
await ctx.wait("wait-before-followup", 3600);
将等待操作放在工作流顶级作用域,而非
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");
}
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("审批被拒绝");
}

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 (Editor Recognition)).
ctx.step
外部使用
Promise.all()
;每次迭代创建自己的步骤:
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 },
      }),
    ),
  ),
);
循环/扇出步骤使用动态ID(
`process-item-${index}`
),因此编辑器会将其渲染为代码步骤——此模式下这是预期行为(见应用操作步骤格式(编辑器识别))。

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.
对不稳定的外部调用使用步骤级重试:
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: {},
    }),
});
当存在Zapier操作时,优先使用
sdk.runAction
。仅当应用操作无法提供所需行为或数据时,才使用
sdk.fetch