workflows-create
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseZapier 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-substrateCompatibility Gate
兼容性检查
Before using this skill, run the bundle compatibility check. If is not installed or cannot be loaded, run or install from before continuing. If 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-doctorworkflows-doctorworkflows-installworkflows-doctorzapier/agent-skillsworkflows-doctor使用本技能前,请运行包兼容性检查。如果未安装或无法加载,请先运行或从安装。如果报告SDK/技能版本不一致,请遵循其刷新说明,停止当前技能调用,必要时重新加载Agent工作区,并请用户重新发起原始请求。
workflows-doctorworkflows-doctorworkflows-installzapier/agent-skillsworkflows-doctorworkflows-doctorPrerequisites
前置条件
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 --helpPass for versions — the platform resolves it. The sandbox installs with , so a dependency published less than 24h ago is rejected, and publishes several times a day. You do not have to work out which release is old enough: is resolved for you to the newest release that clears that 24h gate (COSUB-956).
latestpnpm install --config.minimumReleaseAge=1440@zapier/zapier-sdklatestThat only applies to and . 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.
@zapier/zapier-durable@zapier/zapier-sdkGive 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 from it with no lockfile. So would install whatever is newest at each tick, and a run that spans a major would replay against a different library than it recorded. The install itself would succeed — falls back from the 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.
package.json"zod":"latest"zodpnpmlatestWhen publishing a workflow version you can omit entirely and the service injects a pinned version for you. injects nothing, so declare there whatever the source imports.
zodrun-durableRanges (, ) are rejected with a 400 for the durable runtime version — whether you pass it as or as the entry in . So 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.
^1.2.3~1.2--zapier-durable-version@zapier/zapier-durable--dependencieslatestEvery package the generated imports must still appear in — the sandbox installs from , not your local , so a missing import (such as ) fails the run with .
workflow.ts--dependencies--dependenciespackage.jsonzodCannot find packageThe 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版本参数传入即可——平台会自动解析合适版本。沙箱环境通过安装依赖,因此发布时间不足24小时的依赖会被拒绝,而每天会发布多次。无需自行计算符合要求的版本:会自动解析为满足24小时限制的最新版本(COSUB-956)。
latestpnpm install --config.minimumReleaseAge=1440@zapier/zapier-sdklatest此规则仅适用于和。它们的版本会在服务端解析,无论是发布还是运行提交时,确切版本会存储在工作流版本或运行记录中——因此工作流会始终使用解析时的版本,无论运行时长多久。
@zapier/zapier-durable@zapier/zapier-sdk其他所有依赖请指定精确版本:
bash
--zapier-durable-version latest
--dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}'未在服务端解析的依赖会按原样存储,且每次运行都会重新生成,无锁文件。因此会在每次运行时安装最新版本,如果跨越了zod的大版本更新,重放时使用的库会与记录时不同。安装本身会成功——pnpm会从标签回退到发布超过24小时的最新版本——这也是需要明确说明的原因:问题会在后续重放不匹配时显现。
package.json"zod":"latest"latest发布工作流版本时可以完全省略,服务会自动注入固定版本。不会注入任何依赖,因此需要声明源代码中导入的所有依赖。
zodrun-durable持久化运行时版本不接受范围版本(如、),无论是通过还是中的条目传入,都会返回400错误。因此是唯一可使用的非精确值。其他依赖可以接受范围版本并按原样存储,但仍建议指定精确版本,原因如上。精确版本必须发布超过24小时。
^1.2.3~1.2--zapier-durable-version--dependencies@zapier/zapier-durablelatest生成的中导入的每个包都必须出现在中——沙箱环境从安装依赖,而非本地,因此缺少的导入(如)会导致运行失败,提示。
workflow.ts--dependencies--dependenciespackage.jsonzodCannot find packagePhase 1: Understand The Intent
阶段1:理解用户意图
Read the user's natural language request and extract:
- Steps and ordering.
- Apps involved.
- Data passed between steps.
- Manual input fields or trigger input fields.
- Conditional logic.
- Waits, callbacks, or human approval gates.
- 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.
阅读用户的自然语言请求,提取以下信息:
- 步骤及顺序。
- 涉及的应用。
- 步骤间传递的数据。
- 手动输入字段或触发器输入字段。
- 条件逻辑。
- 等待、回调或人工审批环节。
- 启动模式(必填——见下文)。
在进一步操作前,将拟议的工作流总结给用户。针对缺失的细节(如目标渠道、文件夹、收件人,或搜索无结果时是否停止)提出明确的澄清问题。
在用户确认工作流架构前,请勿生成代码。
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:
- — the workflow starts on its own, either on a schedule (scheduled) or in response to an external event (event-driven). It is published with
triggerand runs without anyone invoking it.--trigger - — the workflow has no trigger and runs only on-demand via
manual. This is a deliberate choice, not the absence of a trigger.trigger-workflow
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") → , scheduled.
trigger - "when X happens in " / "whenever a new
<app>is created" →<record>, event-driven.trigger - 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 just because no trigger was named. A triggerless workflow published on an unconfirmed assumption is the exact failure this classification exists to prevent.
manualThis decision is required even though the platform treats its start-mode input as optional — the write-only 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.
manualCarry 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> --jsonFor 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> --jsonIf 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 ) as the step and select the model inside it: if the user names a provider or model, set that as the (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 ; its generic completion action is ("Analyze and Return Data"), alongside (from a URL) and (confirm the current set with ).
AICLIAPImodel_idlist-apps --search "AI by Zapier"get_completionextract_contentsearch_contentlist-actions AICLIAPI --action-type write --jsonConfiguring the step. Inspect its fields with . The ones that matter for a generated step:
get_completionlist-action-input-fields AICLIAPI write get_completion --json- (required) — the prompt describing what the AI should do.
instructions - (optional) — the AI provider, needed only when the user names one. Choices are
provider_id,openai,anthropic,google,azure-openai(amazon-bedrock). Setting it is what makeslist-action-input-field-choices AICLIAPI write get_completion provider_id --json's choices resolve.model_id - (required, default
model_id) — the model. For a generic step, pass the default"advanced/auto"— auto-pick a model in the Advanced tier (tiers:"advanced/auto"/standard/advanced) on built-in credentials. When the user names a provider or model, setpremiumfirst, then resolve the valid model for it withprovider_id(the list is empty untillist-action-input-field-choices AICLIAPI write get_completion model_id --inputs '{"provider_id":"<provider>"}' --jsonis set) and pass the matchingprovider_idvalue (for example<provider>/<model>,anthropic/claude-sonnet-5). Do not hardcode a model list — resolve it at build time.openai/gpt-4o - (required, default
authentication_id) —"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"0"); if so, tell the user and use their authentication.authentication_iddepends on this field.model_id - (optional, OBJECT) — extra context fields mapped from earlier steps, merged into the prompt.
inputFields
So a default AI step needs only a prompt. and are required but have working defaults; pass them explicitly with those defaults ( and ) so the inputs are complete, and no connection alias is needed for the built-in path:
model_idauthentication_id"advanced/auto""0"runActiontypescript
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 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."
model_idAssign a short snake_case connection alias for each chosen connection, such as or . Track alias to connection ID. The alias goes in workflow code; the connection ID is passed to test/deploy commands through the JSON.
slack_workgmail_primary--connectionsFor 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"}>' \
--jsonFor 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 , the trigger action key for , the trigger source connection ID for when the trigger requires auth, and trigger input values for . Omit optional fields only when the trigger does not need them.
selected_apiactionauthentication_idparamsFor , use the version-pinned implementation identifier — the returned by SDK discovery (/), such as . Do not use the bare app key () and do not substitute a display name. A bare, unversioned 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 , treat that as a blocker and record it in the build plan before publishing — do not publish a trigger with an unversioned identifier.
selected_apiimplementation_idlist-appsget-appGoogleSheetsAPI@2.3.0GoogleSheetsAPIselected_apiimplementation_idFor , match each field's from . ARRAY fields must be JSON arrays (for example ); STRING fields must be plain strings (for example ). Passing a scalar where an array is expected (or vice versa) fails the trigger claim the same silent way.
paramsvalue_typelist-trigger-input-fields <app> <action>"dow": ["1"]"hod": "9:00 AM"Capture app implementation/version information from SDK discovery output when available, such as , , , or trigger/action result metadata. Do not invent app versions. If no implementation/version binding is exposed, omit rather than guessing.
list-appsget-applist-actions--app-versions"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 (or the specific app name) for its (for example — an illustrative example, not a version to hardcode; confirm the current version via discovery), then for its catch-hook trigger action. "Webhooks by Zapier" itself is no-auth () with empty , but confirm its action key via rather than hardcoding one — as of this writing it exposes both (parsed payload; the common default) and (unparsed body and headers, max 2MB), and that pair of action keys is specific to , 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 for those — confirm each app's actual action key, auth, and param requirements via / (see above) rather than generalizing from "Webhooks by Zapier." Configure them through at publish time (Phase 6) like any other trigger — do not treat them as "no trigger" / manual-only workflows.
list-apps --search "webhook"implementation_idWebHookCLIAPI@1.1.0list-triggers <appKey>authentication_id: nullparamslist-triggers WebHookCLIAPIhook_v2hook_rawWebHookCLIAPIparamslist-triggerslist-trigger-input-fields--trigger对于任何AI/“调用LLM”步骤——总结、提取、分类、生成或分析文本——始终使用“AI by Zapier”(应用密钥)作为步骤,并在其中选择模型:如果用户指定了提供商或模型,则将其设置为(见下文);否则使用默认模型。它使用Zapier内置的AI凭证(无需第三方账户),并按常规Zapier任务计费,因此Agent构建的工作流不会静默路由到用户必须连接并付费的独立原始提供商应用。通过发现它;其通用补全操作是(“分析并返回数据”),还有(从URL提取内容)和(通过确认当前可用操作)。
AICLIAPImodel_idlist-apps --search "AI by Zapier"get_completionextract_contentsearch_contentlist-actions AICLIAPI --action-type write --json配置步骤。通过查看其字段。对于生成步骤,关键字段如下:
get_completionlist-action-input-fields AICLIAPI write get_completion --json- (必填)——描述AI应执行操作的提示词。
instructions - (可选)——AI提供商,仅当用户指定时需要。可选值为
provider_id、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 tier(层级:"advanced/auto"/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"是Zapier的内置AI凭证(显示Zap图标的模型)。默认情况及所有内置模型均使用"0"。用户指定的模型可能无法通过内置凭证使用——此时需要用户自己的AI提供商账户(自定义"0");若如此,请告知用户并使用其凭证。authentication_id依赖此字段。model_id - (可选,OBJECT)——从之前步骤映射的额外上下文字段,会合并到提示词中。
inputFields
因此默认AI步骤仅需要提示词。和为必填字段,但有可用默认值;请显式传递这些默认值(和),确保输入完整,且内置路径无需连接别名:
model_idauthentication_id"advanced/auto""0"runActiontypescript
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”的理由——按上述方式设置即可。仅当用户明确要求使用独立应用,或需要“AI by Zapier”不具备的功能时,才使用原始提供商AI应用(Anthropic、OpenAI、Google AI等)。此时需告知用户该步骤使用其自己的提供商连接和计费,而非“AI by Zapier”。
model_id为每个选定的连接分配简短的蛇形命名连接别名,如或。记录别名与连接ID的映射。别名用于工作流代码;连接ID通过JSON参数传递给测试/部署命令。
slack_workgmail_primary--connections对于步骤间的输出映射,仅在用户确认后运行安全的操作测试。使用当前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_apilist-appsget-appimplementation_idGoogleSheetsAPI@2.3.0GoogleSheetsAPIselected_apiimplementation_idparamslist-trigger-input-fields <应用> <操作>value_type"dow": ["1"]"hod": "9:00 AM"从SDK发现输出(如、、或触发器/操作结果元数据)中捕获应用实现/版本信息(如果可用)。请勿编造应用版本。如果未暴露实现/版本绑定,请省略参数,而非猜测。
list-appsget-applist-actions--app-versions“Webhooks by Zapier”及其他带有捕获钩子触发器的应用(PayPal、Salesforce、Twilio、WordPress、Wufoo、Zillow等)的发现和配置方式与其他触发器应用完全相同——无需特殊处理。通过(或特定应用名称)查找其(例如——示例版本,请勿硬编码;请通过发现确认当前版本),然后通过查找其捕获钩子触发器操作。“Webhooks by Zapier”本身无需认证()且为空,但请通过确认其操作密钥,而非硬编码——截至本文撰写时,它同时暴露(解析后的负载;常用默认值)和(未解析的正文和头信息,最大2MB),这对操作密钥是特有的,并非其他应用的通用模式。其他捕获钩子应用(PayPal、Salesforce、Twilio等)通常需要连接,因为声明其触发器意味着调用提供商API注册订阅。请勿假设它们无需认证或为空——请通过/(见上文)确认每个应用的实际操作密钥、认证和参数要求,而非基于“Webhooks by Zapier”进行泛化。在阶段6发布时通过参数配置它们,与其他触发器一样——请勿将其视为“无触发器”/仅手动工作流。
list-apps --search "webhook"implementation_idWebHookCLIAPI@1.1.0list-triggers <appKey>authentication_id: nullparamslist-triggers WebHookCLIAPIhook_v2hook_rawWebHookCLIAPIparamslist-triggerslist-trigger-input-fields--triggerPhase 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. 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).
manualAsk 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分类的两种模式之一。是用户确认的刻意选择——切勿将其显示为“无触发器”或隐含未提及触发器。如果此时模式仍不明确,请与用户解决后再继续(阶段1)。
manual请用户确认后再生成文件,包括明确确认启动模式。
Phase 4: Generate The Workflow Project
阶段4:生成工作流项目
Create a workflow directory:
text
<working-directory>/
<kebab-case-workflow-name>/
package.json
workflow.tspackage.json--dependencieslatestjson
{
"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 for now to avoid type-check failures from SDK/durable transitive type declarations:
--skipLibCheckjson
{
"scripts": {
"build": "tsc --target es2022 --module nodenext --moduleResolution nodenext --skipLibCheck --outDir dist workflow.ts"
}
}workflow.ts- Import from
defineDurable.@zapier/zapier-durable - Import from
createZapierSdk.@zapier/zapier-sdk - Create the SDK client once at module level: above
const sdk = createZapierSdk()defineDurable - Use Zod for input validation when the workflow has input.
- Keep external side effects (app actions, fetches) inside calls.
ctx.step - Make each app action exactly one whose body is a single
ctx.stepcall — onereturn sdk.runAction({...})per step.runAction - 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 for the first result, or
stepVar.data[0].fieldfor the whole array.stepVar.data - Normalize manual input before Zod validation. In the current path, input may arrive as a JSON string rather than an already-parsed object.
run-durable
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.tspackage.json--dependencieslatestjson
{
"type": "module",
"dependencies": {
"@zapier/zapier-sdk": "latest",
"@zapier/zapier-durable": "latest",
"zod": "4.3.6"
},
"devDependencies": {
"typescript": "latest"
}
}如果添加构建脚本,请暂时使用以避免SDK/durable传递类型声明导致的类型检查失败:
--skipLibCheckjson
{
"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验证前规范化手动输入。在当前路径中,输入可能以JSON字符串形式传入,而非已解析的对象。
run-durable
对于有输入的工作流,使用以下辅助模式:
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.
defineDurablerundefineDurable("workflow-name", async (ctx, input) => { ... })defineDurable({ name: "workflow-name", inputSchema, outputSchema, description, run: async (ctx, input) => { ... } })ctxruninputasync (ctx) => { ... }durable.run is not a function- — a bare function with no name. The function is treated as an options object, so
defineDurable(async (ctx, input) => { ... })is never set. This is the most common mistake.run - — object missing
defineDurable({ name: "workflow-name" }).run - —
defineDurable({ name: "workflow-name", run: someNonFunction })is not a function.run
durable.run is not a functiondefineDurable@zapier/zapier-durable@zapier/zapier-sdknamerunDefault to this parser-friendly shape — module-level , hoisted app-key/connection constants, and a bare body for each app action:
sdkrunActiontypescript
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 };
},
);生成可转换为有意义步骤图的持久化源代码。避免过度动态构造。
defineDurablerundefineDurable("workflow-name", async (ctx, input) => { ... })defineDurable({ name: "workflow-name", inputSchema, outputSchema, description, run: async (ctx, input) => { ... } })ctxruninputasync (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 functiondefineDurable@zapier/zapier-durable@zapier/zapier-sdknamerun默认使用以下解析友好的格式——模块级别、提升的应用密钥/连接常量,以及每个应用操作的简洁主体:
sdkrunActiontypescript
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 as an app-action step (with the app icon) when its body is a single call with , , and (object literal, or a that resolves to one; the / spellings also work). A string-literal step id () and an inline callback are the recognized form; object form works too.
ctx.stepsdk.runAction({...})appKeyactionTypeactionKeyconstappactionctx.step("create-todoist-task", ...)async () => ...ctx.step({ name, run })Other steps render as plain code steps — for example a step with no , or with more than one, or one created in a loop with a dynamic id (). That is expected, not a regression; loops and fan-out legitimately need dynamic ids.
runAction`process-item-${index}`当的主体为单个带有、和的调用(对象字面量或解析为对象的;/拼写也可)时,编辑器会将其渲染为应用操作步骤(带应用图标)。字符串字面量步骤ID()和内联回调是可识别的格式;对象形式也可使用。
ctx.stepappKeyactionTypeactionKeysdk.runAction({...})constappactionctx.step("create-todoist-task", ...)async () => ...ctx.step({ name, run })其他步骤会渲染为普通代码步骤——例如无的步骤、包含多个的步骤,或通过循环创建的带动态ID的步骤()。这是预期行为,而非退化;循环和扇出确实需要动态ID。
runActionrunAction`process-item-${index}`Phase 5: Test The Workflow
阶段5:测试工作流
Build from :
source_filesworkflow.tsbash
SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')"Build the JSON from the selected aliases. It's a nested object — each alias maps to an object holding a (never a bare string). The same shape is used for in Phase 6:
connectionsconnectionIdpublish-workflow-versionjson
{
"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>' \
--privaterun-durablebash
zapier-sdk --experimental get-durable-run <run-id> --jsonTerminal success means the run has , an expected , , and top-level . Terminal failure means or a non-null . Continue polling while the run is initialized or started.
status: "finished"outputerror: nullerrors: []status: "failed"errorFix code and retest until the behavior matches the confirmed plan.
从构建:
workflow.tssource_filesbash
SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')"从选定的别名构建JSON。它是嵌套对象——每个别名映射到包含的对象(切勿使用裸字符串)。此格式与阶段6中使用的格式相同:
connectionsconnectionIdpublish-workflow-versionjson
{
"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>' \
--privaterun-durablebash
zapier-sdk --experimental get-durable-run <run-id> --json终止成功意味着运行记录的,有预期的,,且顶级。终止失败意味着或非空。当运行记录处于初始化或启动状态时,继续轮询。
status: "finished"outputerror: nullerrors: []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 \
--jsonOmit only if the user explicitly wants the workflow visible to the broader account.
--privateCapture the returned workflow ID. Then decide how to ship the code:
-
Direct publish (the default below): publish the first version straight away with. This is the legitimate no-open-draft case — the container was just created, so no draft exists to publish past.
publish-workflow-version -
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> \ --jsonPass the same,--dependencies,--zapier-durable-version,--connections, and — for a--app-versionsworkflow —Start mode: triggervalues Phase 6 would have passed to the publish. Then hand the user the draft's editor link —--trigger, using thehttps://zapier.com/durables-editor/<workflow-id>/draft/<draft-slug>/workflow.tsfrom the draft response; the final segment is one of the draft'sslugkeys (source_filesin 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: aworkflow.tsworkflow publishes withStart mode: manual(--manual); apublish-workflow-draft <workflow-id> <draft-id> --manual --enabled --jsonworkflow's draft already holds itsStart mode: trigger, so publish without--trigger(--manual). Never passpublish-workflow-draft <workflow-id> <draft-id> --enabled --jsonand--triggertogether. Publishing consumes the draft. Skip Phase 7's version read-backs if nothing was published.--manual
For a direct publish, the current SDK CLI expects as a JSON object, not a path to .
source_filesworkflow.tsFor publish, use the same nested shape as — each alias maps to an object holding a :
connectionsrun-durableconnectionIdjson
{
"slack_work": { "connectionId": "123-or-uuid" },
"gmail_primary": { "connectionId": "456-or-uuid" }
}If app implementation/version information is known, build the payload:
--app-versionsjson
{
"slack": { "implementation_name": "SlackCLIAPI", "version": "optional" }
}Omit the entire flag when no app implementation/version binding is needed. Likewise, omit when the workflow has no connection bindings. Do not pass placeholder text like "if needed" to the CLI.
--app-versions--connectionsFor trigger-backed workflows, build the JSON from Phase 2. Keep version-pinned to the (for example ) and keep each field shaped to its (see Phase 2) — a bare app key or a wrong param shape makes the trigger claim fail silently at publish:
triggerselected_apiimplementation_idGoogleSheetsAPI@2.3.0paramsvalue_typejson
{
"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 using the config captured in Phase 2, the same as any other app trigger.
--triggerHow 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: → the publish passes and not ; → it passes and not . Pass exactly one — the platform contract is a discriminated union (a version is either triggered or , 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--trigger--manualStart mode: manual--manual--triggermanual: trueStart mode: trigger--trigger--manualbash
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 \
--jsonStart mode: manual--trigger--manualbash
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--trigger--manualDo not use the old , , , or flags. The current trigger publish path is the single JSON object.
--trigger-app--trigger-action--trigger-auth--trigger-params--triggerIf 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 : fold your changes into that draft and publish it ( + ), or — with their explicit confirmation, since it drops the draft's unpublished work — discard the draft () and retry the direct publish.
workflows-modifyupdate-workflow-draftpublish-workflow-draftdiscard-workflow-draft在创建工作流前,决定其是否应为私有。对于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。发布后草稿会被消耗。如果未发布任何内容,请跳过阶段7的版本回读。--manual
对于直接发布,当前SDK CLI期望为JSON对象,而非的路径。
source_filesworkflow.ts发布时,使用与相同的嵌套格式——每个别名映射到包含的对象:
run-durableconnectionsconnectionIdjson
{
"slack_work": { "connectionId": "123-or-uuid" },
"gmail_primary": { "connectionId": "456-or-uuid" }
}如果已知应用实现/版本信息,构建负载:
--app-versionsjson
{
"slack": { "implementation_name": "SlackCLIAPI", "version": "optional" }
}当无需应用实现/版本绑定时,省略整个标志。同样,当工作流无连接绑定时,省略。请勿向CLI传递“如需”之类的占位文本。
--app-versions--connections对于基于触发器的工作流,从阶段2构建JSON。确保固定到版本(例如),且每个字段的格式符合其(见阶段2)——裸应用密钥或错误的参数格式会导致触发器声明在发布时静默失败:
triggerselected_apiimplementation_idGoogleSheetsAPI@2.3.0paramsvalue_typejson
{
"selected_api": "GoogleSheetsAPI@2.3.0",
"action": "new_row",
"authentication_id": "connection-id-or-null",
"params": {}
}“Webhooks by Zapier”或其他捕获钩子触发器是真实的触发器——使用阶段2捕获的配置,通过参数发布,与其他应用触发器相同。
--trigger发布方式直接由阶段3确认的启动模式决定——两者并非同等默认选项;需严格遵循用户选择的模式。
发布前,请确认负载与声明的启动模式匹配:→发布时传入,不传入;→发布时传入,不传入。仅传入其中一个——平台约定为互斥选项(版本要么是触发式,要么是,绝不能同时满足),如果同时传入会被拒绝;CLI也会在客户端阻止这种情况。发布的工作流必须携带选定的触发器,或明确标记为手动。在此处捕获任何不一致,避免在阶段7才发现触发器缺失或遗漏。
启动模式: trigger--trigger--manual启动模式: manual--manual--triggermanual: true启动模式: trigger--trigger--manualbash
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--trigger--manualbash
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请勿使用旧的、、或标志。当前触发器发布路径为单个JSON对象。
--trigger-app--trigger-action--trigger-auth--trigger-params--trigger如果发布因开放草稿冲突被拒绝,可能是有人(通常是用户在Zapier编辑器中)在此流程中途创建了该工作流的草稿。开放草稿始终包含未发布的工作,因此切勿静默跳过它。告知用户并提供与相同的选择:将你的更改合并到该草稿并发布( + ),或——在用户明确确认(因为这会丢弃草稿中的未发布工作)——丢弃草稿()并重试直接发布。
workflows-modifyupdate-workflow-draftpublish-workflow-draftdiscard-workflow-draftPhase 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> --jsonGate On The Start Mode (Required)
基于启动模式的验证(必填)
Verification must confirm the deployed workflow matches the start mode confirmed in Phase 3 — not merely that it is . A triggerless workflow reads back as , so an 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:
enabledenabled: trueenabledbash
zapier-sdk --experimental get-workflow <workflow-id> --json- → require both
Start mode: triggerand a non-emptyenabled: true. An emptytriggers[]means the trigger was dropped ortriggers[]was omitted — the claim failed or was never attempted. Do not report the workflow as done. The most common cause is a--triggerthat is not version-pinned to theselected_api, or aimplementation_idfield with the wrong shape (see Phase 2);paramsafter publishing withenabled: falseis the same failure. Re-publish with a corrected--enabledand re-check.--trigger - → require
Start mode: manualto be empty by design, and confirm this workflow was deliberately classified manual in Phase 3 (never triggered). A manual workflow with a non-emptytriggers[]is also a mismatch — stop and reconcile with the user. It is invoked on-demand viatriggers[]; there is no trigger claim to verify.trigger-workflow
triggers[]manualget-workflowRegardless of trigger type, check the matching entry in from the read-back above for (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 , it is meant to be shared. Most triggers have no , and that is normal — do not flag its absence.
triggers[]get-workflow --jsondetails.webhook_urltrigger_urlwebhook_urlIf you configured a catch-hook trigger in Phase 2 (a "Webhooks by Zapier" or similar catch-hook app/action) and is still absent once the trigger is active, the installed may predate this field — run to check for an update, and in the meantime tell the user to copy the URL from the trigger step in the Zapier editor ().
details.webhook_url@zapier/zapier-sdkworkflows-doctorhttps://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>' --jsonIf returns a trigger ID before a workflow run ID is available, bridge from trigger to run:
trigger-workflowbash
zapier-sdk --experimental get-trigger-run <trigger-id> --jsonThen 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> --jsonFinish by reporting:
- Workflow name and ID.
- Where lives locally.
workflow.ts - Whether testing passed.
- Whether the deployed workflow is enabled.
- Whether the workflow is private or account-visible.
- The confirmed start mode: (a Zapier app trigger or a catch-hook trigger — report its
triggerif available) orwebhook_url(on-demand only viamanual), and that the Phase 7 gate confirmed the deployed workflow matches it.trigger-workflow - The Zapier editor link: .
https://zapier.com/durables-editor/<workflow-id>
验证必须确认部署的工作流与阶段3确认的启动模式匹配——不仅仅是确认它已。无触发器工作流的会显示为,因此仅检查会静默通过本应包含触发器但实际没有的工作流。重新读取工作流(触发器声明是异步的,可能静默失败,因此请等待几秒后重新读取或轮询),并基于声明的模式进行验证:
enabledenabledtrueenabledbash
zapier-sdk --experimental get-workflow <workflow-id> --json- →要求同时满足
启动模式: trigger和非空enabled: true。空triggers[]意味着触发器被丢弃或未传入triggers[]——声明失败或从未尝试。请勿报告工作流已完成。最常见的原因是--trigger未固定到selected_api版本,或implementation_id字段格式错误(见阶段2);发布时传入params但--enabled也是同样的失败。使用修正后的enabled: false重新发布并重新检查。--trigger - →要求
启动模式: manual为空,并确认此工作流在阶段3被刻意分类为手动模式(从未设置触发器)。带有非空triggers[]的手动工作流也是不匹配的——请停止并与用户协调。它通过triggers[]按需调用;无需验证触发器声明。trigger-workflow
triggers[]manualget-workflow无论触发器类型如何,从上述回读的匹配条目中检查(如果自上次读取以来已过足够时间,声明状态可能已更改,请重新运行同一命令)。如果存在,这是外部服务调用的捕获URL——请明确显示给用户;与工作流级别的不同,它是 meant to be shared的。大多数触发器没有,这是正常的——请勿将其缺失标记为问题。
get-workflow --jsontriggers[]details.webhook_urltrigger_urlwebhook_url如果在阶段2配置了捕获钩子触发器(“Webhooks by Zapier”或类似捕获钩子应用/操作),且触发器激活后仍缺失,可能是安装的版本早于此字段的引入——运行检查更新,同时告知用户从Zapier编辑器的触发器步骤中复制URL()。
details.webhook_url@zapier/zapier-sdkworkflows-doctorhttps://zapier.com/durables-editor/<workflow-id>如果工作流支持手动触发,请在确认无副作用后进行测试:
bash
zapier-sdk --experimental trigger-workflow <workflow-id> --input '<JSON>' --json如果在工作流运行ID可用前返回触发器ID,请从触发器关联到运行:
trigger-workflowbash
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 - 测试是否通过。
- 部署的工作流是否已启用。
- 工作流是私有还是对账户可见。
- 确认的启动模式:(Zapier应用触发器或捕获钩子触发器——如果可用,报告其
trigger)或webhook_url(仅通过manual按需运行),且阶段7验证确认部署的工作流与此模式匹配。trigger-workflow - 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.steptypescript
await ctx.wait("wait-before-followup", 3600);将等待操作放在工作流顶级作用域,而非内部。
ctx.stepCallbacks
回调
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 outside ; each iteration creates its own step:
Promise.all()ctx.steptypescript
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 (), so the editor renders them as code steps — expected for this pattern (see App-Action Step Shape (Editor Recognition)).
`process-item-${index}`在外部使用;每次迭代创建自己的步骤:
ctx.stepPromise.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 when a Zapier action exists. Use only when the app action cannot provide the needed behavior or data.
sdk.runActionsdk.fetch对不稳定的外部调用使用步骤级重试:
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.runActionsdk.fetch