Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion skills/zapier-sdk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,37 @@ The Zapier SDK (`@zapier/zapier-sdk`) is new. Your training data does not contai
4. Never invent action keys. Run `zapier-sdk list-actions <app>` or `zapier.listActions({ app })` first.
5. Never assume input field shapes for dynamic actions. Run `zapier-sdk list-action-input-fields <app> <type> <action>` against the live connection.

## Shell discovery (agents working in the terminal)

Prefer the default output for reading. It already includes slug, key, title, action type, and description in a compact form. Pass `--json` only when you need to pipe into `jq` or another tool. `--json` is a per-command flag (it appears on every subcommand's `--help`, not on `zapier-sdk --help`).

```bash
# Find apps. --search is a substring match, so `notion` returns Motion,
# Potion, Tree-Nation too. Read the default output to confirm you picked
# the right app before moving on.
npx zapier-sdk list-apps --search notion

# List actions on an app. Short slugs like `notion` work everywhere in
# the CLI. Filter by type to trim the list.
npx zapier-sdk list-actions notion --action-type search

# Inspect an action's inputs. Many are dynamic, so verify against a
# live connection.
npx zapier-sdk list-action-input-fields notion search page_by_title

# Run an action end-to-end. Use --json when you want to parse the result.
CONN=$(npx zapier-sdk find-first-connection notion --json | jq -r '.data.id')

npx zapier-sdk run-action notion search page_by_title \
--connection "$CONN" \
--inputs '{"title":"Meeting Notes","exact_match":"no"}' \
--json
```

The CLI's `run-action` accepts the short slug (`notion`, `slack`, `github`), the CLIAPI-suffixed form (`NotionCLIAPI`), or a versioned ID. The CLIAPI-suffix requirement in the "Gotchas" section applies to the typed SDK library method (`sdk.runAction`), not the CLI.

Full CLI walkthrough: [`references/cli.md`](references/cli.md). Complete command inventory: [`references/cli-commands.md`](references/cli-commands.md).

## Authentication

The SDK supports two auth modes. Browser login is the default for local development:
Expand Down Expand Up @@ -143,7 +174,8 @@ const { data: schema } = await zapier.getActionInputFieldsSchema({
- **Two connection reference shapes.** At runtime, pass `connection.id` (UUID) to `runAction`. Inside a durable workflow (`@zapier/zapier-durable`), pass a string alias like `"notion_primary"` that the runtime resolves at deploy time. Don't mix them up.
- **`runAction` returns `{ data: T[] }`, always an array.** Search-style actions typically return one row; downstream code destructures `data: [result]`. Write actions also return an array (usually one element).
- **Dynamic input fields.** Notion database properties, HubSpot custom fields, Jira per-project schemas: none of these are knowable ahead of time. Always run `list-action-input-fields` against the live connection before authoring.
- **App keys have a canonical form.** `list-actions` returns `app_key: "NotionCLIAPI"` for Notion. Pass that exact string to `runAction`'s `appKey` field. The short slug `"notion"` works for `list-actions` and `findFirstConnection`, but `runAction` wants the CLIAPI-suffixed form.
- **App keys: the SDK's `runAction` wants the CLIAPI-suffixed form.** In TypeScript, `sdk.runAction({ appKey })` requires `"NotionCLIAPI"`. Get it from `list-apps` or `list-actions`. Everywhere else (the CLI's `run-action`, `sdk.findFirstConnection`, `sdk.listActions`, and so on) accepts either the short slug (`"notion"`) or the CLIAPI form.
- **Multiple connections per app is normal.** Users often have several (personal + work Gmail, multiple Slack workspaces). Default to `findFirstConnection` / `find-first-connection` and take the first result. Don't stop to disambiguate unless the user explicitly asks; filter by `title` or `owner` if you need a specific one.

## SDK method reference

Expand All @@ -169,6 +201,10 @@ const response = await zapier.fetch("https://api.example.com/data", {

Same auth and audit trail as `runAction`. Use this when the app's Zapier action catalog doesn't cover what you need (bulk reads, custom endpoints, partner-specific APIs).

## Examples

For fill-in-the-blank skeletons (plain script, Zapier Table, durable workflow), see [`references/examples.md`](references/examples.md). These are shape-only; the real, action-key-verified corpus is at https://github.com/zapier/sdk/tree/main/examples. Grep the corpus by app or pattern when you need a working reference.

## Full documentation

- Quickstart: https://docs.zapier.com/sdk/quickstart.md
Expand Down
149 changes: 149 additions & 0 deletions skills/zapier-sdk/references/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Example skeletons

Fill-in-the-blank shells for orientation, not runnable examples. They answer "what does a plain script / Table script / durable workflow *look like*" without pretending to be verified against a real action catalog.

**The real, verified corpus lives at https://github.com/zapier/sdk/tree/main/examples** (or the `examples/` directory of a local clone). Grep it for the app or pattern you actually need.

Every example in that corpus has had its action key verified against the live catalog by CI. The skeletons below have not. Copy the shape, then look up real action keys and inputs before shipping.

## 1. Plain single-action script

The simplest thing you can write with the SDK. One authenticated call. No durable wrapper, no `ctx.step`. Runs wherever your code runs (Node script, Next.js route, Lambda).

Matches the style of `examples/by-app/<app>/*.ts`.

```typescript
// examples/by-app/<app>/<verb-noun>.ts
import { createZapierSdk } from "@zapier/zapier-sdk";

const zapier = createZapierSdk();

async function main() {
const { data: connection } = await zapier.findFirstConnection({
app: "<app-slug>", // short slug from `zapier-sdk list-apps`, e.g. "notion"
owner: "me",
});

const result = await zapier.runAction({
appKey: "<AppCLIAPI>", // CLIAPI-suffixed form, e.g. "NotionCLIAPI"
actionType: "<read|write|search>",
actionKey: "<action-key>", // from `zapier-sdk list-actions <app>`
connection: connection.id,
inputs: {
// Verify shape with `zapier-sdk list-action-input-fields <app> <type> <action>`.
// Mark dynamic inputs with `// dynamic` so future readers know to re-verify.
},
});

console.log(result.data);
}

main().catch(console.error);
```

## 2. Zapier Tables

Tables uses first-class SDK methods (`createTable`, `createTableFields`, `createTableRecords`, `listTables`, `listTableRecords`). No connection lookup, no action discovery. The method names in the SDK reference are the surface.

Matches `examples/by-app/zapier-tables/log-event.ts`.

```typescript
import { createZapierSdk } from "@zapier/zapier-sdk";

const zapier = createZapierSdk();

async function main() {
// Find-or-create the table by name.
const { data: tables } = await zapier.listTables({ search: "<table-name>" });
let table = tables[0];

if (!table) {
const created = await zapier.createTable({
name: "<table-name>",
description: "<what this table is for>",
});
await zapier.createTableFields({
table: created.data.id,
fields: [
{ name: "<field-name>", type: "string" }, // or number | boolean | datetime | json
],
});
table = created.data;
}

// Write records. keyMode: "names" addresses fields by declared name.
await zapier.createTableRecords({
table: table.id,
keyMode: "names",
records: [
{ data: { "<field-name>": "<value>" } },
],
});
}

main().catch(console.error);
```

## 3. Durable workflow (notify-on-event shape)

Deployable to Zapier's infrastructure. Runs on a trigger (webhook, poll, schedule). Every side-effect goes through `ctx.step` with the trigger's primary id in the step name so retries are idempotent.

Matches `examples/by-pattern/notify-on-event/<name>/workflow.ts`.

```typescript
// workflow.ts
import { defineDurable } from "@zapier/zapier-durable";
import { createZapierSdk } from "@zapier/zapier-sdk";
import { z } from "zod";

const sdk = createZapierSdk();

// Constants get pulled out and documented in the leaf directory's README.
const DESTINATION_CONNECTION = "<connection-alias>"; // e.g. "gmail_primary"
const DESTINATION_APP_KEY = "<AppCLIAPI>"; // e.g. "GoogleMailV2CLIAPI"

const InputSchema = z.object({
primaryId: z.string(), // whatever id the trigger emits (charge id, response id, ...)
// ...other fields the trigger provides
});
type Input = z.infer<typeof InputSchema>;

export default defineDurable<Input, { done: boolean }>(
"<workflow-name>",
async (ctx, rawInput) => {
const input = InputSchema.parse(rawInput);

// Step name MUST include `${input.primaryId}`. Without it, a retry
// would double-write. This is the whole reason `defineDurable` exists.
await ctx.step(`<action-name>-${input.primaryId}`, async () =>
sdk.runAction({
appKey: DESTINATION_APP_KEY,
actionType: "<write|search|read>",
actionKey: "<action-key>",
connection: DESTINATION_CONNECTION,
inputs: {
// ...
},
}),
);

return { done: true };
},
);
```

Deploy loop:

```bash
npm install
npx tsc --noEmit workflow.ts
npx zapier-sdk publish-workflow-version --file workflow.ts
```

## What these skeletons deliberately leave out

- **Real action keys and input shapes.** Both are per-app and change over time. Look them up.
- **Sensitive-partner apps.** The corpus excludes some partners (Salesforce, Slack, others). Don't copy skeletons for those without checking.
- **Error handling, retries, structured logging.** The corpus examples don't add these either; the `defineDurable` runtime handles retries.

For anything past the shape, go to the corpus.
Loading