# null Source: https://docs.zapier.com/mcp/clients Zapier MCP integrates with a variety of Model Context Protocol (MCP) clients, allowing you to connect your AI workflows with different development environments and platforms. ## Supported Clients Zapier MCP currently supports the following official MCP clients: ### AI Platforms & APIs * **Anthropic API** - Direct integration with Anthropic's API * **Claude** - Claude desktop application * **Claude Code** - Claude's code-focused interface * **OpenAI API** - Direct integration with OpenAI's API ### Development Environments * **Cursor** - AI-powered code editor * **VS Code** - Visual Studio Code with MCP extension * **Windsurf** - Modern development environment ### Voice Assistants * **ElevenLabs** - AI-powered voice assistant * **Vapi** - Voice agents for developers ### Programming Languages * **Python** - MCP client for Python applications * **TypeScript** - MCP client for TypeScript/JavaScript applications ### Other Integrations * **Other** - Additional community and third-party clients ## Request a New Client Looking to get your MCP client featured as an official client compatible with Zapier MCP? [Submit a Client Request →](https://mcp.zapier.app/client-request) ### Requirements for New Clients To ensure compatibility with Zapier MCP, your client must: * **Support streamable HTTP** - We no longer support SSE MCP servers Not a requirement, but preferred: * **Implement OAuth with Dynamic Client Registration** - As outlined in the [official MCP authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-flow) These requirements ensure secure, scalable integration with Zapier's infrastructure while providing the best experience for users. # Submit an issue Source: https://docs.zapier.com/mcp/help/bug # Chatbot Helper Source: https://docs.zapier.com/mcp/help/chat-bot # Join our Community Source: https://docs.zapier.com/mcp/help/community # Enterprise Access Source: https://docs.zapier.com/mcp/help/enterprise-access # Need Higher Limits Source: https://docs.zapier.com/mcp/help/higher-limits # null Source: https://docs.zapier.com/mcp/home # Zapier MCP Connect your AI to thousands of apps with the [Model Context Protocol](https://modelcontextprotocol.io/). **Beta Status**: Zapier MCP is currently in beta and part of your existing [Zapier Plan](https://zapier.com/pricing). ## What is Zapier MCP? Zapier MCP (Model Context Protocol) is a standardized way to connect AI assistants to thousands of apps and services. It enables your AI to take real actions—like sending messages, creating tasks, or updating records—using natural language commands. Connect Anthropic's [Claude](https://claude.ai/) to Zapier's 8,000+ app integrations without writing code. Just describe what you want in natural language. Build AI applications with direct access to Zapier's ecosystem through APIs and developer tools like Cursor and Windsurf. ## Key Features * **8,000+ App Connections**: Access Zapier's massive library of pre-built integrations * **30,000+ Actions**: Enable your AI to perform specific tasks or searches across apps * **Natural Language**: Just describe what you want—no complex commands to have the AI execute actions * **Secure by Default**: Zapier manages authentication, encryption, and rate limiting * **Multiple Client Support**: Create a single server that works with Claude, Cursor, Windsurf, and more ## Quick Comparison ### Zapier MCP vs Zapier Agents | Feature | Zapier MCP | Zapier Agents | | ------------------------ | -------------------------------- | --------------------------- | | **Best For** | Developers & Claude users | Non-technical users | | **Setup** | Direct LLM/AI integration | Web-based interface | | **Coding Required** | No (for Claude) / Yes (for APIs) | No | | **Multi-step Workflows** | One action at a time | Complex workflows/behaviors | | **Background Execution** | No | Yes | | **Interface** | Within your AI tool (MCP Client) | Zapier web app | ## Beta Limits During beta, all users have the following rate limits: * **80 tool calls per hour** (rolling window) * **160 tool calls per day** (rolling 24 hours) * **300 tool calls per month** (calendar month) [Join the waitlist](https://mcpp.zapier.app/waitlis) for higher limits when we exit beta. **Enterprise Users**: Zapier MCP doesn't currently support app and action restrictions on Enterprise accounts. If you'd still like to beta test Zapier MCP, reach out [here](https://mcpp.zapier.app/enterprise-access). ## Get Started Ready to get started? [Follow our quickstart guide](/mcp/quickstart) to connect your AI assistant to Zapier MCP in just a few minutes. ## Popular Tools Send messages, create channels, update status Add rows, update data, create spreadsheets, find data Send emails, search messages, manage labels Create tasks, update epics, search stories Create tasks, update projects, add comments Create issues, manage PRs, update repos Manage contacts, create deals, update records Send messages, manage servers, create events [View all 8,000+ integrations →](https://zapier.com/apps) ## Example Use Cases ### Personal Productivity * "Add this meeting to my calendar and notify the team in Slack" * "Create a task in Asana for each unread email from clients" * "Save this information to my Notion database" ### Sales & Marketing * "Find all leads from last week and add them to our CRM" * "Send a personalized follow-up email to each new subscriber" * "Update the deal status in HubSpot and notify the sales channel" ### Development & Operations * "Create a GitHub issue for this bug report" * "Post deployment notes to our Discord channel" * "Update the project status in Linear and notify stakeholders" # Quickstart Source: https://docs.zapier.com/mcp/quickstart Get up and running with Zapier MCP in 5 minutes This guide will help you connect your AI assistant to Zapier MCP and run your first tool call. We'll use Claude as an example, but the process is similar for other MCP clients. **Beta Status**: Zapier MCP is currently in beta and part of your existing [Zapier Plan](https://zapier.com/pricing). **Enterprise Users**: Zapier MCP doesn't currently support app and action restrictions on Enterprise accounts. If you'd still like to beta test Zapier MCP, reach out [here](https://mcpp.zapier.app/enterprise-access). ## Prerequisites * A Zapier account ([sign up free](https://zapier.com/sign-up)) * An MCP-compatible AI client (Claude, Cursor, Windsurf, etc.) * At least one app connected to your Zapier account ## Step 1: Create an MCP Server First, let's create your MCP server in Zapier: 1. Go to [mcp.zapier.com](https://mcp.zapier.com) 2. Click **"+ New MCP Server"** 3. Select your AI client from the dropdown (e.g., "Claude") 4. Give your server a name (e.g., "My MCP Assistant") 5. Click **"Create MCP Server"** You'll be taken to the configuration page for your new server. ## Step 2: Configure Your Tools Now add the Zapier actions your AI can use: 1. Click **"+ Add tool"** to add your first tool 2. Type the name of an app (e.g., "Slack", "Google Sheets") 3. From the list of available actions, pick the action you want (e.g., "Send Channel Message", "Create Spreadsheet Row") 4. Connect your app account if prompted 5. Fill out any mandatory or optional fields for the action * You can let AI suggest values for certain fields 6. Click **"Save"** Repeat this process for any other tools you want your AI to access. ## Step 3: Connect Your AI Client Click on the **Connect** which will give you step by step instructions on how to configure your selected client. Or you can visit the [MCP Clients](/mcp/clients/overview) page to find setup instructions for your specific AI assistant. Each client has its own connection process and requirements. ## Step 4: Test Your Connection Once connected, test that everything is working: 1. Ask your AI: "What tools do I have available?" 2. Your AI should list the Zapier actions you configured 3. Try a simple command like: "Send a test message to #general in Slack" (If you have enabled the "Send Channel Message" in Slack) ## Step 5: Start Automating Now you can use natural language to interact with your connected apps: * "Add a row to my sales spreadsheet with today's numbers" * "Create a task in Asana for the marketing team" * "Search for recent emails from John" * "Post a message to Discord announcing the meeting" ### Tips for Better Results 1. **Be Clear and Specific** * Include all necessary details in your request * Specify recipients, dates, and other important information 2. **Provide Context** * The clients can maintain context throughout a conversation * Reference previous messages: "Send that report to Sarah in Slack" 3. **Review Before Confirming** * The clients will often show you what it plans to do * You can ask for changes before execution ## Common Use Cases ### Personal Productivity ```text "Check my unread emails from today and summarize the important ones" "Create a reminder in Todoist to prepare for tomorrow's presentation" "Add this week's expenses to my tracking spreadsheet" ``` ### Team Collaboration ```text "Post a message in #general about the server maintenance tonight" "Create a meeting invite for the team sync next Monday at 2 PM" "Update the project status in Notion to 'In Review'" ``` ### Content Creation ```text "Draft a blog post about AI productivity tips and save it to Google Docs" "Schedule a tweet for tomorrow morning about our new feature" "Create a new page in Confluence for the API documentation" ``` ## Privacy & Security ### Data Handling * Each client has their own Data and Usage Policies * For Zapier, the Actions are logged in your MCP server's History tab * No data is stored by Zapier unless required for the action ## Advanced Features ### Action Chaining Have the LLM perform multiple actions in sequence: ```text "Find all emails from clients today, summarize them, and create tasks for any that need follow-up" ``` ### Conditional Logic Have the LLM make decisions based on results: ```text "Check if I have any meetings this afternoon. If not, schedule time for deep work" ``` ### Data Transformation Have the LLM process data between actions: ```text "Get my recent expenses from the spreadsheet and create a summary report in Notion" ``` ## Troubleshooting * Ensure you've completed the integration setup * Verify the Integration URL was entered correctly * Check that you've added at least one tool in your MCP server configuration * Make sure you're enabling the Zapier MCP connection in whatever client you are using * Verify you've authenticated the specific app in Zapier * Some apps require re-authentication periodically * Check the app's permissions in your Zapier account * Make sure the specific action is configured in your MCP server * Some actions may have required fields - provide all necessary information * Check your [usage](/mcp/usage) limits ## Next Steps * [Explore available clients](/mcp/clients/overview) - Set up MCP with other AI assistants * [Understand rate limits](/mcp/usage/overview) - Learn about beta limitations * [Browse integrations](https://zapier.com/apps) - Discover what apps you can connect * [Compare with Zapier Agents](https://zapier.com/agents) - Explore our no-code/no-setup alternative # Usage & Billing Overview Source: https://docs.zapier.com/mcp/usage/overview Understand how Zapier MCP usage works, rate limits, and pricing during beta Zapier MCP provides access to thousands of apps and actions through the Model Context Protocol. This guide explains how usage is tracked, what limits apply, and how billing works. **Beta Status**: Zapier MCP is currently in beta and part of your existing [Zapier Plan](https://zapier.com/pricing). ## Key Concepts ### What counts as a tool call? A **tool call** in Zapier MCP is any operation that interacts with an external app: * ✅ Sending a Slack message * ✅ Creating a Google Sheets row * ✅ Searching for emails in Gmail * ✅ Creating a task in Asana * ❌ Asking what tools are available * ❌ Getting help or documentation * ❌ Failed tool calls due to configuration errors ## Beta Limits During beta, all users have the following rate limits: * **80 tool calls per hour** (rolling window) * **160 tool calls per day** (rolling 24 hours) * **300 tool calls per month** (calendar month) [Join the waitlist](https://mcpp.zapier.app/waitlis) for higher limits when we exit beta. **Enterprise Users**: Zapier MCP doesn't currently support app and action restrictions on Enterprise accounts. If you'd still like to beta test Zapier MCP, reach out [here](https://mcpp.zapier.app/enterprise-access). ## How Usage is Calculated ### What uses a tool call? Each successful API call to an external service counts as one tool call: ```text Examples that use 1 tool call each: - "Send a message to Slack" - "Create a row in Google Sheets" - "Find contacts in HubSpot" - "Update a Notion page" ``` ### What doesn't use a tool call? These operations are free and don't count against your limit: * Listing available tools * Authentication and setup * Viewing action history * Failed tool calls due to: * Invalid authentication * Missing required fields * Configuration errors ### Batch Operations Some operations may use multiple tool calls: ```text "Add 5 rows to a spreadsheet" = 5 tool calls "Send emails to 3 people" = 3 tool calls "Search and update 10 records" = 11 tool calls (1 search + 10 updates) ``` ## Monitoring Your Usage ### Check Current Usage Visit [mcp.zapier.com](https://mcp.zapier.com) to see usage across all servers. ![Usage](https://mintlify.s3.us-west-1.amazonaws.com/zapier-82f0e938/images/mcp/usage.webp) ### Usage Notifications Zapier will notify you when you approach rate limits. You'll see clear error messages if you exceed any limit. ## What Happens at the Limit? When you reach any rate limit: 1. **Immediate Effect**: New tool call requests will be declined 2. **Error Message**: Your AI client will receive a clear error about the limit exceeded 3. **No Interruption**: Your regular Zaps continue working normally ### Options When You Hit the Limit Rate limits use rolling windows: * Hourly limit: Wait up to 60 minutes * Daily limit: Wait up to 24 hours * Monthly limit: Resets on the 1st at 12:00 AM UTC Need higher limits? [Join the waitlist](https://mcpp.zapier.app/waitlist) for: * Increased rate limits * Priority access to new features * Early access to production plans ## Frequently Asked Questions Zapier MCP doesn't currently support app and action restrictions on Enterprise accounts. If you'd still like to beta test Zapier MCP, reach out [here](https://mcpp.zapier.app/enterprise-access). Yes, all successful tool calls count, including tests. During beta, the 300/month limit applies to all users. [Join the waitlist](https://mcpp.zapier.app/waitlist) for higher limit options. [Join the waitlist](https://mcpp.zapier.app/waitlist) to be notified when higher limits become available. Yes, MCP tool calls are completely separate from Zap tasks. Using MCP doesn't affect your Zap task limit. Currently, Zapier MCP itself is free and included in your existing Zapier plan. Available to all Zapier users (except some Enterprise accounts) Beta period end date TBD. When Zapier MCP exits beta: * Production pricing will be announced * Beta users will receive advance notice ## Next Steps * [Join the waitlist](https://mcpp.zapier.app/waitlist) for higher limits * [Monitor your usage](https://mcp.zapier.com) * [Try Zapier Agents](https://zapier.com/agents) for a no-code alternative Rate limits help ensure quality service for all beta users. If you consistently need more tool calls, consider [joining the waitlist](https://mcpp.zapier.app/waitlist) or converting repetitive MCP actions into automated Zaps. # Core Reference Source: https://docs.zapier.com/platform/build-cli/core Reference for `zapier-platform-core` Most functions get called with `(z, bundle)`. This document is a reference for how to use these objects. > If you use TypeScript, you can import `ZObject`, `Bundle` and `PerformFunction` from `zapier-platform-core`. ## `z` Object We provide several methods off of the `z` object, which is provided as the first argument to all function calls in your integration. > The `z` object is passed into your functions as the first argument - IE: `perform: (z) => {}`. ### `z.request([url], options)` `z.request([url], options)` is a promise based HTTP client with some Zapier-specific goodies. See [Making HTTP Requests](/platform/build-cli/overview#making-http-requests). `z.request()` will [percent-encode](https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding) non-ascii characters and these reserved characters: ``:$/?#[]@$&+,;=^@`\``. Use [`skipEncodingChars`](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#requestschema) to modify this behaviour. ### `z.console` `z.console.log(message)` is a logging console, similar to Node.js `console` but logs remotely, as well as to stdout in tests. See [Log Statements](/platform/build-cli/overview#console-logging) ### `z.dehydrate(func, inputData)` `z.dehydrate(func, inputData)` is used to lazily evaluate a function, perfect to avoid API calls during polling or for reuse. See [Dehydration](/platform/build-cli/overview#dehydration). ### `z.dehydrateFile(func, inputData)` `z.dehydrateFile` is used to lazily download a file, perfect to avoid API calls during polling or for reuse. See [File Dehydration](/platform/build-cli/overview#file-dehydration). ### `z.stashFile(bufferStringStream, [knownLength], [filename], [contentType])` `z.stashFile(bufferStringStream, [knownLength], [filename], [contentType])` is a promise based file stasher that returns a URL file pointer. See [Stashing Files](/platform/build-cli/overview#stashing-files). ### `z.JSON` `z.JSON` is similar to the JSON built-in like `z.JSON.parse('...')`, but catches errors and produces nicer tracebacks. ### `z.hash()` `z.hash()` is a crypto tool for doing things like `z.hash('sha256', 'my password')` ### `z.errors` `z.errors` is a collection error classes that you can throw in your code, like `throw new z.errors.HaltedError('...')`. The available errors are: * `Error` (*added in v9.3.0*) - Stops the current operation, allowing for (auto) replay. Read more on [General Errors](/platform/build-cli/overview#general-errors) * `HaltedError` - Stops current operation, but will never turn off Zap. Read more on [Halting Execution](/platform/build-cli/overview#halting-execution) * `ExpiredAuthError` - Stops the current operation and emails user to manually reconnect. Read more on [Stale Authentication Credentials](/platform/build-cli/overview#stale-authentication-credentials) * `RefreshAuthError` - (OAuth2 or Session Auth) Tells Zapier to refresh credentials and retry operation. Read more on [Stale Authentication Credentials](/platform/build-cli/overview#stale-authentication-credentials) * `ThrottledError` (*new in v11.2.0*) - Tells Zapier to retry the current operation after a delay specified in seconds. Read more on [Handling Throttled Requests](/platform/build-cli/overview#handling-throttled-requests) For more details on error handling in general, see [here](/platform/build-cli/overview#error-handling). ### `z.cursor` The `z.cursor` object exposes two methods: * `z.cursor.get(): Promise` * `z.cursor.set(string): Promise` Any data you `set` will be available to that Zap for about an hour (or until it's overwritten). For more information, see: [paging](/platform/build-cli/overview#paging). ### `z.generateCallbackUrl()` The `z.generateCallbackUrl()` will return a callback URL your app can `POST` to later for handling long running tasks (like transcription or encoding jobs). In the meantime, the Zap and Task will wait for your response and the user will see the Task marked as waiting. For example, in your `perform` you might do: ```js const perform = async (z, bundle) => { // something like this url: // https://zapier.com/hooks/callback/123/abcdef01-2345-6789-abcd-ef0123456789/abcdef0123456789abcdef0123456789abcdef01/ // consider checking bundle.meta.isLoadingSample to determine if this is a test run or real run! const callbackUrl = z.generateCallbackUrl(); await z.request({ url: "https://example.com/api/slow-job", method: "POST", body: { // ... whatever your integration needs url: callbackUrl, }, }); return { hello: "world" }; // available later in bundle.outputData }; ``` And in your own `/api/slow-job` view (or more likely, an async job) you'd make this request to Zapier when the long-running job completes to populate `bundle.cleanedRequest`: ```http POST /hooks/callback/123/abcdef01-2345-6789-abcd-ef0123456789/abcdef0123456789abcdef0123456789abcdef01/ HTTP/1.1 Host: zapier.com Content-Type: application/json {"foo":"bar"} ``` > We recommend using `bundle.meta.isLoadingSample` to determine if the execution is happening in the foreground (IE: during Zap setup) as using `z.generateCallbackUrl()` can be inappropriate given the disconnect. Instead, wait for the long running request without generating a callback, or if you must, return stubbed data. By default the payload `POST`ed to the callback URL will augment the data returned from the initial `perform` to compose the final value. If you need to customize what the final value should be you can define a `performResume` method that receives three bundle properties: * `bundle.outputData` is `{"hello": "world"}`, the data returned from the initial `perform` * `bundle.cleanedRequest` is `{"foo": "bar"}`, the payload from the callback URL * `bundle.rawRequest` is the full request object corresponding to `bundle.cleanedRequest` ```js const performResume = async (z, bundle) => { // this will give a final value of: {"hello": "world", "foo": "bar"} // which is the default behavior when a custom `performResume` is not // defined. return { ...bundle.outputData, ...bundle.cleanedRequest }; }; ``` > The app will have a maximum of 30 days to `POST` to the callback URL. If a user deletes or modifies the Zap or Task in the meantime, we will not resume the task. > `performResume` will only run when the Zap runs live, and cannot be tested in the Zap Editor when configuring the Zap. It is possible to use `bundle.meta.isLoadingSample` to load a fixed sample to allow users to test a step that includes `performResume`. Some considerations: * `performResume` is not supported by the Platform UI at the moment. It can only be used by integrations built with the CLI. * In a search-or-write step, if the search part fails and proceeds to the write part, the callback URL generated for the write step might not be recognized or waited for. This can result in the `performResume` operation not being executed, leading to issues in the task flow. * When migrating actions that use `performResume`, it is important to ensure that the `performResume` code for the new API is backward compatible. This ensures that if a migration occurs while a run is waiting for a callback, it will succeed after being migrated ## `bundle` Object This object holds the user's auth details and the data for the API requests. > The `bundle` object is passed into your functions as the second argument - IE: `perform: (z, bundle) => {}`. ### `bundle.authData` `bundle.authData` is user-provided authentication data, like `api_key` or `access_token`. [Read more on authentication.](/platform/build-cli/overview#authentication) ### `bundle.inputData` `bundle.inputData` is user-provided data for this particular run of the trigger/search/create, as defined by the [`inputFields`](/platform/build-cli/input-fields). For example: ```js { createdBy: 'his name is Bobby Flay', style: 'he cooks mediterranean', scheduledAt: "2021-09-09T09:00:00-07:00" } ``` ### `bundle.inputDataRaw` `bundle.inputDataRaw` is like `bundle.inputData`, but before processing such as interpreting friendly datetimes and rendering `{{curlies}}`: ```js { createdBy: 'his name is {{123__chef_name}}', style: 'he cooks {{456__style}}', scheduledAt: "today" } ``` > "curlies" represent data mapped in from previous steps. They take the form `{{NODE_ID__key_name}}`. You'll usually want to use `bundle.inputData` instead. ### `bundle.meta` `bundle.meta` contains extra information useful for doing advanced behaviors depending on what the user is doing. It has the following options: | key | default | description | | -------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `isLoadingSample` | `false` | If true, this run was initiated manually via the Zap Editor | | `isFillingDynamicDropdown` | `false` | If true, this poll is being used to populate a dynamic dropdown. You only need to return the fields you specified (such as `id` and `name`), though returning everything is fine too | | `isPopulatingDedupe` | `false` | If true, the results of this poll will be used to initialize the deduplication list rather than trigger a zap. You should grab as many items as possible. See also: [deduplication](/platform/build/deduplication) | | `limit` | `-1` | The number of items you should fetch. `-1` indicates there's no limit. Build this into your calls insofar as you are able | | `page` | `0` | Used in [paging](/platform/build-cli/faqs#whats-the-deal-with-pagination-when-is-it-used-and-how-does-it-work) to uniquely identify which page of results should be returned | | `timezone` | `null` | The timezone the user has configured for their account or specfic automation. Received as [TZ identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), such as "America/New\_York". | | `isTestingAuth` | `false` | (legacy property) If true, the poll was triggered by a user testing their account (via [clicking "test"](https://cdn.zapier.com/storage/photos/5c94c304ce11b02c073a973466a7b846.png) or during setup). We use this data to populate the auth label, but it's mostly used to verify we made a successful authenticated request | | `withSearch` | `undefined` | When a create is called as part of a search-or-create step, `withSearch` will be the key of the search. | | `inputFields` | `{}` | Contains extra input field context if one or more input fields define this data via their respective `meta` property. If defined, then this object's keys are the respective input field `key` values, and the values for each `key` are an object corresponding to that input field's `meta` object value. See the [FieldSchema reference](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema) for more details on how to define input field meta. | > Before v8.0.0, the information in `bundle.meta` was different. See [the old docs](https://github.com/zapier/zapier-platform-cli/blob/a058e6d538a75d215d2e0c52b9f49a97218640c4/README.md#bundlemeta) for the previous values and [the wiki](https://github.com/zapier/zapier-platform/wiki/bundle.meta-changes) for a mapping of old values to new. Here's an example of a polling trigger that is also used to power a dynamic dropdown: ```js const perform = async (z, bundle) => { const params = { per_page: 100 }; // poll for the most recent 100 teams if (bundle.meta.isFillingDynamicDropdown) { // dynamic dropdowns support pagination params.per_page = 30; params.offset = params.per_page * bundle.meta.page; } const response = await z.request({ url: `${API_BASE_URL}/teams`, params, }); return response.json; }; // ... ``` ### `bundle.rawRequest` > `bundle.rawRequest` is only available in the `perform` for webhooks, `getAccessToken` for OAuth authentication methods, and `performResume` in a callback action. `bundle.rawRequest` holds raw information about the HTTP request that triggered the `perform` method or that represents the user's browser request that triggered the `getAccessToken` call: ``` { method: 'POST', querystring: 'foo=bar&baz=qux', headers: { 'Content-Type': 'application/json' }, content: '{"hello": "world"}' } ``` In `bundle.rawRequest`, headers other than `Content-Length` and `Content-Type` will be prefixed with `Http-`, and all headers will be named in Camel-Case. For example, the header `X-Time-GMT` would become `Http-X-Time-Gmt`. ### `bundle.cleanedRequest` > `bundle.cleanedRequest` is only available in the `perform` for webhooks, `getAccessToken` for OAuth authentication methods, and `performResume` in a callback action. `bundle.cleanedRequest` will return a formatted and parsed version of the request. Some or all of the following will be available: ``` { method: 'POST', querystring: { foo: 'bar', baz: 'qux' }, headers: { 'Content-Type': 'application/json' }, content: { hello: 'world' } } ``` ### `bundle.outputData` > `bundle.outputData` is only available in the `performResume` in a callback action. `bundle.outputData` will return a whatever data you originally returned in the `perform`, allowing you to mix that with `bundle.rawRequest` or `bundle.cleanedRequest`. ### `bundle.targetUrl` > `bundle.targetUrl` is only available in the `performSubscribe` and `performUnsubscribe` methods for webhooks. This the URL to which you should send hook data. It'll look something like [`https://hooks.zapier.com/1234/abcd`.](https://hooks.zapier.com/1234/abcd.) We provide it so you can make a POST request to your server. Your server should store this URL and use is as a destination when there's new data to report. For example: ```js const subscribeHook = async (z, bundle) => { const options = { url: "https://57b20fb546b57d1100a3c405.mockapi.io/api/hooks", method: "POST", body: { url: bundle.targetUrl, // bundle.targetUrl has the Hook URL this app should call }, }; const response = await z.request(options); return response.data; // or response.json if you're using core v9 or older }; module.exports = { // ... performSubscribe: subscribeHook, // ... }; ``` Read more in the [REST hook example](https://github.com/zapier/zapier-platform/blob/main/example-apps/rest-hooks/triggers/recipe.js). ### `bundle.subscribeData` > `bundle.subscribeData` is available in the `perform` and `performUnsubscribe` method for webhooks. This is an object that contains the data you returned from the `performSubscribe` function. It should contain whatever information you need send a `DELETE` request to your server to stop sending webhooks to Zapier. Read more in the [REST hook example](https://github.com/zapier/zapier-platform/blob/main/example-apps/rest-hooks/triggers/recipe.js). ## `bufferedBundle` Object *Added in v15.15.0.* This object holds a user's auth details (`bufferedBundle.authData`) and the buffered data (`bufferedBundle.buffer`) for the API requests. It is used only with a `create` action's `performBuffer` function. > The `bufferedBundle` object is passed into the `performBuffer` function as the second argument - IE: `performBuffer: async (z, bufferedBundle) => {}`. ### `bufferedBundle.authData` It is a user-provided authentication data, like `api_key` or `access_token`. [Read more on authentication.](/platform/build-cli/overview#authentication) ### `bufferedBundle.groupedBy` It is a user-provided data for a set of selected [`inputFields`](/platform/build-cli/input-fields) to group the multiple runs of a `create` action by. ### `bufferedBundle.buffer` It is an array of objects of user-provided data and some meta data to allow multiple runs of a `create` action be processed in a single API request. #### `bufferedBundle.buffer[].inputData` It is a user-provided data for a particular run of a `create` action in the buffer, as defined by the [`inputFields`](/platform/build-cli/input-fields). #### `bufferedBundle.buffer[].meta` It contains an idempotency `id` provided to the `create` action to identify each run's data in the buffered data. # Dynamic Dropdowns Source: https://docs.zapier.com/platform/build-cli/dynamic-dropdowns Sometimes, API endpoints require clients to specify a parent object in order to create or access the child resources. For instance, specifying a spreadsheet id in order to retrieve its worksheets. Since people don't speak in auto-incremented ID's, it is necessary that Zapier offer a simple way to select that parent using human readable handles. Our solution is to present users a dropdown that is populated by making a live API call to fetch a list of parent objects. We call these special dropdowns "dynamic dropdowns." ## Definition To define one you include the `dynamic` property on the `inputFields` object. The value for the property is a dot-separated *string* concatenation. ```js //... issue: { key: 'issue', //... create: { //... operation: { inputFields: [ { key: 'project_id', required: true, label: 'This is a dynamic dropdown', dynamic: 'project.id.name' }, // will call the trigger with a key of project { key: 'title', required: true, label: 'Title', helpText: 'What is the name of the issue?' } ] } } } ``` The dot-separated string concatenation follows this pattern: * The key of the trigger you want to use to power the dropdown. *required* * The value to be made available in bundle.inputData. *required* * The human friendly value to be shown on the left of the dropdown in bold. *optional* In the above code example the dynamic property makes reference to a trigger with a key of project. Assuming the project trigger returns an array of objects and each object contains an id and name key, i.e. ```js [ { id: "1", name: "First Option", dateCreated: "01/01/2000" }, { id: "2", name: "Second Option", dateCreated: "01/01/2000" }, { id: "3", name: "Third Option", dateCreated: "01/01/2000" }, { id: "4", name: "Fourth Option", dateCreated: "01/01/2000" }, ]; ``` The dynamic dropdown would look something like this. ![screenshot of dynamic dropdown in Zap Editor](https://cdn.zappy.app/6a90fcc532704f6c14b91586f5cd1d5b.png) ## Use a resource In the first code example the dynamic dropdown is powered by a trigger. You can also use a resource to power a dynamic dropdown. To do this combine the resource key and the resource method using camel case. ```js index.js const App = { // ... resources: { project: { key: "project", // ... list: { // ... operation: { perform: () => { return [{ id: 123, name: "Project 1" }]; }, // called for project_id dropdown }, }, }, issue: { key: "issue", // ... create: { // ... operation: { inputFields: [ { key: "project_id", required: true, label: "Project", dynamic: "projectList.id.name", }, // calls project.list { key: "title", required: true, label: "Title", helpText: "What is the name of the issue?", }, ], }, }, }, }, }; ``` ## Hide the trigger In some cases you will need to power a dynamic dropdown but do not want to make the Trigger available to the end user. Here it is best practice to create the trigger and set `hidden: true` on it's display object. ```js const App = { // ... triggers: { new_project: { key: "project", noun: "Project", // `display` controls the presentation in the Zapier Editor display: { label: "New Project", description: "Triggers when a new project is added.", hidden: true, }, operation: { perform: projectListRequest, }, }, another_trigger: { // Another trigger definition... }, }, }; ``` ## Dependencies between dropdowns You can have multiple dynamic dropdowns in a single trigger or action. In some cases, a dynamic dropdown depends on the value chosen in another dynamic dropdown when making its API call. The [Google Sheets](https://zapier.com/apps/google-sheets/integrations#triggers-and-actions) integration displays an example of this pattern. The example below illustrates a 'New Worksheet' trigger that populates a dynamic dropdown input field to select a worksheet: ```js { key: "worksheet", // ... operation: { // ... perform: async (z, bundle) => { const response = await z.request("https://example.com/api/v2/projects.json", { params: { spreadsheet_id: bundle.inputData.spreadsheet_id, }, }); // response.throwForStatus() if you're using core v9 or older return response.data; // or response.json if you're using core v9 or older } } } ``` Assume there is another `New Records` trigger with `Spreadsheet` and `Worksheet` dynamic dropdown input fields, which have keys `spreadsheet_id` and `worksheet_id` respectively. The selected spreadsheet value is available via `bundle.inputData.spreadsheet_id` to be used by the `Worksheet` trigger. ```js const App = { // ... triggers: { // ... issue: { key: "new_records", // ... operation: { inputFields: [ { key: "spreadsheet_id", required: true, label: "Spreadsheet", dynamic: "spreadsheet.id.name", altersDynamicFields: true, }, { key: "worksheet_id", required: true, label: "Worksheet", dynamic: "worksheet.id.name", }, ], }, }, }, }; ``` > Note: Be mindful that a dynamic dropdown can depend on the value chosen in another dynamic dropdown. Two types of dependencies can exist between fields: > > *Requirement dependency*: Affects how dependent fields are enabled or disabled within the UI > > * Setting `required: false` makes a field optional and always enabled in the UI. > * Having no required value set makes a field optional and disabled until the dependencies are selected. > > *Value dependency*: Affects how dynamic dropdown field options are retrieved > > * Setting a required value or not does not affect how the options of a dynamic field are retrieved. > > So, if you have an optional dynamic dropdown that depends on another dropdown input field, that field should not have `required: false` set. Input fields are optional by default, but setting `required: false` on an optional dynamic dropdown field that depends on another removes the requirement dependency relationship. > In the example above, the `worksheet_id` input field will be disabled until the `spreadsheet_id` input field has a value in Zapier's products such as the Zap editor. Notice that setting `altersDynamicFields: true` signifies other input fields need to be recomputed whenever the value of that field changes. ## Detect when a trigger is used for a dynamic dropdown If you want your trigger to perform specific scripting for a dynamic dropdown you will need to make use of `bundle.meta.isFillingDynamicDropdown`. This can be useful if need to make use of [pagination](/platform/build-cli/faqs#whats-the-deal-with-pagination-when-is-it-used-and-how-does-it-work) in the dynamic dropdown to load more options. ```js const App = { // ... resources: { project: { key: "project", // ... list: { // ... operation: { canPaginate: true, perform: () => { if (bundle.meta.isFillingDynamicDropdown) { // perform pagination request here } else { return [{ id: 123, name: "Project 1" }]; } }, }, }, }, issue: { key: "issue", // ... create: { // ... operation: { inputFields: [ { key: "project_id", required: true, label: "Project", dynamic: "projectList.id.name", }, // calls project.list { key: "title", required: true, label: "Title", helpText: "What is the name of the issue?", }, ], }, }, }, }, }; ``` ## Link a search action This feature makes it easier for users to handle the following scenario in a workflow that has multiple steps: * The value for the input field depends on an output field from an earlier step. * The value of that output field cannot be used directly. * They need an additional search step that takes the output they *cannot* use directly, and translate it into something they *can*. **Example:** Let's say the input field takes the ID of a lead. The user could select a lead from the dynamic dropdown, but then the workflow would act on the same lead every time it runs. An earlier step returns the email address of the lead, but not their ID. The user will need to prepend a search-step that takes the email address and returns the ID. Users can do this themselves, but by using this feature, Zapier products can make this task easier. ### How it works for the user In the Zap editor for example, dynamic dropdowns that use this feature will display a button next to the dynamic dropdown. When the user clicks the button, the right search step is automatically prepended, and correct output field mapped into the dynamic dropdown. ![](https://cdn.zappy.app/c6bd53c4bf3efe9870493dc7c3c2dafc.gif) ### How to configure it In the definition of the input field, configure `search` with a value of `.`. * Replace `` with the `key` of the search action that should prepeded to the user's workflow. * Replace `` with the `key` of the output field from that search action that should be mapped as value for the input field. Here's an example: ```js { key: 'project_id', required: true, label: 'Project', dynamic: 'list_projects.id.name', search: 'search_projects.id', } ``` # Frequently Asked Questions Source: https://docs.zapier.com/platform/build-cli/faqs ### Why doesn't Zapier support newer versions of Node.js? We run your code on AWS Lambda, which only supports a few [versions](https://docs.aws.amazon.com/lambda/latest/dg/programming-model.html) of Node. Sometimes that doesn't include the latest version. Additionally, with thousands of integrations running on the Zapier platform, we have to be sure upgrading to the latest Node version will not have a negative impact. ### How do I manually set the Node.js version to run my integration with? Update your `zapier-platform-core` dependency in `package.json`. Each major version ties to a specific version of Node.js. You can find the mapping [here](https://github.com/zapier/zapier-platform/blob/main/packages/cli/src/version-store.js). We only support the version(s) supported by [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/programming-model.html). **IMPORTANT CAVEAT:** AWS periodically deprecates Node versions as they reach EOL. They announce this [on their blog](https://aws.amazon.com/blogs/developer/node-js-6-is-approaching-end-of-life-upgrade-your-aws-lambda-functions-to-the-node-js-10-lts/). Similar info and dates are available on [github](https://github.com/nodejs/Release). Well before this date, we'll have a version of `core` that targets the newer Node version. If you don't upgrade before the cutoff date, there's a chance that AWS will throw an error when attempting to run your integration's code. If that's the case, we'll instead run it under the oldest Node version still supported. All that is to say, **we may run your code on a newer version of Node.js than you intend** if you don't update your integration's dependencies periodically. ### When to use placeholders or curlies? You will see both [template literal placeholders](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) `${var}` and (double) "curlies" `{{var}}` used in examples. **Rule of thumb:** Use `${var}` inside functions, and `{{var}}` in [shorthand requests](/platform/build-cli/overview#shorthand-http-requests). Template literal placeholders get evaluated as soon as the line of code is executed. This means that if you use `${process.env.VAR}` in a shorthand request for a trigger configuration, `zapier push` will substitute it with your local environment's value for `VAR` when it builds your integration, so the value set via `zapier env:set` will not be used. If you're not familiar with [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals), know that `const val = "a" + b + "c"` is essentially the same as: ```js const val = `a${b}c`; ``` Since v17, `z.request()` throws an error if there's `{{var}}` in the request object. ### Does Zapier support XML (SOAP) APIs? Not natively, but it can! Users have reported that the following `npm` modules are compatible with the CLI Platform: * [pixl-xml](https://github.com/jhuckaby/pixl-xml) * [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) * [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) Since core v10, it's possible for [shorthand requests](/platform/build-cli/overview#shorthand-http-requests) to parse XML. Use an `afterResponse` [middleware](/platform/build-cli/overview#using-http-middleware) that sets `response.data` to the parsed XML: ```js const xml = require("pixl-xml"); const App = { // ... afterResponse: [ (response, z, bundle) => { // Only works on core v10+! response.throwForStatus(); response.data = xml.parse(response.content); return response; }, ], // ... }; ``` ### What's the deal with pagination? When is it used and how does it work? Moved to [paging](/platform/build-cli/overview#paging). ### How does deduplication work? Each time a polling Zap runs, Zapier extracts a unique "primary key" for each item in the response. Zapier needs to decide which of the items should trigger the Zap. To do this, we compare the primary keys to all those we've seen before, trigger on new objects, and update the list of seen primary keys. When a Zap is turned on, we initialize the list of seen primary keys with a single poll. When it's turned off, we clear that list. For this reason, it's important that calls to a polling endpoint always return the newest items. For example, the initial poll returns objects 4, 5, and 6 (where a higher primary key is newer). If a later poll increases the limit and returns objects 1-6, then 1, 2, and 3 will be (incorrectly) treated like new objects. By default, the primary key is the item's `id` field. Since v15.6.0, you can customize the primary key by setting `primary` to true in `outputFields`. There's a more in-depth explanation [here](/platform/build/deduplication). ### Why are my triggers complaining if I don't provide an explicit `id` field? For deduplication to work, we need to be able to identify and use a unique field. In older, legacy Zapier Web Builder integrations, we guessed if `id` wasn't present. In order to ensure we don't guess wrong, we now require that the developers send us an `id` field. If your objects have a differently-named unique field, feel free to adapt this snippet and ensure this test passes: ```js // ... let items = response.data.items; // or response.json.items if you're using core v9 or older return items.map((item) => { item.id = item.contactId; return item; }); ``` Since v15.6.0, instead of using the default `id` field, you can also define one or more `outputFields` as `primary`. For example: ```js { triggers: { recipe: { operation: { outputField: [ { key: "userId", primary: true }, { key: "slug", primary: true }, { key: "name" }, ]; } } } } ``` will tell Zapier to use `(userId, slug)` as the unique primary key to deduplicate items when running a polling trigger. **Limitation:** The `primary` option currently doesn't support mixing top-level fields with nested fields that use double underscores in their keys. For example, if you set `primary: true` on both `id` and `user__id`, the `primary` setting on the `user__id` field will be ignored; only `id` will be used for deduplication. However, if all the `primary` fields are all nested, such as `user__id` + `user__name`, it will work as expected. ### Node X No Longer Supported If you're seeing errors like the following: ``` InvalidParameterValueException An error occurred (InvalidParameterValueException) when calling the CreateFunction operation: The runtime parameter of nodejs6.10 is no longer supported for creating or updating AWS Lambda functions. We recommend you use the new runtime (nodejsX.Y) while creating or updating functions. ``` ... then you need to update your `zapier-platform-core` dependency to a non-deprecated version that uses a newer version of Node.js. Complete the following instructions as soon as possible: 1. Edit `package.json` to depend on a later major version of `zapier-platform-core`. There's a list of all breaking changes (marked with a :exclamation:) in the [changelog](https://github.com/zapier/zapier-platform/blob/main/CHANGELOG.md). 2. Increment the `version` property in `package.json` 3. Ensure you're using version `v18` (or greater) of node locally (`node -v`). Use [nvm](https://github.com/nvm-sh/nvm) to use a different one if need be. 4. Run `rm -rf node_modules && npm i` to get a fresh copy of everything 5. Run `zapier test` to ensure your tests still pass 6. Run `zapier push` 7. Run `zapier promote YOUR_NEW_VERSION` (from step 2) 8. Migrate your users from the previous version (`zapier migrate OLD_VERSION YOUR_NEW_VERSION`) ### What Analytics are Collected? Starting with v8.4.0, Zapier collects information about each invocation of the CLI tool. This data is collected purely to improve the CLI experience and will **never** be used for advertising or any non-product purpose. There are 3 collection modes that are set on a per-computer basis. **Anonymous** When you run a command with analytics in `anonymous` mode, the following data is sent to Zapier: * which command you ran * if that command is a known command * how many arguments you supplied (but not the contents of the arguments) * which flags you used (but not their contents) * the version of CLI that you're using * the integration app the CLI commands are run in **Enabled** (the default) When analytics are fully `enabled`, the above is sent, plus: * your operating system (the result of calling [`process.platform`](https://nodejs.org/api/process.html#process_process_platform)) * your Zapier user id **Disabled** Lastly, analytics can be `disabled` entirely, either by running `zapier analytics --mode disabled` or setting the `DISABLE_ZAPIER_ANALYTICS` environment variable to `1`. We take great care not to collect any information about your filesystem or anything otherwise secret. You can see exactly what's being collecting at runtime by prefixing any command with `DEBUG=zapier:analytics`. ### What's the Difference Between an "App" and an "Integration"? We're in the process of doing some renaming across our Zapier marketing terms. Eventually we'll use "integration" everywhere. Until then, know that these terms are interchangeable and describe the code that you write that connects your API to Zapier. ### What does performGet do? The `performGet` method is an optional feature in Zapier that allows you to retrieve detailed information about an object. For instance, if your `create` action's `perform` method only returns the new object's `ID`, you can use `performGet` to fetch the object's full properties using that `ID`. `performGet` is only available for `Create` or `Search` actions and is most useful when the initial `perform` result is limited, and additional information is needed. The results from `perform` are automatically passed to `performGet` via `bundle.inputData` each time the `create` or `search` runs, allowing you to retrieve more comprehensive details. It's important to note that `performGet` is only invoked when the result returned by `perform` is not empty. # Hydration Source: https://docs.zapier.com/platform/build-cli/hydration The best answer to this lives in our [CLI docs](https://docs.zapier.com/platform/reference/cli-docs#dehydration): ## What is dehydration & hydration? Dehydration, and its counterpart hydration, is a tool that can lazily load data that might be otherwise expensive to retrieve aggressively. From a developer's perspective, you only need to worry about dehydration—Zapier will cover the hydration side of things. ## When to use dehydration? The two most common times you should use dehydration in a Zapier integration are when: 1. You need to retrieve extra information from an API (e.g. a resource's list endpoint only returns IDs, but content must be retrieved per ID) 2. You need to provide access to a file (or files) ## Why use dehydration? The core reason is reducing load to your API in case #1 above, where Zapier could fetch a list of known IDs of resources every 1-15 minutes per Zap, instead of the full definition of each of those resources. Putting any secondary requests behind a dehydration pointer means the request is made only once, although a Zap might see the same records again and again based on its polling cycle. Dehydration saves even more bandwidth with files. No polling trigger should return files without dehydration, because otherwise, your app will send that file to Zapier around 100-300 times per day. For file outputs, implementing dehydration means the file will only be accessed and downloaded when a later Zap step asks for it. The second reason is time. Your integration gets [30 seconds to run its API calls and any additional code](/platform/build/troubleshoot-trigger-timeouts#trigger-runs-in-a-zap) each time a Zap step runs before the step would time out. If you are running into that time limit, consider if work could be offloaded to dehydration and hydration. ## How to use dehydration? Check out our [example “files” app](https://github.com/zapier/zapier-platform/tree/main/example-apps/files) for an example of file dehydration in action with a working Zapier demo integration. You can even initialize a Zapier app based on that repo by entering `zapier init . --template=files` in Terminal to see it in your local code editor. ## Hydration in action Some key areas include `index.js`, `hydrators.js`, `triggers/newFile.js`, and `creates/uploadFile.js`. When building your integration, you'll likely be retrieving file info from a remote server. Instead, this example integration hard codes file urls to demonstrate. The `New File` Trigger returns those file urls. The method [`z.dehydrateFile`](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#zdehydratefilefunc-inputdata) is used to create a pointer to the `downloadFile` function. In order to pass those files to other apps in actions, we reference `hydrators.downloadFile`, our hydrating function given a file url. If you look at the `hydrators.js` file, you can see the `downloadFile` function. `downloadFile` calls the method[`z.stashFile`](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#zstashfilebufferstringstream-knownlength-filename-contenttype) to return a URL file pointer. All of these will work together to lazily fetch the trigger data only when needed, avoiding API calls during polling or for reuse. The only Action for this app is to upload the file, given a `bundle.inputData.file`. ### Setup First, install the sample Zapier app `zapier init . --template=files` and `zapier push` it to Zapier. If you've not worked with the CLI before, start by checking out the [tutorial](/platform/quickstart/cli-tutorial). Here's how the integration looks in [Zapier's developer dashboard](https://developer.zapier.com/). Add an optional icon to it if you like. Next, we'll want to add a Zap. Open the [Zap editor](https://zapier.com/editor), and select your integration's trigger. Select continue - you'll notice this app has no authentication, as the file urls are accessible without it. Select `Test trigger` to see the three sample urls pulled in and hydrated pointer for each. Now let's add the `Upload File` action to the Zap. Normally, we wouldn't want a setup like this (trigger off of new file / create a new file), because it would result in a [Zap loop](https://help.zapier.com/hc/en-us/articles/8496232045453-Zap-is-stuck-in-a-loop). But this is just a test—and be sure to turn the Zap off shortly after it's turned on. Above, you'll see the string that prompts Zapier to hydrate a file. When the Zap runner encounters a string like this, Zapier will call the defined hydration function with the proper arguments. After selecting `Test step`, you will see three new requests show in the `Monitoring` [tab of your integration](/platform/build/test-monitoring): The POST at the top was from the upload itself. The GET requests retrieve the file from the pointer provided by the trigger. Now the Zap is ready to be turned on! In this example app integration, the trigger will not run automatically due to the hard coded file urls used for illustrative purposes. Once you replace the `fileURLs` in the trigger `perform`, with a request to your API that returns the triggering file, you'll be able to test this out fully. *** *Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.* # Input Field Configuration Source: https://docs.zapier.com/platform/build-cli/input-fields On each trigger, search, or create in the `operation` directive, you can provide fields as an array of objects under `inputFields`. Those fields have various options you can provide. Here is a brief example: ```js const App = { // ... creates: { create_recipe: { // ... operation: { // an array of objects is the simplest way inputFields: [ { key: "title", required: true, label: "Title of Recipe", helpText: "Name your recipe!", }, { key: "style", required: true, choices: { mexican: "Mexican", italian: "Italian" }, }, ], perform: () => {}, }, }, }, }; ``` Notably, fields come in different types, which may look and act differently in the Zap editor. The default field display is a single-line input field. | Type | Behavior | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `string` | Accepts text input. | | `text` | Displays large, `