# Zapier

## Docs

- [Get Action Details](https://docs.zapier.com/ai-actions/api-reference/actions/get-action-details): Get details of a specific action, including its needs, gives, and a sample of the action.
- [Get Prefill Choices](https://docs.zapier.com/ai-actions/api-reference/actions/get-prefill-choices): Get prefill choices for an app's prefill.
- [Search Actions](https://docs.zapier.com/ai-actions/api-reference/actions/search-actions): Search for Zapier actions by app, ordered by relevancy.
- [Create AI Action](https://docs.zapier.com/ai-actions/api-reference/ai-actions/create-ai-action): Create a new AI Action that can be executed by the user later.
- [Delete AI Action](https://docs.zapier.com/ai-actions/api-reference/ai-actions/delete-ai-action): Delete an AI Action.

Returns `true` if the action was deleted, `false` if it was not found.
- [Get AI Action](https://docs.zapier.com/ai-actions/api-reference/ai-actions/get-ai-action): Get the details of a specific AI Action.
- [List AI Actions](https://docs.zapier.com/ai-actions/api-reference/ai-actions/list-ai-actions): List all the current actions for the current user.
- [Update AI Action](https://docs.zapier.com/ai-actions/api-reference/ai-actions/update-ai-action): Update an existing AI Action.
- [Get App Details](https://docs.zapier.com/ai-actions/api-reference/apps/get-app-details)
- [List Authentications For App](https://docs.zapier.com/ai-actions/api-reference/apps/list-authentications-for-app): Get list of Zapier authentications associated with the requesting user and account.
- [Search Apps](https://docs.zapier.com/ai-actions/api-reference/apps/search-apps)
- [Check User Auth](https://docs.zapier.com/ai-actions/api-reference/auth/check-user-auth): Test that the API and auth are working.
- [Get Account List](https://docs.zapier.com/ai-actions/api-reference/auth/get-account-list): Get a list of Zapier accounts for the current user.
- [Get Oauth Login Link](https://docs.zapier.com/ai-actions/api-reference/auth/get-oauth-login-link): This will create a link that can be used for "quick account creation" followed by OAuth login for a user.

When going to this link, if the provided email already has a Zapier account, the user will be asked to log in.
If the email does not have a Zapier account, the user will be asked to create an account with the provided email and name.

Upon creating an account or logging in, the user will be taken to

The user will get a Zapier account, be brought to AI Actions where they will see the OAuth consent screen, and then will be brought back to your site
with an OAuth `code` that can be used with `https://actions.zapier.com/oauth/token/` with the following data in the body with `Content-Type: application/x-www-form-urlencoded`:
    - client_id: Your AI Actions OAuth client ID
    - grant_type: `authorization_code`
    - code_verifier: The verifier stored for your user when generating the login URL
    - redirect_uri: The same URL you used for the `redirect_uri` in the previous step.
    - code: The code in the query parameters of the URL that the user was redirected to.
- [Get User Login Link](https://docs.zapier.com/ai-actions/api-reference/auth/get-user-login-link): This will create a link that can be used for "quick account creation" for a user.

When going to this link, if the provided email already has a Zapier account, the user will be asked to log in.
If the email does not have a Zapier account, the user will be asked to create an account with the provided email and name.

Upon creating an account or logging in, the user will be taken to the `redirect_to` URL.

A suggested flow for this is:
- Check if you have an AI Actions OAuth token for a user
- If you do not, send the user to a page on your site for initiating PKCE OAuth flow, storing the `code_verifier` for them securely.
- Use this endpoint, with a `redirect_to` of `https://actions.zapier.com/oauth/authorize/` with the following query parameters:
    - client_id: Your AI Actions OAuth client ID
    - scope: `openid nla:exposed_actions:execute`
    - response_type: `code`
    - redirect_uri: The URL on your site that will handle the OAuth callback, must be in the list of allowed redirect URIs for your AI Actions OAuth client.
    - code_challenge: The SHA256 hash of the `code_verifier` from the previous step.
    - code_challenge_method: `S256`

The user will get a Zapier account, be brought to AI Actions where they will see the OAuth consent screen, and then will be brought back to your site
with an OAuth `code` that can be used with `https://actions.zapier.com/oauth/token/` with the following data in the body with `Content-Type: application/x-www-form-urlencoded`:
    - client_id: Your AI Actions OAuth client ID
    - grant_type: `authorization_code`
    - code_verifier: The verifier stored for your user when generating the login URL
    - redirect_uri: The same URL you used for the `redirect_uri` in the previous step.
    - code: The code in the query parameters of the URL that the user was redirected to.
- [Execute Stateless AI Action](https://docs.zapier.com/ai-actions/api-reference/execution/execute-stateless-ai-action)
- [Execute Stored AI Action](https://docs.zapier.com/ai-actions/api-reference/execution/execute-stored-ai-action): Given an action ID and instructions, this will execute the action and return the results.
- [Preview Stored AI Action](https://docs.zapier.com/ai-actions/api-reference/execution/preview-stored-ai-action)
- [Rate Execution Log](https://docs.zapier.com/ai-actions/api-reference/execution/rate-execution-log): Rate a specific execution log given feedback from the user.

Rating should either be `-1` (bad, thumbs down), `0` (neutral, the default), or `1` (good, thumbs up).

You can also provide plain text feedback provided by the user. Supplied rating/feedback are patched onto execution log, so you can progressively call this endpoint.
- [App Health Check](https://docs.zapier.com/ai-actions/api-reference/system/app-health-check): Check that the app is up and running.
- [Guess Actions](https://docs.zapier.com/ai-actions/api-reference/utilities/guess-actions)
- [Shrink Result](https://docs.zapier.com/ai-actions/api-reference/utilities/shrink-result): **WARNING: This endpoint is not officially supported, and is subject to be changed or removed at any time.**

Shrinks the given result payload to git into a specified token budget.

This tries its best to keep around more important values and truncate less important ones.

The intent of this endpoint is that after executing an AI Action, you can pass the result into this to get a smaller result that can be passed in to LLMs without hitting the token limit.
- [API Key](https://docs.zapier.com/ai-actions/developer-resources/keys)
- [API Logs](https://docs.zapier.com/ai-actions/developer-resources/logs)
- [OpenAPI Specification](https://docs.zapier.com/ai-actions/developer-resources/openapi)
- [Submit an issue](https://docs.zapier.com/ai-actions/help/bug)
- [Join our Community](https://docs.zapier.com/ai-actions/help/community)
- [Zapier account list](https://docs.zapier.com/ai-actions/how-tos/account-list): Get a list of Zapier accounts for the current user.
- [Get action details and needs](https://docs.zapier.com/ai-actions/how-tos/action-info/action-details): Get details of a specific action, including its needs, gives, and a sample of the action.
- [Choose authentication](https://docs.zapier.com/ai-actions/how-tos/action-info/choose-auth): Get list of Zapier authentications associated with the requesting user and account.
- [Find an action](https://docs.zapier.com/ai-actions/how-tos/action-info/find-action): Search for Zapier actions by app, ordered by relevancy.
- [Find an app](https://docs.zapier.com/ai-actions/how-tos/action-info/find-app)
- [Introduction](https://docs.zapier.com/ai-actions/how-tos/action-info/introduction)
- [Get prefill choices](https://docs.zapier.com/ai-actions/how-tos/action-info/prefill-choices): Get prefill choices for an app's prefill.
- [Authentication](https://docs.zapier.com/ai-actions/how-tos/auth): Test that the API and auth are working.
- [Stored Vs. Stateless Actions](https://docs.zapier.com/ai-actions/how-tos/stateless-vs-stored)
- [Stateless actions introduction](https://docs.zapier.com/ai-actions/how-tos/stateless/intro)
- [Run a Stateless Action](https://docs.zapier.com/ai-actions/how-tos/stateless/run-stateless-action)
- [Configure stored actions in the AI Actions UI](https://docs.zapier.com/ai-actions/how-tos/stored/action-configuration)
- [Stored actions introduction](https://docs.zapier.com/ai-actions/how-tos/stored/intro)
- [List stored Actions](https://docs.zapier.com/ai-actions/how-tos/stored/list-actions): List all the current actions for the current user.
- [Create stored action](https://docs.zapier.com/ai-actions/how-tos/stored/manage/create-action): Create a new AI Action that can be executed by the user later.
- [Delete stored action](https://docs.zapier.com/ai-actions/how-tos/stored/manage/delete-action): Delete an AI Action.

Returns `true` if the action was deleted, `false` if it was not found.
- [Get stored action](https://docs.zapier.com/ai-actions/how-tos/stored/manage/get-action): Get the details of a specific AI Action.
- [Update stored action](https://docs.zapier.com/ai-actions/how-tos/stored/manage/update-action): Update an existing AI Action.
- [Run a stored action](https://docs.zapier.com/ai-actions/how-tos/stored/run-stored-action): Given an action ID and instructions, this will execute the action and return the results.
- [Introduction](https://docs.zapier.com/ai-actions/intro): AI Actions exposes Zapier's 7,000+ apps and 30,000+ actions, for selected partners, to build your own custom AI assistants.
- [Authentication](https://docs.zapier.com/ai-actions/libraries/nodejs/authentication)
- [Getting started](https://docs.zapier.com/ai-actions/libraries/nodejs/getting-started)
- [Next.js](https://docs.zapier.com/ai-actions/libraries/nodejs/nextjs-reccommendation)
- [Usage examples](https://docs.zapier.com/ai-actions/libraries/nodejs/usage)
- [ActionList](https://docs.zapier.com/ai-actions/libraries/react/action-list)
- [AiActionsProvider](https://docs.zapier.com/ai-actions/libraries/react/ai-actions-provider)
- [EditAction and CreateAction](https://docs.zapier.com/ai-actions/libraries/react/edit-action)
- [Getting started](https://docs.zapier.com/ai-actions/libraries/react/getting-started)
- [StatelessActionCreator](https://docs.zapier.com/ai-actions/libraries/react/stateless-creator)
- [Quickstart](https://docs.zapier.com/ai-actions/quickstart): Learn how to kick-start AI Actions by obtaining a partner key, setting up allowed actions, and executing an action through our API.
- [Stateless action creator/runner](https://docs.zapier.com/ai-actions/tools/stateless-action)
- [Create Account](https://docs.zapier.com/partner-solutions/api-reference/accounts/create-account): Create a new user and obtain an access token. See [here](/guides/quac/intro-quac#add-support-to-the-workflow-api) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.
- [User Profile](https://docs.zapier.com/partner-solutions/api-reference/accounts/user-profile): This endpoint returns the authenticated user information

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `profile` OAuth scope.
- [Get Actions](https://docs.zapier.com/partner-solutions/api-reference/actions/get-actions): Fetch the available actions for the provided App. It's typical to filter by type so that only actions that make sense for a particular step are shown. Action IDs may not be reused, see our documentation [here](https://docs.zapier.com/partner-solutions/workflow-api/Hardcoding-an-Action) for how to hardcode a particular action.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap` OAuth scope.
- [Get Choices](https://docs.zapier.com/partner-solutions/api-reference/actions/get-choices): Get the possible values for a `SELECT` Input Field.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap` OAuth scope.
- [Get Input Fields](https://docs.zapier.com/partner-solutions/api-reference/actions/get-input-fields): Get the Input Fields for a particular Action, using the provided authentication and inputs.  See [here](/guides/getting-started/Fields-and-Fieldsets) for more information.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.
- [Get Output Fields](https://docs.zapier.com/partner-solutions/api-reference/actions/get-output-fields): Get the Output Fields for a particular Action, using the provided authentication and inputs.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.
- [Step Test](https://docs.zapier.com/partner-solutions/api-reference/actions/step-test): Tests the action (step) in the third party api, using the provided authentication and inputs.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.
- [Get Apps [v1]](https://docs.zapier.com/partner-solutions/api-reference/apps/get-apps-[v1]): This endpoint returns a list of apps sorted popularity. See [here](/guides/list-apps) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.
- [Get Apps [v2]](https://docs.zapier.com/partner-solutions/api-reference/apps/get-apps-[v2]): This endpoint returns a list of apps sorted popularity. See [here](/guides/list-apps) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap` OAuth scope.
- [Zapier Authentication](https://docs.zapier.com/partner-solutions/api-reference/authentication): Authenticate with The Zapier Workflow API
- [Create Authentication](https://docs.zapier.com/partner-solutions/api-reference/authentications/create-authentication): Creates a new Authentication for the provided App.  See [here](/guides/custom-flows/adding-an-authentication) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `authentication:write` OAuth scope.
- [Get Authentications](https://docs.zapier.com/partner-solutions/api-reference/authentications/get-authentications): Fetch the available Authentications for the provided App. This will only return Authentications that are owned by the user and not those that are shared with them, since it's not possible to create Zaps with Authentications you don't own.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `authentication` OAuth scope.
- [Get Categories](https://docs.zapier.com/partner-solutions/api-reference/categories/get-categories): List of Zap categories

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/action)
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/app)
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/authentication)
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/choice)
- [Errors](https://docs.zapier.com/partner-solutions/api-reference/common-types/errors): Errors in the API follow the <a href="https://jsonapi.org/format/#error-objects" target="_blank">recommendation</a> from the JSON API spec.
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/fieldset)
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/infoField)
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/inputField)
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/outputField)
- [Pagination](https://docs.zapier.com/partner-solutions/api-reference/common-types/pagination)
- [Requests](https://docs.zapier.com/partner-solutions/api-reference/common-types/requests)
- [Responses](https://docs.zapier.com/partner-solutions/api-reference/common-types/responses): Responses for the API follow the <a href="https://jsonapi.org/format/#fetching-resources-responses-200" target="_blank">JSON API spec</a> for fetching resources.
- [null](https://docs.zapier.com/partner-solutions/api-reference/common-types/zap)
- [Create a Workflow step](https://docs.zapier.com/partner-solutions/api-reference/experimental/create-a-workflow-step): Creates a new Workflow Step based on a single provided step and returns a webhook URL that can be used to invoke the Workflow Step and retrieve a response. When creating a Workflow write action step, field values can be hardcoded, or they can contain mapped values. The mapped values should be surrounded with double curly braces.

In the example request body the Workflow Step contains the field values `email`, `name`, `phone`, and `address` in double curly braces. Then the webhook URL returned can be called with a `POST` request containing a JSON body in the following format:

```
{
"email": "user@example.com",
"name": "John Doe",
"phone": "1234567890",
"address": "123 Maple Lane"
}
```

Note that authentications may become invalid over time, and if this occurs, users will be notified by email to fix their authentication. If the authentication is not fixed, trying to run the Workflow Step will time out. Workflow Steps are also (for now) visible and editable as Zaps at https://zapier.com/app/zaps. If a user turns off a Workflow Step Zap, the request will similarly time out. Finally, it is possible that a user can edit the Workflow Step Zap such that it returns unexpected outputs.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.
- [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting): Rate limits when accessing the Workflow API   
- [Get Zap Templates](https://docs.zapier.com/partner-solutions/api-reference/zap-templates/get-zap-templates): List popular Zap Templates using your app. See [here](/guides/list-zap-templates) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.
- [Create a Zap](https://docs.zapier.com/partner-solutions/api-reference/zaps/create-a-zap): This URL creates a Zap based on the given steps and title.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.
- [Get Zaps [v1]](https://docs.zapier.com/partner-solutions/api-reference/zaps/get-zaps-[v1]): This endpoint returns a list of Zaps for the authenticated Zapier user.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap` OAuth scope.
- [Get Zaps [v2]](https://docs.zapier.com/partner-solutions/api-reference/zaps/get-zaps-[v2]): This endpoint returns a list of Zaps for the authenticated Zapier user.

The `expand` array can be used to expand selected fields into full objects in the response.  Inputs with keys can
also be passed to filter Zaps by certain criteria.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap`, or `zap:all` OAuth scope.
- [Guess a Zap [Beta]](https://docs.zapier.com/partner-solutions/api-reference/zaps/guess-a-zap): This endpoint returns a suggested Zap and pre-filled URL to Zapier from a given prompt.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.
- [Element Security](https://docs.zapier.com/partner-solutions/elements-security): Keeping our elements secure and usable is critical at Zapier
- [Embed Insights](https://docs.zapier.com/partner-solutions/embed-insights): Insights are available to review the performance of your embed, and track usage growth.
- [Zapier Partner Solutions](https://docs.zapier.com/partner-solutions/getting-started): Add automation to your product with Zapier.
- [Pre-filled Zaps](https://docs.zapier.com/partner-solutions/pre-filled-zaps): Prefills allow you to define the input fields on behalf of the user, simplying the experience of setting up their Zap.
- [Quick Account Creation](https://docs.zapier.com/partner-solutions/quick-account-creation): Quick Account Creation is a seamless, accelerated sign-up feature allowing first time Zapier users to skip the standard sign-up procedure and onboarding survey. Enabling Quick Account Creation as part of your embed tool code helps provide a more frictionless experience for end users.
- [How to Build a Workflow](https://docs.zapier.com/partner-solutions/workflow-api/Building-a-Zap): This guide walks through the entire process of building an automated workflow for your users to use -- front picking apps, adding authentication, filling inputs and publishing.
- [Fields and Fieldsets](https://docs.zapier.com/partner-solutions/workflow-api/Fields-and-Fieldsets)
- [Hardcoding an Action](https://docs.zapier.com/partner-solutions/workflow-api/Hardcoding-an-Action): To help focus the user experience, it can be helpful to hardcode a certain Action to guide users in selecting the most appropriate action for their use-case.
- [Selecting an Action](https://docs.zapier.com/partner-solutions/workflow-api/Selecting-an-Action): An Action is an operation that can be performed against a third-party API; either a `READ` or a `WRITE`.
- [Selecting an Authentication](https://docs.zapier.com/partner-solutions/workflow-api/Selecting-an-Authentication): Support users in selecting 3rd party authentications, either through an existing authentication or by adding new.
- [Adding an Authentication to your app](https://docs.zapier.com/partner-solutions/workflow-api/adding-an-authentication): Reduce friction when adding an authentication to your own app.
- [Embedding the Zapier Editor](https://docs.zapier.com/partner-solutions/workflow-api/embed-zap-editor): With an embedded Zap editor in your product, your users can create and edit their Zaps without leaving your app.
- [Integrate the Workflow API](https://docs.zapier.com/partner-solutions/workflow-api/intro): Our most powerful tool for building native workflows in your product
- [Known Limitations](https://docs.zapier.com/partner-solutions/workflow-api/limitations): Creating workflows using the Zapier Workflow API is a recent addition, and there are some known limitations.
- [Retrieving Apps on Zapier](https://docs.zapier.com/partner-solutions/workflow-api/list-apps): Listing apps available on Zapier is a simple way to show users all of what's possible on Zapier
- [Retrieving Zap Templates](https://docs.zapier.com/partner-solutions/workflow-api/list-zap-templates): Zap templates are pre-made Zaps that help users discover popular use cases for automating their work. Each template features a specific use case and the apps needed for it to work.
- [Retrieving a list of users Zaps](https://docs.zapier.com/partner-solutions/workflow-api/list-zaps): Listing a users zaps reveals existing workflows created by users.
- [Testing a Step](https://docs.zapier.com/partner-solutions/workflow-api/testing-a-step): Step testing allows for the validation of a configured step, executing any `READ` or `WRITE` actions.
- [Embed the Workflow Element](https://docs.zapier.com/partner-solutions/workflow-element/intro): The Workflow Element is a prebuilt UI component that offers the quickest—and easiest—way to surface your Zapier integration directly within your own product.
- [Core Reference](https://docs.zapier.com/platform/build-cli/core): Reference for `zapier-platform-core`
- [Dynamic Dropdowns](https://docs.zapier.com/platform/build-cli/dynamic-dropdowns)
- [Frequently Asked Questions](https://docs.zapier.com/platform/build-cli/faqs)
- [Input Field Configuration](https://docs.zapier.com/platform/build-cli/input-fields)
- [Build with CLI](https://docs.zapier.com/platform/build-cli/overview): Zapier is a platform for creating integrations and workflows. This CLI is your gateway to creating custom applications on the Zapier platform.
- [Action](https://docs.zapier.com/platform/build/action): Every Zap starts with a single trigger that watches for new or updated data, starting the user's workflow. Action steps then make use of that data.
- [Add input fields to triggers and actions](https://docs.zapier.com/platform/build/add-fields): When building in the Platform UI, you'll use the Input Designer to create the form users will input data into, to send to your app's API.
- [Add authentication with API Key](https://docs.zapier.com/platform/build/apikeyauth): API Key authentication passes along a user-entered API Key with every API call. In your Zapier integration using API Key authentication, the API key—and optionally any other data your API needs—is included every time a Zap step runs.
- [Authentication](https://docs.zapier.com/platform/build/auth): Connecting an app to Zapier starts with authentication. Users select an app they wish to use in their Zap, authenticating their account with that app to allow Zapier to access their data.
- [Add authentication with Basic Authentication](https://docs.zapier.com/platform/build/basicauth): APIs using Basic Authentication will authenticate users with a username and password. In your Zapier integration using Basic Auth, Zapier includes the username and password credentials in the API request bundle every time Zapier polls an API endpoint for new data or posts new data to an API endpoint.
- [Reference user-entered details with data bundles](https://docs.zapier.com/platform/build/bundle): Zapier stores data from users' authentication and input forms for API calls in the `bundle` object. You can reference that data in your integration using `{{bundle.bundleName.field}}` text in API requests and connection labels, replacing `bundleName` with the bundle name and `field` with the input field key or API response field key you need.
- [Add an instant trigger using REST Hooks in Zapier Platform CLI](https://docs.zapier.com/platform/build/cli-hook-trigger): REST Hooks are an alternative to polling. The main differences are allowing your customers' Zaps to trigger instantly; and avoiding polling triggers' numerous - and sometimes unnecessary - requests to your API's endpoints to check for new data.
- [Use Code Mode to refine your API call](https://docs.zapier.com/platform/build/code-mode)
- [Use computed fields in OAuth or Session Authentication](https://docs.zapier.com/platform/build/computed-fields): When adding a field in your integration's authentication configuration, Zapier offers two field type options; field and computed field. The field option allows users to enter account information needed for authentication.
- [Compute a field from the data of the Test API call](https://docs.zapier.com/platform/build/computed-test-field): Zapier doesn't store the responses from the test API call for OAuth v2 and session authentication. Using computed fields, you can use data from a test API call later in your Zapier integration.
- [Add a connection label](https://docs.zapier.com/platform/build/connection-label): Zapier users can authenticate multiple accounts for any app. By default, every new app account added to Zapier is identified by the app's name, followed by a number (#2, #3, …) for accounts connected after the first.
- [Add a create action](https://docs.zapier.com/platform/build/create)
- [How deduplication works in Zapier](https://docs.zapier.com/platform/build/deduplication): Zapier automatically deduplicates incoming trigger data for your integration, so that Zaps do not run multiple times on the same data. Consider the following requirements for your “New Item” and “Updated Item” triggers to work as users expect.
- [Add authentication with Digest Authentication](https://docs.zapier.com/platform/build/digestauth): Digest Auth prompts users to enter their username and password, optionally along with any additional data your API requires for authentication. Zapier makes an unauthenticated API call to get the nonce from your server, and uses it to encrypt and pass the authentication data to your server with each API call.
- [Send or receive dynamic user-defined fields through your API](https://docs.zapier.com/platform/build/dynamic-field): Dynamic fields are a type of field built from an API call. Custom code runs to show fields based on other input field data. These are especially useful with project management apps, CRM apps, databases, and any other app where users can add custom, user-defined fields.
- [Use environment variables in your API call](https://docs.zapier.com/platform/build/env): Integrations can define environment variables that are available when the app's code executes. They are useful when you have data like an OAuth client ID and secret that you don't want to commit to source control. Environment variables can also be used as a way to toggle between a staging and production environment during app development and this would be recommended instead of the use of an independent integration for staging purposes.
- [Error: An array is expected](https://docs.zapier.com/platform/build/error-array-expected): When you add a polling trigger or search action to your integration, the Zapier platform [expects a bare array of new or found items returned](/platform/build/response-types), sorted in reverse chronological order. An API may instead return a result _object_ that contains the array of items the trigger/search needs.
- [Error: Got a non-object result, expected an object from create](https://docs.zapier.com/platform/build/error-non-object)
- [Error: Got a non-object result in the array, expected only objects](https://docs.zapier.com/platform/build/error-non-object-array): When using a REST Hook trigger, the data returned by the perform must be an array.
- [Add error response handling](https://docs.zapier.com/platform/build/errors): If your API returns responses with a status code above 400 that should not automatically throw an error then Zapier recommends enabling skipThrowForStatus.
- [Field types](https://docs.zapier.com/platform/build/field-definitions)
- [Use form mode to setup your API calls](https://docs.zapier.com/platform/build/form-mode): In the Platform UI, when building your authentication, triggers and actions, the default setting under _API Configuration_ is to create each component of your integration using Form Mode.
- [Add a REST Hook trigger](https://docs.zapier.com/platform/build/hook-trigger): Set up your REST Hook trigger in the Platform UI with the Settings, Input Designer and API Configuration tabs.
- [Use hydration in Platform CLI](https://docs.zapier.com/platform/build/hydration-cli): The best answer to this lives in our [CLI docs](https://docs.zapier.com/platform/reference/cli-docs#dehydration):
- [Hydration/dehydration limits](https://docs.zapier.com/platform/build/hydration-limits): [File dehydration](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dehydration) is an extremely useful tool to remain within time and size constraints for Zapier triggers and actions. However, it does have its own limits.
- [Add line item group field to actions](https://docs.zapier.com/platform/build/line-items): Input fields in Zapier add one item each time the Zap runs. But, if you want users to be able to add multiple items in a single Zap run, then this can be achieved by using a line item group. This group takes line items, which are comma-separated values, and adds each instance of the values to the app in a single Zap run.
- [Add authentication with OAuth v2](https://docs.zapier.com/platform/build/oauth): OAuth v2 authentication matches in appearance the login process users expect from most modern apps.
- [Zapier operating constraints](https://docs.zapier.com/platform/build/operating-constraints): Zapier offers a relatively unique run-time environment for your integration and its requests to your API. The environment is stateless and restricts both execution time and payload size to offer normalized reliability and running time. There are three distinct contexts of this run-time that your integration will need to consider.
- [Use pagination in triggers](https://docs.zapier.com/platform/build/pagination-trigger): By default, Zapier triggers fetch new or recently updated data to start Zaps, and only need to find the most recently added items. Triggers can also be used to populate [dynamic dropdown fields](/platform/build/add-fields#dynamic-dropdown), and there they need to find all possible items to populate the field.
- [Add a polling trigger](https://docs.zapier.com/platform/build/polling-trigger): Set up your polling trigger in the Platform UI with the Settings, Input Designer and API Configuration tabs.
- [Reduce requests to your API](https://docs.zapier.com/platform/build/reduce-api-requests)
- [Reorder or remove action](https://docs.zapier.com/platform/build/reorder-action): Whenever a user selects your app's integration in a Zapier action step, they'll see every _create_ and _search_ action in your integration. 
- [Reorder or remove triggers](https://docs.zapier.com/platform/build/reorder-trigger): Triggers are listed in alphabetical order in the Zap editor and this order cannot be changed.
- [Add authentication fields to Request Template](https://docs.zapier.com/platform/build/requesttemplate): The Request Template is a request editor that lets users set static values that apply to all requests made by this integration. Users can configure the URL params, HTTP headers and request body. This is the perfect place to set authentication fields.
- [Response types Zapier expects](https://docs.zapier.com/platform/build/response-types): With every API call, Zapier expects the response data to be returned in a specific response type. This can vary depending on what part of your integration you're working on. Use the table below to identify the correct response type to use
- [Output data, defining sample data and output fields](https://docs.zapier.com/platform/build/sample-data): This guide will explain what output data, sample data and output fields are and how to modify them in your triggers or actions.
- [Add a search action](https://docs.zapier.com/platform/build/search)
- [Add a search or create action](https://docs.zapier.com/platform/build/search-or-create): When adding a _search_ action type, you'll see the option to _Pair an existing search and a create to enable “Find or Create” functionality_ in the _Settings_ page. This embeds the _create_ inside the _search_ step to find or create items in one step of the Zap.
- [Add authentication with Session Authentication](https://docs.zapier.com/platform/build/sessionauth): Session authentication has elements of Basic authentication — where Zapier requests a username and password, and OAuth v2 — where Zapier redirects users to the app's site to allow access. User credentials are exchanged for a token used to authenticate subsequent API calls.
- [Enable static IP address connection for customers](https://docs.zapier.com/platform/build/static-ip): To enable customers to access your integration by static IP address, all outbound traffic from Zapier to your integration will need to be routed through a smaller set of consistent IP addresses. Any app owner/developer can request to enable the static IP address feature on a private or public app.
- [Validate domain and subdomain input fields during authentication](https://docs.zapier.com/platform/build/subdomain-validation): When adding a subdomain input field, commonly used in OAuth implementations, additional validation is strongly recommended to prevent a potential security vulnerability. If not taken into account, an attacker could utilize a maliciously constructed subdomain field (like `attacker-domain.com/`) in order to redirect OAuth connection requests to that attacker-controlled domain (because `attacker-domain.com/.your-domain.com` resolves to the attacker's domain instead of the expected one). Taking the following steps prevents the potential for an attacker to access your integration's sensitive authentication information, such as the OAuth client ID or secret.
- [Test authentication](https://docs.zapier.com/platform/build/test-auth): Testing a user's authentication is crucially important, as it is later used to test subsequent trigger and action steps when built.
- [Test and monitor your integration in your Zapier account](https://docs.zapier.com/platform/build/test-monitoring): Testing inside the Platform UI is crucial during the building process. To ensure users can benefit from your integration's features, it is equally crucial to test your integration within the Zap editor. This is the best way to notice details that might have been overlooked while building your integration.
- [Testing Tools](https://docs.zapier.com/platform/build/test-tools): The Zapier platform provides a set of tools to help inform and validate your integration before pushing changes out to users.
- [Test triggers or actions](https://docs.zapier.com/platform/build/test-triggers-actions): Once authentication is tested, trigger and action steps are easy to test inside Zapier visual builder. Set up the trigger or action settings and API calls, then as the last step the familiar _Test Your API Response_ box appears. It will show any accounts you added to your integration previously during the authentication testing.
- [Trigger](https://docs.zapier.com/platform/build/trigger)
- [Troubleshoot action payload size](https://docs.zapier.com/platform/build/troubleshoot-action-payload)
- [Troubleshoot action timeouts](https://docs.zapier.com/platform/build/troubleshoot-action-timeouts)
- [Troubleshoot custom fields](https://docs.zapier.com/platform/build/troubleshoot-custom-fields)
- [Troubleshoot throttles](https://docs.zapier.com/platform/build/troubleshoot-throttles)
- [Troubleshoot trigger payload size](https://docs.zapier.com/platform/build/troubleshoot-trigger-payload)
- [Troubleshoot trigger timeouts](https://docs.zapier.com/platform/build/troubleshoot-trigger-timeouts)
- [Developer Platform Login](https://docs.zapier.com/platform/dev-platform-login)
- [Partner Solutions Documentation](https://docs.zapier.com/platform/embed/partner-solutions): Heads up, our [Embed docs](https://docs.api.zapier.com/guides/intro) have moved.
- [Welcome](https://docs.zapier.com/platform/home)
- [Active users retention](https://docs.zapier.com/platform/manage/active-users): At Zapier, churn means a user used your integration in their Zaps 29 - 56 days ago, but hasn't run a successful task in one of those Zaps in the past 28 days. This user is considered to have churned from the integration. Maybe they switched to using a competing integration or their workflow had a more periodic or seasonal cadence.
- [Invite team members to your integration](https://docs.zapier.com/platform/manage/add-team): Integrations do not have a dedicated owner, instead they are managed by a team that can be modified as needed. Add team members to your integration to collaborate, contribute, and view analytics data for your integration on the Developer Platform. Your integration team can have up to 200 team members, regardless of whether your integration is Private or Public.
- [null](https://docs.zapier.com/platform/manage/api-outage): Zapier recognizes that temporary unavailability is sometimes inevitable for your API.
- [Change authentication field keys](https://docs.zapier.com/platform/manage/auth-keys)
- [Add required authentication field](https://docs.zapier.com/platform/manage/auth-required)
- [Change authentication type](https://docs.zapier.com/platform/manage/auth-scheme): If your API's authentication method changes, you would need to change the method Zapier uses to authenticate user accounts.
- [Add integration branding in Platform CLI](https://docs.zapier.com/platform/manage/branding-cli): When you make a new integration in Zapier CLI, you can add the app's name, description, and homepage to the `package.json` file.
- [Changes to your API can impact your integration](https://docs.zapier.com/platform/manage/change-api)
- [Change trigger or action key](https://docs.zapier.com/platform/manage/change-keys)
- [Update perform method for polling trigger](https://docs.zapier.com/platform/manage/change-perform)
- [Change trigger from polling to REST Hook](https://docs.zapier.com/platform/manage/change-trigger)
- [Clone a version](https://docs.zapier.com/platform/manage/clone): Cloning allows you to duplicate an existing version of your integration. This is particularly useful when you want to introduce new features or fixes without altering the original integration. When a previous version of your integration has more than 5 active users, you will need to clone that version to make modifications.
- [Deprecate or delete a version](https://docs.zapier.com/platform/manage/deprecate): Deprecation is an optional process that allows you to set a date from which a non-public version of your integration will no longer be updated. Deprecation is only recommended if the older integration version will eventually stop working, such as if the related API will be removed. Zapier is normally a “set it and forget it” experience for users, so use this feature carefully. Only if the older version will no longer function, should it be deprecated. Please note that deprecating a version is significantly more disruptive to our mutual users than migrating to the latest promoted version, or than leaving users on an older (now) private version if migration is not possible.
- [Embed activation rates](https://docs.zapier.com/platform/manage/embed-activation): Consider all the user clicks on Zap Templates surfaced in your embeds. The embed activation rate is the percentage of those Zaps that actually activated within 24 hours of creation, meaning the Zap ran at least one successful task. It measures the efefctiveness of the Zapier embeds in your product at converting user clicks on Zap Templates to Zap activations.
- [Embed insights definitions](https://docs.zapier.com/platform/manage/embed-insights): Embed features are available for public integrations.
- [Improve error response handling](https://docs.zapier.com/platform/manage/error-handling): Errors from your API cause pain for users at two vital points:
- [Essential tips for integrating quality health practices](https://docs.zapier.com/platform/manage/essential-tips-iq): Our shared customers rely on Zapier and your integration for business-critical workflows. Addressing feedback early and often ensures users have the best experience, both with Zapier's platform and yours. Follow these tips on how. 
- [Export integration to Platform CLI](https://docs.zapier.com/platform/manage/export-cli): The Zapier Platform CLI (Command Line Interface) is a toolset you install and run in your local development environment. It allows you to build, test, and manage your Zapier integration through JavaScript code and terminal commands.
- [Export integration to Platform UI](https://docs.zapier.com/platform/manage/export-ui): The Zapier Platform UI is the easiest way for anyone with API experience to build Zapier integrations. It is for users more comfortable with a visual form editor.
- [Change input form field key](https://docs.zapier.com/platform/manage/input-key)
- [Integration insights definitions](https://docs.zapier.com/platform/manage/integration-insights): Integration quality on Zapier boils down to two main pillars: **Health** and **Depth**.
- [Migrate users to a new version](https://docs.zapier.com/platform/manage/migrate): If this isn't the first time you've promoted your app - you might have users on older versions.
- [Change output data response](https://docs.zapier.com/platform/manage/output)
- [Change output field key](https://docs.zapier.com/platform/manage/output-key)
- [Planning and implementing integration changes](https://docs.zapier.com/platform/manage/planning-changes): Before making updates to your integration, it's important to consider the potential impact on user migration and existing Zaps. Ensuring your API and Zapier integration remains backwards compatible is crucial to avoid disruption to users. However, we acknowledge certain changes are sometimes necessary and unavoidable. In such cases, consider the best practice for implementation.
- [Promote a version](https://docs.zapier.com/platform/manage/promote): After your integration has entered the beta or public status, you can set a new default version for public use. This process is called promoting a version.
- [Add new required input field](https://docs.zapier.com/platform/manage/required-input)
- [Share your integration](https://docs.zapier.com/platform/manage/sharing): Once an integration is public, all users would have access to it when searching for an app's name in the Zap Editor, or in the [Zapier App Directory](https://zapier.com/apps).
- [Respond to user feedback and bugs](https://docs.zapier.com/platform/manage/user-feedback): For public integrations, Zapier's Support team logs user requests and reported problems in Zapier's issue tracker, that your team can see from the _Bug & Feature Requests_ page in the Manage section.
- [Versions](https://docs.zapier.com/platform/manage/versions): Versions in Developer Platform allow developers to create multiple iterations of their integration to experiment with and implement new features without affecting existing users. Each integration can have many versions, but only one version can have a public status at a one time.
- [Manage a legacy integration](https://docs.zapier.com/platform/manage/versions-legacy)
- [Zap activation rates](https://docs.zapier.com/platform/manage/zap-activation): Consider all of the Zaps that users try to create with your integration's triggers, actions, or searches. The Zap activation rate is the percentage of those Zaps that actually activated within 24 hours of creation, meaning the Zap ran at least one successful task.
- [Add or modify integration branding and details](https://docs.zapier.com/platform/publish/add-or-modify-branding): When creating a new integration in the Platform UI from the link `https://developer.zapier.com/app/new`, you'll be prompted to add the app name, description, homepage URL and logo.
- [A Guide to Zapier Partner Program Benefits: Accelerate your integration's growth and reach](https://docs.zapier.com/platform/publish/benefits-guide): Zapier offers a variety of marketing and support benefits to partners. This cheat sheet is designed to help you understand when and how you can access each of these benefits as you unlock them.
- [Best practices for showcasing your integration](https://docs.zapier.com/platform/publish/best-practices): Sharing well-crafted content about your Zapier integration can help you improve user adoption, highlight key use cases, and simplify integration processes. Need some inspiration? The following examples show how some of our partners are effectively communicating their Zapier integrations across different platforms.
- [Integration branding guidelines](https://docs.zapier.com/platform/publish/branding-guidelines): When creating your integration, you'll add your app’s name, logo, description, category, and primary brand color. Consistent branding is essential for helping users recognize and discover your app on Zapier.
- [Integration build guidelines](https://docs.zapier.com/platform/publish/integration-build-guidelines): Before publishing your integration on Zapier, it is essential to ensure that your integration is well-prepared to provide a seamless and efficient user experience. The following guidelines are designed to assist you in refining your integration before submitting it for review. Adhering to these guidelines will help enhance the functionality and user interaction with your integration and will provide you with the best value and opportunities to harness Zapier as a method of obtaining new users and most commonly, boosting the lifetime value of your current customers.
- [Integration check reference](https://docs.zapier.com/platform/publish/integration-checks-reference): Before you can submit your integration for publishing, it runs through a set of automated checks to ensure it's working properly and giving our users (and yours) the best possible experience.
- [Integration publishing requirements](https://docs.zapier.com/platform/publish/integration-publishing-requirements): We're excited you are creating an integration for the [Zapier Platform](https://zapier.com/developer-platform). We're here to help you understand our platform and its requirements so that you can successfully prepare your Zapier integration for publishing. Thousands of partners have built integrations on the Zapier Platform that enable our mutual users to set up Zaps as easily and quickly as possible.
- [Integration success strategies](https://docs.zapier.com/platform/publish/partner-faq): With 7,000+ public integration partners on Zapier, use these 10 tried-and-true tactics from our top partners to skyrocket your growth and earn you more [benefits from the Partner Program](https://zapier.com/developer-platform/partner-program).
- [Partner Program](https://docs.zapier.com/platform/publish/partner-program): The [Zapier Partner Program](https://zapier.com/developer-platform/partner-program) is a program for Zapier's [6,000+ integration partners](https://zapier.com/apps). It is designed to give all partners a clear path to success for their integrations and reward them with benefits along the way.
- [Build your first public integration on Zapier](https://docs.zapier.com/platform/publish/public-integration): This guide gives an overview of the process to publishing a public integration.
- [Create help documentation for your users](https://docs.zapier.com/platform/publish/user-help): The Zapier team provide frontline support for your integration, and in order to provide the best experience for your users, help documentation about using your integration in Zaps is hosted in the [Zapier Help Center](https://help.zapier.com/hc/en-us).
- [Zap templates](https://docs.zapier.com/platform/publish/zap-templates): Zapier empowers apps to do together what they can't on their own. With a bit of inspiration and creativity, your users can pull dozens of apps together into unique workflows to get more done with your app in far less time.
- [Zapier Partner Sandbox](https://docs.zapier.com/platform/publish/zps): Zapier Partner Sandbox is a workspace for people in your organization who are on your integration team.
- [Build your integration on Zapier](https://docs.zapier.com/platform/quickstart/build-integration): This guide will walk you through what steps you need to take to build an integration from start to finish. There are no fees to build an integration with Zapier.
- [Platform CLI tutorial](https://docs.zapier.com/platform/quickstart/cli-tutorial): This tutorial walks you through the process of building, testing, and pushing an example app to Zapier using Platform CLI. We'll use a mock API for recipes in this tutorial, but for production Zapier apps, you'd want to connect to a real API.
- [Get help with the Zapier Platform](https://docs.zapier.com/platform/quickstart/get-help): Get help building your app integration through the following channels:
- [Zapier Glossary](https://docs.zapier.com/platform/quickstart/glossary)
- [How Zapier works](https://docs.zapier.com/platform/quickstart/how-zapier-works): [Zapier is a tool](https://zapier.com/how-it-works) that helps you automate repetitive tasks between two or more apps, no code necessary. Our customers use Zapier to move information from one app to another automatically rather than manually. Each Zap you create starts with a trigger (something that happens in one app) and then one or more actions (something else that happens in another app).
- [Private vs public integrations](https://docs.zapier.com/platform/quickstart/private-vs-public-integrations): When building an integration on the Zapier Platform, you must specify the intended audience.
- [Recommended triggers and actions](https://docs.zapier.com/platform/quickstart/recommended-triggers-and-actions): Whether you’re just starting to scope out a new Zapier integration build or have successfully launched your app in the Zapier App Directory, it’s helpful to know what features users find the most valuable and are the most widely used across Zapier’s various [app categories](https://zapier.com/apps). Ensuring your integration covers the foundational triggers, actions, and searches applicable to your app will provide more utility to your users.
- [Zapier trusted app developers](https://docs.zapier.com/platform/quickstart/trusted-developers): If you don't have the time/resources to dedicate towards building and launching your own integration, there are Zapier trusted developers who may be able to assist you. These are individuals/groups who have demonstrated their capabilities by satisfying our requirements for building apps on our Developer Platform, and we recommend as a substitute for building your integration in-house.
- [Platform UI tutorial](https://docs.zapier.com/platform/quickstart/ui-tutorial): This tutorial walks you through the process of building an integration on Zapier with authentication, a trigger and an action using the Platform UI.
- [Platform UI vs Platform CLI](https://docs.zapier.com/platform/quickstart/ui-vs-cli): There are two different developer tools to build either private or public integrations with on the Zapier Developer Platform: Platform UI or Platform CLI.
- [Zapier integration structure](https://docs.zapier.com/platform/quickstart/zapier-integration-structure): [Zapier's Developer Platform](https://developer.zapier.com/) includes everything needed to build and manage a new Zapier integration. When you access your integration project by name, you'll see the left sidebar outlines the core project structure.
- [AI Actions](https://docs.zapier.com/platform/reference/ai-actions): Zapier's [AI Actions](https://actions.zapier.com/) is an AI alpha product designed to work with natural language-based products. It leverages the Zapier platform, with over [6000 apps](https://zapier.com/apps). You can include the capabilities of Zapier's platform in your own product.
- [Zapier integration structure for an AI app](https://docs.zapier.com/platform/reference/ai-app): AI app integrations built on Zapier allow users to automate tasks using AI capabilities. Here are some common pain points and recommendations when building AI apps on Zapier.
- [Changelog](https://docs.zapier.com/platform/reference/changelog)
- [CLI Reference](https://docs.zapier.com/platform/reference/cli)
- [Zapier integration structure for a CRM app](https://docs.zapier.com/platform/reference/crm-app): CRM (customer relationship management) apps are detailed databases that link contacts with companies, companies with deals, and more.
- [Custom Actions and API Requests Actions](https://docs.zapier.com/platform/reference/custom-actions-api-requests): [Custom Actions](https://help.zapier.com/hc/en-us/articles/16276574838925-App-Extensions-in-Zapier) and [API Requests](https://help.zapier.com/hc/en-us/articles/12899607716493-Set-up-an-API-request-action#prerequisites-0-0) are features that have been developed internally at Zapier, designed to help our mutual customers achieve the most value out of your app integration.
- [Zapier integration structure for a forms app](https://docs.zapier.com/platform/reference/forms-app): Form and survey app integrations built on Zapier allow users to connect mobile data collection forms to send the responses into other apps as new contacts, document templates, messages, and more.
- [Scripting in converted Legacy Web Builder Integrations](https://docs.zapier.com/platform/reference/legacy-scripting): This guide provides instructions on editing and maintaining existing scripting methods for legacy web builder integrations that have been converted to either the Platform UI or Platform CLI.
- [Zapier integration structure for a project management app](https://docs.zapier.com/platform/reference/project-app): While you can't automate project work, you can automatically add tasks, create new projects, and keep track of progress via an app integration on Zapier.
- [Schema Reference](https://docs.zapier.com/platform/reference/schema)
- [Transfer](https://docs.zapier.com/platform/reference/transfer): [Transfer](https://help.zapier.com/hc/en-us/articles/8496274335885) is a Zapier functionality that enables users to perform bulk operations using their historical data.


## Optional

- [Community](https://community.zapier.com/)
- [Blog](https://zapier.com/blog/)
- [Contact Us](https://developer.zapier.com/contact)


# Get Action Details

post /api/v2/apps/{app}/actions/{action}/
Get details of a specific action, including its needs, gives, and a sample of the action.



# Get Prefill Choices

post /api/v2/apps/{app}/choices/{prefill}/
Get prefill choices for an app's prefill.



# Search Actions

get /api/v2/apps/{app}/actions/
Search for Zapier actions by app, ordered by relevancy.



# Create AI Action

post /api/v2/ai-actions/
Create a new AI Action that can be executed by the user later.



# Delete AI Action

delete /api/v2/ai-actions/{ai_action_id}/
Delete an AI Action.

Returns `true` if the action was deleted, `false` if it was not found.



# Get AI Action

get /api/v2/ai-actions/{ai_action_id}/
Get the details of a specific AI Action.



# List AI Actions

get /api/v2/ai-actions/
List all the current actions for the current user.



# Update AI Action

put /api/v2/ai-actions/{ai_action_id}/
Update an existing AI Action.



# Get App Details

get /api/v2/apps/{app}/



# List Authentications For App

get /api/v2/apps/{app}/auths/
Get list of Zapier authentications associated with the requesting user and account.



# Search Apps

get /api/v2/apps/search/



# Check User Auth

get /api/v2/auth/check/
Test that the API and auth are working.



# Get Account List

get /api/v2/auth/accounts/
Get a list of Zapier accounts for the current user.



# Get Oauth Login Link

get /api/v2/auth/oauth-login-link/
This will create a link that can be used for "quick account creation" followed by OAuth login for a user.

When going to this link, if the provided email already has a Zapier account, the user will be asked to log in.
If the email does not have a Zapier account, the user will be asked to create an account with the provided email and name.

Upon creating an account or logging in, the user will be taken to

The user will get a Zapier account, be brought to AI Actions where they will see the OAuth consent screen, and then will be brought back to your site
with an OAuth `code` that can be used with `https://actions.zapier.com/oauth/token/` with the following data in the body with `Content-Type: application/x-www-form-urlencoded`:
    - client_id: Your AI Actions OAuth client ID
    - grant_type: `authorization_code`
    - code_verifier: The verifier stored for your user when generating the login URL
    - redirect_uri: The same URL you used for the `redirect_uri` in the previous step.
    - code: The code in the query parameters of the URL that the user was redirected to.



# Get User Login Link

get /api/v2/auth/login-link/
This will create a link that can be used for "quick account creation" for a user.

When going to this link, if the provided email already has a Zapier account, the user will be asked to log in.
If the email does not have a Zapier account, the user will be asked to create an account with the provided email and name.

Upon creating an account or logging in, the user will be taken to the `redirect_to` URL.

A suggested flow for this is:
- Check if you have an AI Actions OAuth token for a user
- If you do not, send the user to a page on your site for initiating PKCE OAuth flow, storing the `code_verifier` for them securely.
- Use this endpoint, with a `redirect_to` of `https://actions.zapier.com/oauth/authorize/` with the following query parameters:
    - client_id: Your AI Actions OAuth client ID
    - scope: `openid nla:exposed_actions:execute`
    - response_type: `code`
    - redirect_uri: The URL on your site that will handle the OAuth callback, must be in the list of allowed redirect URIs for your AI Actions OAuth client.
    - code_challenge: The SHA256 hash of the `code_verifier` from the previous step.
    - code_challenge_method: `S256`

The user will get a Zapier account, be brought to AI Actions where they will see the OAuth consent screen, and then will be brought back to your site
with an OAuth `code` that can be used with `https://actions.zapier.com/oauth/token/` with the following data in the body with `Content-Type: application/x-www-form-urlencoded`:
    - client_id: Your AI Actions OAuth client ID
    - grant_type: `authorization_code`
    - code_verifier: The verifier stored for your user when generating the login URL
    - redirect_uri: The same URL you used for the `redirect_uri` in the previous step.
    - code: The code in the query parameters of the URL that the user was redirected to.



# Execute Stateless AI Action

post /api/v2/execute/



# Execute Stored AI Action

post /api/v2/ai-actions/{ai_action_id}/execute/
Given an action ID and instructions, this will execute the action and return the results.



# Preview Stored AI Action

post /api/v2/ai-actions/{ai_action_id}/preview/



# Rate Execution Log

post /api/v2/execute/log/{execution_log_id}/rate/
Rate a specific execution log given feedback from the user.

Rating should either be `-1` (bad, thumbs down), `0` (neutral, the default), or `1` (good, thumbs up).

You can also provide plain text feedback provided by the user. Supplied rating/feedback are patched onto execution log, so you can progressively call this endpoint.



# App Health Check

get /api/v2/health/
Check that the app is up and running.



# Guess Actions

post /api/v2/guess-actions/



# Shrink Result

post /api/v2/shrink-result/
**WARNING: This endpoint is not officially supported, and is subject to be changed or removed at any time.**

Shrinks the given result payload to git into a specified token budget.

This tries its best to keep around more important values and truncate less important ones.

The intent of this endpoint is that after executing an AI Action, you can pass the result into this to get a smaller result that can be passed in to LLMs without hitting the token limit.



# API Key





# API Logs





# OpenAPI Specification





# Submit an issue





# Join our Community





# Zapier account list

get /api/v2/auth/accounts/
Get a list of Zapier accounts for the current user.

Each user can have multiple Zapier accounts. This endpoint returns a list of all the accounts that the user has access to.

When interacting with the AI Actions API, you will need to provide a specific account ID to most endpoints.


# Get action details and needs

post /api/v2/apps/{app}/actions/{action}/
Get details of a specific action, including its needs, gives, and a sample of the action.

"Needs" are the values that need to be provided when the action is run. Needs that are not provided will be guessed by the AI when the action is run.

"Gives" are the values that the action returns when it is run.

"Sample" is a sample of what the action returns when it is run.

When you [Run a stateless action](/ai-actions/how-tos/stateless/run-stateless-action), "needs" are provided to the endpoint. For each need, you can either have AI guess a value for it or you can ask the user to provide a specific value.

## Custom fields

<Warning>
  The list of needs that this returns can change based on the `params` provided in the request body.

  As such, this endpoint may need to be hit multiple times as needs are chosen by the user.
</Warning>

This endpoint needs to be hit again whenever the selected value for a need changes and *any of the following* are true:

*   The changed need has `alters_custom_fields` set to `true`
*   The changed need is in the `depends_on` array of another need
*   The changed need has `custom_field` set to `true`

As an example, when using the `GoogleSheetsV2API` API with the `add_row` action, the first time you get the action you will get back these `action_needs`:

```json
{
  "action_needs": [
    {
      "key": "spreadsheet",
      "label": "Spreadsheet",
      "prefill": "spreadsheet.id.title",
      "custom_field": false,
      "depends_on": [],
      "alters_custom_fields": true
    },
    {
      "key": "worksheet",
      "label": "Worksheet",
      "prefill": "worksheet.id.title",
      "custom_field": false,
      "depends_on": ["spreadsheet"],
      "alters_custom_fields": true
    }
  ]
}
```

In order to get the column names of the spreadsheet, you'll have to get the prefill choices. See [Get Prefill Choices](/ai-actions/how-tos/action-info/prefill-choices) for more information.

In this example scenario, after getting values for the `spreadsheet` and `worksheet` prefills, another call can be made to this endpoint.

This time, in the body of the `POST`, we can include the selected values:

```json
{
  "params": {
    "spreadsheet": "SOME_SPREADSHEET_ID", // this is the ID of a spreadsheet in Google Sheets
    "worksheet": "0" // the first worksheet in the spreadsheet
  }
}
```

Now, the action details will include the columns from the spreadsheet as custom fields:

```json
{
  "action_details": [
    // ... same results as above, in addition to...
    {
      "key": "COL$A",
      "label": "My first column",
      "prefill": null,
      "custom_field": true,
      "depends_on": [],
      "alters_custom_fields": null
    },
    {
      "key": "COL$B",
      "label": "My second column",
      "prefill": null,
      "custom_field": true,
      "depends_on": [],
      "alters_custom_fields": null
    },
    {
      "key": "COL$C",
      "label": "My third column",
      "prefill": null,
      "custom_field": true,
      "depends_on": [],
      "alters_custom_fields": null
    }
  ]
}
```

## Choices

If the need has a `choices` array, then the value being chosen should come from that list.

Often, you will see this with `boolean` fields.

For example, the `SlackAPI` API's `direct_message` action contains the following need (irrelevant fields removed):

```json
{
  "key": "as_bot",
  "type": "boolean",
  "label": "Send as a bot?",
  "choices": [
    {
      "key": "no",
      "label": "No"
    },
    {
      "key": "yes",
      "label": "Yes"
    }
  ],
  "default": "yes",
  "format": "SELECT"
}
```

## Other field types

See [Field definitions and types](/platform/build/field-definitions) in the Zapier Platform documentation for details on the other types of fields that a need can have.


# Choose authentication

get /api/v2/apps/{app}/auths/
Get list of Zapier authentications associated with the requesting user and account.

<Tip>
  Authentications for each app can be managed [in your Zapier account](https://zapier.com/app/connections).
</Tip>


# Find an action

get /api/v2/apps/{app}/actions/
Search for Zapier actions by app, ordered by relevancy.



# Find an app

get /api/v2/apps/search/



# Introduction



When using AI Actions, you will first have to build up information about the action that you want to run.

## When do I need to gather action information?

See [Stored vs. Stateless Actions](/ai-actions/how-tos/stateless-vs-stored) for more information.

You do **not** need to gather this information if:

*   You're running a **stored action** that is configured in the AI Actions UI

You **do** need to gather this information if:

*   You're running a **stateless action**
*   You're setting up a new stored action using the stored action management API

## Gathering action information

<Steps>
  <Step stepNumber={1} title="Find the app you want to use" titleSize="h3">
    Each action lives under an app, for example Google Sheets or Slack.

    You first need to choose which app you will want to use to run an action.

    See [Find an app](/ai-actions/how-tos/action-info/find-app) for more details.
  </Step>

  <Step stepNumber={2} title="Find the action you want to run" titleSize="h3">
    Once you know the app you want to use, you can choose which action you want to run for that app.

    See [Find an action](/ai-actions/how-tos/action-info/find-action) for more details.
  </Step>

  <Step stepNumber={3} title="Find the user's auth" titleSize="h3">
    Every app has authentications that are configured in Zapier for each user.

    To run an action, you need to know which Zapier account and app authentication that you want to run the action with.

    See [Choose authentication](/ai-actions/how-tos/action-info/choose-auth) for more details.
  </Step>

  <Step stepNumber={4} title="Get action details and prefill choices" titleSize="h3">
    Each action has a number of "needs" that can be used to decide what values to run the action with.

    See [Get action details and needs](/ai-actions/how-tos/action-info/action-details) for more details.

    Some of these needs are have a `prefill` key, which means that they provide a list of values that can be chosen from.

    See [Get prefill choices](/ai-actions/how-tos/action-info/prefill-choices) for more details.

    <Tip>
      **You don't have to get a value for every need!**

      When running an action, you can have the AI guess values based on instructions.
    </Tip>
  </Step>

  <Step stepNumber={5} title="Run or save the action" titleSize="h3">
    Once you have all of the details you need, you can either run the action as a stateless action, or save the details for later.

    See [Stored vs Stateless Actions](/ai-actions/how-tos/stateless-vs-stored) for more information.
  </Step>
</Steps>


# Get prefill choices

post /api/v2/apps/{app}/choices/{prefill}/
Get prefill choices for an app's prefill.

If a need that comes back from the [Get Action Details](/ai-actions/how-tos/action-info/action-details) endpoint has a `prefill` key, then this endpoint can be used to get the choices for that prefill.

## Dependent needs

If a need has any values in its `depends_on` array, the values for each of the needs in `depends_on` *must* be included in the `params` in the body of the `POST` to get prefill choices.

As an example, the `action_needs` list for the `add_row` action for the `GoogleSheetsV2API` app from [Get action details and needs](/ai-actions/how-tos/action-info/action-details) includes a `spreadsheet` and a `worksheet` need:

```json
{
  "action_needs": [
    {
      "key": "spreadsheet",
      "label": "Spreadsheet",
      "prefill": "spreadsheet.id.title",
      "depends_on": []
    },
    {
      "key": "worksheet",
      "label": "Worksheet",
      "prefill": "worksheet.id.title",
      "depends_on": ["spreadsheet"]
    }
  ]
}
```

This means that, in order to get the `worksheet.id.title` prefill choices, you need to include a `spreadsheet` value in the request to this endpoint:

```json
{
  "params": { "spreadsheet": "SOME_SPREADHSEET_ID" }
}
```

`SOME_SPREADHSEET_ID` would have come from a call to get prefill choices for the `spreadsheet.id.title` value.

## Caching

By default, responses from this endpoint for a given `app`, `prefill`, `params`, and user will be cached for 24 hours.

When this endpoint is first hit, a background job is started that will paginate through all of the available choices.

While this background job is running, the results from this endpoint will return with `"is_loading": true`.

If `is_loading` is `true`, then the endpoint should be polled every \~1 second until `is_loading` is `false`, at which point all of the available results are returned.

The time that the cache was last updated is returned with `last_refresh_seconds_ago`.

To force a refresh of the cache, the `force_refresh` query parameter can be set to `true`.

<Warning>
  `force_refresh` should only be used when the choices are expected to have changed, and should only be used with a single request.

  Repeated reqeusts with `force_refresh` set to `true` will cause results to continuously load.
</Warning>


# Authentication

get /api/v2/auth/check/
Test that the API and auth are working.

# Introduction

There are two methods that can be used to authenticate with `@zapier/ai-actions`:

*   [API Key](#api-key): If you plan on only using the AI Actions client to interact with AI Actions on your behalf
*   [OAuth](#oauth): If you plan on creating an OAuth app to allow users to interact with AI Actions on their behalf

<Tip>
  If you plan on using the interactive playground available in this
  documentation, then an API key must be user.
</Tip>

## API Key

Follow the steps below to create your API key:

1.  [Log in](https://actions.zapier.com/custom/start/) or [sign up](https://zapier.com/sign-up) to Zapier.
2.  [Connect](https://actions.zapier.com/custom/actions/) your Zapier account to the `custom` integration app.
3.  Your API key can be retrieved from the [Credentials page](https://actions.zapier.com/credentials/).

<Warning>
  **Treat your API key like a password.**

  It can be used to run your AI Actions.

  For example: if you set up a "Gmail: Find email" action, anyone with your API key can read all your email.
</Warning>

## OAuth

### Creating an OAuth app

<Card title="Click here to create an OAuth app" icon="alien-8bit" href="https://actions.zapier.com/providers/add">
  Click here to create a new OAuth app to use for authenticating with AI
  Actions.

  <Warning>
    In order to work with the `@zapier/ai-actions` API client, **your app must
    have "Public Client" checked.**
  </Warning>
</Card>

### Authenticating with OAuth

<Tip>
  If you're using JavaScript/TypeScript, you can use the `@zapier/ai-actions` library to handle authentication for you!

  [See the `@zapier/ai-actions` documentation here](/ai-actions/libraries/nodejs/authentication#getting-an-oauth-token)
</Tip>

### Getting a token

Your OAuth client can be used with the [PKCE flow](https://www.oauth.com/playground/authorization-code-with-pkce.html) to authenticate with AI Actions using a `Bearer` token.

To begin this process, you first need to generate a code verifier and a code challenge.

Here is some sample code in TypeScript to do this:

```ts
/*
 * https://nodejs.org/api/crypto.html#cryptogetrandomvaluestypedarray
 *
 * Store the verifier securely, as it will be needed later
 *
 * @returns Verifier to use when getting a token
 */
const generateVerifier = () => {
  const array = new Uint32Array(28);
  crypto.getRandomValues(array);

  return Array.from(array, (item) => `0${item.toString(16)}`.slice(-2)).join(
    ""
  );
};

/** Base64 URL encode a buffer */
const base64URLEncode = (buffer: ArrayBuffer) => {
  const uint8Array = new Uint8Array(buffer);
  const base64String = btoa(String.fromCharCode(...uint8Array));
  return base64String
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
};

/**
 * @param verifier Verifier from `generateVerifier`
 * @returns Base64 URL encoded SHA-256 hash of the verifier
 */
const generateCodeChallenge = async (verifier: string) => {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
  return base64URLEncode(hashBuffer);
};
```

<Warning>
  Make sure to store the verifier securely! It will be needed when returning to
  your site after authenticating with AI Actions
</Warning>

You can then send the user to AI Actions to see your OAuth consent screen:

```
https://actions.zapier.com/oauth/authorize?
  client_id=YOUR_CLIENT_ID&
  redirect_uri=YOUR_REDIRECT_URI&
  scope=openid%20nla%3Aexposed_actions%3Aexecute&
  response_type=code&
  code_challenge=YOUR_CODE_CHALLENGE&
  code_challenge_method=S256
```

After the user has authorized your app, they will be redirected to the `redirect_uri` you provided with a `code` query parameter.

You can then exchange this code for a token, using the `verifier` that you generated previously:

```sh
curl -X POST "https://actions.zapier.com/oauth/token/" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     --data-urlencode "client_id=YOUR_CLIENT_ID" \
     --data-urlencode "grant_type=authorization_code" \
     --data-urlencode "code_verifier=YOUR_VERIFIER" \
     --data-urlencode "redirect_uri=YOUR_REDIRECT_URI" \
     --data-urlencode "code=YOUR_CODE"
```

This will return the following JSON:

```ts
interface OAuthAccessToken {
  /** JWT with user info */
  id_token: string;

  /** Token that can be used with the `Authorization: Bearer ...` header */
  access_token: string;

  /** Token that can be used to get a new access token */
  refresh_token: string;

  /** Number of seconds until the token expires */
  expires_in: number;

  token_type: "Bearer";
  scope: "openid nla:exposed_actions:execute";
}
```

The `access_token` can then be used to make API calls to AI Actions:

```sh
curl -X GET "https://actions.zapier.com/api/v2/auth/check" -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

### Quick account creation

Since your users may not already have a Zapier account, we offer a quick account creation flow that allows users to create a Zapier account and connect their account to your app in one step.

To use this flow, first generate the `/oauth/authorize` URL as outlined above.

Then, get the account creation URL for your OAuth client:

```sh
https://actions.zapier.com/api/v2/auth/login-link/?
  redirect_uri=OAUTH_AUTHORIZE_URL&
  sign_up_first_name=USER_FIRST_NAME&
  sign_up_last_name=USER_LAST_NAME&
  sign_up_email=USER_EMAIL
```

This will return the following JSON:

```ts
{
  "login_link": "https://zapier.com/....."
}
```

When you send a user to the provided `login_link`, they will go through a quick Zapier account creation flow. If the provided email address is already associated with a Zapier account, they will be asked to log in.
Users will receive a follow-up email from Zapier to confirm their email address and to let them set a password for the account.

The user will then see the AI Actions OAuth consent screen and be redirected back to your `redirect_uri` with a `code` query parameter, which can be exchanged for an access token as outlined above.

### Refreshing tokens

After `expires_in` seconds, the `access_token` will expire. To get a new token, you can use the `refresh_token` that was returned when you got the original token:

```sh
curl -X POST "https://actions.zapier.com/oauth/token/" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     --data-urlencode "client_id=YOUR_CLIENT_ID" \
     --data-urlencode "grant_type=refresh_token" \
     --data-urlencode "refresh_token=YOUR_REFRESH_TOKEN"
```

This will return a new OAuth token that can be used in an `Authorization: Bearer ...` header.


# Stored Vs. Stateless Actions



When using the AI Actions API, there are two ways that actions can be run: stored and stateless.

## Stored Actions

With stored actions, you let us handle storing all of the details about an action.

You will only need to provide the user instructions and parameter hints when running the action.

<Card title="Click here to get started with stored actions!" icon="rocket-launch" href="/ai-actions/how-tos/stored/intro">
  Stored actions are configured ahead of time, either using the AI Actions UI or
  the provided management endpoints.

  <br />

  Only user instructions and parameter hints are needed when running them.
</Card>

<Card title="Click here for the React library documentation!" icon="react" href="/ai-actions/libraries/react/getting-started">
  A `@zapier/ai-actions-react` package is available to make it easy to interact
  with stored actions from a React application.
</Card>

## Stateless Actions

With stateless actions, you provide all of the necessary parameters at runtime.

This means you will have to store action details yourself or generate the action every time you want to run it.

<Card title="Click here to get started with stateless actions!" icon="rabbit-running" href="/ai-actions/how-tos/stateless/intro">
  Stateless actions are run by providing all the necessary parameters at
  runtime.

  <br />

  This gives you greater control over how actions are run.
</Card>


# Stateless actions introduction



When running a stateless action, all of the parameters that are needed for it to run are provided at runtime.

See [Gathering Action Information](/ai-actions/how-tos/action-info/introduction) for details on how to build up the needed values that are required

See [Run a Stateless Action](/ai-actions/how-tos/stateless/run-stateless-action) for information on actually running the action.


# Run a Stateless Action

post /api/v2/execute/

See `params_constraints` in the documentation below for details on how to tell the AI which values to use and which to guess when running the action.

See [Get action details and needs](/ai-actions/how-tos/action-info/action-details) for how to discover the needs for an action.


# Configure stored actions in the AI Actions UI



<Note>
  *Planned improvements to stored actions*<br />
  While stored actions can only currently be configured on the AI Actions domain, work is planned to provide APIs and components that allow you to embed action configuration in your own UI.
</Note>

Actions can be configured by you at a specific URL or with our quick configuration popup.

The setup URL is the same for everyone: `https://actions.zapier.com/<PROVIDER>/start/`

You'd typically include a button or link somewhere to open the setup window. Here's an example of how you might do that:

```javascript
var actions = window.open('https://actions.zapier.com/<PROVIDER>/start', 'actions', 'width=650,height=700');
```


# Stored actions introduction



Running a stored action involves setting up the action in the AI Actions UI and then running it by its ID.

This is done in the following steps:

<Steps>
  <Step stepNumber={1} title="Set up the action" titleSize="h3">
    Actions need to be configured by a user before they can be used.

    There are two ways to set up stored actions:

    1.  Using the AI Actions website- see [Configure stored actions](/ai-actions/how-tos/stored/action-configuration) for more details.
    2.  Using the AI Action management API provided under [Manage stored actions](/ai-actions/how-tos/stored/manage/create-action).
  </Step>

  <Step stepNumber={2} title="Provide actions as tools to an LLM" titleSize="h3">
    After actions are set up, they can be provided as tools to an LLM.

    See [List stored Actions](/ai-actions/how-tos/stored/list-actions) for more details.
  </Step>

  <Step stepNumber={3} title="Run the action" titleSize="h3">
    The LLM can then decide which action it wants to run.

    See [Run a stored action](/ai-actions/how-tos/stored/run-stored-action) for more details.
  </Step>
</Steps>


# List stored Actions

get /api/v2/ai-actions/
List all the current actions for the current user.

These actions can then be provided as tools to an LLM, which can decide what parameters to use when running the action.

<Tip>See [Run a stored action](/ai-actions/how-tos/stored/run-stored-action) for details on running an action.</Tip>


# Create stored action

post /api/v2/ai-actions/
Create a new AI Action that can be executed by the user later.



# Delete stored action

delete /api/v2/ai-actions/{ai_action_id}/
Delete an AI Action.

Returns `true` if the action was deleted, `false` if it was not found.



# Get stored action

get /api/v2/ai-actions/{ai_action_id}/
Get the details of a specific AI Action.

<Tip>See [Run a stored action](/ai-actions/how-tos/stored/run-stored-action) for details on running an action.</Tip>


# Update stored action

put /api/v2/ai-actions/{ai_action_id}/
Update an existing AI Action.

<Warning>
  Partial updates are not currently supported.

  Use the [Get AI Action](/ai-actions/how-tos/stored/manage/get-action) endpoint to get the action details first.
</Warning>


# Run a stored action

post /api/v2/ai-actions/{ai_action_id}/execute/
Given an action ID and instructions, this will execute the action and return the results.



# Introduction

AI Actions exposes Zapier's 7,000+ apps and 30,000+ actions, for selected partners, to build your own custom AI assistants.

You can use the API to:

*   Send messages in Slack
*   Add a row to a Google Sheet
*   Draft a new email in Gmail
*   And thousands of other app actions, all with one universal natural language API.

AI Actions is optimized for receiving user input in natural language like chat, assistant, or other large language model based experience. Think of AI Actions as a more human-friendly integrations API.

## Integrations made easy

AI Actions contains a decade of Zapier's experience in solving API shenanigans, so you don't have to.

Here are some common API complexities, automatically handled by AI Actions.

*   **Support for every type of auth**: Zapier securely handles and signs requests for you including Basic, Session, API Key, OAuth v1, Oauth v2, and Digest.
*   **Support for create, update, and search actions**: With endpoints optimized for natural language usage.
*   **Support for custom fields**: Spreadsheet, CRM, and mailing list friendly.
*   **Reference by name, not ID**: Humans use natural language names, not IDs, to reference things in their apps, so AI Actions does too.
*   **Smart, human defaults**: APIs sometimes have 100 options. Zapier's platform data helps us make AI Actions simpler for users out of the box.

## API design notes

Here are a few design choices we've made with this API.

*   Let individual users control access to their actions and accounts.
*   Minimize the amount of work users must do to set up and run actions.
*   Let users override the AI when desired.


# Authentication



# Introduction

There are two methods that can be used to authenticate with `@zapier/ai-actions`:

*   [API Key](#api-key): If you plan on only using the AI Actions client to interact with AI Actions on your behalf
*   [OAuth](#oauth): If you plan on creating an OAuth app to allow users to interact with AI Actions on their behalf

## API Key

Your API key can be retrieved from the [Credentials page](https://actions.zapier.com/credentials/).

<Warning>
  **Treat your API key like a password.**

  It can be used to run your AI Actions.

  For example: if you set up a "Gmail: Find email" action, anyone with your API key can read all your email.
</Warning>

Create an `AiActions` client with the API key:

```ts
import { AiActions } from "@zapier/ai-actions";

const client = new AiActions({
  auth: {
    apiKey: "YOUR_API_KEY",
  },
});
```

## OAuth

### Creating an OAuth app

<Card title="Click here to create an OAuth app" icon="alien-8bit" href="https://actions.zapier.com/providers/add">
  Click here to create a new OAuth app to use for authenticating with AI
  Actions.

  <Tip>
    In order to work with the API client, **your app must have "Public Client"
    checked.**
  </Tip>
</Card>

### Getting an OAuth token

#### Creating the `AiActionsAuth` object

`@zapier/ai-actions` provides a helper class to handle the auth process.

This is done with the `AiActionsAuth` class:

```ts
import { AiActionsAuth } from "@zapier/ai-actions";

const aiActionsAuth = new AiActionsAuth({
  clientId: "YOU_APP_CLIENT_ID",
  redirectUri: "YOUR_APP_REDIRECT_URI",
});
```

#### Storing the code verifier securely

`@zapier/ai-actions` uses the [OAuth 2.0 PKCE flow](https://www.oauth.com/playground/authorization-code-with-pkce.html) to facilitate the OAuth process. As part of this, a "code verifier" is generated.

<Warning>
  By default, `AiActionsAuth` will store the verifier in the browser's
  `localStorage`.

  **This is not recommended for production use** since it can be easily accessed
  by malicious scripts, and it will not work server-side.
</Warning>

To facilitate storing the code verifier securely, the constructor for `AiActionsAuth` has options that can be used.

```ts
import { AiActionsAuth } from "@zapier/ai-actions";

const aiActionsAuth = new AiActionsAuth({
  clientId: "YOU_APP_CLIENT_ID",
  redirectUri: "YOUR_APP_REDIRECT_URI",
  async storeVerifier(verifier) {
    // store the verifier somewhere secure for your user (i.e. database or KV store)
  },
  async getVerifier(): Promise<string> {
    // this function MUST return the verifier stored for the user by `storeVerifier`
    // each verifier can only be used once, and it must be consistend throughout the OAuth flow
  },
});
```

#### Generating the authorization OR quick account creation URL

Once you have an `AiActionsAuth` object, you can use it to generate the authorization URL:

```tsx
const authUrl = await aiActionsAuth.generateAuthUrl();

// ...

<a href={authUrl}>Authenticate with AI Actions</a>;
```

This URL can be used to redirect the user to the authorization page. Once the user has authorized your app, they will be redirected to the `redirectUri` you provided.

***

You can also generate a **quick account creation** URL. This will ensure that the user has a Zapier account before authorizing your app.

If they do not have a Zapier account, one will be created for them. If they already have a Zapier account, they will be asked to log in on zapier.com.

```tsx
const quickAccountCreationUrl = await aiActionsAuth.generateCreateAccountUrl(
  "Firstname",
  "Lastname",
  "user@example.com"
);

// ...

<a href={quickAccountCreationUrl}>Authenticate with AI Actions</a>;
```

After going through this flow, they will end up back on the `redirectUri` that you provided.

#### Getting an access token

After authorizing your app, the user will be redirected to the `redirectUri` you provided along with a `code` query parameter.

This `code` can now be used to get an access token

```ts
import { AiActions } from "@zapier/ai-actions";

// `code` will be in the query params; get it however you like (most likely server-side)
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");

// this access token can now be used to generate an AiActions client
const oauthToken = await aiActionsAuth.getToken(code);

const aiActionsClient = new AiActions({
  auth: {
    token: oauthToken.access_token,
  },
});

try {
  // this will pass if auth succeeds, or throw an error if it fails
  await aiActionsClient.checkAuth();
} catch (e) {
  console.error("Error checking AI Actions auth", e);
}
```

#### Refreshing the access token

Access tokens expire after a certain amount of time. To refresh the token, you can use the `refreshToken` method along with the `refresh_token`:

```ts
import {
  OAuthAccessToken,
  refreshOAuthToken,
  tokenNeedsRefresh,
} from "@zapier/ai-actions";

// get the stored token for the current user; `getAiActionsToken` is a placeholder for your own function
const oauthToken: OAuthAccessToken = await getAiActionsToken();

if (tokenNeedsRefresh(oauthToken)) {
  const refreshed = await refreshOAuthToken({
    clientId: "YOUR_CLIENT_ID",
    refreshToken: oauthToken.refresh_token,
  });

  if (refreshed) {
    // `setAiActionsToken` is a placeholder for your own function
    await setAiActionsToken(refreshed);
  }
}
```


# Getting started



<Note>
  ### **The `@zapier/ai-actions` package is still in development!**

  If you want to check it out, please [contact us](https://nla.zapier.app/_z/embed/page/clh5sdteo0001ml0pdz8a2aqr?&)
</Note>

The `@zapier/ai-actions` library makes it easy to interact with AI Actions from the client or the server.

The library includes an API client, Typescript types, and utilities to make authentication easy.

## Installation

To install the library, run:

```bash
# npm
npm install @zapier/ai-actions

# pnpm
pnpm add @zapier/ai-actions

# yarn
yarn add @zapier/ai-actions
```

## Quick start

[Get your API Key here](https://actions.zapier.com/credentials/)

Create a new `AiActions` client with your API key:

```ts
import { AiActions } from "@zapier/ai-actions";

const aiActionsClient = new AiActions({
  auth: {
    apiKey: "sk-YOUR_API_KEY",
  },
});
```

Use the client:

```ts
const actionList = await aiActionsClient.getActionList();

actionList.results.forEach((action) => {
  console.log(action.id, action.description);
});
```

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key-skeleton" href="/ai-actions/libraries/nodejs/authentication">
    Set up OAuth to allow users to interact with AI Actions on their behalf
  </Card>

  <Card title="Usage examples" icon="sparkles" href="/ai-actions/libraries/nodejs/usage">
    Check out some examples of how to use the library
  </Card>

  <Card title="Use with Next.js" icon="circle-n" href="/ai-actions/libraries/nodejs/nextjs-reccommendation">
    Set up the library with Next.js
  </Card>

  <Card title="React Components" icon="react" href="/ai-actions/libraries/react/getting-started">
    See how to use the React component library
  </Card>
</CardGroup>


# Next.js



This page details recommended ways to use the `@zapier/ai-actions` library with [Next.js](https://nextjs.org/).

The examples here are using the [Next.js app router](https://nextjs.org/docs/app).

For storage, these examples are using [Vercel KV](https://vercel.com/docs/storage/vercel-kv) but any other form of server-side storage can be used.

It's assumed that your Next.js app also has authentication set up, using a library such as [NextAuth.js](https://next-auth.js.org/).

# Set up environment variables

After [generating your OAuth client](/ai-actions/libraries/nodejs/authentication#getting-an-oauth-token), you can set your client ID and redirect URL inside of your `.env` file:

```sh .env
NEXT_PUBLIC_ZAPIER_AI_ACTIONS_OAUTH_CLIENT_ID=nla-YOUR_CLIENT_ID
NEXT_PUBLIC_ZAPIER_AI_ACTIONS_OAUTH_REDIRECT_URI=https://example.com/ai-actions-auth-callback
```

The `NEXT_PUBLIC_` prefix ensures that they can be referenced client-side.

# Handle authentication server-side

Authentication with AI Actions can be handled fully server-side.

```ts @/lib/ai-actions/server.ts
import "server-only"; // make sure this code only ever runs on the server

// using `@vercel/kv` as an example, but you can use any server-side storage
import { kv } from "@vercel/kv";

import {
  AiActionsAuth,
  OAuthAccessToken,
  refreshOAuthToken,
  tokenNeedsRefresh,
} from "@zapier/ai-actions";

/**
 * @param email User's email
 * @returns OAuth token to use for AI Actions API calls
 */
export async function getAiActionsToken(
  email: string
): Promise<OAuthAccessToken | null> {
  // NOTE: You don't have to use email here; it can be any sort of user identifier
  const token = await kv.hgetall<{ token: string }>(
    `ai-actions-token:${email}`
  );

  if (!token?.token) {
    return null;
  }

  const oauthToken = token.token as unknown as OAuthAccessToken;

  if (tokenNeedsRefresh(oauthToken)) {
    console.log(`Refreshing AI Actions token for ${email}`);
    const refreshed = await refreshOAuthToken({
      clientId: process.env.NEXT_PUBLIC_ZAPIER_AI_ACTIONS_OAUTH_CLIENT_ID!,
      refreshToken: oauthToken.refresh_token,
    });

    if (refreshed) {
      // see below; this just stores the token in the KV store
      await setAiActionsToken(email, refreshed);
      return refreshed;
    }
  }

  // typescript thinks this is a string, but it's actually an object
  return token.token as unknown as OAuthAccessToken;
}

/**
 * @param email User's email
 * @param token AI Actions OAuth token to set for the user
 */
export async function setAiActionsToken(
  email: string,
  token: OAuthAccessToken | null
) {
  await kv.hset(`ai-actions-token:${email}`, { token: JSON.stringify(token) });
}

/**
 * @param session Current user's session
 * @returns AI Actions auth object to use for OAuth
 */
export const getAiActionsAuth = (email: string) =>
  new AiActionsAuth({
    clientId: process.env.NEXT_PUBLIC_ZAPIER_AI_ACTIONS_OAUTH_CLIENT_ID!,
    redirectUri: process.env.NEXT_PUBLIC_ZAPIER_AI_ACTIONS_OAUTH_REDIRECT_URI!,
    async storeVerifier(verifier) {
      await kv.hset(`ai-actions-verifier:${email}`, { verifier });
    },
    async getVerifier(): Promise<string> {
      const key = `ai-actions-verifier:${email}`;
      const verifier = await kv.hgetall<{ verifier: string }>(key);

      // this can only be used once, so we delete it after getting it
      await kv.hdel(key, "verifier");

      return verifier?.verifier || "";
    },
  });
```

# Generating the authorization URL

Here is an example of a button that will initiate authentication with AI Actions, using the server-side code from above.

```tsx initiate-ai-actions-auth.tsx
"use server";

import { getAiActionsAuth } from "@/lib/ai-actions/server";
import { auth } from "@/auth";
import { Session } from "@/lib/types";
import Link from "next/link";

export const InitiateAiActionsAuth = async () => {
  const session = (await auth()) as Session;
  const aiActionsAuth = getAiActionsAuth(session.user.email);
  const url = await aiActionsAuth.generateAuthUrl();

  return (
    <div className="w-full h-full flex flex-col items-center mt-6 gap-3">
      <div>Please authenticate with AI Actions to proceed</div>
      <Link href={url.toString()}>Authenticate with AI Actions</Link>
    </div>
  );
};
```

# Getting and storing the access token

To get the access token after the user has authenticated, a page needs to exist at `NEXT_PUBLIC_ZAPIER_AI_ACTIONS_OAUTH_REDIRECT_URI` that will handle the OAuth flow.

```tsx @/app/NEXT_PUBLIC_ZAPIER_AI_ACTIONS_OAUTH_REDIRECT_URI/page.tsx
import { auth } from "@/auth";
import { Session } from "@/lib/types";
import { getAiActionsAuth, setAiActionsToken } from "@/lib/ai-actions/server";
import { redirect } from "next/navigation";

/**
 * The user will get redirected here after they login to Zapier/AI Actions.
 *
 * This will store the OAuth access token for the user.
 */
export default async function LoginSuccess({
  searchParams,
}: {
  searchParams: { code: string };
}) {
  const { code } = searchParams;

  const session = (await auth()) as Session;

  const aiActionsAuth = getAiActionsAuth(session.user.email);
  const response = await aiActionsAuth.getToken(code);

  if (response) {
    await setAiActionsToken(session.user.email, response);
  } else {
    console.error("Error getting AI Actions token", response);
    return redirect("/error");
  }

  return redirect("/");
}
```

# Using the access token to create an `AiActions` client

Once the token is stored, it can be used to get an `AiActions` client that can be used to make API calls.

It can also be passed to the `AiActionsProvider` if you're using the `@zapier/ai-actions-react` library.

<Warning>
  Make sure that the `refresh_token` for the OAuth token doesn't leave the
  server
</Warning>

```tsx @/app/example-route/page.tsx
import { AiActions } from "@zapier/ai-actions";
import { Session } from "@/lib/types";
import { getAiActionsToken, setAiActionsToken } from "@/lib/ai-actions/server";
import { ExampleClientComponent } from "./example-component";

export default async function ExamplePage() {
  const session = (await auth()) as Session;

  let aiActionsToken = session?.user
    ? await getAiActionsToken(session.user.email)
    : null;

  if (aiActionsToken) {
    const aiActionsClient = new AiActions({
      auth: {
        token: aiActionsToken.access_token,
      },
    });

    try {
      await aiActionsClient.checkAuth();
    } catch (e) {
      console.error("Error checking AI Actions auth", e);
      aiActionsToken = null;
      setAiActionsToken(session.user.email, null);
    }
  }

  return <ExampleComponent aiActionsToken={aiActionsToken.access_token} />;
}
```

```tsx @/app/example-route/example-component.tsx
"use client";

import { AiActionsProvider, ActionList } from "@zapier/ai-actions-react";

export const ExampleComponent = ({
  aiActionsToken,
}: {
  aiActionsToken: string;
}) => {
  return (
    <AiActionsProvider token={aiActionsToken}>
      <ActionList />
    </AiActionsProvider>
  );
};
```


# Usage examples



# Introduction

The AI Actions API client can be used to interact with any of the API endpoints that AI Actions supports.

Below are a few examples of common uses for the client.

# Creating a client

See the [authentication guide](/ai-actions/libraries/nodejs/authentication) for more information on how to authenticate.

```ts
import { AiActions } from "@zapier/ai-actions";

const aiActionsClient = new AiActions({
  auth: {
    apiKey: "YOUR-API-KEY",
    // or...
    token: "OAUTH-TOKEN",
  },
});
```

# Checking a user's authentication

This is useful if you want to check if a user's AI Actions OAuth token is valid.

```ts
try {
  await aiActionsClient.checkAuth();
} catch (error) {
  console.error("User is not authenticated", error);
}
```

# Error handling

The `@zapier/ai-actions` package exports an `AiActionsHttpError` type that can be used to get JSON from errors.

```ts
import { AiActionsHttpError } from "@zapier/ai-actions";

try {
  await aiActionsClient
    .executeAction
    // ...
    ();
} catch (error) {
  if (error instanceof AiActionsHttpError) {
    const parsedError = await error.response.json();
    console.error("Error executing action", parsedError);
  }
}
```

# Listing a user's actions

Actions can be created or edited either on the [AI Actions website](https://actions.zapier.com/providers/) or using the [`@zapier/ai-actions-react` library](/ai-actions/libraries/react/getting-started)

```ts
const actionList = await aiActionsClient.getActionList();

actionList?.results.forEach((action) => {
  // Each action has an ID that can be used to execute it, and it includes information about how the user
  // has set it up, such as the description and the app it's associated with.
  console.log(action.id, action.description); // "01EXAMPLE_ID Gmail: Find Email"
});
```

# Executig an action

See the [Run a stored action guide](/ai-actions/how-tos/stored/run-stored-action) for more information on the available parameters.

```ts
const result = await aiActionsClient.executeAction(
  {
    ai_action_id: "YOUR_ACTION_ID",
  },
  {
    instructions: "Create a row with the following data....",
    params: {
      spreadsheet: {
        mode: "guess",

        // AI Actions will figure out the proper ID to use based on the hint,
        // that way you don't need to worry about knowing IDs!
        value: "My Spreadsheet",
      },
    },
    preview_only: false,
  }
);

console.log(result?.resolved_params, result?.full_results);
```

# Search for Zapier apps and actions

```ts
const appSearch = await aiActionsClient.searchApps({ query: "Google Sheets" });

appSearch?.results.forEach(async ({ name, app }) => {
  console.log(name);

  const actionSearch = await aiActionsClient.searchActions({
    app,

    //  omit `query` to get all actions for the app
    query: "add spreadsheet row",
  });
});
```

# Give feedback on an action run

If an execution doesn't work properly, you can provide feedback on it to help us improve the AI.

```ts
const result = await aiActionsClient.executeAction({
  // ...
});

if (result) {
  await aiActionsClient.rateExecution({
    execution_log_id: result.execution_log_id,

    // -1: Negative feedback
    // 0: Neutral feedback
    // 1: Positive feedback
    rating: -1,
    feedback: "It didn't work for me!",
  });
}
```


# ActionList



The `ActionList` component can be used to list all of the current [stored actions](/ai-actions/how-tos/stored/intro) that are available for a user.

If you are using an API key for authentication, then the stored actions listed will be only for that API Key.

If you are using OAuth for authentication, then the stored actions listed will only be the actions that a user has set up for your OAuth provider.

## Props

### `onActionSelected`

This is called when the user clicks on an action in the list. It is passed the `AiAction` object that was clicked.

See the [List stored actions](/ai-actions/how-tos/stored/list-actions) documentation for more information about what this object contains.

## Usage

<Warning>
  Make sure that this component is rendered unerneath an
  [`AiActionsProvider`](/ai-actions/libraries/react/ai-actions-provider)!
</Warning>

See [EditAction and CreateAction](/ai-actions/libraries/react/edit-action) for a more complete example of usage.

```tsx ai-action-list.tsx
import { AiAction } from "@zapier/ai-actions";
import { ActionList } from "@zapier/ai-actions-react";

export const AiActionList = () => {
  return <ActionList onActionSelected={(action) => console.log(action)} />;
};
```

## Example

On the left is the `<ActionList />` component. Click on an action to see the details on the right.

<iframe
  style={{
   width: "100%",
   height: "500px",
   background: "white",
   borderRadius: "4px",
  }}
  src="https://actions.zapier.com/config/embed/v1/docs-examples/action-list"
/>

```tsx action-list-example.tsx
import { AiAction } from "@zapier/ai-actions";
import { ActionList, AiActionsProvider } from "@zapier/ai-actions-react";
import { FunctionComponent, useState } from "react";
import ReactJson from "react-json-view";

interface ActionListExampleProps {
  apiKey: string;
}

export const ActionListExample: FunctionComponent<ActionListExampleProps> = ({
  apiKey,
}) => {
  const [selectedAction, setSelectedAction] = useState<AiAction | null>(null);

  return (
    <div className="flex justify-between h-full">
      <div className="flex-1 w-0 max-h-full overflow-auto pl-2">
        <AiActionsProvider
          auth={{
            apiKey,
          }}
        >
          <ActionList
            onActionSelected={(action) => {
              setSelectedAction(action);
            }}
          />
        </AiActionsProvider>
      </div>
      <div className="flex-1 max-h-full overflow-auto">
        {selectedAction ? (
          <ReactJson src={selectedAction} />
        ) : (
          "Select an action to see its details"
        )}
      </div>
    </div>
  );
};
```


# AiActionsProvider



All `@zapier/ai-actions-react` components **must** be rendered underneath an `AiActionsProvider`.

## Props

### `auth`

The `auth` prop is used to handle authenticating with AI Actions.

It takes an object that can have one of two properties:

*   `apiKey`: A string containing your API key.
*   `token`: A string containing your OAuth token.

See the [Authentication guide](/ai-actions/libraries/nodejs/authentication) for more information on how to get an API key or an OAuth token.

<Tip>
  If you plan on using the AI Actions components to allow users to create and
  manage their own actions, then you will have to use the OAuth `token`
  authentication method.
</Tip>

## Usage

<Warning>
  If you're using a framework like Next.js, the `AiActionsProvider` and the
  components rendered as children of it need to be rendered client-side.

  This can be done by putting `"use client";` at the top of the file that uses `AiActionsProvider`.
</Warning>

```tsx
import { AiActionsProvider } from "@zapier/ai-actions-react";

export const MyAiActionsProvider = ({ children }) => {
  return (
    <AiActionsProvider
      auth={{
        apiKey: "YOUR_API_KEY",

        // or, if you have an OAuth token...
        // token: "YOUR_ACCESS_TOKEN"
      }}
    >
      {children}
    </AiActionsProvider>
  );
};
```


# EditAction and CreateAction



There are two separate components for managing a specific stored action with `@zapier/ai-actions-react`: `EditAction` and `CreateAction`.

`CreateAction` should be used when you do not have an existing action that you want to edit, and `EditAction` should be used when you know the ID of the action you want to edit.

## Props

### `onActionSaved`

This callback will be called with the updated action when the user clicks the "Save" button.

See the [Get stored action](/ai-actions/how-tos/stored/manage/get-action) documentation for more information about what the object contains.

### `actionId`

**Only for `EditAction`**

This is the ID of the action that you want to edit. When the component renders, it will fetch the latest version of the action to edit from the API.

### `onActionDeleted`

**Only for `EditAction`**

This callback wall be called with the ID of the action that was deleted when the user clicks the "Delete" button.

## Usage

<Warning>
  Make sure that this component is rendered unerneath an
  [`AiActionsProvider`](/ai-actions/libraries/react/ai-actions-provider)!
</Warning>

<CodeGroup>
  ```tsx create-action.tsx
  import { FunctionComponent } from 'react'
  import { AiAction } from "@zapier/ai-actions";
  import { CreateAction } from "@zapier/ai-actions-react";

  interface CreateAiActionsProps {
  onActionSaved: (action: AiAction) => void;
  }

  export const CreateAiAction: FunctionComponent<CreateAiActionsProps> = ({
    onActionSaved,
  }) => {
    return <CreateAction onActionSaved={onActionSaved} />;
  };
  ```

  ```tsx edit-action.tsx
  import { FunctionComponent } from "react";
  import { AiAction } from "@zapier/ai-actions";
  import { EditAction } from "@zapier/ai-actions-react";

  interface EditAiActionsProps {
    actionId: string;
    onActionSaved: (action: AiAction) => void;
    onActionDeleted: (actionId: string) => void;
  }

  export const EditAiAction: FunctionComponent<EditAiActionsProps> = ({
    actionId,
    onActionSaved,
    onActionDeleted,
  }) => {
    return (
      <EditAction
        actionId={actionId}
        onActionSaved={onActionSaved}
        onActionDeleted={onActionDeleted}
      />
    );
  };
  ```
</CodeGroup>

## Example

This example uses the [`ActionList` component](/ai-actions/libraries/react/action-list) to show a list of action and lets you select an action to edit. It also lets you create new actions.

<iframe
  style={{
   width: "100%",
   height: "750px",
   background: "white",
   borderRadius: "4px",
  }}
  src="https://actions.zapier.com/config/embed/v1/docs-examples/edit-action"
/>

```tsx edit-action-example.tsx
import { AiAction } from "@zapier/ai-actions";
import {
  ActionList,
  AiActionsProvider,
  CreateAction,
  EditAction,
} from "@zapier/ai-actions-react";
import { FunctionComponent, useState } from "react";

export const EditActionExample: FunctionComponent<{ apiKey: string }> = ({
  apiKey,
}) => {
  return (
    <AiActionsProvider
      auth={{
        apiKey: apiKey ?? "",
      }}
    >
      <div className="flex-1 w-full max-h-full overflow-auto pl-2">
        <ActionEditor />
      </div>
    </AiActionsProvider>
  );
};

export const ActionEditor: FunctionComponent = () => {
  const [selectedAction, setSelectedAction] = useState<AiAction | null>(null);
  const [createNewAction, setCreateNewAction] = useState<boolean>(false);

  if (selectedAction) {
    return (
      <div>
        <button onClick={() => setSelectedAction(null)}>
          ↩ Back to action list
        </button>
        <EditAction
          actionId={selectedAction.id}
          onActionSaved={() => setSelectedAction(null)}
          onActionDeleted={() => setSelectedAction(null)}
        />
      </div>
    );
  }

  if (createNewAction) {
    return (
      <div>
        <button onClick={() => setCreateNewAction(false)}>
          ↩ Back to action list
        </button>
        <CreateAction onActionSaved={() => setCreateNewAction(false)} />
      </div>
    );
  }

  return (
    <div>
      <button onClick={() => setCreateNewAction(true)}>
        Create new action
      </button>
      <ActionList
        onActionSelected={(action) => {
          setSelectedAction(action);
        }}
      />
    </div>
  );
};
```


# Getting started



<Note>
  ### **The `@zapier/ai-actions-react` package is still in development!**

  If you want to check it out, please [contact us](https://nla.zapier.app/_z/embed/page/clh5sdteo0001ml0pdz8a2aqr?&)
</Note>

The `@zapier/ai-actions-react` library provides utilities to make it easy to interact with AI Actions from a React application.

## Installation

To install the library, run:

```bash
# npm
npm install @zapier/ai-actions @zapier/ai-actions-react

# pnpm
pnpm add @zapier/ai-actions @zapier/ai-actions-react

# yarn
yarn add @zapier/ai-actions @zapier/ai-actions-react
```

## Quick start

[Get your API Key here](https://actions.zapier.com/credentials/)

Render the `AiActionsProvider` at the root of your application, or around any components that need to interact with AI Actions.

You can pass the API key as a prop to the provider component, or you can get an OAuth token. See the [Authentication guide](/ai-actions/libraries/nodejs/authentication) for more information.

```tsx
import { AiActionsProvider } from "@zapier/ai-actions-react";

export const MyAiActionsProvider = ({ children }) => {
  return (
    <AiActionsProvider
      auth={{
        apiKey: "YOUR_API_KEY",

        // or, if you have an OAuth token...
        // token: "YOUR_ACCESS_TOKEN"
      }}
    >
      {children}
    </AiActionsProvider>
  );
};
```

<Warning>
  If you're using a framework like Next.js, the `AiActionsProvider` and the
  components rendered as children of it need to be rendered client-side.

  This can be done by putting `"use client";` at the top of the file that uses `AiActionsProvider`.
</Warning>

Then, you can use the `ActionList` component to list the actions that are available to the user:

```tsx
import { AiAction } from "@zapier/ai-actions";
import { ActionList } from "@zapier/ai-actions-react";

export const AiActionList = () => {
  // When the user clicks on an action in the list, the `onActionSelected` callback will be called.
  // This can be used to open a modal to edit the action, or to execute it using the `AiActions` client.
  return <ActionList onActionSelected={(action) => console.log(action)} />;
};
```


# StatelessActionCreator



The `StatelessActionCreator` component can be used to create a new stateless action that you can use to run a stateless action.

## Props

### `onActionGenerated`

This callback will contain JSON that can be passed, along with instructions, to the [execute statless action](/ai-actions/how-tos/stateless/run-stateless-action) endpoint.

## Usage

<Warning>
  Make sure that this component is rendered unerneath an
  [`AiActionsProvider`](/ai-actions/libraries/react/ai-actions-provider)!
</Warning>

```tsx stateless-action-creator.tsx
import { StatelessAction } from "@zapier/ai-actions";
import { StatelessActionCreator } from "@zapier/ai-actions-react";

export const StatelessActionCreatorExample = () => {
  return (
    <StatelessActionCreator
      onActionGenerated={(action: StatelessAction) => console.log(action)}
    />
  );
};
```

This JSON can then be used with [the Node.js client](/ai-actions/libraries/nodejs/getting-started) to execute a stateless action:

```ts execute-stateless-action.ts
import { AiActions, StatelessAction } from "@zapier/ai-actions";

const aiActionsClient = new AiActions({
  auth: {
    apiKey: "sk-YOUR_API_KEY",
  },
});

const action: StatelessAction = {
  // JSON from the StatelessActionCreator
};

action.instructions =
  "Set the instructions you want to use when running the action";

const result = await aiActionsClient.executeStatelessAction(action, {
  // set this to `false` to actually run the action
  // otherwise, you can check the parameters that AI Actions will guess
  preview_only: true,
});

console.log(result);
```

## Example

[See the "Stateless action creator/runner" tool for a full example](/ai-actions/tools/stateless-action)


# Quickstart

Learn how to kick-start AI Actions by obtaining a partner key, setting up allowed actions, and executing an action through our API.

## Step 1: Get a Partner Key for Authentication

Follow the steps below to create your API key:

1.  [Log in](https://actions.zapier.com/custom/start/) or [sign up](https://zapier.com/sign-up) to Zapier.
2.  [Connect](https://actions.zapier.com/custom/actions/) your Zapier account to the `custom` integration app.
3.  Your API key will be available on the [credentials page](https://actions.zapier.com/credentials/).

Test that the API key is working:

````bash
curl -v \
    -H "Content-Type: application/json" \
    -H "x-api-key: <API_KEY>" \```
    'https://actions.zapier.com/api/v2/check/'
````

## Step 2: Set Up Allowed Actions

Now that you're authenticated, let's set up the actions you're allowed to execute.

Actions can be configured by you at a specific URL or with our quick configuration popup.

The setup URL is the same for everyone: `https://actions.zapier.com/<PROVIDER>/start/`.

## Step 3: Execute an Action via the API

With your partner key and actions set up, you're ready for action.

To run an action, you need to execute a command like the one below, filling in the `instructions` and any optional hint parameters.

<CodeGroup>
  ```bash run_action1.sh
  curl -v \
  -d '{"instructions": "grab the latest email from bryan helmig"}' \
  -H "Content-Type: application/json" \
  -X POST 'https://actions.zapier.com/api/v1/dynamic/exposed/01GTA3G1WD49GN1XXXXXXXXX/execute/'
  One more example, this time requesting a preview of the action:
  ```

  ```bash run_action2.sh
  curl -v \
      -d '{"instructions": "send a short poem about automation and robots to slack", "Channel_Name": "#fun-zapier"}' \
      -H "Content-Type: application/json" \
      -X POST 'https://actions.zapier.com/api/v1/dynamic/exposed/01GTB1KMX72QTJEXXXXXXXXXX/execute/'
  Another example, this time an action to retrieve data:
  ```

  ```bash preview_action_run.sh
  curl -v \
  -d '{"instructions": "say Hello World to #fun-zapier", "preview_only": true}' \
  -H "Content-Type: application/json" \
  -X POST 'https://actions.zapier.com/api/v1/dynamic/exposed/01GTB1KMX72QTJEXXXXXXXXXX/execute/'
  ```
</CodeGroup>

<CodeGroup>
  ```json error_response.json
  {
    "action_used": "Gmail: Send Email",
    "result": null,
    "status": "error",
    "error": "Error from app: Required field 'subject' (subject) is missing. Required field 'Body' (body) is missing."
  }
  ```

  ```json preview_response.json
  {
  "action_used": "Slack: Send Channel Message",
  "input_params": {
  "Channel": "fun-zapier",
  "Message_Text": "Hello World"
  },
  "review_url": "https://actions.zapier.com/execution/01GW2E2ZNE5W07D32E41HFT5GJ/?needs_confirmation=true",
  "status": "preview",
  }
  ```

  ```json trimmed_response.json
  {
  "result": {
  "from__email": "mike@zapier.com",
  "from__name": "Mike Knoop",
  "subject": "Re: Getting setup",
  "body_plain": "Hi Karla, thanks for following up. I can confirm I got access to everything! ... Thanks! Mike",
  "cc__emails": "bryan@zapier.com, wade@zapier.com",
  "to__email": "Karla"
  }
  }
  ```
</CodeGroup>


# Stateless action creator/runner



<Info>
  [See the "Stored Vs. Stateless Actions" documentation for more information
  about stored vs. stateless actions](/ai-actions/how-tos/stateless-vs-stored)
</Info>

Use the tool below to generate and run a stateless action.

If you have stateless action JSON available already, you can input it on the "Run stateless action" tab.

<iframe
  style={{
   width: "100%",
   height: "1500px",
   background: "white",
   borderRadius: "4px",
  }}
  src="https://actions.zapier.com/config/embed/v1/docs-examples/stateless-action-creator"
/>


# Create Account

get /v2/authorize
Create a new user and obtain an access token. See [here](/guides/quac/intro-quac#add-support-to-the-workflow-api) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.



# User Profile

get /v1/profiles/me
This endpoint returns the authenticated user information

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `profile` OAuth scope.



# Get Actions

get /v2/actions
Fetch the available actions for the provided App. It's typical to filter by type so that only actions that make sense for a particular step are shown. Action IDs may not be reused, see our documentation [here](https://docs.zapier.com/partner-solutions/workflow-api/Hardcoding-an-Action) for how to hardcode a particular action.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap` OAuth scope.



# Get Choices

post /v2/actions/{action_id}/inputs/{input_id}/choices
Get the possible values for a `SELECT` Input Field.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap` OAuth scope.



# Get Input Fields

post /v2/actions/{action_id}/inputs
Get the Input Fields for a particular Action, using the provided authentication and inputs.  See [here](/guides/getting-started/Fields-and-Fieldsets) for more information.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.



# Get Output Fields

post /v2/actions/{action_id}/outputs
Get the Output Fields for a particular Action, using the provided authentication and inputs.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.



# Step Test

post /v2/actions/{action_id}/test
Tests the action (step) in the third party api, using the provided authentication and inputs.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.



# Get Apps [v1]

get /v1/apps
This endpoint returns a list of apps sorted popularity. See [here](/guides/list-apps) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.



# Get Apps [v2]

get /v2/apps
This endpoint returns a list of apps sorted popularity. See [here](/guides/list-apps) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap` OAuth scope.



# Zapier Authentication

Authenticate with The Zapier Workflow API

### Prerequisites

*   Your app needs to be published as a [public integration](https://platform.zapier.com/quickstart/private-vs-public-integrations) in Zapier's App Directory.

### Retrieving an access Token

The Workflow API uses [OAuth 2.0 authentication with the authorization code grant type](https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type). At the end of the Oauth authentication code flow, you'll get a user access token that you'll pass in a header with each API request.

<Steps>
  <Step title="Configure a redirect URI">
    You can configure one (or multiple) redirect URIs in the [Zapier Developer Platform](https://developer.zapier.com/) under `Embed` → `Settings` → `Redirect URIs`

    <Frame>
      ![Configure redirect URIs](https://cdn.zappy.app/f8fbb9cf76864f457a0132b7eb8db196.png)
    </Frame>

    <Warning>
      You will only be able to configure redirect URIs after you've published your app as a [public integration in Zapier's App Directory](https://platform.zapier.com/quickstart/private-vs-public-integrations).
    </Warning>
  </Step>

  <Step title="Get your Client ID and Client Secret">
    You can find your Client ID and Client Secret in the [Zapier Developer Platform](https://developer.zapier.com/) under `Embed` → `Settings` → `Credentials`

    <Frame>
      ![Client ID and Secret](https://cdn.zappy.app/cb3660c17a3d26b36f438ab80c0860d5.png)
    </Frame>

    <Warning>
      Your application's **Client ID** and **Client Secret** are only available after you've published your app as a [public integration in Zapier's App Directory](https://platform.zapier.com/quickstart/private-vs-public-integrations).
    </Warning>

    <Info>
      Regenerating your client secret will invalidate any previous secret.
    </Info>
  </Step>

  <Step title="Determine which OAuth scopes are required for your use case">
    The various endpoints of the Zapier Workflow API require different OAuth scopes. Information on specific scopes required is included within the API reference for each endpoint.

    <Frame>
      <img src="https://cdn.zappy.app/3667a31edfb56bc1187739d8b1303c5c.png" />
    </Frame>
  </Step>

  <Step title="Initiate the OAuth flow and get the user's permission.">
    Construct a URL like the one below (with your own redirect url, client id, OAuth scopes, etc.) and open a browser to that URL.

    ```js
    https://api.zapier.com/v2/authorize
        ?response_type=code
        &client_id={YOUR_CLIENT_ID}
        &redirect_uri={YOUR_REDIRECT_URI}
        &scope={YOUR_OAUTH_SCOPES}
        &response_mode=query
        &state={RANDOM_STRING}
    ```

    Here's an explaination for each query parameter:

    | Parameter            | Meaning                                                                                                                                                                                                  |
    | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `response_type=code` | This tells the authorization server that the application is initiating the authorization code flow.                                                                                                      |
    | `client_id`          | The public identifier for your application. This will be the same client id that you retrieved in step #2.                                                                                               |
    | `redirect_uri`       | Tells Zapier's authorization server where to send the user back to after they approve the request. This should be the redirect URI that you configured in step #1.                                       |
    | `scope`              | One or more space-separated strings indicating which permissions your application is requesting. Information on specific scopes required is included within the API reference for each endpoint.         |
    | `state`              | Your application generates a random string and includes it in the request. It should then check that the same value is returned after the user authorizes the app. This is used to prevent CSRF attacks. |

    A full example would be:

    ```js
    https://api.zapier.com/v2/authorize
      ?response_type=code
      &client_id=5672067294567789354752
      &redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback
      &scope=zap%20zap:write%20authentication
      &response_mode=query
      &state=tney4952
    ```

    When the user visits this URL, Zapier's authorization server will present them with a prompt asking if they would like to authorize your application's request.

    <Frame>
      ![OAuth prompt example](https://cdn.zappy.app/a58205b77ec7e6aa52226354af296125.png)
    </Frame>
  </Step>

  <Step title="Receive a Redirect at your Redirect URI">
    If the user approves the above request, then Zapier's authorization server will redirect the browser back to the `redirect_uri` that you specified and configured earlier. Two query string parameters, `code` and `state` will also be included.

    For example, the browser would be redirected to:

    ```js
    https://your-app.com/redirect
    ?code=dfJ2KuL0vKLRCwQIOL5NDGKQ&H9mlc
    &state=tney4952
    ```

    | Parameter | Meaning                                                                                                                                                                                                                                                                                                  |
    | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `code`    | This value is the **authorization code** generated by Zapier's authorization server. In the next step, you'll exchange this code for an access token. Keep in mind that authorization codes are only valid for 2 minutes, and you'll need to do the exchange within that window of time to avoid errors. |
    | `state`   | This value should match the state query parameter that you used in step 2. Your application should verify that these values match.                                                                                                                                                                       |
  </Step>

  <Step title="Exchange authorization code for an access token">
    The final step is to exchange the **authorization code** that you just recieved for an **access token** that can be used to make authorized requests to the Zapier Workflow API. You make the exchange with a `POST` request to Zapier's token endpoint `https://zapier.com/oauth/token/`.

    Below is an example of a request that can be used to do the exchange.

    <CodeGroup>
      ```sh cURL
      curl -v -u {CLIENT_ID}:{CLIENT_SECRET} \
      -H "Content-Type: multipart/form-data" \
      -F grant_type=authorization_code \
      -F code={AUTHORIZATION_CODE} \
      -F redirect_uri="{REDIRECT_URI}" \
      https://zapier.com/oauth/token/
      ```

      ```python Python
      import requests

      data = 'grant_type=authorization_code&code={AUTHORIZATION_CODE}&redirect_uri={REDIRECT_URI}'

      response = requests.post('https://zapier.com/oauth/token/', headers=headers, data=data, auth=('{CLIENT_ID}', '{CLIENT_SECRET}'))
      ```

      ```js Javascript
      fetch('https://zapier.com/oauth/token/', {
        method: 'POST',
        headers: {
          'Content-Type': 'multipart/form-data',
          'Authorization': 'Basic ' + btoa('{CLIENT_ID}:{CLIENT_SECRET}')
        },
        body: 'grant_type=authorization_code&code={AUTHORIZATION_CODE}&redirect_uri={REDIRECT_URI}'
      });
      ```

      ```php PHP
      <?php
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, 'https://zapier.com/oauth/token/');
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
          'Content-Type: multipart/form-data',
      ]);
      curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
      curl_setopt($ch, CURLOPT_USERPWD, '{CLIENT_ID}:{CLIENT_SECRET}');
      curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=authorization_code&code={AUTHORIZATION_CODE}&redirect_uri={REDIRECT_URI}');

      $response = curl_exec($ch);

      curl_close($ch);
      ```
    </CodeGroup>

    | Parameter            | Meaning                                                                                                                                        |
    | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
    | `CLIENT_ID`          | This will be the same client id that you retrieved in step #2.                                                                                 |
    | `CLIENT_SECRET`      | This is a secret known only to your application and the authorization server. It will be the same client secret that you retrieved in step #2. |
    | `AUTHORIZATION_CODE` | This is the authorization code you received in the above step #5.                                                                              |
    | `REDIRECT_URI`       | This should be the redirect URI that you configured in step #1.                                                                                |

    Note that, in addition to client id and secret being passed as a Basic Authentication header as above, they can be
    passed as part of the body, using the keys `client_id` and `client_secret`.

    You'll recieve a response that looks like this:

    ```js
    HTTP/1.1 200 OK
    Content-Type: multipart/form-data
    Cache-Control: no-store
    Pragma: no-cache

    {
      "access_token": "jk8s9dGJK39JKD93jkd03JD",
      "expires_in": 36000,
      "token_type": "Bearer",
      "scope": "zap zap:write authentication",
      "refresh_token": "9D9oz2ZzaouT12LpvklQwNBf6s4vva"
    }
    ```

    <Check>This response contains the `access_token` that you'll use to make API request on the user's behalf, as well as a refresh token.</Check>

    <Warning>
      Both tokens should be stored securely, to protect your users' privacy.

      The refresh token in particularly important, since it would allow a nefarious entity to generate access tokens indefinitely:

      *   The refresh token **MAY NOT** be stored in localStorage
      *   The refresh token **MAY NOT** be stored in sessionStorage
      *   The refresh token **MAY NOT** be stored in indexedDB
      *   The refresh token **MAY NOT** be stored in a regular cookie
      *   The refresh token **MAY** be stored in a Secure; HTTPOnly cookie
      *   The refresh token **MAY** be stored in a server side database, only accessible to the current user
    </Warning>
  </Step>

  <Step title="Using the access token">
    The access token should be passed with requests as an `Authorization` header. For example:

    ```
    Authorization: Bearer jk8s9dGJK39JKD93jkd03JD
    ```
  </Step>

  <Step title="Refreshing the access token">
    All access tokens will expire after 10 hours (the number of seconds in `expires_in`), for security purposes. After that point, any request using that access token will return a 401 status code. To proceed, the refresh token should be exchanged for a new access token *and* a new refresh token. This will not require any interaction by the user.

    Below is an example request that can be used:

    <CodeGroup>
      ```sh cURL
      curl -v -u {CLIENT_ID}:{CLIENT_SECRET} \
      -H "Content-Type: multipart/form-data" \
      -d "grant_type=refresh_token&refresh_token={REFRESH_TOKEN}" \
      https://zapier.com/oauth/token/
      ```

      ```python Python
      import requests

      data = 'grant_type=refresh_token&refresh_token={REFRESH_TOKEN}'

      response = requests.post('https://zapier.com/oauth/token/', headers=headers, data=data, auth=('{CLIENT_ID}', '{CLIENT_SECRET}'))
      ```

      ```js Javascript
      fetch('https://zapier.com/oauth/token/', {
        method: 'POST',
        headers: {
          'Content-Type': 'multipart/form-data',
          'Authorization': 'Basic ' + btoa('{CLIENT_ID}:{CLIENT_SECRET}')
        },
        body: 'grant_type=refresh_token&refresh_token={REFRESH_TOKEN}'
      });
      ```

      ```php PHP
      <?php
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, 'https://zapier.com/oauth/token/');
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
          'Content-Type: multipart/form-data',
      ]);
      curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
      curl_setopt($ch, CURLOPT_USERPWD, '{CLIENT_ID}:{CLIENT_SECRET}');
      curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=refresh_token&refresh_token={REFRESH_TOKEN}');

      $response = curl_exec($ch);

      curl_close($ch);
      ```
    </CodeGroup>

    | Parameter       | Meaning                                                                                                                                     |
    | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
    | `CLIENT_ID`     | This will be the same client id that you retrieved earlier.                                                                                 |
    | `CLIENT_SECRET` | This is a secret known only to your application and the authorization server. It will be the same client secret that you retrieved earlier. |
    | `REFRESH_TOKEN` | This is the refresh token code you received with the access token.                                                                          |

    You'll receive a response that looks like this:

    ```js
    HTTP/1.1 200 OK
    Content-Type: multipart/form-data
    Cache-Control: no-store
    Pragma: no-cache
    {
      "access_token": "NEfSRKpUjVd3Nj9yyaXKs15BrM7SVA",
      "expires_in": 36000,
      "token_type": "Bearer",
      "scope": "zap zap:write authentication",
      "refresh_token": "zzdumGAW2TmayeKjzu0z9oHJiziKdn"
    }
    ```

    Note that you will receive a *new* refresh token - the old refresh token can no longer be used again, and so the new refresh token should be stored securely for future use. Refresh tokens don't have an expiration date - they only expire when they are used to get a new access token.
  </Step>
</Steps>

### Retrieving your client id

The previous version of the Workflow API (the PartnerAPI) supports passing the `client_id` to make an authenticated request.

<Steps>
  <Step title="Get your client id">
    You can find your client id in the [Zapier Developer Platform](https://developer.zapier.com/) under `Embed` → `Settings` → `Credentials`

    <Frame>
      ![Client ID and Secret](https://cdn.zappy.app/cb3660c17a3d26b36f438ab80c0860d5.png)
    </Frame>

    <Warning>
      Your application's **Client ID** and **Client Secret** are only available after you've published your app as a [public integration in Zapier's App Directory](https://platform.zapier.com/quickstart/private-vs-public-integrations).
    </Warning>

    <Info>
      Regenerating your client secret will invalidate any previous secret.
    </Info>
  </Step>

  <Step title="Pass the client id as a query parameter">
    From there, simply pass your `client_id` as a query param to any V1 endpoints that require it.

    ```js Example for Zap-templates
        https://api.zapier.com/v1/zap-templates?client_id={YOUR_CLIENT_ID}
    ```
  </Step>
</Steps>


# Create Authentication

post /v2/authentications
Creates a new Authentication for the provided App.  See [here](/guides/custom-flows/adding-an-authentication) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `authentication:write` OAuth scope.



# Get Authentications

get /v2/authentications
Fetch the available Authentications for the provided App. This will only return Authentications that are owned by the user and not those that are shared with them, since it's not possible to create Zaps with Authentications you don't own.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `authentication` OAuth scope.



# Get Categories

get /v1/categories
List of Zap categories

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.



# null





# null





# null





# null





# Errors

Errors in the API follow the <a href="https://jsonapi.org/format/#error-objects" target="_blank">recommendation</a> from the JSON API spec.

An example error object returned in a response body looks like this:

```json
{
  "errors": [
    {
      "status": "400",
      "title": "Bad Request",
      "detail": "The request could not be understood by the server due to malformed syntax.",
      "source": {
        "pointer": "/data/attributes/first-name"
      }
    }
  ]
}
```

Other potential error codes that may be returned are:

<CardGroup>
  <Card title="401 Unauthorized" icon="circle" iconType="solid" color="#dbdb00" />

  <Card title="403 Forbidden" icon="circle" iconType="solid" color="#FF0000" />
</CardGroup>

In all cases the response will be a JSON object with a single key `errors` which will be an array of error objects.

For more information, check out the [responses](https://jsonapi.org/format/#error-objects) in the API reference.


# null





# null





# null





# null





# Pagination



Section to be completed.


# Requests



Requests follow the [JSON API spec](https://jsonapi.org/format/#fetching) and should send the header Accept: application/vnd.api+json.

Typical arguments for requests are sent via query params, for more information check the specific requests for each endpoint in the API Reference section.

Example:

```bash
curl --request GET \
  --url https://stoplight.io/mocks/zapier/public-api/181772442/zaps \
  --header 'Accept: application/vnd.api+json'
```


# Responses

Responses for the API follow the <a href="https://jsonapi.org/format/#fetching-resources-responses-200" target="_blank">JSON API spec</a> for fetching resources.

A successful response will always have a data key, which will be an array of objects depending on the request.

```json
{
  "data": [
    {
      "id": "00000000-0000-c000-8000-000000012345",
      "type": "zap",
      "links": {
        "html_editor": "https://zapier.com/webintent/edit-zap/00000000-0000-c000-8000-000000012345"
      },
      "steps": [
        {
          "action": "uag:1f188536-6dd0-4172-8414-2b90914ddee9",
          "inputs": {
            "deal_stage": "CLOSED_WON"
          },
          "authentication": "b3eYnwl"
        },
        {
          "action": "uag:1f188536-6dd0-4172-8414-2b90914ddaa7",
          "inputs": {
            "full_name": "{{customer__full_name}}"
          },
          "authentication": "BRn9rRg"
        }
      ],
      "title": "My Zap",
      "is_enabled": false,
      "updated_at": "2019-08-24T14:15:22Z",
      "last_successful_run_date": "2019-08-24T14:15:22Z"
    }
  ],
  "meta": {
    "count": 133,
    "limit": 10,
    "offset": 10
  },
  "links": {
    "next": "https://api.zapier.com/v2/zaps?offset=20&limit=10",
    "prev": "https://api.zapier.com/v2/zaps?offset=0&limit=10"
  }
}
```

Every response should also include a `links` key that provides information for pagination and a `meta` key that provides information about the response.


# null





# Create a Workflow step

post /v2/workflow-steps
Creates a new Workflow Step based on a single provided step and returns a webhook URL that can be used to invoke the Workflow Step and retrieve a response. When creating a Workflow write action step, field values can be hardcoded, or they can contain mapped values. The mapped values should be surrounded with double curly braces.

In the example request body the Workflow Step contains the field values `email`, `name`, `phone`, and `address` in double curly braces. Then the webhook URL returned can be called with a `POST` request containing a JSON body in the following format:

```
{
"email": "user@example.com",
"name": "John Doe",
"phone": "1234567890",
"address": "123 Maple Lane"
}
```

Note that authentications may become invalid over time, and if this occurs, users will be notified by email to fix their authentication. If the authentication is not fixed, trying to run the Workflow Step will time out. Workflow Steps are also (for now) visible and editable as Zaps at https://zapier.com/app/zaps. If a user turns off a Workflow Step Zap, the request will similarly time out. Finally, it is possible that a user can edit the Workflow Step Zap such that it returns unexpected outputs.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.



# Rate Limiting

Rate limits when accessing the Workflow API   

Currently all endpoints are rate limited by the following, whichever occurs first:

*   IP address, and is limited to **60 requests per minute.**
*   Partner, and is limited to **150 requests per minute.**

## Rate Limit Response

If you hit the rate limit, you will receive a `429 Too Many Requests`. There will be a cooldown of 60 seconds before you can make any more requests.


# Get Zap Templates

get /v1/zap-templates
List popular Zap Templates using your app. See [here](/guides/list-zap-templates) to get started.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.



# Create a Zap

post /v2/zaps
This URL creates a Zap based on the given steps and title.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap:write` OAuth scope.



# Get Zaps [v1]

get /v1/zaps
This endpoint returns a list of Zaps for the authenticated Zapier user.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap` OAuth scope.



# Get Zaps [v2]

get /v2/zaps
This endpoint returns a list of Zaps for the authenticated Zapier user.

The `expand` array can be used to expand selected fields into full objects in the response.  Inputs with keys can
also be passed to filter Zaps by certain criteria.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.

#### OAuth
This endpoint requires the `zap`, or `zap:all` OAuth scope.



# Guess a Zap [Beta]

post /v2/guess
This endpoint returns a suggested Zap and pre-filled URL to Zapier from a given prompt.

This API is rate limited. See [Rate Limiting](https://docs.zapier.com/partner-solutions/api-reference/rate-limiting) for more details.



# Element Security

Keeping our elements secure and usable is critical at Zapier

User security is paramount. By default, Zapier denies any embedding of our product unless you provide us with a list of domains that you expect to embed Zapier in.

This protects the user from malicious activities like [Clickjacking](https://www.owasp.org/index.php/Clickjacking). :

<Frame caption="If you were to attempt to embed Zapier and the embedding domain was not registered, we would present an error.">
  ![If you were to attempt to embed Zapier and the embedding domain was not registered, we would present an error](https://cdn.zappy.app/4fb49db62ac6d5c41df46db7ccf3aab7.png)
</Frame>

## Provide a list of domains

If you've already embedded our Product, this would have already been captured and your product domains are permitted.

*   To add domains, navigate to the Settings tab under the Embed section in the sidebar of your integration's [Platform UI](https://developer.zapier.com/), and add the missing domains under the 'Embedding Domains' section.

<Frame caption="Adding Domains within the Zapier Developer Platform.">
  ![](https://cdn.zappy.app/da56277eb07303d8ce8ef42cafc8511e.png)
</Frame>

These specific domains are then permitted to embed Zapier. The domains provided by you should be registered with your company with a public registrar. That is to say a `randomcnamedomain.com` is not valid for the same reason that a user or bad actor could register that domain.

## Troubleshooting

*   `localhost`, `yourcomp.local` and `127.0.0.1` are not valid supported domains. An option during your embed development would be to use a tunnel service like [ngrok](https://ngrok.com/) and to register that ngrok tunnel with us. Be advised, that we will ask for a static domain from ngrok.com or similar tunneling service.

*   If the domain you're embedding on is added to the allowlist within *Manage Domains*, but you're seeing the `This embed is blocked` error, the [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) may be too restrictive/overly strict. You'll want to check Console/Network for the appropriate request to see the [referrer-policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) header. Using `strict-origin-when-cross-origin` as the referrer-policy is recommended.

*   For local development, use [ngrok](https://ngrok.com/) to make `https` test URLs when needed, as using `http` would be blocked, even if the domain has been added to the allowlist.


# Embed Insights

Insights are available to review the performance of your embed, and track usage growth.

## Embed insights definitions

Embed features are available for public integrations. All metrics can be filtered by All Embeds or individual embeds, and the last 7, 30, and 90 days, hover over the metric to see the percentage and count figures. To view insights from your embed, visit the [Zapier Developer Platform](https://developer.zapier.com/).

Definitions for each of the metrics provided in the integration dashboard below:

| Metric                             | Definition                                                                   |
| ---------------------------------- | ---------------------------------------------------------------------------- |
| Total conversion                   | Percentage of users who clicked on Zap Templates and activated a Zap.        |
| Users who clicked on Zap Templates | Percentage and count of users who clicked on a Zap Template in an embed.     |
| Users who signed up for Zapier     | Percentage and count of users who signed up for a new account on Zapier.     |
| Users who created Zaps             | Percentage and count of users who started a Zap in the editor.               |
| Users who enabled Zaps             | Percentage and count of users who created and successfully published a Zap.  |
| Users who activated Zaps           | Percentage and count of users who created a Zap that successfully ran tasks. |

## Best practices

*   Test different iterations of embeds and compare rates to see which ones resonate most with your users. Do certain sets of Zap Templates garner more clicks and activations than others? Does enabling “App search” on the Workflow Element, so users can see all the apps they can integrate with, increase activations?
*   Improve your Zap Templates with short, descriptive titles, and as much [prefilled field](/partner-solutions/pre-filled-zaps) mapping as possible, so users are immediately aware of the use case and can activate the Zap with the least amount of clicks.
*   Provide context with your embed through help docs or an onboarding flow guiding users on how to connect your app with others using Zapier to improve the Users who signed up for Zapier rate.


# Zapier Partner Solutions

Add automation to your product with Zapier.

Our partner solutions offer the most efficient and comprehensive ways to add automation and surface integrations directly within your product. Whether you aim to build native automated workflows for your users or simply display the most popular integration use cases, our [Workflow API](/partner-solutions/workflow-api/intro) and [Workflow Element](/partner-solutions/workflow-element/intro) provide robust solutions to meet your needs. These tools replace the need to develop your own automation capabilities, ensuring you can deliver powerful automation experiences to your users with ease.

## Why us?

Partners who embed Zapier see higher retention rates, more customer upgrades, and improved lifetime value from their users. For example, [Jotform](https://zapier.com/blog/how-jotform-retains-users-with-zapier/) found that users who used their Workflow API implementation were **25x more likely to upgrade to a paid plan, and had a 50% higher retention rate!**

<Tip>Check out our collection of [partner case studies](https://zapier.com/blog/all-articles/partner-case-studies/) for more testimonials on how embedding Zapier has boosted growth for our partners </Tip>

## Pricing

Embedding Zapier is free for all partners.

## What are the different partner solutions?

We have 2 main products for you to add automation to your product:

1.  Our [Workflow API](/partner-solutions/workflow-api/intro) is a powerful tool for building native integrations and workflows powered by Zapier for your end users in your product
2.  Our [Workflow Element](/partner-solutions/workflow-element/intro) is a prebuilt UI component that offers the quickest and easiest way to integrate Zapier directly into your product. With just a few lines of code from our code generator, it provides a complete end-to-end automation solution, allowing users to discover, create, and maintain automated workflows.

Along with our main products, we have a suite of product enhancements, meant to help create an even more frictionless experience for your users

1.  [Pre-filled Zaps](/partner-solutions/pre-filled-zaps) allow you to define the input fields on behalf of the user, simplying the experience of setting up their Zap.
2.  [Quick Account Creation](/partner-solutions/quick-account-creation) is a seamless, accelerated sign-up feature allowing first time Zapier users to skip the standard sign-up procedure and onboarding survey and have an account created on their behalf.
3.  [Guess a Zap (New! ✨)](/partner-solutions/api-reference/zaps/guess-a-zap) Simplifies the automation setup process by using natural language to help users describe what they want to automate. Guess a Zap analyzes their description and suggests the most relevant workflows, minimizing the need for manual setup and helping users quickly find the right automation.
4.  [Workflow Steps (New! ✨)](/partner-solutions/experimental/create-a-workflow-step) enables you to augment your native workflow tools with Zapier’s apps and actions.

<Tip>To see our partner solutions in action, check out our [Embed Gallery](https://zapier.com/developer-platform/partner-embeds)</Tip>

<Info>Zapier's partner solutions are available for public integrations. To take advantage, first ensure your integration has been published to [Zapier's App Directory](https://zapier.com/apps). If your integration isn't yet published, learn more about [submitting your integration for review](https://platform.zapier.com/publish/public-integration#4-submit-your-integration-for-app-review).</Info>


# Pre-filled Zaps

Prefills allow you to define the input fields on behalf of the user, simplying the experience of setting up their Zap.

Simplifying zap setup is one of the most effective ways to make sure the zaps created work as desired. As integration owners, you can likely pre-fill some of the zap fields on behalf of your users; making zap setup faster, easier and more reliable.

### What are they?

Pre-filled Zaps are simply URLs with field values added as parameters, which you can use to direct users to the Zap editor with some input fields already filled. Place these URLs inside your product or within an embedded Zap editor to facilitate users creating and publishing Zaps.

The following is an example of a simple pre-filled zap that sends the weather to a slack dm every day. The **latitude** and **longitude** inputs have been pre-filled in this case.

`https://api.zapier.com/v1/embed/space-by-zapier/create?steps[0][app]=WeatherCLIAPI@latest&steps[0][action]=today_forecast&steps[0][params][latitude]=40.7127&steps[0][params][longitude]=-74.0059&steps[1][app]=SlackAPI&steps[1][action]=direct_message`

<Card title="Test this pre-filled zap" icon="bolt" href="https://api.zapier.com/v1/embed/space-by-zapier/create?steps[0][app]=WeatherCLIAPI@latest&steps[0][action]=today_forecast&steps[0][params][latitude]=40.7127&steps[0][params][longitude]=-74.0059&steps[1][app]=SlackAPI&steps[1][action]=direct_message" horizontal="true" />

## Creating pre-filled Zaps using the Zap Generator

The UI-based generator within the [Developer Platform](https://developer.zapier.com/) facilitates constructing pre-filled Zaps. Start by selecting your app and navigating to `Embed` → `Pre-Filled Zaps` and scroll down to the generator.

<Frame caption="Screenshot of pre-Filled Zaps tab">
  <img src="https://cdn.zappy.app/2a4f9c6de6c525cbcdc1b513ec88c519.png" />
</Frame>

<Steps>
  <Step title="Select an app">
    Start by selecting an app and event for both the trigger and action to see the fields you can pre-fill. If no fields appear, the event has no input fields.

    <Frame caption="Screenshot of generator trigger step setup">
      <img src="https://cdn.zappy.app/87b9c4951d55298780ff209e217bb750.png" />
    </Frame>
  </Step>

  <Step title="Select the fields you want to pre-fill">
    *   If you don't want to pre-fill the field, leave the box unchecked. In the Zap, the field will be empty or set to a default value, if there is one.
    *   If there's a static value for a field that applies to every user's Zap (like the title of an email), check the field and provide the value in the text field.
    *   If the values are dynamic or you don't know them yet, replace the placeholders represented in curly brackets (i.e \{TRIGGER\_LIST\_ID}) in the generated URL from Step 2 before using it in your app. This could be something like an account or list ID field. The placeholder serves as a reminder to replace the value at runtime.
    *   If the field is greyed out, it requires a complex field value and cannot be pre-filled.
        <Frame caption="Screenshot of prefill input field options">
          <img src="https://cdn.zappy.app/9e845182506292214c866f3278861e8d.png" />
        </Frame>

    <Info>Fields denoting (required) are required for turning the Zap on, not required to be pre-filled.</Info>
  </Step>

  <Step title="Test the Zap">
    *   Use the handy test button to make sure the resulting Zap is what you intended. You'll need to connect app accounts on both steps to see the pre-filled fields.
    *   Copy the code and embed it inside your app to make setting up a Zap easier and faster for users.
  </Step>
</Steps>

The generator only supports creating 2-step Zaps, but you can construct multi-step Zaps by building upon the generated URL and adding `steps` parameters with increased indeces.

## Creating pre-filled Zaps from Zap Templates

### Prerequisites

*   You will need to know the required input fields per trigger or action step. You can find the fields as defined in your [Zapier integration](https://developer.zapier.com/).

<Steps>
  <Step title="Fetch the Zap Template's create_url">
    Get the appropriate `create_url` property from the specific Zap Template desired. This can be retrieved from the [zap-templates](/partner-solutions/api-reference/zap-templates/get-zap-templates) endpoint. It follows the following format: `https://api.zapier.com/v1/embed/{app_name}/create/{template_id}`
  </Step>

  <Step title="Define the input values">
    The url params should be defines as follows; `step[{step_index}][params][{param_name}]`- with steps being zero-indexed. As an example, Trello could prefill the name of a Trello card `name` in the second step of the Zap template using the following:

    `https://api.zapier.com/v1/embed/trello/create/113?steps[1][params][name]=hello`

    where the `create_url` of `https://api.zapier.com/v1/embed/trello/create/113` was retrieved from the [zap-templates](/partner-solutions/api-reference/zap-templates/get-zap-templates) endpoint, and input fields defined in their zapier integration.
  </Step>
</Steps>

Here's what it would look like in the editor:

<Frame caption="Zap Editor showing `hello` pre-filled into the name field of a new Zap titled Create Card in Trello">
  <img src="https://cdn.zappy.app/415ac51b31ab86dbf2b1589fe524395c.png" />
</Frame>

You can prefill multiple values for the user. In this example `name` and `desc` are prefilled

```js

https://api.zapier.com/v1/embed/trello/create/113
    ?steps[1][params][name]=yoyoyo
    &steps[1][params][desc]=yeehaw
```

*   `template=113`
*   `steps[1][params][name]=yoyoyo`
*   `steps[1][params][desc]=yeehaw`

<Frame caption="Zap Editor showing `yoyoyo` pre-filled into the name field, and `yeehaw` pre-filled into the description field of a new Zap titled Create Card in Trello">
  <img src="https://cdn.zappy.app/278dc4d00844240ff8289bb0267db63c.png" />
</Frame>

You can provide a label for prefill dropdowns as we won't fetch all of the pages of choices until the user opens the dropdown:

```js

https://api.zapier.com/v1/embed/trello/create/113
    ?steps[1][params][board]=1234
    &steps[1][meta][parammap][board]=Test
```

*   `template=113`
*   `steps[1][params][board]=1234`
*   `steps[1][meta][parammap][board]=Test`

<Frame caption="Zap Editor showing `Test` pre-filled into the board field of a new Zap titled Create Card in Trello">
  <img src="https://cdn.zappy.app/ecadae9e61631a7cb1a456b581ab4705.png" />
</Frame>


# Quick Account Creation

Quick Account Creation is a seamless, accelerated sign-up feature allowing first time Zapier users to skip the standard sign-up procedure and onboarding survey. Enabling Quick Account Creation as part of your embed tool code helps provide a more frictionless experience for end users.

Instead of being directed to a sign-up screen, users are presented with a consent page to connect to Zapier, then proceed to the Zap editor if consented. If an existing Zapier account under the email does not exist, users will receive an email prompting them to finish setting up their account. This allows users to dive directly into the Zap editor to accomplish the task at hand efficiently and without context switching.

## Case study

Signups to use Adalo's integration jumped 40% after embedding Zapier with Quick Account Creation into their app builder. [Read more about Adalo's user experience and results.](https://zapier.com/blog/adalo-user-experience-with-zapier/)

## Example implementation

<Frame caption="This video demonstrates how to implement Quick Account Creation using the Zap template element within Adalo's platform. This provides users with a faster, seamless sign-up experience.">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/1f21dbddbc570407248253c13859a5d9.mp4" />
</Frame>

## Prerequisites

*   New or existing implementation of the [Workflow Element](/partner-solutions/workflow-element/intro), or [Workflow API](/partner-solutions/workflow-api/intro).
*   Access to your user's first name, last name, and email on the page supporting Quick Account Creation.

## Add support to embed elements

<Steps>
  <Step title="Visit the generator">
    Go to the [generator tool](https://zapier.com/partner/embed/workflow) for the Workflow Element.
  </Step>

  <Step title="Customize">
    Customize the visual design and features of the embed solution of choice.
  </Step>

  <Step title="Generate your embed code">
    Generate the embed code in HTML, Vanilla JS, React, Angular, or Vue.js.
  </Step>

  <Step title="Replace placeholder properties">
    Replace the placeholder values set to `sign-up-email`, `sign-up-first-name`, and `sign-up-last-name` in the code Body. All three values and the client ID are required for Quick Account Creation.
  </Step>

  <Step title="Embed your embed code">
    Embed both the Head and Body code on your product's page.
  </Step>
</Steps>

You can still embed the Workflow Element or utilize the Workflow API without support for Quick Account Creation. If the four required fields aren't provided, the embed will use the default behaviour redirecting users to Zapier's signup page from your product's page.

If you have an existing Workflow Element embed, you can add the new required fields for Quick Account Creation to your current code implementation, instead of re-customizing and regenerating the code. Make sure the Body code includes the below four fields with the placeholder values replaced:

```js
clientId="your_integration_client_id"
signUpEmail="email_of_your_user@example.com"
signUpFirstName="first_name_of_your_user"
signUpLastName="last_name_of_your_user"
```

## Add support to the Workflow API

### Redirect users to the Zap editor

After an account is created with this implementation, a user token still needs to be procured to access specific Workflow API endpoints. Generally, since the user will already be signed in to their newly created account in an active session on Zapier, users won't have to explicitly sign in again when prompted with Zapier's OAuth flow.

<Steps>
  <Step>
    Use the URL below to initiate Quick Account Creation, then redirect users into the Zap editor with the Zap template set in the parameters:

    ```js

    https://zapier.com/webintent/create-zap
        ?template=<zap-template-id>
        &utm_source=partner
        &utm_medium=embed
        &utm_campaign=partner_api
        &utm_content=partner_quick_account_creation
        &entry-point-location=partner_embed
        &referer=<referer>
        &referrer=<referrer>
        &sign-up-first-name=<sign-up-first-name>
        &sign-up-last-name=<sign-up-last-name>
        &sign-up-email=<sign-up-email>
        &client-id=<client-id>
    ```
  </Step>

  <Step>
    Replace the following query parameter placeholders in the URL:

    | Parameter          | Requirement | Explanation                                      |
    | ------------------ | ----------- | ------------------------------------------------ |
    | client\_id         | Required    | Your application Client ID.                      |
    | template           | Required    | An ID of a Zap Template.                         |
    | sign-up-first-name | Required    | First name of the user signing up.               |
    | sign-up-last-name  | Required    | Last name of the user signing up.                |
    | sign-up-email      | Required    | Email address of the user signing up.            |
    | refer or referrer  | Required    | URL of the page where the user clicked the link. |
  </Step>
</Steps>

### Procure a token and redirect users to a custom URL

This implementation allows a new Zapier account to be created, provides an access token to Zapier's [Workflow API](/partner-solutions/workflow-api/intro), then allows you to redirect users to a custom URL of your choosing.

<Steps>
  <Step>
    Use the URL below to initiate Quick Account Creation, then redirect users to a specified URL:

    ```js

    https://api.zapier.com/v2/authorize
        ?redirect_uri=<redirect_uri>
        &scope=<scope>
        &response_type=<response_type>
        &client_id=<client_id>
        &sign_up_first_name=<sign_up_first_name>
        &sign_up_last_name=<sign_up_last_name>
        &sign_up_email=<sign_up_email>
    ```
  </Step>

  <Step>
    Replace the following query parameter placeholders in the URL:

    | Parameter             | Requirement | Explanation                                                                                                                                                           |
    | --------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | redirect\_uri         | Required    | The page the user will be redirect to after OAuth.                                                                                                                    |
    | scope                 | Required    | Space (`%20`) separated values. See the [API reference](/partner-solutions/api-reference/categories/get-categories) for more information on specific scopes required. |
    | response\_type        | Required    | Set to `token`.                                                                                                                                                       |
    | client\_id            | Required    | Your application Client ID.                                                                                                                                           |
    | sign\_up\_first\_name | Required    | First name of the user signing up.                                                                                                                                    |
    | sign\_up\_last\_name  | Required    | Last name of the user signing up.                                                                                                                                     |
    | sign\_up\_email       | Required    | Email address of the user signing up.                                                                                                                                 |
  </Step>
</Steps>

## When Quick Account Creation is enabled

*   If the user is already logged into Zapier, they are redirected to the Zap editor.
*   If the user's email is already associated with a Zapier account, but the user is not logged in, they are redirected to Zapier's sign-in page.
*   If the user's email is not associated with an existing Zapier account, they are redirected to the consent page.
    *   If the user consents to the terms and selects "Continue", a Zapier account is created on their behalf and they are redirected to the Zap editor. They receive an email to finish setting up their account shortly after the account is created.
    *   If the user closes the consent page, no Zapier account is created.
    *   If there is an error creating an account, the user is redirected to an error page.
*   In the event a user wants to sign up with a different email than the one supplied, they can edit that email within the consent page.
*   In the event a user has an existing Zapier account, but it's not under the supplied email they also have an option to sign in to any other account from the consent page.


# How to Build a Workflow

This guide walks through the entire process of building an automated workflow for your users to use -- front picking apps, adding authentication, filling inputs and publishing.

<Info>Authenticating with Zapier is required to build alongside this guide. This is covered separately [here](/partner-solutions/api-reference/authentication). </Info>

For this guide, let's assume that we want give our users the ability to automate workflows with the (imaginary) apps SuperExampleCRM and PlatformAdManager so that when there is a new lead in SuperExampleCRM, a new engagement report is sent to PlatformAdManager. We need to build a zap in your product with SuperExampleCRM and PlatformAdManager. Zaps are comprised of a series of steps, so in this example our two steps would be:

<CardGroup cols={2}>
  <Card title="First Step" icon="circle-1">
    Whenever there is a new lead in SuperExampleCRM...
  </Card>

  <Card title="Second Step" icon="circle-2">
    ...a new engagement report is sent to PlatformAdManager
  </Card>
</CardGroup>

### Zap Step Requirements

Each Zap step in turn, is comprised of:

<CardGroup cols={1}>
  <Card title="An Action" icon="person-running-fast" iconType="duotone">
    An Action is an operation that can be performed against a third-party API; either a `READ` or a `WRITE`.
  </Card>

  <Card title="An Authentication" icon="lock-keyhole" iconType="duotone">
    An Authentication is a set of user credentials for an App that is stored securely by Zapier.
  </Card>

  <Card title="A Group of Inputs" icon="list-check" iconType="duotone">
    Inputs are fields that are provided to an Action so that it can run. They are analogous to the arguments that a function takes.
  </Card>
</CardGroup>

To build a Zap, we'll need to select an ***Action***, ***Authentication***, and ***Inputs*** for each step.

## Configuring the first step

> "When there is a new lead in SuperExampleCRM ..."\*

<Info>The first step of a Zap must to have a `READ` action.</Info>

### Selecting an Action

The first thing that we need to do is select an Action for the first step of the Zap. See [Selecting an Action](/partner-solutions/workflow-api/Selecting-an-Action) for more details.

Let's say that the `id` of the SuperExampleCRM app is `4b3920d6-1d5a-4071-b837-9383dc511b80`. Given that id and the constraint that the first action of an app must have the `action_type` `READ`, we can fetch a list of available actions for the selected app by making a request to the `/actions` endpoint.

```js

GET /actions?app=4b3920d6-1d5a-4071-b837-9383dc511b80&action_type=READ
```

Our user can then select one of these Actions that they wish to use as the Trigger for their Zap. For this guide, we'll say that the user chose the "New Lead" Action:

```js
{
  "id": "core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ",
  "key": "new_lead"
  "app": "4b3920d6-1d5a-4071-b837-9383dc511b80",
  "type": "action",
  "action_type": "READ",
  "is_instant": true,
  "title": "New Lead",
  "description": "Triggers when a new lead is added to SuperExampleCRM"
}
```

### Selecting an Authentication

The next step is to select an authentication for the first step of the Zap.  See [Selecting an Authentication](/partner-solutions/workflow-api/Selecting-an-Authentication) for more details.

We can make a request to `GET /authentications?app=4b3920d6-1d5a-4071-b837-9383dc511b80` and allow the user to select which one of the returned Authentications they wish to use. (See [Selecting an Authentication](/partner-solutions/workflow-api/Selecting-an-Authentication) for what to do when there are no Authentications, Authentication isn't required, or if a new Authentication should be created)

For this guide, we'll say that the user chose the authentication with `id` `"49509"`

```js
{
  "type": "authentication",
  "id": "49509",
  "app": "4b3920d6-1d5a-4071-b837-9383dc511b80",
  "title": "SuperExampleCRM (wade@zapier.com)",
  "is_expired": false
}
```

### Configuring Inputs

The last thing that we need to do for step 1 of the Zap is to select input values. Please see [Fields and Fieldsets](/partner-solutions/workflow-api/Fields-and-Fieldsets) for more details on fetching available Input Fields for a given Action, and reloading Input Fields as the user provides input data.

In an above step, the user selected the "New Lead" action with id `core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ`. With the `/actions/{action_id}/inputs` endpoint, we can get a list of the input fields that our first action requires

<CodeGroup>
  ```js Request
  //POST /actions/core:core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ/inputs
  {
    "data": {
      "authentication": "49509",
      "inputs": {}
    }
  }
  ```

  ```js Response
  //POST /actions/core:853266/inputs
  {
    "data": [
      {
        "type": "input_field",
        "id": "lead_type",
        "depends_on": [],
        "value_type": "STRING",
        "format": "SELECT",
        "is_required": true,
        "title": "Lead type",
        "description": "The type of Lead to filter on.",
        "invalidates_input_fields": false,
        "default_value": "company",
        "placeholder": ""
      }
    ]
  }
  ```
</CodeGroup>

<br />

<Note>
  Please see [Fields and Fieldsets](/partner-solutions/workflow-api/Fields-and-Fieldsets) for more details on fetching available Input Fields for a given Action, fetching Choices for Input Fields, and reloading Input Fields as the user provides input data.
</Note>

As you can see, this action only has a single input field: the `Lead type`.
This gives the user the opportunity to configure the action so that it only
returns a certain type of lead.

In this example, the `format` of this input field is `SELECT`, which means we
now need to fetch the possible values which are available:

<CodeGroup>
  ```js Request
  // POST /actions/core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ/inputs/lead_type/choices
  {
    "data": {
      "authentication": "49509",
      "inputs": {}
    }
  }
  ```

  ```js Response
  // POST /actions/core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ/inputs/lead_type/choices
  {
    "data": [
      {
        "type": "choice",
        "id": "company",
        "label": "Company",
        "value": "company"
      },
      {
        "type": "choice",
        "id": "person",
        "label": "Person",
        "value": "person"
      }
    ]
  }
  ```
</CodeGroup>

Again, we've provided an empty `inputs` field object, as the user has not yet
provided any input. If there are multiple input fields, this object should be
populated as the user progresses through them.

In this case, there are two available choices for the `lead_type` field:
`company` and `person`. With this information, we can render a dropdown field
and allow the user to select one of them.

For this guide, we'll say that the user selected `person` from the dropdown.

## Configuring the second step

> "... a new engagement report is sent to PlatformAdManager"\*

### Selecting an Action

Selecting an Action for the second step of a Zap follows the same process and uses the same API endpoints as selecting an Action for the first step of a Zap, with the two exceptions that a different App id should be used, and that the `action_type` of the second action should be `WRITE`.  In this case, let's say that the id of the PlatformAdManager App is `9c29df46-f9b9-48e2-a879-8f5479d8401d`. We can fetch a list of available actions for PlatformAdManager by making a request to the `/actions` endpoint.

```js

GET /actions?app=9c29df46-f9b9-48e2-a879-8f5479d8401d&action_type=WRITE
```

Again, our user can then select one of these Actions that they wish to use. For this guide, we'll say that the user chose the "Create Engagement Report" Action:

```js
{
  "id": "core:3ZYFzZKkjbDK2AwQopVqrZWL9pK",
  "key": "create_engagement_report"
  "app": "9c29df46-f9b9-48e2-a879-8f5479d8401d",
  "type": "action",
  "action_type": "WRITE",
  "is_instant": true,
  "title": "Create Engagement Report",
  "description": "Creates a report of as engagement"
}
```

### Selecting an Authentication

Selecting an Authentication for the second step of a Zap follows the same process and uses the same API endpoints as selecting an Authentication for the first step of a Zap, with the exception that a a different App id should be used. In this case, we would make a request to `GET /authentications?app=9c29df46-f9b9-48e2-a879-8f5479d8401d`

Again, our user can select which of the available authentications they would like to use with this Zap. For this guide, we'll say that the user chose the authentication with `id` `"857610"`

```js
{
  "type": "authentication",
  "id": "857610",
  "title": "PlatformAdManager (wade@zapier.com)",
  "app": "9c29df46-f9b9-48e2-a879-8f5479d8401d",
  "is_expired": false
}
```

### Configuring Inputs

For the second step of a Zap, you can follow the same process outlined above to fetch Input Fields, Choices, and reload Input fields as a user provides input data.

For this guide, lets say that we make the following request and get the below fields in response.

<CodeGroup>
  ```js Request
  // POST /actions/core:3ZYFzZKkjbDK2AwQopVqrZWL9pK/inputs
  {
    "data": {
      "authentication": "857610",
      "inputs": {}
    }
  }
  ```

  ```js Response
  // POST /actions/core:3ZYFzZKkjbDK2AwQopVqrZWL9pK/inputs
  {
    "data": [
      {
        "type": "input_field",
        "id": "engaged_party",
        "depends_on": [],
        "value_type": "STRING",
        "is_required": true,
        "title": "Engaged party",
        "description": "The name of party that had engagement",
        "invalidates_input_fields": false,
        "placeholder": ""
      },
      {
        "type": "input_field",
        "id": "engagement_type",
        "depends_on": [],
        "value_type": "STRING",
        "format": "SELECT",
        "is_required": true,
        "title": "Engagement type",
        "description": "The action that the engaged party took.",
        "invalidates_input_fields": false,
        "default_value": "lead_form_completed",
        "placeholder": ""
      }
    ]
  }
  ```
</CodeGroup>

<Note>
  Again, please see [Fields and Fieldsets](/partner-solutions/workflow-api/Fields-and-Fieldsets) for more details on fetching available Input Fields for a given Action, fetching Choices for Input Fields, and reloading Input Fields as the user provides input data.
</Note>

We can go through the same process that we did in Step 1 of presenting input fields to the user and receiving input configuration. However, we also have the option of creating dynamic input by mapping the Output Fields of step 1 to the Input Fields of step 2.

### Mapping Outputs to Inputs

We get the available output fields of our first action from the outputs endpoint:

<CodeGroup>
  ```js Request
  // POST /actions/core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ/outputs
  {
    "data": {
      "authentication": "49509",
      "inputs": {
        "lead_type": "person"
      }
    }
  }
  ```

  ```js Response
  // POST /actions/core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ/outputs
  {
    "data": [
      {
        "type": "output_field",
        "id": "id",
        "title": "Lead ID",
        "sample": "a3bx91sl"
      },
      {
        "type": "output_field",
        "id": "full_name",
        "title": "Full Name",
        "sample": "Neve Hahn"
      },
      {
        "type": "output_field",
        "id": "lead_status",
        "title": "Lead Status",
        "sample": "qualified"
      }
    ]
  }
  ```
</CodeGroup>

Now, we can use these output fields when we configure the second action of our Zap.

When we configure the inputs of step 2, we can map Step 1's Output Field `full_name` to step 2's Input Field `engaged_party` by using double curly braces `{{step1_field_id}}`, or in this case `{{full_name}}`.

## Create a Zap

Now that we have an **Action**, **Authentication**, and **Inputs** for each step of our Zap, we can use the [POST /zaps](partner-solutions/api-reference/zaps/create-a-zap) endpoint to create a fully configured Zap.

<CodeGroup>
  ```js Request
  // POST /zaps
  {
    "data": {
      "enabled": true,
      "title": "Register engagement report on lead creation",
      "steps": [
        {
          "action": "core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ",
          "inputs": {
            "lead_type": "person"
          },
          "authentication": "49509"
        },
        {
          "action": "core:3ZYFzZKkjbDK2AwQopVqrZWL9pK",
          "inputs": {
            "engaged_party": "{{full_name}}",
            "engagement_type": "lead_form_completed"
          },
          "authentication": "857610"
        }
      ]
    }
  }
  ```

  ```js Response
  {
  	"type": "zap",
  	"id": "233307281",
  	"is_enabled": true,
  	"last_successful_run_date": null,
  	"updated_at": "2024-03-28T17:25:08+00:00",
  	"title": "Register engagement report on lead creation",
  	"links": {
  		"html_editor": "https://zapier.com/editor/233307281?utm_source=partner&utm_medium=embed&utm_campaign=partner_api&referer=None"
  	},
  	"steps": [
  		{
  			"action": "core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ",
  			"authentication": "49509",
  			"inputs": null,
  			"title": null
  		},
  		{
  			"action": "core:3ZYFzZKkjbDK2AwQopVqrZWL9pK",
  			"authentication": "857610",
  			"inputs": null,
  			"title": null
  		}
  	]
  }
  ```
</CodeGroup>

<Info>It is expected that the returned Zap doesn't include any step `inputs`</Info>

Notice that we've combined all of the configurations we collected above to construct the body of this request.

*   The `action` of each step is the `id` of the Action that the user selected.
*   The `authentication` of each step is the `id` of the Authentication that the user selected.
*   The `inputs` of each step is an object where each key is the `id` of an Input Field
    *   In the case of `engaged_party` (where we mapped an Output Field from step 1 to an Input Field of step 2), the value is the `id` of the Output Field from step 1 wrapped in double curly braces `{{...}}`
    *   In all other cases, the value is a static value that was selected or otherwise configured by the user


# Fields and Fieldsets



There are two main types of fields to consider when dealing with the Zapier API:
input fields and output fields:

<Card title="Input fields" icon="inbox-in" iconType="duotone">
  Those that are provided to an Action so that it can run. They are analogous to
  the arguments that a function takes.
</Card>

<Card title="Output fields" icon="inbox-out" iconType="duotone">
  Those that are returned by an Action. These may then be *mapped* into the input
  fields of a subsequent Action in a Zap.
</Card>

## Input Fields

To fetch the input fields for an Action, make a request to the
`/actions/{action_id}/inputs` endpoint:

<CodeGroup>
  ```js Request
  // POST /actions/core:853266/inputs
  {
    "data": {
      "authentication": "762331",
      "inputs": {}
    }
  }
  ```

  ```js Response
  // POST /actions/core:853266/inputs
  {
    "data": [
      {
  			"type": "input_field",
  			"id": "spreadsheet",
  			"default_value": "",
  			"depends_on": [],
  			"description": "The spreadsheet to add a new row",
  			"format": "SELECT",
  			"invalidates_input_fields": true,
  			"is_required": true,
  			"placeholder": "",
  			"title": "Spreadsheet",
  			"value_type": "STRING"
  		}
    ]
  }
  ```
</CodeGroup>

### Invalidation

The `invalidates_input_fields` field signifies whether the input fields should
be *refetched* from `/inputs` when the value of the given field is changed. A real-world example
of when this might be required is for spreadsheet apps. When adding a new row to
a sheet, the first input field allows the user to select the relevant sheet and,
once that is selected, more input fields become available (one for each column
in the selected sheet). If the user changes the sheet, then the input fields
must be refetched.

When making a request to refetch the input fields, make sure to include all the
fields that have been populated so far:

<CodeGroup>
  ```js Request
  // POST /actions/core:5381/inputs
  {
    "data": {
      "authentication": "928117",
      "inputs": {
        "spreadsheet": "my_sheet"
      }
    }
  }
  ```

  ```js Response
  // POST /actions/core:5381/inputs
  {
    "data": [
      {
        "type": "input_field",
        "id": "spreadsheet",
        "title": "Spreadsheet",
        "description": "The spreadsheet to add a row to.",
        "value_type": "string",
        "format": "SELECT",
        "depends_on": [],
        "is_required": true,
        "invalidates_input_fields": true
      },
      {
        "type": "input_field",
        "id": "worksheet",
        "title": "Worksheet",
        "description": "The worksheet to add a row to.",
        "value_type": "string",
        "format": "SELECT",
        "depends_on": ["spreadsheet"],
        "is_required": true,
        "invalidates_input_fields": true
      },
      {
        "type": "input_field",
        "id": "column_A",
        "title": "Column A",
        "description": "",
        "value_type": "string",
        "depends_on": [],
        "is_required": false,
        "invalidates_input_fields": false
      },
      {
        "type": "input_field",
        "id": "column_B",
        "title": "Column B",
        "description": "",
        "value_type": "string",
        "depends_on": [],
        "is_required": false,
        "invalidates_input_fields": false
      }
    ]
  }
  ```
</CodeGroup>

In this example, if the user changes the value of `spreadsheet`, we should refetch `/inputs` and pass this new value to reload all fields.
If `worksheet` is modified, once again we'd refetch `/inputs` and reload the non-dependant fields.
This is because both `spreadsheet` and `worksheet` have `invalidates_input_fields` field set to `true`.

### Dependencies

Sometimes one input field *depends* on one or more other fields, meaning `/inputs` should be refetched
if any of the dependent fields are changed. This is represented via the `depends_on` array field, which has strings of field IDs in
it. Continuing with the spreadsheet example, the `worksheet` field depends on
the `spreadsheet` field. That is, if the user changes which spreadsheet they are
working in, the value of the worksheet field must be cleared (and `/inputs` refetched)---as the set of
inputs available for the action will now be different.

### Invalidation & Dependancy Flow

<Frame caption="The sequencing of inputs can be complex for certain actions, this flow demonstrates how to present these inputs to users.">
  ![Invalidation & Dependancy
  Flow](https://cdn.zappy.app/9cff01e591e7c205326aa4b6b212c89a.jpg)
</Frame>

### Input Field Types

<Note>[Input Field Schema](/partner-solutions/api-reference/common-types/inputField)</Note>
The `value_type` key indicates what type of user data is accepted for a given field.

Per [the schema](/partner-solutions/api-reference/common-types/inputField), the options for `value_type` are: `STRING`, `NUMBER`, `INTEGER`, `BOOLEAN`, `ARRAY`, and `OBJECT`.
The following variants warrant additional consideration:

<AccordionGroup>
  <Accordion title="Array fields">
    Array fields accept JSON arrays of the underlying value type. The `items`
    key will be defined for arrays, and it will contain a key `type` with the
    value type of the array items.

    This input field:

    ```js
    "column_names": {
      "value_type": "ARRAY",
      "items": {
        "type": "STRING"
      }
    }
    ```

    would accept this value:

    ```js
    "column_names": ["Word", "Definition"]
    ```
  </Accordion>

  <Accordion title="Object fields">
    Object fields accept JSON objects with arbitrary keys and *string* values.

    This input field:

    ```js
    "dictionary": {
      "value_type": "OBJECT"
    }
    ```

    would accept this value:

    ```js
    "dictionary": {
      "apple": "A fruit!",
      "salmon": "A fish!"
    }
    ```
  </Accordion>

  <Accordion title="Boolean fields">
    While boolean fields are logically `true` or `false`, their actual values can
    change depending on the specific action. For this reason boolean values are paired with a `format`
    of `SELECT` and options should be fetched from `/choices`. See [choices](/partner-solutions/workflow-api/Fields-and-Fieldsets#choices) for more information.
    This input field:

    ```js
    {
      "type": "input_field",
      "id": "replace_all_rows",
      "format": "SELECT",
      "value_type": "BOOLEAN"
      ...
    }
    ```

    would require a request to `/choices`;

    ```js
    // repsonse from POST https://api.zapier.com/v2/actions/core:gq7MXdsdsaaqqDBj/inputs/replace_all_rows/choices
    {
      "id": "False",
      "type": "choice",
      "label": "No",
      "value": "False"
    },
    {
      "id": "True",
      "type": "choice",
      "label": "Yes",
      "value": "True"
    }
    ```

    and ultimately would accept this value:

    ```js
    "False"
    ```

    Where this is the `id` of the selection. See [choices](/partner-solutions/workflow-api/Fields-and-Fieldsets#choices) for more information.
  </Accordion>
</AccordionGroup>

### Input Formats

The `format` key indicates how to present a given input field to the user, per [the schema](/partner-solutions/api-reference/common-types/inputField).
Its options are: `DATETIME`, `MULTILINE`, `PASSWORD`, `CODE`, `READONLY`, `FILE`, `SELECT`. Additional information on
how these fields are processed by Zapier can be found in our [help docs](https://help.zapier.com/hc/en-us/articles/8496259603341-Different-field-types-in-Zaps).

The following variants warrant additional consideration:

<AccordionGroup>
  <Accordion title="Readonly fields">
    These fields are to be presented as text only, and require no input. The `description` field contains the text to be presented.

    ```js
    "format": "READONLY"
    ```
  </Accordion>

  <Accordion title="File fields">
    These fields should contain a URL to a file, which will be fetched during zap execution by the zapier platform.

    ```js
    "format": "FILE"
    ```
  </Accordion>

  <Accordion title="Select fields">
    These fields require an additional request to `/choices` to fetch the choices available for
    that input. See [choices](/partner-solutions/workflow-api/Fields-and-Fieldsets#choices) for more information.

    ```js
    "format": "SELECT"
    ```
  </Accordion>
</AccordionGroup>

### Choices

<Note>[Choice Schema](/partner-solutions/api-reference/common-types/choice)</Note>

When the `format` key is equal to `SELECT`, the `/choices` endpoint returns a list of options from which the user should select.

Continuing the spreadsheet example, the first input field is (`spreadsheet`) which is
a `string` type. The `format` is `SELECT`, which implies this field has a set of
*choices* for the user to select from (which would normally be rendered as a
dropdown menu in the UI).

To fetch these, use the `/actions/{action_id}/inputs/{input_id}/choices`
endpoint:

<CodeGroup>
  ```js Request
  // POST /actions/core:853266/inputs/spreadsheet/choices
  {
    "data": {
      "authentication": "762331",
      "inputs": {}
    }
  }
  ```

  ```js Response
  // POST /actions/core:853266/inputs/spreadsheet/choices
  {
    "data": [
      {
  			"id": "ABCD12345",
  			"type": "choice",
  			"label": "Group Standup",
  			"value": "ABCD12345"
      },
      {
  			"id": "ABCDE123456",
  			"type": "choice",
  			"label": "Incident Tracking",
  			"value": "ABCDE123456"
      }
    ]
  }
  ```
</CodeGroup>

So, this input field has only two choices available. When making a zap pass the `id` of the selected choice for the desired input.

### Fieldsets

<Note>[Fieldset Schema](/partner-solutions/api-reference/common-types/fieldset)</Note>

Input fields may be *grouped* together into fieldsets (just like in HTML). This
gives a hint as to how the fields should be rendered in the UI.

Only one level of nesting is supported (a Fieldset cannot be contained within
another Fieldset).

### Informational Fields

<Note>[Info Field Schema](/partner-solutions/api-reference/common-types/infoField)</Note>

An informational field is one that has no input, but contains some
markdown-formatted text which should be rendered in the relevant position in the
form. This is often used to give helpful tips to the user.

## Output Fields

<Note>[Output Field Schema](/partner-solutions/api-reference/common-types/outputField)</Note>

Once the user has populated all the input fields, we can determine which output
fields are available from the given action using the
`/actions/{action_id}/outputs` endpoint:

<CodeGroup>
  ```js Request
  // POST /actions/core:853266/outputs
  {
    "data": {
      "authentication": "762331",
      "inputs": {
        "worksheet": "ABC123",
        "spreadsheet": "EFGH456"
      }
    }
  }
  ```

  ```js Response
  // POST /actions/core:853266/outputs
  {
    "data": [
      {
        "type": "output_field",
        "id": "id",
        "title": "Id",
        "sample": "1"
      },
      {
        "type": "output_field",
  			"id": "COL$A",
  			"title": "Col$A",
  			"sample": "Value in column A"
      },
      {
  			"type": "output_field",
  			"id": "COL$B",
  			"title": "Col$B",
  			"sample": "Value in column B"
      },
      {
  			"type": "output_field",
  			"id": "COL$C",
  			"title": "Col$C",
  			"sample": "Value in column C"
      }
    ]
  }
  ```
</CodeGroup>

In reality, many more output fields would likely be available. For details on
how these can be mapped into the input fields of subsequent steps in a Zap, see
[Building a Zap](/partner-solutions/workflow-api/Building-a-Zap).


# Hardcoding an Action

To help focus the user experience, it can be helpful to hardcode a certain Action to guide users in selecting the most appropriate action for their use-case.

<Warning>While it may be tempting to reuse the `id` field returned by the `/actions`
endpoint, this is unsupported. The `id` field should not be considered stable, and the `key` field should instead be used.</Warning>

To hardcode an action, first make a request to the [`/actions` endpoint](/partner-solutions/api-reference/actions/get-actions), filtered by App using the `app` query param (e.g.
`/actions?app=dc4dfc3e-c5bd-47a4-ac85-6d5ef8a50c50`).

<Steps>
  <Step title="Store the App ID">
    This ID is guaranteed to remain the same for a particular
    App, even as different versions of the App are promoted in the background.
  </Step>

  <Step title="Look up the list of Actions by App ID">
    i.e.
    `/api/v2/actions?app=768dedb4-54b8-4585-a166-2140216e9b13`
  </Step>

  <Step title="Filter for the Action required using the `key` field.">
    This is the value
    provided in the Zapier Developer Platform, and remains the same for the same
    action across versions. The Action ID field returned can then be used as usual to create a Zap.
  </Step>
</Steps>


# Selecting an Action

An Action is an operation that can be performed against a third-party API; either a `READ` or a `WRITE`.

<Note>
  [Action Schema](/partner-solutions/api-reference/common-types/action)
</Note>

Once you've [selected an app](/partner-solutions/api-reference/apps/get-apps-\[v2]), we can fetch the list of available actions for a selected App by making a request to the [`/actions` endpoint](/partner-solutions/api-reference/actions/get-actions):

```js
// GET /actions?app=4b3920d6-1d5a-4071-b837-9383dc511b80&action_type=READ
{
  "data": [
    {
      "type": "action",
      "id": "core:853266",
      "action_type": "READ",
      "title": "New Lead",
      "description": "Triggers when a new lead is added to SuperExampleCRM",
      "is_instant": true
    },
    {
      "type": "action",
      "id": "uag:1f188536-6dd0-4172-8414-2b90914ddee9",
      "action_type": "READ",
      "title": "New Deal",
      "description": "Triggers when a new deal is added to SuperExampleCRM",
      "is_instant": false
    }
  ]
}
```

<Info>`READ` and `WRITE` map to *Trigger* and *Action* respectively In the Zapier ecosystem. </Info>

Our user can then select one of these Actions that they wish to use as the Trigger for their Zap. The `id` field is accepted in several other endpoints.

<Tip>Looking to focus your experience to just a few actions? Check out [Hardcoding an Action](/partner-solutions/workflow-api/Hardcoding-an-Action).</Tip>


# Selecting an Authentication

Support users in selecting 3rd party authentications, either through an existing authentication or by adding new.

An Authentication is a set of user credentials for an [App](/partner-solutions/api-reference/apps/get-apps-\[v2]) that is stored securely by Zapier. When required, a user must select which of the Authentications they have for that App (they may have multiple) that they would like to use when a Zap executes.

<Note>
  [Authentication Schema](/partner-solutions/api-reference/common-types/authentication)
</Note>

We can fetch a list of Authentications available for an App by making a request to the [`/authentications` endpoint](/partner-solutions/api-reference/authentications/get-authentications):

```js
// GET /authentications?app=4b3920d6-1d5a-4071-b837-9383dc511b80
{
  "data": [
    {
      "type": "authentication",
      "id": "49509",
      "app": "4b3920d6-1d5a-4071-b837-9383dc511b80",
      "title": "SuperExampleCRM (wade@zapier.com)",
      "is_expired": false
    },
    {
      "type": "authentication",
      "id": "96983",
      "app": "4b3920d6-1d5a-4071-b837-9383dc511b80",
      "title": "SuperExampleCRM (bryan@zapier.com)",
      "is_expired": false
    }
  ]
}
```

Our user can then select one of these Authentications to use with a Zap.

## When no Authentications exist

It's possible that the user doesn't have *any* Authentications for an App they've picked, as in every case when it's a new Zapier account. In these cases the `/authentications` endpoint will return an empty list under the `data` key. In this scenario, we should direct the user to the url provided by the `/apps` endpoint under the `links.connect_new_authentication` key to add a new Authentication.

This is also the best approach to take if you want to offer the user the option to use a new Authentication with this Zap, even if they already have Authentications available. (e.g. If the user wants to use a different SuperExampleCRM account than the ones already linked to Zapier)

<Note>
  If `links.connect_new_authentication` is `null`, then this app doesn't require authentication, and `null` should be passed instead of a valid id. Read more about that [below](/partner-solutions/workflow-api/Selecting-an-Authentication#when-authentication-is-not-required).
</Note>

### Directing the user to create a new Authentication

The best way to use this `links.connect_new_authentication` link is as follows:

<Steps>
  <Step title="Open the `links.connect_new_authentication` link in a popup">
    In this popup, the user will be prompted to authenticate with the app, and to allow Zapier to access that app.
  </Step>

  <Step title="Create an event listener to listen for `zapier.popup.close` messages from that popup">
    A message with that type will be posted when the auth flow in the popup is complete.
  </Step>

  <Step title="From the message, retrieve the new `authentication_id`">
    Afterwards, use that `authentication_id` to continue the Zap creation process

    ```js
    // 1. Open a popup window to the `links.  connect_new_authentication` url. 
    const authPopup = window.open(app.links.  connect_new_authentication, '_blank', 'width=1280,height=1024');
    if (!authPopup) {
        alert("Please allow popups to continue.");
      return;
    }

    // 2. Add an event listener to be alerted when the user has completed the auth flow.
    window.addEventListener('message', function(event) {
      if (event.origin === 'https://zapier.com') {
        const action = event.data;
        if (action?.type === "zapier.popup.close") {
          if (authPopup) {  // In case popup has already been closed
            authPopup.close();
          }
          const new_authentication_id = action.authentication_id;
          // 3. Use `new_authentication_id` to continue the Zap creation process.
          // The `new_authentication_id` is the id of the authentication that the 
          // user just created.
        }
      }
    });
    ```
  </Step>
</Steps>

<Tip>Looking to add an authentication to your own app? You can supply auth details directly and streamline the process. Check out [Adding an Authentication](/partner-solutions/workflow-api/adding-an-authentication).</Tip>

## When Authentication is not required

Some apps don't require authentication at all - like Webhooks. You'll know this is the case when fetching the app and it's not possible to add a new authentication.

```js Sample response from /apps
...
"links": {
          "connect_new_authentication": null
        },
...
```

When creating zaps with these apps, `null` should be passed in place of a valid authentication id;

<CodeGroup>
  ```js Sample POST to /inputs
  // POST https://api.zapier.com/v2/actions/core:8yjfgwyq03zskh3LOe9jPa5dOeW/inputs
  {
    "data": {
      "authentication": null,
      "inputs": {}
    }
  }
  ```

  ```js Sample POST to /zaps
  // POST https://api.zapier.com/v2/zaps
  ...
  "steps": [
        ...
        {
          "action": "core:3ZYFzZKkjbDK2AwQopVqrZWL9pK",
          "inputs": {
            "url": "https://greatWebhooks.com/myWebhook"
          },
          "authentication": null
        }
      ]
  ...
  ```
</CodeGroup>


# Adding an Authentication to your app

Reduce friction when adding an authentication to your own app.

[Adding a missing authentication](/partner-solutions/workflow-api/Selecting-an-Authentication#when-no-authentications-exist) for 3rd party apps can be straightforward, but if it's an authentication to your app it can result in sub-optimal UX.

> *"If an end user is logged into my app already, why do I need to do the oauth flow for my own app? Why can't I skip it?"*

### Adding an authentication to your own app

<Steps>
  <Step title="Confirm your app's `authentication_fields`">
    When you created your app on Zapier's Developer Platform, a required step was to [define the fields required for authentication](https://platform.zapier.com/build/auth). Depending on your authentication scheme the `authentication_fields` below will differ.
  </Step>

  <Step title="Form & send your request">
    Leveraging the [Create Authentication](/partner-solutions/api-reference/authentications/create-authentication) endpoint, supply your app's required `authentication_fields` in the request.

    <CodeGroup>
      ```js Example with Basic Authentication
          // POST /authentications
          "data": {
              "app": "{MY_APP_ID}",
              "title": "{AUTHENTICATION_NAME}",
              "authentication_fields": {
                  "username": "{MY_USERS_USERNAME}",
                  "password": "{MY_USERS_PASSWORD}"
              }
          }
      ```

      ```js Example with API Key Authentication
          // POST /authentications
          "data": {
              "app": "{MY_APP_ID}",
              "title": "{AUTHENTICATION_NAME}",
              "authentication_fields": {
                  "api_key": "{MY_USERS_API_KEY}",
              }
          }
      ```
    </CodeGroup>

    Be sure to supply whatever `authentication_fields` are required by your apps' authentication scheme.
    <Tip>Pick an authentication name that'll be recognizeable to a user should they find it outside of this flow, such as "Carter's LinkedIn Authentication", or "Micah's Slack Authentication"</Tip>
  </Step>

  <Step title="Leverage the new authentication in subsequent workflow steps ✨">
    A successful response from this request will include an `id`, which can be used in subsequent steps.
  </Step>
</Steps>


# Embedding the Zapier Editor

With an embedded Zap editor in your product, your users can create and edit their Zaps without leaving your app.

### Prerequisites

*   Your app has passed the review process and is Published in the Zapier App Directory
*   Any [Zap templates](/partner-solutions/api-reference/zap-templates/get-zap-templates) you want to query have been reviewed and made public

### Example implementation

<Frame caption="This video shows how Paperform prefills Zaps for their embedded Zap editor with contextual data from the platform, such as the Form ID, to make Zap setup easier for users. By doing so, Paperform reduces the number of clicks it takes for users to publish a Zap and removes mental friction of users needing to remember details for it.">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/0625a071fbee914968480b0b58d696a8.mp4" />
</Frame>

<Tip>Looking for even more customization? Learn about [creating Zaps directly with the Workflow API](/partner-solutions/workflow-api/Building-a-Zap).</Tip>

## Option 1: Create Zaps from Zap Templates

Use the Workflow API to query the public Zap Templates featuring your integration (using the [`GET /v1/zap-templates` endpoint](/partner-solutions/api-reference/zap-templates/get-zap-templates)) and feature them in your product. When a user chooses a Zap template they'd like to try, use the `create_url` value as the source to load in an embedded frame such as:

```js

<iframe 
  src="https://api.zapier.com/v1/embed/trello/create/113"
>
</iframe>
```

Where `https://api.zapier.com/v1/embed/trello/create/113` is the `create_url` value of the Zap template.

Optionally, you can add additional parameters to the `create_url` to [prefill the user's Zap](/partner-solutions/pre-filled-zaps) with custom values, simplifying the Zap configuration for the user.

## Option 2: Create Zaps without Zap Templates

You can also facilitate a user's Zap creation via URL parameters instead of an existing Zap Template. This provides flexibility to redirect users to a pre-populated Zap Editor with known context from your app without publishing a Zap Template for each specific use case.

[Use the Zap Pre-fill Generator](/partner-solutions/pre-filled-zaps#creating-pre-filled-zaps-using-the-zap-generator)  to easily construct pre-filled Zaps for your embedded editor experience or as a lightweight entry point into the Zap Editor from your app.

## Editing existing Zaps

Use the Workflow API to load a user's Zaps (using the [`GET /v1/zaps` endpoint](/partner-solutions/api-reference/zaps/get-zaps-\[v1])). When the user chooses to open or edit a Zap use the the `url` value of the Zap as the source of an embedded frame like this:

```js
<iframe 
  src="https://zapier.com/editor/123456"
>
</iframe>
```

Where `https://zapier.com/editor/123456` is the `url` of the Zap to be edited.

If you prefer, you can open these URLs in a separate window, new tab, or popup from your app.

## `postMessage` events

If you decide to embed the Zap editor within your product you can listen to [message events from `postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/message_event) to help you improve the interactivity with the iframe (e.g. automatically close the iframe modal.)

The messages available include:

*   `zap:unpause` = Zap turned on / published
*   `zap:unpause:done` = Zap turned on / published (success)
*   `zap:unpause:fail` = Zap turned on / published (failure)
*   `zap:pause` = Zap turned off
*   `zap:pause:done` = Zap turned off (success)
*   `zap:pause:fail` = Zap turned off (failure)

## Turning off a Zap

The API does not currently have an endpoint to turn off/on a user's Zaps. If your Zapier app uses [Webhook Subscriptions](https://platform.zapier.com/build/hook-trigger), you can send a `DELETE` to the unique target URL that was provided when the subscription was created and that will then pause/turn off a Zap.


# Integrate the Workflow API

Our most powerful tool for building native workflows in your product

The Workflow API is a powerful tool for building native feeling integrations and workflows for your end users. It allows your app access to the industry's largest integration directory that spans over 7,000 apps, enabling you to easily build and manage the most impactful workflows for your users. This tool enables users to discover, craft, and manage workflows from directly within your product while being customized to your product's look and feel, offering a seamless integration experience.

Whether you want to build an end-to-end native feeling integration for your users, or surface the most popular zap templates customized to your product's branding, the Workflow API extends your product's functionality by showcasing premier Zapier-powered workflows exactly where your users need them the most—in the very heart of your tool. It allows for customization in styling, simplifies the Zap setup process for users, and provides immediate access to pertinent Zap details, ensuring a cohesive and intuitive user experience.

The Workflow API is accessible for all publicly listed integrations.

## Capabilities

<Card title="List all apps available on Zapier" icon="list-ul" href="/partner-solutions/workflow-api/list-apps">
  Get a list of all the apps available in Zapier's app directory to power your own integration marketplace and show your users all the integration possibilities with your Zapier integration.
</Card>

<Card title="Show Zap Templates, your way" icon="rectangle-vertical-history" href="/partner-solutions/workflow-api/list-zap-templates">
  Have complete style control over how you present Zap templates in your product. The Workflow API gives you access to the raw Zap Template data so you can give your users access to your Zap templates with your product's style, look and feel.
</Card>

<Card title="Pre-fill Zaps for your users" icon="wand-sparkles" href="/partner-solutions/pre-filled-zaps">
  Define input fields on behalf of your users, simplying the experience of setting up their Zap
</Card>

<Card title="Embed the Zapier editor" icon="rectangle-code" href="/partner-solutions/workflow-api/embed-zap-editor">
  Embed our Zap Editor to allow your users to create new Zaps and modify existing ones, without needing to leave your product.
</Card>

<Card title="Build Native Workflows" icon="hammer" href="/partner-solutions/workflow-api/Building-a-Zap">
  Build native feeling integrations to your app powered by the Workflow APIs to enable your users to create automated workflows from within your product.
</Card>

<Card title="View and Manage Zaps" icon="bolt" href="/partner-solutions/workflow-api/list-zaps">
  Show users the Zaps they have set up from right within your product, keeping them on your site longer and giving them complete confidence in your Zapier integration.
</Card>

***

Check out the recent ZapConnect Session on the Workflow API, discussing features & popular use cases.

<Frame caption="Empower Your Users: Unlock Automation with Zapier's Workflow API, ZapConnect 2024">
  <video controls className="w-full aspect-video" poster="https://res.cloudinary.com/zapier-media/video/upload/so_1/q_auto/f_auto/v1728050896/ZapConnect%202024/Registration%20page/zapconnect_sizzle-updated-16-9_d1e7br.jpg" src="https://cdn.zappy.app/ea0480447f61a88282cd2a9eaa25d768.mp4" />
</Frame>


# Known Limitations

Creating workflows using the Zapier Workflow API is a recent addition, and there are some known limitations.

*   Zaps are currently limited to two steps. eg. a `READ` action and a `WRITE` action. Zaps with more than two steps are not supported.
*   Zaps can only be created with Public Apps. Private Apps are not currently supported.
*   More complex Action types, such as Searches, Filters, and Paths, are not supported.
*   The API does not currently have an endpoint to turn off/on a user's Zaps. If your Zapier app uses [Webhook Subscriptions](https://platform.zapier.com/build/hook-trigger), you can send a `DELETE` to the unique target URL that was provided when the subscription was created and that will then pause/turn off a Zap.
*   Webhook `READ` actions are not currently supported.

If you've got a use case that you think *should* be supported, let us know!


# Retrieving Apps on Zapier

Listing apps available on Zapier is a simple way to show users all of what's possible on Zapier

### Prerequisites

*   Your app needs to be published as a [public integration](https://platform.zapier.com/quickstart/private-vs-public-integrations) in Zapier's App Directory.

### Example Implementation

<Frame caption="Jotform uses the /apps endpoint to power their own app directory by intertwining the thousands of public integrations on Zapier's directory with their native integrations.">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/a77eb4b1861eb1ad5b04c3975d16ad02.mp4" />
</Frame>

## Fetching a list of Apps available on Zapier

<Steps>
  <Step title="Authenticate with Zapier">
    Following the [authentication guide](/partner-solutions/api-reference/authentication), receive a token from the Zapier Workflow API.
  </Step>

  <Step title="Create your request, and filter as needed">
    Leveraging the [`/apps` endpoint](/partner-solutions/api-reference/apps/get-apps-\[v2]), filter the list of apps to meet your needs, available query params include:

    *   `category` - could be useful for listing popular CRMs for example.
    *   `query` - a string by which to seach for for apps.
    *   `ids` - filter the list to specific apps, by their associated identifier.
  </Step>

  <Step title="Integrate with your product">
    Shape the apps returned to match the look and feel of your product, showcasing the additional apps available ✨
  </Step>
</Steps>


# Retrieving Zap Templates

Zap templates are pre-made Zaps that help users discover popular use cases for automating their work. Each template features a specific use case and the apps needed for it to work.

### Prerequisites

*   Your app needs to be published as a [public integration](https://platform.zapier.com/quickstart/private-vs-public-integrations) in Zapier's App Directory.
*   Only `published` Zap templates will be visible through the API.

### Example Implementation

<Frame caption="Wufoo uses the /zap-templates endpoint to retrieve raw data about their Zap Templates to customize the look and feel of how they are surfaced to users within their own integration directory.">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/f25a20ea30b58eec65888744e939a497.mp4" />
</Frame>

## Fetching a list of Zap Templates

<Steps>
  <Step title="Get your Client ID">
    You can find your Client ID in the [Zapier Developer Platform](https://developer.zapier.com/) under `Embed` → `Settings` → `Credentials`

    <Frame>
      ![Client ID and Secret](https://cdn.zappy.app/cb3660c17a3d26b36f438ab80c0860d5.png)
    </Frame>

    <Warning>
      Your application's **Client ID** and **Client Secret** are only available after you've published your app as a [public integration in Zapier's App Directory](https://platform.zapier.com/quickstart/private-vs-public-integrations).
    </Warning>

    <Info>
      Regenerating your client secret will invalidate any previous secret.
    </Info>
  </Step>

  <Step title="Create your request, and filter as needed">
    Leverage the [`/zap-templates` endpoint](/partner-solutions/api-reference/zap-templates/get-zap-templates) to fetch zap templates.

    *   You can optionally provide an `apps` query param to only return Zap Templates that have both your app and one of the app ids provided.
        <Info>By default, all zap templates paired with your app are returned.</Info>
  </Step>

  <Step title="Integrate with your product">
    Shape the zap templates returned to match the look and feel of your product,  workflow opportinuties that are 1-click away ✨
  </Step>
</Steps>


# Retrieving a list of users Zaps

Listing a users zaps reveals existing workflows created by users.

### Prerequisites

*   Your app needs to be published as a [public integration](https://platform.zapier.com/quickstart/private-vs-public-integrations) in Zapier's App Directory.

### Example Implementation

<Frame caption="Unbounce uses the /zaps endpoint to display users' Zaps using their integration directly within the Unbounce platform.">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/c272eef3c4261c529e8e9e0ce9d5630b.mp4" />
</Frame>

## Fetching a list of Zaps for a user

<Steps>
  <Step title="Authenticate with Zapier">
    Following the [authentication guide](/partner-solutions/api-reference/authentication), receive a token from the Zapier Workflow API.
  </Step>

  <Step title="Create your request, and filter as needed">
    Leverage the [`/zaps` endpoint](/partner-solutions/api-reference/zaps/get-zaps-\[v2]) to fetch a users zaps.

    *   You can optionally filter the list by specific input values, which can be helpful for instances where a user has a zap associated to specific resources. Example might be a specific trello board or google sheet. See [the api-reference](/partner-solutions/api-reference/zaps/get-zaps-\[v2]) for more info.
    *   Leverage the `expand` parameter to return
        additional zap detail such as step information.
    *   Use the `zap:all` scope to return *all* owned zaps on a users acccount regardless of paired apps.
        <Info> By default, only zaps that contain the authenticating app and that are **owned** by the user are returned. Shared zaps or zaps the user may have permission to view/edit will not be returned. </Info>
  </Step>

  <Step title="Integrate with your product">
    Shape the zaps returned to match the look and feel of your product, showcasing any existing workflows ✨
  </Step>
</Steps>


# Testing a Step

Step testing allows for the validation of a configured step, executing any `READ` or `WRITE` actions.

After supplying the required inputs for a given action, it can be helpful to validate the configuration by testing the action, in a similar way the main editor at zapier.com requires step testing. While not required via this API, it can be helpful to test actions;

*   When there are common configuration challenges specific to your use case.
*   To reduce the chance of user error when creating workflows.

<Steps>
  <Step title="Supply all the necessary inputs for a given action">
    Following the steps to [Build a Zap](/partner-solutions/workflow-api/Building-a-Zap), retrieve all necessary inputs and authentications.
  </Step>

  <Step title="Execute the test">
    Leveraging the [/test](/partner-solutions/api-reference/actions/step-test) endpoint, make a request supplying all of the necessary `inputs` along with an `authentication` - or `null` if none is required.

    For example for an authenticated action with id `core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ`, we might make the following request:

    <CodeGroup>
      ```js Request
      //POST /actions/core:wJ3PxHpNArZ8MqvloW3L1ZyMDQ4nJ/test
      "data": {
      	"authentication": "KrG4mmZs",
      	"inputs" : {
      		"channel": "G45J5309P",
      		"text": ":rotating_light: THIS IS A TEST :rotating_light:",
      		"as_bot": "yes",
      		"username": "Step Test",
      		"icon": ":robot:"
      	}
      }
      ```

      ```js Successful Test Response
      {
      "links": {
      	"next": null,
      	"prev": null
      },
      "meta": {
      	"count": 1,
      	"limit": null,
      	"offset": null
      },
      //Note: the data returned along with it's schema is unique to the action being performed. 
      "data": [
      	{
      		"channel": "G45J5309P",
      		"ts": "1704292753.233659",
      		"message": {
      			"type": "message",
      			"subtype": "bot_message",
      			"text": ":rotating_light: THIS IS A TEST :rotating_light:",
      			"ts": "1704292753.233659",
      			"username": "Step Test",
      			"icons": {
      				"emoji": ":robot:"
      			}
              }
              ...
          }
      ]
      }
      ```

      ```js Successful Test Response (no results)
      // Note: Tests that are configured correctly but return 0 results are returned with a 200 OK status code.
      {
      "links": {
      	"next": null,
      	"prev": null
      },
      "meta": {
      	"count": 0,
      	"limit": null,
      	"offset": null
      }, 
      "data": []
      }
      ```

      ```js Failed Test Response
      {
      "errors": [
      	{
      		"status": 400,
      		"code": "test_failed",
      		"title": "ActionTestError",
      		"detail": "Error during execution: Error from Slack: channel_not_found",
      		"source": null,
      		"meta": {
      			"source": "ZAPIER",
      			"full_details": {
      				"message": "Error during execution: Error from Slack: channel_not_found",
      				"code": "test_failed"
      			}
      		}
      	}
      ]
      }
      ```
    </CodeGroup>

    <Warning>Note that in the event of a successful test, the `data` object returned is directly from the 3rd party app, and therefore has no defined schema. </Warning>
  </Step>

  <Step title="Clean up any created resources as part of the test">
    If possible, clean up any created resources as part of a successful test. Remember the test executes the action, so these assets could cause confusion for users should they discover them after a successful test.
  </Step>
</Steps>


# Embed the Workflow Element

The Workflow Element is a prebuilt UI component that offers the quickest—and easiest—way to surface your Zapier integration directly within your own product.

<Info>As of 6/25/2024, the Workflow Element has fully replaced our previous suite of embedded UI Elements: the Full Zapier Experience, the Zap Templates Element, the App Directory Element, and the Zap Manager Element.</Info>

With the Workflow Element, you can create a seamless experience for your users that allows them to automate and manage their workflows right from your app.

Whether you want to surface pre-built zap templates in your product, allow your users to manage their zaps from your product, enable thousands of integrations to your app, or allow your users to build workflows without ever leaving your product, the Workflow Element can be customized to your particular use case, and plugged into your product in minutes, no engineering resources required!

[Get started with the Workflow Element](https://zapier.com/partner/embed/workflow)

<Frame caption="Workflow Element Demo">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/8aa623bff94e5e85387c6d6afc0b1a48.mp4" />
</Frame>

## Capabilities

The Workflow Element is the easiest and best way to bring automation to your users, with customizable features that allow your users to discover, use, create, and manage automations without ever leaving your product.

### Discover

Take advantage of Zapier's extensive and industry-leading app directory by embedding a customized version of Zapier's App Directory. Users can search through thousands of available apps to pair with yours and see popular workflows at a glance. Our generator allows you to customize the number of apps that are displayed and allows you to exclude apps by name or by app category.

<Frame caption="Give your users instant access to over 7000 integrations">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/c835acfec476da341ac603d225cd447e.mp4" />
</Frame>

### Use

Easily surface pre-built zap templates in your product for your users to use. You can surface the most popular zap templates for your app, or choose specific zap templates to showcase.

<Frame caption="Showcase pre-built zap templates in your user flow">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/01b1d163d5733a8db107ad3fca8ee52e.mp4" />
</Frame>

### Create

When clicking on a template or app listing, a modal opens up within your site, prompting users to log in or sign up if they don't have a Zapier account. Enable Quick Account Creation to make this even simpler for users. Once done, the Zap Editor will open on this same modal, and users can create Zaps.

<Frame caption="Allow your user to create and edit Zaps without leaving your products">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/e2228fb4acd30ccb861f365a85411afc.mp4" />
</Frame>

### Manage

Once users have created Zaps, they're able to see them directly inside your product. They can view their Zaps and see their status at a glance without ever having to leave your product

<Frame caption="Allow your users to manage their Zaps right from your app">
  <video controls className="w-full aspect-video" src="https://cdn.zappy.app/edd9b9e0a75ddfbb62fa42cdfad3f64c.mp4" />
</Frame>

<Tip>See more ways our partners have used the Workflow Element in our [Embed Gallery](https://zapier.com/developer-platform/partner-embeds)</Tip>

### Prerequisites

In order to embed the Workflow Element:

*   Your app has to have passed the review process and is Published in the Zapier App Directory, i.e. your app is in the Beta or Public stage.
*   You have created Zap templates for your app, and they have been reviewed and made public

## How to Embed the Workflow Element

<Steps>
  <Step title="Add your domains">
    The Workflow Element uses iframes, which are blocked by default for security purposes. Provide us with a list of domains you intend to embed it in and we'll permit these specific domains. Head to the [Zapier Developer Platform](https://developer.zapier.com/), find your app and select settings to get started.

    <Frame caption="Adding domains is a required security measure.">
      ![Customize your embed](https://cdn.zappy.app/da56277eb07303d8ce8ef42cafc8511e.png)
    </Frame>
  </Step>

  <Step title="Navigate to the Workflow Element in the Developer Platform">
    You can [configure your embed](https://zapier.com/partner/embed/workflow) and select your app.
  </Step>

  <Step title="Customize your embed">
    Using the sidebar panel in the tool, customize your embed. When it's ready, click Generate Code.

    <Frame caption="Use the options on the left panel to customize your embed.">
      ![Customize your embed](https://cdn.zappy.app/d6113da7c1934b6e0402453b77f2fb8d.png)
    </Frame>

    <Note>The preview shows what your embed looks like for a user logged in to Zapier. The Intro to Zapier is only shown to Zapier users who are not logged in. This is why if you toggle to show the Intro to Zapier, you will not see it appear in the preview, but your users who are not logged in to Zapier will see a short section explaining Zapier at the top of the embed.</Note>
  </Step>

  <Step title="Copy the code and place the embed anywhere in your product ✨">
    <Frame caption="As the sample above shows, you can choose from a variety of code samples & guides to copy right over to your product. ">
      ![Code generator](https://cdn.zappy.app/72ecc13062e1b04b931ebac5483e4962.png)
    </Frame>
  </Step>

  <Step title="Review our security practices">
    To ensure a consistent experience for your users and avoid common challenges, it's worth reading over our [Security Practices.](/partner-solutions/elements-security)
  </Step>
</Steps>

<Tip>Consider augmenting with [Quick Account Creation](/partner-solutions/quick-account-creation) - it makes signing up to Zapier more seamless. Users are able to bypass signup and onboarding and and jump right into creating a Zap with your integration with the click of a button. Note, Quick Account Creation only works if your embed is behind a login wall.</Tip>
<Info>We do not support iframes inside of iframes. If you try to embed the Workflow Element in an iframe, links will open in pop ups rather than in an iframe.</Info>


# Core Reference

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<InputFields>` and `PerformFunction<InputFields>` 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](#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](#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](#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](#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](#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](#general-errors)

*   `HaltedError` - Stops current operation, but will never turn off Zap. Read more on [Halting Execution](#halting-execution)

*   `ExpiredAuthError` - Stops the current operation and emails user to manually reconnect. Read more on [Stale Authentication Credentials](#stale-authentication-credentials)

*   `RefreshAuthError` - (OAuth2 or Session Auth) Tells Zapier to refresh credentials and retry operation. Read more on [Stale Authentication Credentials](#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](#handling-throttled-requests)

For more details on error handling in general, see [here](#error-handling).

### `z.cursor`

The `z.cursor` object exposes two methods:

*   `z.cursor.get(): Promise<string|null>`

*   `z.cursor.set(string): Promise<null>`

Any data you `set` will be available to that Zap for about an hour (or until it's overwritten). For more information, see: [paging](#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`.

## `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.](#authentication)

### `bundle.inputData`

`bundle.inputData` is user-provided data for this particular run of the trigger/search/create, as defined by the [`inputFields`](#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](#dedup)                                                                                                                                   |
| `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](#paging) 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.                                                                                                                                                                                                                       |

> 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.](#authentication)

### `bufferedBundle.groupedBy`

It is a user-provided data for a set of selected [`inputFields`](#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`](#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



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. And a dynamic dropdown can depend on the value chosen in another dynamic dropdown when making it's API call. Such as a Spreadsheet and Worksheet dynamic dropdown in a trigger or action. This means you must make sure that the key of the first dynamic dropdown is the same as referenced in the trigger powering the second.

Let's say you have a Worksheet trigger with a `perform` method similar to this.

```js
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
};
```

And your New Records trigger has a Spreadsheet and a Worksheet dynamic dropdown. The Spreadsheet dynamic dropdown must have a key of `spreadsheet_id`. When the user selects a spreadsheet via the dynamic dropdown the value chosen is made available in `bundle.inputData`. It will then be passed to the Worksheet trigger when the user clicks on the Worksheet dynamic dropdown.

```js
const App = {
  // ...
  triggers: {
    // ...
    issue: {
      key: "new_records",
      // ...
      operation: {
        inputFields: [
          {
            key: "spreadsheet_id",
            required: true,
            label: "Spreadsheet",
            dynamic: "spreadsheet.id.name",
          },
          {
            key: "worksheet_id",
            required: true,
            label: "Worksheet",
            dynamic: "worksheet.id.name",
          },
        ],
      },
    },
  },
};
```

The [Google Sheets](https://zapier.com/apps/google-sheets/integrations#triggers-and-actions) integration is an example of this pattern.

> Note: Even if a trigger to power a dynamic dropdown is hidden for direct use, you still need to define the input fields, and whether these are required. In the above example, when the Worksheet trigger defines a required `spreadsheet_id` field (which also needs to be a dynamic dropdown), products such as the editor will use this in two ways. Firstly, the worksheet field will be disabled until the spreadsheet field has been set. Secondly, when the user changes the spreadsheet field, the worksheet field gets cleared.

## 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 `<searchActionKey>.<outputFieldKey>`.

*   Replace `<searchActionKey>` with the `key` of the search action that should prepeded to the user's workflow.
*   Replace `<outputFieldKey>` 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



### 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#Expression_interpolation) `${var}` and (double) "curlies" `{{var}}` used in examples.

In general, use `${var}` within functions and use `{{var}}` anywhere else.

Placeholders get evaluated as soon as the line of code is evaluated. This means that if you use `${process.env.VAR}` in a trigger configuration, `zapier push` will substitute it with your local environment's value for `VAR` when it builds your integration and 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 ``const val = `a$``{b}`` c` ``.

### 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](#shorthand-http-requests) to parse XML. Use an `afterResponse` [middleware](#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;
    },
  ],
  // ...
};
```

### Is it possible to iterate over pages in a polling trigger?

Yes, though there are caveats. Your entire function only gets 30 seconds to run. HTTP requests are costly, so paging through a list may time out (which you should avoid at all costs).

```js
// some async call
const makeCall = (z, start, limit) => {
  return z.request({
    url: "https://jsonplaceholder.typicode.com/posts",
    params: {
      _start: start,
      _limit: limit,
    },
  });
};

// triggers on paging with a certain tag
const performPaging = async (z, bundle) => {
  // array of promises
  const promises = [];

  // 5 requests with page size = 3
  let start = 0;
  const limit = 3;
  for (let i = 0; i < 5; i++) {
    promises.push(makeCall(z, start, limit));
    start += limit;
  }

  // send requests concurrently
  const responses = await Promise.all(promises);
  return responses.map((res) => res.data);
};

module.exports = {
  key: "paging",
  noun: "Paging",

  display: {
    label: "Get Paging",
    description: "Triggers on a new paging.",
  },

  operation: {
    inputFields: [],
    perform: performPaging,
  },
};
```

If you need to do more requests conditionally based on the results of an HTTP call (such as the "next URL" param or similar value), using `async/await` (as shown in the example below) is a good way to go. If you go this route, only page as far as you need to. Keep an eye on the polling [guidelines](https://zapier.com/developer/documentation/v2/deduplication/), namely the part about only iterating until you hit items that have probably been seen in a previous poll.

```js
// a hypothetical API where payloads are big so we want to heavily limit how much comes back
// we want to only return items created in the last hour

const asyncExample = async (z, bundle) => {
  const limit = 3;
  let start = 0;
  const twoHourMilliseconds = 60 * 60 * 2 * 1000;
  const hoursAgo = new Date() - twoHourMilliseconds;

  let response = await z.request({
    url: "https://jsonplaceholder.typicode.com/posts",
    params: {
      _start: start,
      _limit: limit,
    },
  });

  let results = response.data; // response.json if you're using core v9 or older

  // keep paging until the last item was created over two hours ago
  // then we know we almost certainly haven't missed anything and can let
  //   deduper handle the rest

  while (new Date(results[results.length - 1].createdAt) > hoursAgo) {
    start += limit; // next page

    response = await z.request({
      url: "https://jsonplaceholder.typicode.com/posts",
      params: {
        _start: start,
        _limit: limit,
      },
    });

    results = results.concat(response.data);
  }

  return results;
};
```

<a id="paging" />

### What's the deal with pagination? When is it used and how does it work?

Paging is **only used when a trigger is part of a dynamic dropdown**. Depending on how many items exist and how many are returned in the first poll, it's possible that the resource the user is looking for isn't in the initial poll. If they hit the "see more" button, we'll increment the value of `bundle.meta.page` and poll again.

Paging is a lot like a regular trigger except the range of items returned is dynamic. The most common example of this is when you can pass a `offset` parameter:

```js
const perform = async (z, bundle) => {
  const response = await z.request({
    url: "https://example.com/api/list.json",
    params: {
      limit: 100,
      offset: 100 * bundle.meta.page,
    },
  });
  return response.data; // or response.json you're using core v9 or older
};
```

If your API uses cursor-based paging instead of an offset, you can use `z.cursor.get` and `z.cursor.set`:

```js
const perform = async (z, bundle) => {
  let cursor;

  // if fetching a page other than the first (first page is 0),
  // get the cursor stored after fetching the previous page.
  if (bundle.meta.page > 0) {
    cursor = await z.cursor.get();

    // if the previous page was the last one and cursor is empty/null,
    // return an empty array.
    if (!cursor) {
      return [];
    }
  }

  const response = await z.request(
    "https://5ae7ad3547436a00143e104d.mockapi.io/api/recipes",
    {
      // cursor typically is a param to pass along to the next request,
      // or the full URL for the next page of items.
      params: { cursor },
    }
  );

  // after fetching a page, set the returned cursor for the next page,
  // or an empty string if the cursor is null
  await z.cursor.set(response.nextPage ?? "");

  return response.items;
};
```

Cursors are stored per-zap and last about an hour. Per the above, make sure to only include the cursor if `bundle.meta.page > 0`, so you don't accidentally reuse a cursor from a previous poll.

Lastly, you need to set `canPaginate` to `true` in your polling definition (per the [schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#basicpollingoperationschema)) for the `z.cursor` methods to work as expected.

<a id="dedup" />

### 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](https://platform.zapier.com/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`)

<a id="analytics" />

### 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

**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.


# Input Field Configuration



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, `<textarea>`-style entry box, accepts text input.                                                                                                                                                  |
| `code`     | Displays large, `<textarea>`-style box with a fixed-width font, accepts text input.                                                                                                                                |
| `integer`  | Accepts integer number values.                                                                                                                                                                                     |
| `number`   | Accepts any numeric value, including decimal numbers.                                                                                                                                                              |
| `boolean`  | Displays dropdown menu offering true and false options. Passes along `true` or `false`.                                                                                                                            |
| `datetime` | Accepts both [precise and human-readable date-time values](https://help.zapier.com/hc/en-us/articles/8496259603341-Different-field-types-in-Zaps#date-time-fields-0-0). Passes along an ISO-formatted time string. |
| `file`     | Accepts a file object or a string. If a URL is provided in the string, Zapier will automatically make a GET for that file. Otherwise, a text file will be generated.                                               |
| `password` | Displays entered characters as hidden, accepts text input. Does not accept input from previous steps.                                                                                                              |
| `copy`     | Does not allow users to enter data. Shows the value of the Markdown-formatted Help Text for the field as a rich text note in the Zap editor. Good for important notices to users.                                  |

You can find more details on the different field schema options at [our Field Schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema).

### Custom/Dynamic Fields

In some cases, you may need to provide dynamically-generated fields - especially for custom ones. This is common functionality for CRMs, form software, databases, and other highly-customizable platforms. Instead of an explicit field definition, you can provide a function we'll evaluate to return a list of fields - merging the dynamic with the static fields.

> You should see `bundle.inputData` partially filled in as users provide data - even in field retrieval. This allows you to build hierarchical relationships into fields (e.g. only show issues from the previously selected project).

> A function that returns a list of dynamic fields cannot include additional functions in that list to call for dynamic fields.

```js
const recipeFields = async (z, bundle) => {
  const response = await z.request("https://example.com/api/v2/fields.json");

  // Call response.throwForStatus() if you're using zapier-platform-core v9 or older

  // Should return an array like [{"key":"field_1"},{"key":"field_2"}]
  return response.data; // response.json if you're using core v9 or older
};

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" },
          },
          recipeFields, // provide a function inline - we'll merge the results!
        ],
        perform: () => {},
      },
    },
  },
};
```

Additionally, if there is a field that affects the generation of dynamic fields, you can set the property `altersDynamicFields: true`. This informs the Zapier UI whenever the value of that field changes, the input fields need to be recomputed. For example, imagine the selection on a static dropdown called "Dessert Type" determining whether the function generating dynamic fields includes the field "With Sprinkles?" or not. If the value in one input field affects others, this is an important property to set.

```js
module.exports = {
  key: "dessert",
  noun: "Dessert",
  display: {
    label: "Order Dessert",
    description: "Orders a dessert.",
  },
  operation: {
    inputFields: [
      {
        key: "type",
        required: true,
        choices: { 1: "cake", 2: "ice cream", 3: "cookie" },
        altersDynamicFields: true,
      },
      function (z, bundle) {
        if (bundle.inputData.type === "2") {
          return [{ key: "with_sprinkles", type: "boolean" }];
        }
        return [];
      },
    ],
    perform: function (z, bundle) {
      /* ... */
    },
  },
};
```

> Only dropdowns support `altersDynamicFields`.

When using dynamic fields, the fields will be retrieved in three different contexts:

*   Whenever the value of a field with `altersDynamicFields` is changed, as described above.
*   Whenever the Zap Editor opens the "Set up" section for the trigger or action.
*   Whenever the "Refresh fields" button at the bottom of the Editor's "Set up" section is clicked.

Be sure to set up your code accordingly - for example, don't rely on any input fields already having a value, since they won't have one the first time the "Set up" section loads.

### Dynamic Dropdowns

See [Dynamic Dropdowns](/platform/build-cli/dynamic-dropdowns).

### Computed Fields

In OAuth and Session Auth, Zapier automatically stores every value from an integration’s auth API response i.e. that’s `getAccessToken` and `refreshAccessToken` for OAuth and `getSessionKey` for session auth.

You can return additional fields in these responses, on top of the expected `access_token` or `refresh_token` for OAuth and `sessionKey` for Session auth. They will be saved in `bundle.authData`. You can reference these fields in any subsequent API call as needed.

> Note: Only OAuth and Session Auth support computed fields.

If you want Zapier to validate that these additional fields exist, you need to use Computed Fields. If you define computed fields in your integration, Zapier will check to make sure those fields exist when it runs the authentication test API call.

Computed fields work like any other field, though with `computed: true` property, and `required: false` as user can not enter computed fields themselves. Reference computed fields in API calls as `{{bundle.authData.field}}`, replacing `field` with that field's name from your test API call response.

You can see examples of computed fields in the [OAuth2](#oauth2) or [Session Auth](#session) example sections.

### Nested & Children (Line Item) Fields

When your action needs to accept an array of items, you can include an input field with the `children` attribute. The `children` attribute accepts a list of [fields](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema) that can be input for each item in this array.

```js
const App = {
  // ...
  operation: {
    // ...
    inputFields: [
      {
        key: "lineItems",
        children: [
          {
            key: "lineItemId",
            type: "integer",
            label: "Line Item ID",
            required: true,
          },
          {
            key: "name",
            type: "string",
            label: "Name",
            required: true,
          },
          {
            key: "description",
            type: "string",
            label: "Description",
          },
        ],
      },
    ],
    // ...
  },
};
```


# Build with CLI

Zapier is a platform for creating integrations and workflows. This CLI is your gateway to creating custom applications on the Zapier platform.

This doc describes the latest CLI version (**15.18.1**), as of this writing. If you're using an older version of the CLI, you may want to check out these historical releases:

*   CLI Docs: [14.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-cli@14.1.2/packages/cli/README.md), [13.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-cli@13.0.0/packages/cli/README.md)

*   CLI Reference: [14.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-cli@14.1.2/packages/cli/docs/cli.md), [13.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-cli@13.0.0/packages/cli/docs/cli.md)

*   Schema Reference: [14.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-schema@14.1.2/packages/schema/docs/build/schema.md), [13.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-schema@13.0.0/packages/schema/docs/build/schema.md)

## Getting Started

> If you're new to Zapier Platform CLI, we strongly recommend you to walk through the [Tutorial](/platform/quickstart/cli-tutorial) for a more thorough introduction.
> If you haven’t used Zapier before, we would highly recommend that you learn the basics in our [Zapier Getting Started Guide](https://zapier.com/learn/zapier-quick-start-guide/).

### Requirements

All Zapier CLI integrations are run using Node.js `v18`.

You can develop using any version of Node you'd like, but your eventual code must be compatible with `v18`. If you're using features not yet available in `v18`, you can transpile your code to a compatible format with [Babel](https://babeljs.io/) (or similar).

To ensure stability for our users, we strongly encourage you run tests on `v18` sometime before your code reaches users. This can be done multiple ways.

Firstly, by using a CI tool (like [Travis CI](https://travis-ci.org/) or [Circle CI](https://circleci.com/), which are free for open source projects). We provide a sample [.travis.yml](https://github.com/zapier/zapier-platform/blob/main/example-apps/trigger/.travis.yml) file in our template integrations to get you started.

Alternatively, you can change your local node version with tools such as [nvm](https://github.com/nvm-sh/nvm#installation-and-update). Then you can either swap to that version with `nvm use v18`, or do `nvm exec v18 zapier test` so you can run tests without having to switch versions while developing.

### Quick Setup Guide

First up is installing the CLI and setting up your auth to create a working "Zapier Example" integration. It will be private to you and visible in your live [Zap Editor](https://zapier.com/app/editor).

```bash
# install the CLI globally
npm install -g zapier-platform-cli

# setup auth to Zapier's platform with a deploy key
zapier login
```

> Note: If you log into Zapier via the single sign-on (Google, Facebook, or Microsoft), you may not have a Zapier password. If that's the case, you'll need to generate a deploy key, go to [your Zapier developer account here](https://developer.zapier.com/partner-settings/deploy-keys/) and create/copy a key, then run `zapier login` command with the --sso flag.

Your Zapier CLI should be installed and ready to go at this point. Next up, we'll create our first integration!

```bash
# create a directory with the minimum required files
zapier init example-app

> Note: When you run `zapier init`, you'll be presented with a list of templates to start with. Pick the one that matches a feature you'll need (such as "dynamic-dropdown" for an integration with [dynamic dropdown fields](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dynamic-dropdowns)), or select "minimal" for an integration with only the essentials. [View more example integrations here](https://github.com/zapier/zapier-platform/tree/main/example-apps).

# move into the new directory
cd example-app

# install all the libraries needed for your integration
npm install
```

Depending on the authentication method for your integration, you'll also likely need to set your `CLIENT_ID` and `CLIENT_SECRET` as environment variables. These are the consumer key and secret in OAuth1 terminology.

```bash
# setting the environment variables on Zapier.com
$ zapier env:set 1.0.0 CLIENT_ID=1234
$ zapier env:set 1.0.0 CLIENT_SECRET=abcd
```

You should now have a working local integration. You can run several local commands to try it out.

```bash
# run the local tests
# the same as npm test, but adds some extra things to the environment
zapier test
```

Next, you'll probably want to upload integration to Zapier itself so you can start testing live.

```bash
# push your integration to Zapier
zapier push
```

> Go check out our [full CLI reference documentation](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#zapier-cli-reference) to see all the other commands!

## Creating a Local Integration

Creating an Integration can be done entirely locally and they are fairly simple Node.js apps using the standard Node environment and should be completely testable. However, a local integration stays local until you `zapier register`.

```bash
# make your folder
mkdir zapier-example
cd zapier-example

# create the needed files from a template
zapier init . --template minimal

# install all the libraries needed for your integration
npm install
```

If you'd like to manage your **local Integration**, use these commands:

*   `zapier init myapp` - initialize/start a local integration project

*   `zapier convert 1234 .` - initialize/start from an existing integration

*   `zapier scaffold resource Contact` - auto-injects a new resource, trigger, etc.

*   `zapier test` - run the same tests as `npm test`

*   `zapier validate` - ensure your integration is valid

*   `zapier describe` - print some helpful information about your integration

### Local Project Structure

In your integration's folder, you should see this general recommended structure. The `index.js` is Zapier's entry point to your integration. Zapier expects you to export an `App` definition there.

```
$ tree .
.
├── README.md
├── index.js
├── package.json
├── triggers
│   └── contact-by-tag.js
├── resources
│   └── Contact.js
├── test
│   ├── basic.js
│   ├── triggers.js
│   └── resources.js
├── build
│   └── build.zip
└── node_modules
    ├── ...
    └── ...
```

### Local App Definition

The core definition of your `App` will look something like this, and is what your `index.js` should provide as the *only* export:

```js
const App = {
  // both version strings are required
  version: require("./package.json").version,
  platformVersion: require("zapier-platform-core").version,

  // see "Authentication" section below
  authentication: {},

  // see "Dehydration" section below
  hydrators: {},

  // see "Making HTTP Requests" section below
  requestTemplate: {},
  beforeRequest: [],
  afterResponse: [],

  // See "Resources" section below
  resources: {},

  // See "Triggers/Searches/Creates" section below
  triggers: {},
  searches: {},
  creates: {},
};

module.exports = App;
```

> Tip: You can use higher order functions to create any part of your App definition!

### Zapier Platform Schema

The [Zapier Platform Schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md) provides details for various schema components of a Zapier integration. Each schema outlines the required fields, data types, and configurations for features such as authentication, triggers, creates, searches, and HTTP requests. It's essential for integration developers to refer to this schema documentation when building their Zapier integrations, as it ensures that the correct structure is followed and the necessary components are used. The detailed examples, valid configurations, and anti-examples help guide integraton developers in creating reliable and functional integrations, avoid common errors, and ensure compatibility with the Zapier platform.

## Registering an Integration

Registering your Integration with Zapier is a necessary first step which only enables basic administrative functions. It should happen before `zapier push` which is to used to actually expose an Integration Version in the Zapier interface and editor.

```bash
# register your integration
zapier register "Zapier Example"

# list your integrations
zapier integrations
```

> Note: This doesn't put your integration in the editor - see the docs on pushing an Integration Version to do that!

If you'd like to manage your **Integration**, use these commands:

*   `zapier integrations` - list the integrations in Zapier you can administer

*   `zapier register "Integration Title"` - creates a new integration in Zapier

*   `zapier link` - lists and links a selected integration in Zapier to your current folder

*   `zapier history` - print the history of your integration

*   `zapier team:add `[`user@example.com`](mailto:user@example.com)` admin` - add an admin to help maintain/develop your integration

*   `zapier users:add `[`user@example.com`](mailto:user@example.com)` 1.0.0` - invite a user try your integration version 1.0.0

## Converting an Existing Integration

If you have an existing Zapier [legacy Web Builder integration](https://platform.zapier.com/manage/versions-legacy), you can use it as a template to kickstart your local integration.

```bash
# Convert an existing Web Builder integration to a CLI integration in the my-app directory
# Integration ID 1234 is from URL https://zapier.com/developer/builder/app/1234/development
zapier convert 1234 my-app
```

Your CLI integration will be created and you can continue working on it.

> Note: There is no way to convert a CLI integration to a Web Builder integration and we do not plan on implementing this.

Introduced in v8.2.0, you are able to convert new integrations built in Zapier Platform UI to CLI.

```bash
# the --version flag is what denotes this command is interacting with a Visual Builder integration
# zapier convert <INTEGRATION> --version <INTEGRATION> <PATH>
zapier convert 1234 --version 1.0.1 my-app
```

## Authentication

Most integrations require some sort of authentication. The Zapier platform provides core behaviors for several common authentication methods that might be used with your integration, as well as the ability to customize authentication further.

When a user authenticates to your integration through Zapier, a "connection" is created representing their authentication details. Data tied to a specific authentication connection is included in the [bundle object](/platform/build-cli/core#bundle-object) under `bundle.authData`.

### Basic

Useful if your integration requires two pieces of information to authenticate: `username` and `password`, which only the end user can provide. By default, Zapier will do the standard Basic authentication base64 header encoding for you (via an automatically registered middleware).

> To create a new integration with basic authentication, run `zapier init [your integration name] --template basic-auth`. You can also review an example of that code [here](https://github.com/zapier/zapier-platform/tree/main/example-apps/basic-auth).

If your integration uses Basic auth with an encoded API key rather than a username and password, like `Authorization: Basic APIKEYHERE:x`, consider the [Custom](#custom) authentication method instead.

```js
const authentication = {
  type: "basic",
  // "test" could also be a function
  test: {
    url: "https://example.com/api/accounts/me.json",
  },
  connectionLabel: "{{username}}", // Can also be a function, check digest auth below for an example
  // you can provide additional fields, but we'll provide `username`/`password` automatically
};

const App = {
  // ...
  authentication,
  // ...
};
```

### Digest

*Added in v7.4.0.*

The setup and user experience of Digest Auth is identical to Basic Auth. Users provide Zapier their username and password, and Zapier handles all the nonce and quality of protection details automatically.

> To create a new integration with digest authentication, run `zapier init [your integration name] --template digest-auth`. You can also review an example of that code [here](https://github.com/zapier/zapier-platform/tree/main/example-apps/digest-auth).

> Limitation: Currently, MD5-sess and SHA are not implemented. Only the MD5 algorithm is supported. In addition, server nonces are not reused. That means for every `z.request` call, Zapier will send an additional request beforehand to get the server nonce.

```js
const getConnectionLabel = (z, bundle) => {
  // bundle.inputData will contain what the "test" URL (or function) returns
  return bundle.inputData.username;
};

const authentication = {
  type: "digest",
  // "test" could also be a function
  test: {
    url: "https://example.com/api/accounts/me.json",
  },
  connectionLabel: getConnectionLabel,

  // you can provide additional fields, but we'll provide `username`/`password` automatically
};

const App = {
  // ...
  authentication,
  // ...
};
```

### Custom

Custom auth is most commonly used for integrations that authenticate with API keys, although it also provides flexibility for any unusual authentication setup. You'll likely provide some custom `beforeRequest` middleware or a `requestTemplate` (see [Making HTTP Requests](#making-http-requests)) to pass in data returned from the authentication process, most commonly by adding/computing needed headers.

> To create a new integration with custom authentication, run `zapier init [your integration name] --template custom-auth`. You can also review an example of that code [here](https://github.com/zapier/zapier-platform/tree/main/example-apps/custom-auth).

```js
const authentication = {
  type: "custom",
  // "test" could also be a function
  test: {
    url: "https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json",
  },
  fields: [
    {
      key: "subdomain",
      type: "string",
      required: true,
      helpText: "Found in your browsers address bar after logging in.",
    },
    {
      key: "api_key",
      type: "string",
      required: true,
      helpText: "Found on your settings page.",
    },
  ],
};

const addApiKeyToHeader = (request, z, bundle) => {
  request.headers["X-Subdomain"] = bundle.authData.subdomain;
  const basicHash = Buffer.from(`${bundle.authData.api_key}:x`).toString(
    "base64"
  );
  request.headers.Authorization = `Basic ${basicHash}`;
  return request;
};

const App = {
  // ...
  authentication,
  beforeRequest: [addApiKeyToHeader],
  // ...
};
```

### Session

Session auth gives you the ability to exchange some user-provided data for some authentication data; for example, username and password for a session key. It can be used to implement almost any authentication method that uses that pattern - for example, alternative OAuth flows.

> To create a new integration with session authentication, run `zapier init [your integration name] --template session-auth`. You can also review an example of that code [here](https://github.com/zapier/zapier-platform/tree/main/example-apps/session-auth).

```js
const getSessionKey = async (z, bundle) => {
  const response = await z.request({
    method: "POST",
    url: "https://example.com/api/accounts/login.json",
    body: {
      username: bundle.authData.username,
      password: bundle.authData.password,
    },
  });

  // response.throwForStatus() if you're using core v9 or older

  return {
    sessionKey: response.data.sessionKey,
    // or response.json.sessionKey if you're using core v9 and older
  };
};

const authentication = {
  type: "session",
  // "test" could also be a function
  test: {
    url: "https://example.com/api/accounts/me.json",
  },
  fields: [
    {
      key: "username",
      type: "string",
      required: true,
      helpText: "Your login username.",
    },
    {
      key: "password",
      type: "string",
      required: true,
      helpText: "Your login password.",
    },
    // For Session Auth we store `sessionKey` automatically in `bundle.authData`
    // for future use. If you need to save/use something that the user shouldn't
    // need to type/choose, add a "computed" field, like:
    // {key: 'something': type: 'string', required: false, computed: true}
    // And remember to return it in sessionConfig.perform
  ],
  sessionConfig: {
    perform: getSessionKey,
  },
};

const includeSessionKeyHeader = (request, z, bundle) => {
  if (bundle.authData.sessionKey) {
    request.headers = request.headers || {};
    request.headers["X-Session-Key"] = bundle.authData.sessionKey;
  }
  return request;
};

const App = {
  // ...
  authentication,
  beforeRequest: [includeSessionKeyHeader],
  // ...
};
```

For Session auth, the function that fetches the additional authentication data needed to make API calls (`authentication.sessionConfig.perform`) has the user-provided fields in `bundle.inputData`. Afterwards, `bundle.authData` contains the data returned by that function (usually the session key or token).

### OAuth1

*Added in `v7.5.0`.*

Zapier's [OAuth1](https://oauth.net/1/) implementation matches [X](https://developer.twitter.com/en/docs/tutorials/authenticating-with-twitter-api-for-enterprise/authentication-method-overview#oauth1.0a) (formerlly called Twitter) and [Trello](https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/#using-basic-oauth) implementations of the 3-legged OAuth flow.

> To create a new integration with OAuth1, run `zapier init [your integration name] --template oauth1-trello`. You can also check out [oauth1-trello](https://github.com/zapier/zapier-platform/tree/main/example-apps/oauth1-trello), [oauth1-tumblr](https://github.com/zapier/zapier-platform/tree/main/example-apps/oauth1-tumblr), and [oauth1-twitter](https://github.com/zapier/zapier-platform/tree/main/example-apps/oauth1-twitter) for working example integrations with OAuth1.

The flow works like this:

1.  Zapier makes a call to your API requesting a "request token" (also known as "temporary credentials").

2.  Zapier sends the user to the authorization URL, defined by your integration, along with the request token.

3.  Once authorized, your website sends the user to the `redirect_uri` Zapier provided. Use `zapier describe` command to find out what it is: ![](https://zappy.zapier.com/117ECB35-5CCA-4C98-B74A-35F1AD9A3337.png)

4.  Zapier makes a backend call to your API to exchange the request token for an "access token" (also known as "long-lived credentials").

5.  Zapier stores the `access_token` and uses it to make calls on behalf of the user.

You are required to define:

*   `getRequestToken`: The API call to fetch the request token

*   `authorizeUrl`: The authorization URL

*   `getAccessToken`: The API call to fetch the access token

You'll also likely need to set your `CLIENT_ID` and `CLIENT_SECRET` as environment variables. These are the consumer key and secret in OAuth1 terminology.

```bash
# setting the environment variables on Zapier.com
$ zapier env:set 1.0.0 CLIENT_ID=1234
$ zapier env:set 1.0.0 CLIENT_SECRET=abcd

# and when running tests locally, don't forget to define them in .env or in the command!
$ CLIENT_ID=1234 CLIENT_SECRET=abcd zapier test
```

Your auth definition would look something like this:

```js
const _ = require("lodash");

const authentication = {
  type: "oauth1",
  oauth1Config: {
    getRequestToken: {
      url: "https://{{bundle.inputData.subdomain}}.example.com/request-token",
      method: "POST",
      auth: {
        oauth_consumer_key: "{{process.env.CLIENT_ID}}",
        oauth_consumer_secret: "{{process.env.CLIENT_SECRET}}",

        // 'HMAC-SHA1' is used by default if not specified.
        // 'HMAC-SHA256', 'RSA-SHA1', 'PLAINTEXT' are also supported.
        oauth_signature_method: "HMAC-SHA1",
        oauth_callback: "{{bundle.inputData.redirect_uri}}",
      },
    },
    authorizeUrl: {
      url: "https://{{bundle.inputData.subdomain}}.example.com/authorize",
      params: {
        oauth_token: "{{bundle.inputData.oauth_token}}",
      },
    },
    getAccessToken: {
      url: "https://{{bundle.inputData.subdomain}}.example.com/access-token",
      method: "POST",
      auth: {
        oauth_consumer_key: "{{process.env.CLIENT_ID}}",
        oauth_consumer_secret: "{{process.env.CLIENT_SECRET}}",
        oauth_token: "{{bundle.inputData.oauth_token}}",
        oauth_token_secret: "{{bundle.inputData.oauth_token_secret}}",
        oauth_verifier: "{{bundle.inputData.oauth_verifier}}",
      },
    },
  },
  test: {
    url: "https://{{bundle.authData.subdomain}}.example.com/me",
  },
  // If you need any fields upfront, put them here
  fields: [
    { key: "subdomain", type: "string", required: true, default: "app" },
    // For OAuth1 we store `oauth_token` and `oauth_token_secret` automatically
    // in `bundle.authData` for future use. If you need to save/use something
    // that the user shouldn't need to type/choose, add a "computed" field, like:
    // {key: 'user_id': type: 'string', required: false, computed: true}
    // And remember to return it in oauth1Config.getAccessToken
  ],
};

// A middleware that is run before z.request() actually makes the request. Here we're
// adding necessary OAuth1 parameters to `auth` property of the request object.
const includeAccessToken = (req, z, bundle) => {
  if (
    bundle.authData &&
    bundle.authData.oauth_token &&
    bundle.authData.oauth_token_secret
  ) {
    // Just put your OAuth1 credentials in req.auth, Zapier will sign the request for
    // you.
    req.auth = req.auth || {};
    _.defaults(req.auth, {
      oauth_consumer_key: process.env.CLIENT_ID,
      oauth_consumer_secret: process.env.CLIENT_SECRET,
      oauth_token: bundle.authData.oauth_token,
      oauth_token_secret: bundle.authData.oauth_token_secret,
    });
  }
  return req;
};

const App = {
  // ...
  authentication,
  beforeRequest: [includeAccessToken],
  // ...
};

module.exports = App;
```

For OAuth1, `authentication.oauth1Config.getRequestToken`, `authentication.oauth1Config.authorizeUrl`, and `authentication.oauth1Config.getAccessToken` have fields like `redirect_uri` and the temporary credentials in `bundle.inputData`. After `getAccessToken` runs, the resulting token value(s) will be stored in `bundle.authData` for the connection.

Also, `authentication.oauth1Config.getAccessToken` has access to the additional return values in `rawRequest` and `cleanedRequest` should you need to extract other values (for example, from the query string).

### OAuth2

Zapier's [OAuth2](https://oauth.net/2/) implementation is based on the `authorization_code` flow, similar to [GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps) and [Facebook](https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow).

> To create a new integration with OAuth2, run `zapier init [your integration name] --template oauth2`. You can also check out [our working example integration](https://github.com/zapier/zapier-platform/tree/main/example-apps/oauth2).

If your integration's OAuth2 flow uses a different grant type, such as `client_credentials`, try using [Session auth](#session) instead.

The OAuth2 flow looks like this:

1.  Zapier sends the user to the authorization URL defined by your integration.

2.  Once authorized, your website sends the user to the `redirect_uri` Zapier provided. Use the `zapier describe` command to find out what it is: ![](https://zappy.zapier.com/83E12494-0A03-4DB4-AA46-5A2AF6A9ECCC.png)

3.  Zapier makes a backend call to your API to exchange the `code` for an `access_token`.

4.  Zapier stores the `access_token` and uses it to make calls on behalf of the user.

5.  (Optionally) Zapier can refresh the token if it expires.

You are required to define:

*   `authorizeUrl`: The authorization URL

*   `getAccessToken`: The API call to fetch the access token

If the access token has a limited life and you want to refresh the token when it expires, you'll also need to define the API call to perform that refresh (`refreshAccessToken`). You can choose to set `autoRefresh: true`, as in the example integration, if you want Zapier to automatically make a call to refresh the token after receiving a 401. See [Stale Authentication Credentials](#stale-authentication-credentials) for more details on handling auth refresh.

You'll also likely want to set your `CLIENT_ID` and `CLIENT_SECRET` as environment variables:

```bash
# setting the environment variables on Zapier.com
$ zapier env:set 1.0.0 CLIENT_ID=1234
$ zapier env:set 1.0.0 CLIENT_SECRET=abcd

# and when running tests locally, don't forget to define them in .env or in the command!
$ CLIENT_ID=1234 CLIENT_SECRET=abcd zapier test
```

Your auth definition would look something like this:

```js
const authentication = {
  type: "oauth2",
  test: {
    url: "https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json",
  },
  // you can provide additional fields for inclusion in authData
  oauth2Config: {
    // "authorizeUrl" could also be a function returning a string url
    authorizeUrl: {
      method: "GET",
      url: "https://{{bundle.inputData.subdomain}}.example.com/api/oauth2/authorize",
      params: {
        client_id: "{{process.env.CLIENT_ID}}",
        state: "{{bundle.inputData.state}}",
        redirect_uri: "{{bundle.inputData.redirect_uri}}",
        response_type: "code",
      },
    },
    // Zapier expects a response providing {access_token: 'abcd'}
    // "getAccessToken" could also be a function returning an object
    getAccessToken: {
      method: "POST",
      url: "https://{{bundle.inputData.subdomain}}.example.com/api/v2/oauth2/token",
      body: {
        code: "{{bundle.inputData.code}}",
        client_id: "{{process.env.CLIENT_ID}}",
        client_secret: "{{process.env.CLIENT_SECRET}}",
        redirect_uri: "{{bundle.inputData.redirect_uri}}",
        grant_type: "authorization_code",
      },
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
    },
    scope: "read,write",
  },
  // If you need any fields upfront, put them here
  fields: [
    { key: "subdomain", type: "string", required: true, default: "app" },
    // For OAuth2 we store `access_token` and `refresh_token` automatically
    // in `bundle.authData` for future use. If you need to save/use something
    // that the user shouldn't need to type/choose, add a "computed" field, like:
    // {key: 'user_id': type: 'string', required: false, computed: true}
    // And remember to return it in oauth2Config.getAccessToken/refreshAccessToken
  ],
};

const addBearerHeader = (request, z, bundle) => {
  if (bundle.authData && bundle.authData.access_token) {
    request.headers.Authorization = `Bearer ${bundle.authData.access_token}`;
  }
  return request;
};

const App = {
  // ...
  authentication,
  beforeRequest: [addBearerHeader],
  // ...
};

module.exports = App;
```

For OAuth2, `authentication.oauth2Config.authorizeUrl`, `authentication.oauth2Config.getAccessToken`, and `authentication.oauth2Config.refreshAccessToken` have fields like `redirect_uri` and `state` in `bundle.inputData`. After the code is exchanged for an access token and/or refresh token, those tokens are stored in `bundle.authData` for the connection.

Also, `authentication.oauth2Config.getAccessToken` has access to the additional return values in `rawRequest` and `cleanedRequest` should you need to extract other values (for example, from the query string).

If you define `fields` to collect additional details from the user, please note that `client_id` and `client_secret` are reserved keys and cannot be used as keys for input form fields.

> Note: The OAuth2 `state` param is a [standard security feature](https://auth0.com/docs/secure/attack-protection/state-parameters) that helps ensure that authorization requests are only coming from your servers. Most OAuth clients have support for this and will send back the `state` query param that the user brings to your app. The Zapier Platform performs this check and this required field cannot be disabled. The state parameter is automatically generated by Zapier in the background, and can be accessed at `bundle.inputData.state`.
> Since Zapier uses the `state` to verify that GET requests to your redirect URL truly come from your integration, it needs to be generated by Zapier so that it can be validated later (once the user confirms that they'd like to grant Zapier permission to access their account in your app).

### OAuth2 with PKCE

*Added in v14.0.0.*

Zapier's OAuth2 implementation also supports [PKCE](https://oauth.net/2/pkce/). This implementation is an extension of the OAuth2 `authorization_code` flow described above.

To use PKCE in your OAuth2 flow, you'll need to set the following variables:

1.  `enablePkce: true`

2.  `getAccessToken.body` to include `code_verifier: "{{bundle.inputData.code_verifier}}"`

The OAuth2 PKCE flow uses the same flow as OAuth2 but adds a few extra parameters:

1.  Zapier computes a `code_verifier` and `code_challenge` internally and stores the `code_verifier` in the Zapier bundle.

2.  Zapier sends the user to the authorization URL defined by your integration. We automatically include the computed `code_challenge` and `code_challenge_method` in the authorization request.

3.  Once authorized, your website sends the user to the `redirect_uri` Zapier provided.

4.  Zapier makes a call to your API to exchange the code but you must include the computed `code_verifier` in the request for an `access_token`.

5.  Zapier stores the `access_token` and uses it to make calls on behalf of the user.

Your auth definition would look something like this:

```js
const authentication = {
  type: "oauth2",
  test: {
    url: "https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json",
  },
  // you can provide additional fields for inclusion in authData
  oauth2Config: {
    // "authorizeUrl" could also be a function returning a string url
    authorizeUrl: {
      method: "GET",
      url: "https://{{bundle.inputData.subdomain}}.example.com/api/oauth2/authorize",
      params: {
        client_id: "{{process.env.CLIENT_ID}}",
        state: "{{bundle.inputData.state}}",
        redirect_uri: "{{bundle.inputData.redirect_uri}}",
        response_type: "code",
      },
    },
    // Zapier expects a response providing {access_token: 'abcd'}
    // "getAccessToken" could also be a function returning an object
    getAccessToken: {
      method: "POST",
      url: "https://{{bundle.inputData.subdomain}}.example.com/api/v2/oauth2/token",
      body: {
        code: "{{bundle.inputData.code}}",
        client_id: "{{process.env.CLIENT_ID}}",
        client_secret: "{{process.env.CLIENT_SECRET}}",
        redirect_uri: "{{bundle.inputData.redirect_uri}}",
        grant_type: "authorization_code",
        code_verifier: "{{bundle.inputData.code_verifier}}", // Added for PKCE
      },
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
    },
    scope: "read,write",
    enablePkce: true, // Added for PKCE
  },
  // If you need any fields upfront, put them here
  fields: [
    { key: "subdomain", type: "string", required: true, default: "app" },
    // For OAuth2 we store `access_token` and `refresh_token` automatically
    // in `bundle.authData` for future use. If you need to save/use something
    // that the user shouldn't need to type/choose, add a "computed" field, like:
    // {key: 'user_id': type: 'string', required: false, computed: true}
    // And remember to return it in oauth2Config.getAccessToken/refreshAccessToken
  ],
};

const addBearerHeader = (request, z, bundle) => {
  if (bundle.authData && bundle.authData.access_token) {
    request.headers.Authorization = `Bearer ${bundle.authData.access_token}`;
  }
  return request;
};

const App = {
  // ...
  authentication,
  beforeRequest: [addBearerHeader],
  // ...
};

module.exports = App;
```

The computed `code_verifier` uses this standard: [RFC 7636 Code Verifier](https://www.rfc-editor.org/rfc/rfc7636#section-4.1)

The computed `code_challenge` uses this standard: [RFC 7636 Code Challenge](https://www.rfc-editor.org/rfc/rfc7636#section-4.2)

### Connection Label

When a user connects to your app via Zapier and a connection is created to hold the related data in `bundle.authData`, the connection is automatically labeled with the integration name. You also have the option of setting a connection label (`connectionLabel`), which can be extremely helpful to identify information like which user is connected or what instance of your app they are connected to. That way, users don't get confused if they have multiple connections to your app.

When setting a connection label, you can use either a string with variable references (as shown in [Basic Auth](#basic)) or a function (as shown in [Digest Auth](#digest)).

When using a string, you have access to the information in `bundle.authData` and the information returned from the test request in `bundle.inputData`, all at the top level. So in Basic auth, if `connectionLabel` is `{{username}}`, that refers to the username used for authentication.

When using a function, this "hoisting" of data to the top level is skipped, and you must refer to data items by their fully qualified name, as shown in the line `return bundle.inputData.username;` in the Digest Auth snippet. `return username;` would not work in this context.

**NOTE:** Do not use sensitive authentication data such as passwords or API keys in the connection label. It's visible in plain text on Zapier. The purpose of the label is to identify the connection for the user, so stick with data such as username or instance identifier that is meaningful but not sensitive.

### Domain and subdomain validation

When adding a subdomain input field, commonly used in OAuth implementations, additional validation is strongly recommended to prevent a potential security vulnerability. If not taken into account, an attacker could utilize a maliciously constructed subdomain field (like `attacker-domain.com/`) in order to redirect OAuth connection requests to that attacker-controlled domain (because `attacker-domain.com/.your-domain.com` resolves to the attacker’s domain instead of the expected one).

This vulnerability presents itself when:

*   The authentication method uses pre-configured tokens or secret values (for example, OAuth v2)

*   User is able to input a domain or subdomain when authenticating within Zapier

*   Integration stores sensitive authentication details (in environment variables, for example) which are used as part of the authentication process

Taking the following steps prevents the potential for an attacker to access your integration’s sensitive authentication information, such as the OAuth client ID or secret.

1.  If your integration allows for the user to provide a domain, validate the input against an allow-list of trusted domains.

2.  If your integration allows for the user to provide a subdomain, add conditional validation for the subdomain string whenever you include the value in your OAuth HTTP requests. This change will prevent potential exploitation of the subdomain vulnerability.

*   If you’re using OAuth-based authentications, update the `getAccessToken` and optional `refreshAccessToken` configuration methods. If the integration uses [shorthand HTTP requests](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#shorthand-http-requests), switching to [manual HTTP requests](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#manual-http-requests) will allow you to perform this manual subdomain validation.

Example code for handling subdomain validation:

```js
const refreshAccessToken = async (z, bundle) => {
  // --- UPDATE: add your validation for the subdomain field before using it ---
  if (!/^[a-z0-9-]+$/.test(bundle.authData.yourSubdomainField)) {
    throw new Error(
      "Subdomain can only contain letters, numbers and dashes (-)."
    );
  }

  const response = await z.request({
    url: `https://${bundle.authData.yourSubdomainField}.mydomain.com/oauth/token`,
    method: "POST",
    body: {
      client_id: process.env.CLIENT_ID,
      client_secret: process.env.CLIENT_SECRET,
      grant_type: "refresh_token",
      refresh_token: bundle.authData.refresh_token,
      redirect_uri: bundle.inputData.redirect_uri,
    },
  });

  return {
    access_token: response.data.access_token,
    refresh_token: response.data.refresh_token,
  };
};
```

## Resources

A `resource` is a representation (as a JavaScript object) of one of the REST resources of your API. Say you have a `/recipes`
endpoint for working with recipes; you can define a recipe resource in your integration that will tell Zapier how to do create,
read, and search operations on that resource.

```js
const Recipe = {
  // `key` is the unique identifier the Zapier backend references
  key: "recipe",
  // `noun` is the user-friendly name displayed in the Zapier UI
  noun: "Recipe",
  // `list` and `create` are just a couple of the methods you can define
  list: {
    // ...
  },
  create: {
    // ...
  },
};
```

The quickest way to create a resource is with the `zapier scaffold` command:

```bash
zapier scaffold resource "Recipe"
```

This will generate the resource file and add the necessary statements to the `index.js` file to import it.

### Resource Definition

A resource has a few basic properties. The first is the `key`, which allows Zapier to identify the resource on our backend.
The second is the `noun`, the user-friendly name of the resource that is presented to users throughout the Zapier UI.

> Check out [this working example integration](https://github.com/zapier/zapier-platform/tree/main/example-apps/resource) to see resources in action.

After those, there is a set of optional properties that tell Zapier what methods can be performed on the resource.
The complete list of available methods can be found in the [Resource Schema Docs](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#resourceschema).
For now, let's focus on two:

*   `list` - Tells Zapier how to fetch a set of this resource. This becomes a Trigger in the Zapier Editor.

*   `create` - Tells Zapier how to create a new instance of the resource. This becomes an Action in the Zapier Editor.

Here is a complete example of what the list method might look like

```js
const Recipe = {
  key: "recipe",
  // ...
  list: {
    display: {
      label: "New Recipe",
      description: "Triggers when a new recipe is added.",
    },
    operation: {
      perform: {
        url: "https://example.com/recipes",
      },
    },
  },
};
```

The method is made up of two properties, a `display` and an `operation`. The `display` property ([schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#basicdisplayschema)) holds the info needed to present the method as an available Trigger in the Zapier Editor. The `operation` ([schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#resourceschema)) provides the implementation to make the API call.

Adding a create method looks very similar.

```js
const Recipe = {
  key: "recipe",
  // ...
  list: {
    // ...
  },
  create: {
    display: {
      label: "Add Recipe",
      description: "Adds a new recipe to our cookbook.",
    },
    operation: {
      perform: {
        method: "POST",
        url: "https://example.com/recipes",
        body: {
          name: "Baked Falafel",
          style: "mediterranean",
        },
      },
    },
  },
};
```

Every method you define on a `resource` Zapier converts to the appropriate Trigger, Create, or Search. Our examples
above would result in an integration with a New Recipe Trigger and an Add Recipe Create.

Note the keys for the Trigger, Create, Search, and Search or Create are automatically generated (in case you want to use them in a dynamic dropdown), like: `{resourceName}List`, `{resourceName}Create`, `{resourceName}Search`, and `{resourceName}SearchOrCreate`; in the examples above, `{resourceName}` would be `recipe`.

## Triggers/Searches/Creates

Triggers, Searches, and Creates are the way an integration defines what it is able to do. Triggers read
data into Zapier (i.e. watch for new recipes). Searches locate individual records (find recipe by title). Creates create
new records in your system (add a recipe to the catalog).

The definition for each of these follows the same structure. Here is an example of a trigger:

```js
const App = {
  // ...
  triggers: {
    new_recipe: {
      key: "new_recipe", // uniquely identifies the trigger
      noun: "Recipe", // user-friendly word that is used to refer to the resource
      // `display` controls the presentation in the Zapier Editor
      display: {
        label: "New Recipe",
        description: "Triggers when a new recipe is added.",
      },
      // `operation` implements the API call used to fetch the data
      operation: {
        perform: {
          url: "https://example.com/recipes",
        },
      },
    },
    another_trigger: {
      // Another trigger definition...
    },
  },
};
```

You can find more details on the definition for each by looking at the [Trigger Schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#triggerschema),
[Search Schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#searchschema), and [Create Schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#createschema).

> To create a new integration with a premade trigger, search, or create, run `zapier init [your integration name]` and select from the list that appears. You can also check out our working example integrations [here](https://github.com/zapier/zapier-platform/tree/main/example-apps).

> To add a trigger, search, or create to an existing integration, run `zapier scaffold [trigger|search|create] [noun]` to create the necessary files to your project. For example, `zapier scaffold trigger post` will create a new trigger called "New Post".

### Return Types

Each of the 3 types of function should return a certain data type for use by the platform. There are automated checks to let you know when you're trying to pass the wrong type back. For reference, each expects:

| Method  | Return Type | Notes                                                                                                  |
| ------- | ----------- | ------------------------------------------------------------------------------------------------------ |
| Trigger | Array       | 0 or more objects; passed to the [deduper](https://platform.zapier.com/build/deduplication) if polling |
| Search  | Array       | 0 or more objects. Only the first object will be returned, so if len > 1, put the best match first     |
| Create  | Object      | Return values are evaluated by [`isPlainObject`](https://lodash.com/docs#isPlainObject)                |

When a trigger function returns an empty array, the Zap will not trigger. For REST Hook triggers, this can be used to filter data if the available subscription options are not specific enough for the Zap's needs.

#### Returning Line Items (Array of Objects)

In some cases, you may want to include multiple items in the data you return for Searches or Creates. To do that, return the set of items as an array of objects under a descriptive key. This may be as part of another object (like items in an invoice) or as multiple top-level items.

For example, a Create Order action returning an order with multiple items might look like this:

```
order = {
  name: 'Zap Zaplar',
  total_cost: 25.96,
  items: [
    { name: 'Zapier T-Shirt', unit_price: 11.99, quantity: 3, line_amount: 35.97, category: 'shirts' },
    { name: 'Orange Widget', unit_price: 7.99, quantity: 10, line_amount: 79.90, category: 'widgets' },
    { name:'Stuff', unit_price: 2.99, quantity: 7, line_amount: 20.93, category: 'stuff' },
    { name: 'Allbird Shoes', unit_price: 2.99, quantity: 7, line_amount: 20.93, category: 'shoes' },
  ],
  zip: 01002
}
```

While a Find Users search could return multiple items under an object key within an array, like this:

```
result = [{
  users: [
      { name: 'Zap Zaplar', age: 12, city: 'Columbia', region: 'Missouri' },
      { name: 'Orange Crush', age: 28, city: 'West Ocean City', region: 'Maryland' },
      { name: 'Lego Brick', age: 91, city: 'Billund', region: 'Denmark' },
    ],
  }];
```

A standard search would return just the inner array of users, and only the first user would be provided as a final result. Returning line items instead means that the "first result" return is the object containing all the user details within it.

Using the standard approach is recommended, because not all Zapier integrations support line items directly, so users may need to take additional actions to reformat this data for use in their Zaps. More detail on that at [Use line items in Zaps](https://zapier.com/help/create/basics/use-line-items-in-zaps). However, there are use cases where returning multiple results is helpful enough to outweigh that additional effort.

### Fallback Sample

In cases where Zapier needs to show an example record to the user, but we are unable to get a live example from the API, Zapier will fallback to this hard-coded sample. This should reflect the data structure of the Trigger's perform method, and have dummy values that we can show to any user.

```js
,sample: {
  dummydata_field1: 'This will be compared against your perform method output'
  style: 'mediterranean'
}
```

## Input Fields

Moved to [Input Fields](/platform/build-cli/input-fields).

## Output Fields

On each trigger, search, or create in the operation directive - you can provide an array of objects as fields under the `outputFields`. Output Fields are what users see when they select a field provided by your trigger, search or create to map it to another.

Output Fields are optional, but can be used to:

*   Define friendly labels for the returned fields. By default, we will *humanize* for example `my_key` as *My Key*.

*   Make sure that custom fields that may not be found in every live sample and - since they're custom to the connected account - cannot be defined in the static sample, can still be mapped.

*   (Added in v15.6.0) Define what field(s) can be used to uniquely identify and [deduplicate](#how-does-deduplication-work) items returned by a polling trigger call.

The [schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema) for `outputFields` is shared with `inputFields` but only these properties are relevant:

*   `key` - includes the field when not present in the live sample. When no `label` property is provided, `key` will be *humanized* and displayed as the field name.

*   `label` - defines the field name displayed to users.

*   `type` - defines the type for static sample data. A [validation warning](https://platform.zapier.com/publish/integration-checks-reference#d024---static-sample-respects-output-field-definition) will be displayed if the static sample does not match the specified type.

*   `required` - defines whether the field is required in static sample data. A [validation warning](https://platform.zapier.com/publish/integration-checks-reference#d024---static-sample-respects-output-field-definition) will be displayed if the value is true and the static sample does not contain the field.

*   `primary` - defines whether the field is part of the primary key for polling trigger [deduplication](#how-does-deduplication-work).

Custom/Dynamic Output Fields are defined in the same way as [Custom/Dynamic Input Fields](#customdynamic-fields).

### Nested & Children (Line Item) Fields

To define an Output Field for a nested field use `{{parent}}__{{key}}`. For children (line item) fields use `{{parent}}[]{{key}}`.

```js
const recipeOutputFields = async (z, bundle) => {
  const response = await z.request("https://example.com/api/v2/fields.json");

  // response.throwForStatus() if you're using core v9 or older

  // Should return an array like [{"key":"field_1","label":"Label for Custom Field"}]
  return response.data; // or response.json if you're on core v9 or older
};

const App = {
  // ...
  triggers: {
    new_recipe: {
      // ...
      operation: {
        perform: () => {},
        sample: {
          id: 1,
          title: "Pancake",
          author: {
            id: 1,
            name: "Amy",
          },
          ingredients: [
            {
              name: "Egg",
              amount: 1,
            },
            {
              name: "Milk",
              amount: 60,
              unit: "g",
            },
            {
              name: "Flour",
              amount: 30,
              unit: "g",
            },
          ],
        },
        // an array of objects is the simplest way
        outputFields: [
          {
            key: "id",
            label: "Recipe ID",
            type: "integer",
          },
          {
            key: "title",
            label: "Recipe Title",
            type: "string",
          },
          {
            key: "author__id",
            label: "Author User ID",
            type: "integer",
          },
          {
            key: "author__name",
            label: "Author Name",
            type: "string",
          },
          {
            key: "ingredients[]name",
            label: "Ingredient Name",
            type: "string",
          },
          {
            key: "ingredients[]amount",
            label: "Ingredient Amount",
            type: "number",
          },
          {
            key: "ingredients[]unit",
            label: "Ingredient Unit",
            type: "string",
          },
          recipeOutputFields, // provide a function inline - we'll merge the results!
        ],
      },
    },
  },
};
```

## Buffered Create Actions

*Added in v15.15.0. This feature is currently **internal-only**.*

A Buffered Create allows you to create objects in bulk with a single or fewer API request(s). This is useful when you want to reduce the number of requests made to your server. When enabled, Zapier holds the data until the buffer reaches a size limit or a certain time has passed, then sends the buffered data using the `performBuffer` function you define.

To implement a Buffered Create, you define a [`buffer`](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#bufferconfigschema) configuration object and a `performBuffer` function in the `operation` object. In the `buffer` config object, you specify how you want to group the buffered data using the `groupedBy` setting and the maximum number of items to buffer using the `limit` setting.

The `performBuffer` function should replace the `perform` function. Note that `perform` cannot be defined along with `performBuffer`. Check out the [`create` action operation schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#basiccreateactionoperationschema) for details.

Similar to the general `perform` function accepting two arguments, [`z`](/platform/build-cli/core#z-object) and [`bundle`](/platform/build-cli/core#bundle-object) objects, the `performBuffer` function accepts [`z`](/platform/build-cli/core#z-object) and [`bufferedBundle`](/platform/build-cli/core#bufferedbundle-object) objects. They share the same `z` object, but the `bufferedBundle` object is different from the `bundle` object. The `bufferedBundle` object has an idempotency ID set at `bufferedBundle.buffer[].meta.id` for each object in the buffer. `performBuffer` would have to return the idempotency IDs to tell Zapier which objects were successfully written. Find the details about the `bufferedBundle` object [here](/platform/build-cli/core#bufferedbundle-object).

Here is an example of a Buffered Create action:

```js
const performBuffer = async (z, bufferedBundle) => {
  // Grab the line items, preserving the order
  const rows = bufferedBundle.buffer.map(({ inputData }) => {
    return { title: inputData.title, year: inputData.year };
  });

  // Make the bulk-create API request
  const response = await z.request({
    method: "POST",
    url: "https://api.example.com/add_rows",
    body: {
      spreadsheet: bufferedBundle.groupedBy.spreadsheet,
      worksheet: bufferedBundle.groupedBy.worksheet,
      rows,
    },
  });

  // Create a matching result using the idempotency ID for each buffered invocation run.
  // The returned IDs will tell Zapier backend which items were successfully written.
  const result = {};
  bufferedBundle.buffer.forEach(({ inputData, meta }, index) => {
    let error = "";
    let outputData = {};

    // assuming request order matches response and
    // response.data = {
    //   "rows": [
    //     {"id": "12910"},
    //     {"id": "92830"},
    //     {"error": "Not Created"},
    //     ...
    //   ]
    // }
    if (response.data.rows.length > index) {
      // assuming an error is returned with an "error" key in the response data
      if (response.data.rows[index].error) {
        error = response.data.rows[index].error;
      } else {
        outputData = response.data.rows[index];
      }
    }

    // the performBuffer method must return a data just like this
    // {
    //   "idempotency ID 1": {
    //     "outputData": {"id": "12910"},
    //     "error": ""
    //   },
    //   "idempotency ID 2": {
    //     "outputData": {"id": "92830"},
    //     "error": ""
    //   },
    // "idempotency ID 3": {
    //     "outputData": {},
    //     "error": "Not Created"
    //   },
    //   ...
    // }
    result[meta.id] = { outputData, error };
  });

  return result;
};

module.exports = {
  key: "add_rows",
  noun: "Rows",
  display: {
    label: "Add Rows",
    description: "Add rows to a worksheet.",
  },
  operation: {
    buffer: {
      groupedBy: ["spreadsheet", "worksheet"],
      limit: 3,
    },
    performBuffer,
    inputFields: [
      {
        key: "spreadsheet",
        type: "string",
        required: true,
      },
      {
        key: "worksheet",
        type: "string",
        required: true,
      },
      {
        key: "title",
        type: "string",
      },
      {
        key: "year",
        type: "string",
      },
    ],
    outputFields: [{ key: "id", type: "string" }],
    sample: { id: "12345" },
  },
};
```

## Deploying an Integration Version

An Integration Version is related to a specific Integration but is an "immutable" implementation of your integration. This makes it easy to run multiple versions for multiple users concurrently. The Integration Version is pulled from the version within the `package.json`. To create a new Integration Version, update the version number in that file. By default, **every Integration Version is private** but you can `zapier promote` it to production for use by over 1 million Zapier users.

```bash
# push your integration version to Zapier
zapier push

# list your versions
zapier versions
```

If you'd like to manage your **Version**, use these commands:

*   `zapier versions` - list the versions for the current directory's integration

*   `zapier push` - push the current version of current directory's integration & version (read from `package.json`)

*   `zapier promote 1.0.0` - mark a version as the "production" version

*   `zapier migrate 1.0.0 1.0.1 [100%]` - move users between versions, regardless of deployment status

*   `zapier deprecate 1.0.0 2020-06-01` - mark a version as deprecated, but let users continue to use it (we'll email them)

*   `zapier env:set 1.0.0 KEY=VALUE` - set an environment variable to some value

*   `zapier delete:version 1.0.0` - delete a version entirely. This is mostly for clearing out old test apps you used personally. It will fail if there are any users. You probably want `deprecate` instead.

*   `zapier pull` - pull the latest version from Zapier server. This is used in the event that Zapier made an update since your last version.

> Note: To see the changes that were just pushed reflected in the browser, you have to manually refresh the browser each time you push.

### Private Integration Version (default)

A simple `zapier push` will only create the Integration Version in your editor. No one else using Zapier can see it or use it.

### Sharing an Integration Version

This is how you would share your integration with friends, co-workers or clients. This is perfect for quality assurance, testing with active users or just sharing any app you like.

```bash
# sends an email this user to let them view the integration version 1.0.0 in the UI privately
zapier users:add user@example.com 1.0.0

# sends an email this user to let them admin the integration (make changes just like you)
zapier team:add user@example.com
```

You can also invite anyone on the internet to your integration by using the links from `zapier users:links`. The link should look something like [`https://zapier.com/platform/public-invite/1/222dcd03aed943a8676dc80e2427a40d/`.](https://zapier.com/platform/public-invite/1/222dcd03aed943a8676dc80e2427a40d/.) You can put this in your help docs, post it to Twitter, add it to your email campaign, etc. You can choose an invite link specific to an integration version or for the entire integration (i.e. all integration versions).

### Promoting an Integration Version

Promotion is how you would share your integration with every one of the 1 million+ Zapier users. If this is your first time promoting - you may have to wait for the Zapier team to review and approve your integration.

If this isn't the first time you've promoted your integration - you might have users on older versions. You can `zapier migrate` to either move users over (which can be dangerous if you have breaking changes). Or, you can `zapier deprecate` to give users some time to move over themselves.

```bash
# promote your integration version to all Zapier users
zapier promote 1.0.1

# OPTIONAL - migrate your users between one integration version to another
zapier migrate 1.0.0 1.0.1

# OR - mark the old version as deprecated
zapier deprecate 1.0.0 2020-06-01
```

### Pulling Latest Version from Zapier

Zapier may fix bugs or add new features to your integration and release a new version. If you attempt to use `zapier push` and we've released a newer version, you will be prevented from pushing until you run `zapier pull` to update your local files with the latest version.

Any destructive file changes will prompt you with a confirmation dialog before continuing.

## Environment

Integrations can define environment variables that are available when the integration's code executes. They work just like environment
variables defined on the command line. They are useful when you have data like an OAuth client ID and secret that you
don't want to commit to source control. Environment variables can also be used as a quick way to toggle between
a staging and production environment during integration development.

It is important to note that **variables are defined on a per-version basis!** When you push a new version, the
existing variables from the previous version are copied, so you don't have to manually add them. However, edits
made to one version's environment will not affect the other versions.

### Defining Environment Variables

To define an environment variable, use the `env` command:

```bash
# Will set the environment variable on Zapier.com
zapier env:set 1.0.0 MY_SECRET_VALUE=1234
```

You will likely also want to set the value locally for testing.

```bash
export MY_SECRET_VALUE=1234
```

Alternatively, we provide some extra tooling to work with an `.env` (or `.environment`, see below note) that looks like this:

```
MY_SECRET_VALUE=1234
```

> `.env` is the new recommended name for the environment file since v5.1.0. The old name `.environment` is deprecated but will continue to work for backward compatibility.

And then in your `test/basic.js` file:

```js
const zapier = require("zapier-platform-core");

should("some tests", () => {
  zapier.tools.env.inject(); // testing only!
  console.log(process.env.MY_SECRET_VALUE);
  // should print '1234'
});
```

> This is a popular way to provide `process.env.ACCESS_TOKEN || bundle.authData.access_token` for convenient testing.

> **NOTE** Variables defined via `zapier env:set` will *always* be uppercased. For example, you would access the variable defined by `zapier env:set 1.0.0 foo_bar=1234` with `process.env.FOO_BAR`.

### Accessing Environment Variables

To view existing environment variables, use the `env` command.

```bash
# Will print a table listing the variables for this version
zapier env:get 1.0.0
```

Within your integration, you can access the environment via the standard `process.env` - any values set via local `export` or `zapier env:set` will be there.

For example, you can access the `process.env` in your perform functions and in templates:

```js
const listExample = async (z, bundle) => {
  const httpOptions = {
    headers: {
      "my-header": process.env.MY_SECRET_VALUE,
    },
  };
  const response = await z.request(
    "https://example.com/api/v2/recipes.json",
    httpOptions
  );

  // response.throwForStatus() if you're using core v9 or older

  return response.data; // or response.json if you're using core v9 or older
};

const App = {
  // ...
  triggers: {
    example: {
      noun: "{{process.env.MY_NOUN}}",
      operation: {
        // ...
        perform: listExample,
      },
    },
  },
};
```

> Note! Be sure to lazily access your environment variables - see [When to use placeholders or curlies?](#when-to-use-placeholders-or-curlies).

## Adding Throttle Configuration

*Added in v15.4.0.*

When a throttle configuration is set for an action, Zapier uses it to apply throttling when the limit for the timeframe window is exceeded. It can be set at the root level and/or on an action. When set at the root level, it is the default throttle configuration used on each action of the integration. And when set in an action's operation object, the root-level default is overwritten for that action only. Note that the throttle limit is not shared across actions unless for those with the same key, window, limit, and scope when "action" is not in the scope.

To throttle an action, you need to set a `throttle` object with the following variables:

1.  `window [integer]`: The timeframe, in seconds, within which the system tracks the number of invocations for an action. The number of invocations begins at zero at the start of each window.

2.  `limit [integer]`: The maximum number of invocations for an action, allowed within the timeframe window.

3.  `key [string]` (*added in v15.6.0*): The key to throttle with in combination with the scope. User data provided for the input fields can be used in the key with the use of the curly braces referencing. For example, to access the user data provided for the input field "test\_field", use `{{bundle.inputData.test_field}}`. Note that a required input field should be referenced to get user data always.

4.  `retry [boolean]` (*added in v15.8.0*): The effect of throttling on the tasks of the action. `true` means throttled tasks are automatically retried after some delay, while `false` means tasks are held without retry. It defaults to `true`. NOTE that it has no effect on polling triggers and should not be set.

5.  `filter [string]` (*added in v15.8.0*): EXPERIMENTAL: Account-based attribute to override the throttle by. You can set to one of the following: "free", "trial", "paid". Therefore, the throttle scope would be automatically set to "account" and ONLY the accounts based on the specified filter will have their requests throttled based on the throttle overrides while the rest are throttled based on the original configuration.

6.  `scope [array]`: The granularity to throttle by. You can set the scope to one or more of the following options;

    *   'user' - Throttles based on user ids.

    *   'auth' - Throttles based on auth ids.

    *   'account' - Throttles based on account ids for all users under a single account.

    *   'action' - Throttles the action it is set on separately from other actions.

7.  `overrides [array[object]]` (*added in v15.6.0*): EXPERIMENTAL: Overrides the original throttle configuration based on a Zapier account attribute;

    *   `window [integer]`: Same description as above.

    *   `limit [integer]`: Same description as above.

    *   `filter [string]`: Account-based attribute to override the throttle by. You can set to one of the following: "free", "trial", "paid". Therefore, the throttle scope would be automatically set to "account" and ONLY the accounts based on the specified filter will have their requests throttled based on the throttle overrides while the rest are throttled based on the original configuration.

    *   `retry [boolean]` (*added in v15.6.1*): The effect of throttling on the tasks of the action. `true` means throttled tasks are automatically retried after some delay, while `false` means tasks are held without retry. It defaults to `true`. NOTE that it has no effect on polling triggers and should not be set.

Both `window` and `limit` are required and others are optional. By default, throttling is scoped to the action and account.

Here is a typical usage of the throttle configuration:

```js
const App = {
  version: require("./package.json").version,
  platformVersion: require("zapier-platform-core").version,

  // default throttle used for each action
  throttle: {
    window: 600,
    limit: 50,
    scope: ["account"],
  },

  creates: {
    upload_video: {
      noun: "Video",
      display: {
        label: "Upload Video",
        description: "Upload a video.",
      },
      operation: {
        perform: () => {},
        inputFields: [{ key: "name", required: true, type: "string" }],
        // overwrites the default, for this action
        throttle: {
          window: 600,
          limit: 5,
          key: "test-key-{{bundle.inputData.name}}",
          retry: false,
          scope: ["account"],
          overrides: [
            {
              window: 600,
              limit: 10,
              filter: "free",
              retry: false,
            },
            {
              window: 600,
              limit: 100,
              filter: "trial",
              retry: false,
            },
            {
              window: 0,
              limit: 0,
              filter: "paid",
              retry: true,
            },
          ],
        },
      },
    },
  },
};

module.exports = App;
```

## Making HTTP Requests

There are two ways to make HTTP requests:

1.  [**Shorthand HTTP Requests**](#shorthand-http-requests) - Easy to use, but limits what you can control. Best for simple requests.

2.  [**Manual HTTP Requests**](#manual-http-requests) - Gives you full control over the request and response.

Use these helper constructs to reduce boilerplate:

1.  `requestTemplate` - an object literal of [HTTP request options](#http-request-options) that will be merged with every request.

2.  `beforeRequest` - [middleware](#using-http-middleware) that mutates every request before it is sent.

3.  `afterResponse` - [middleware](#using-http-middleware) that mutates every response before it is completed.

> Note: you can install any HTTP client you like - but this is greatly discouraged as you lose [automatic HTTP logging](#http-logging) and middleware.

### Shorthand HTTP Requests

For simple HTTP requests that do not require special pre- or post-processing, you can specify the [HTTP request options](#http-request-options) as an object literal in your app definition.

This features:

1.  Lazy `{{curly}}` replacement.

2.  JSON and form body de-serialization.

3.  Automatic non-2xx error raising.

```js
const triggerShorthandRequest = {
  url: "https://{{bundle.authData.subdomain}}.example.com/v2/api/recipes.json",
  method: "GET",
  params: {
    sort_by: "id",
    sort_order: "DESC",
  },
};

const App = {
  // ...
  triggers: {
    example: {
      // ...
      operation: {
        // ...
        perform: triggerShorthandRequest,
      },
    },
  },
};
```

In the URL above, `{{bundle.authData.subdomain}}` is automatically replaced with the live value from the bundle. If the call returns a non 2xx return code, an error is automatically raised. The response body is automatically parsed as JSON or form-encoded and returned.

An error will be raised if the response cannot be parsed as JSON or form-encoded. To use shorthand requests with other response types, add [middleware](#using-http-middleware) that sets `response.data` to the parsed response.

### Manual HTTP Requests

Use this when you need full control over the request/response. For example:

1.  To do processing (usually involving [`bundle.inputData`](/platform/build-cli/core#bundle-inputdata)) before a request is made

2.  To do processing of an API's response before you return data to Zapier

3.  To process an unusual response type, such as XML

To make a manual request, pass your [request options](#http-request-options) to `z.request()` then use the resulting [response object](#http-response-object) to return the data you want:

```js
const listRecipes = async (z, bundle) => {
  // Custom processing of bundle.inputData would go here...

  const httpRequestOptions = {
    url: "https://{{bundle.authData.subdomain}}.example.com/v2/api/recipes.json",
    method: "GET",
    params: {
      cuisine: bundle.inputData.cuisine,
    },
  };
  const response = await z.request(httpRequestOptions);
  const recipes = response.data;

  // Custom processing of recipes would go here...

  return recipes;
};

const App = {
  // ...
  triggers: {
    example: {
      // ...
      operation: {
        // ...
        perform: listRecipes,
      },
    },
  },
};
```

Manual requests perform lazy `{{curly}}` replacement. In the URL above, `{{bundle.authData.subdomain}}` is automatically replaced with the live value from the bundle.

#### POST and PUT Requests

To POST or PUT data to your API you can do this:

```js
const App = {
  // ...
  triggers: {
    example: {
      // ...
      operation: {
        // ...
        perform: async (z, bundle) => {
          const recipe = {
            name: "Baked Falafel",
            style: "mediterranean",
            directions: "Get some dough....",
          };

          const options = {
            method: "POST",
            url: "https://example.com/api/v2/recipes.json",
            body: JSON.stringify(recipe),
          };
          const response = await z.request(options);

          // Throw and try to extract message from standard error responses
          if (response.status !== 201) {
            throw new z.errors.Error(
              `Unexpected status code ${response.status}`,
              "CreateRecipeError",
              response.status
            );
          }

          return response.data; // or response.json if you're using core v9 or older
        },
      },
    },
  },
};
```

> Note: you need to call `z.JSON.stringify()` before setting the `body`.

### Using HTTP middleware

HTTP middleware is a function or piece of code that sits between a client request and the server response, allowing you to inspect, modify, or handle the request or response before they reach their destination. You use middleware to perform common tasks like adding security headers, logging requests, handling errors, or modifying data in a centralized way without repeating code. Common examples include adding a header to all outgoing responses to improve security, or catching and handling weird errors so that users receive a friendly error message instead of the system-generated message.

To process all HTTP requests in a certain way, use the `beforeRequest` and `afterResponse` middleware functions.

Middleware functions go in your app definition:

```js
const addHeader = (request, z, bundle) => {
  request.headers["my-header"] = "from zapier";
  return request;
};

// This example only works on core v10+!
const parseXML = (response, z, bundle) => {
  // Parse content that is not JSON
  // eslint-disable-next-line no-undef
  response.data = xml.parse(response.content);
  return response;
};

// This example only works on core v10+!
const handleWeirdErrors = (response, z) => {
  // Prevent `throwForStatus` from throwing for a certain status.
  if (response.status === 456) {
    response.skipThrowForStatus = true;
  } else if (response.status === 200 && response.data.success === false) {
    throw new z.errors.Error(response.data.message, response.data.code);
  }
  return response;
};

const App = {
  // ...
  beforeRequest: [addHeader],
  afterResponse: [parseXML, handleWeirdErrors],
  // ...
};
```

A `beforeRequest` middleware function takes a request options object, and returns a (possibly mutated) request object. An `afterResponse` middleware function takes a response object, and returns a (possibly mutated) response object. Middleware functions are executed in the order specified in the app definition, and each subsequent middleware receives the request or response object returned by the previous middleware.

Middleware functions can be asynchronous - just return a promise from the middleware function.

The second argument for middleware is the `z` object, but it does *not* include `z.request()` as using that would easily create infinite loops.

Here is the full request lifecycle when you call `z.request({...})`:

1.  set defaults on the `request` object

2.  run your `beforeRequest` middleware functions in order

3.  add applicable auth headers (e.g. adding `Basic ...` for `basic` auth), if applicable

4.  add `request.params` to `request.url`

5.  execute the `request`, store the result in `response`

6.  try to auto-parse response body for non-raw requests, store result in `response.data`

7.  log the request to Zapier's logging server

8.  if the status code is `401`, you're using a refresh-able auth (such as `oauth2` or `session`) *and* `autoRefresh` is `true` in your auth configuration, throw a `RefreshAuthError`. The server will attempt to refresh the authentication again and retry the whole step

9.  run your `afterResponse` middleware functions in order

10. call `response.throwForStatus()` unless `response.skipThrowForStatus` is `true`

The resulting response object is returned from `z.request()`.

> Example Integration: check out [https://github.com/zapier/zapier-platform/tree/main/example-apps/middleware](https://github.com/zapier/zapier-platform/tree/main/example-apps/middleware) for a working example integration using HTTP middleware.

#### Error Response Handling

Since `v10.0.0`, `z.request()` calls `response.throwForStatus()` before it returns a response. You can disable automatic error throwing by setting `skipThrowForStatus` on the request object:

```js
// Disable automatic error throwing on the request object
const perform = async (z, bundle) => {
  const response = await z.request({
    url: "...",
    skipThrowForStatus: true,
  });
  // Now you handle error response on your own.
  // The following is equivalent to response.throwForStatus(),
  // but you have to remember to do it on every request
  if (response.status >= 400) {
    throw new z.errors.ResponseError(response);
  }
};
```

You can also do it in `afterResponse` if the API uses a status code >= 400 that should not be treated as an error.

```js
// Don't throw an error when response status is 456
const disableAutoThrowOn456 = (response, z) => {
  if (response.status === 456) {
    response.skipThrowForStatus = true;
  }
  return response;
};
const App = {
  // ...
  afterResponse: [disableAutoThrowOn456],
  // ...
};
```

For developers using v9.x and below, it's your responsibility to throw an exception for an error response. That means you should call `response.throwForStatus()` or throw an error yourself, likely following the `z.request` call.

This behavior has changed periodically across major versions, which changes how/when you have to worry about handling errors. Here's a diagram to illustrate that:

![](https://cdn.zappy.app/e835d9beca1b6489a065d51a381613f3.png)

Ensure you're handling errors correctly for your platform version. The latest released version is **15.18.1**.

### HTTP Request Options

[Shorthand requests](#shorthand-http-requests) and [manual requests](#manual-http-requests) support the following HTTP `options`:

*   `url`: HTTP url, you can provide it as a separate argument (`z.request(url, options)`) or as part of the `options` object (`z.request({url: url, ...})`).

*   `method`: HTTP method, default is `GET`.

*   `headers`: request headers object, format `{'header-key': 'header-value'}`.

*   `params`: URL query params object, format `{'query-key': 'query-value'}`.

*   `body`: request body, can be a string, buffer, readable stream or plain object. When it is an object/array and the `Content-Type` header is `application/x-www-form-urlencoded` the body will be transformed to query string parameters, otherwise we'll set the header to `application/json; charset=utf-8` and JSON encode the body. Default is `null`.

*   `allowGetBody`: include `body` in `GET` requests. Set to `true` to enable. Default is `false`. Set only if required by the receiving API. See [section 4.3.1 in RFC 7231](https://www.rfc-editor.org/rfc/rfc7231#section-4.3.1).

*   `json`: shortcut object/array/etc. you want to JSON encode into body. Default is `null`.

*   `form`: shortcut object. you want to form encode into body. Default is `null`.

*   `raw`: set this to stream the response instead of consuming it immediately. Default is `false`.

*   `redirect`: set to `manual` to extract redirect headers, `error` to reject redirect, default is `follow`.

*   `follow`: maximum redirect count, set to `0` to not follow redirects. default is `20`.

*   `compress`: support gzip/deflate content encoding. Set to `false` to disable. Default is `true`.

*   `agent`: Node.js `http.Agent` instance, allows custom proxy, certificate etc. Default is `null`.

*   `timeout`: request / response timeout in ms. Set to `0` to disable (OS limit still applies), timeout reset on `redirect`. Default is `0` (disabled).

*   `signal` (*added in v15.14.1*): enables cancelling requests via a timeout set by an `AbortController`. More details in `node-fetch` docs [here](https://www.npmjs.com/package/node-fetch#request-cancellation-with-abortsignal). Default is `null`.

*   `size`: maximum response body size in bytes. Set to `0` to disable. Default is `0` (disabled).

*   `skipThrowForStatus` (*added in v10.0.0*): don't call `response.throwForStatus()` before resolving the request with `response`. See [HTTP Response Object](#http-response-object).

```js
const response = await z.request({
  url: "https://example.com",
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  // only provide body, json or form...
  body: { hello: "world" }, // or '{"hello": "world"}' or 'hello=world'
  json: { hello: "world" },
  form: { hello: "world" },
  // access node-fetch style response.body
  raw: false,
  redirect: "follow",
  follow: 20,
  compress: true,
  agent: null,
  timeout: 0,
  size: 0,
});
```

### HTTP Response Object

The response object returned by `z.request([url], options)` supports the following fields and methods:

*   `status`: The response status code, i.e. `200`, `404`, etc.

*   `content`: The response content as a String. For Buffer, try `options.raw = true`.

*   `data` (*added in v10.0.0*): The response content as an object if the content is JSON or `application/x-www-form-urlencoded` (`undefined` otherwise).

*   `headers`: Response headers object. The header keys are all lower case.

*   `getHeader(key)`: Retrieve response header, case insensitive: `response.getHeader('My-Header')`

*   `skipThrowForStatus` (*added in v10.0.0*): don't call `throwForStatus()` before resolving the request with this response.

*   `throwForStatus()`: Throws an error if `400 <= statusCode < 600`.

*   `request`: The original request options object (see above).

Additionally, if `request.raw` is `true`, the raw response has the following properties:

*   `json()`: Get the response content as an object, if `options.raw = true` and content is JSON (returns a promise). `undefined` in non-raw requests.

*   `body`: A stream available only if you provide `options.raw = true`.

```js
const response = await z.request({
  // options
});

// A bunch of examples for demonstration
response.status;
response.headers["Content-Type"];
response.getHeader("content-type");
response.request; // original request options
response.throwForStatus();

if (options.raw === false) {
  // (default)
  // If you're core v10+
  response.data; // same as...
  z.JSON.parse(response.content); // or...
  querystring.parse(response.content);

  // If you're core v9 or older...
  response.json; // same as
  z.JSON.parse(response.content);
} else {
  const buf = await response.buffer();
  buf.toString();

  const text = await response.text();

  const json = await response.json();

  response.body.pipe(otherStream);
}
```

## Dehydration

Dehydration, and its counterpart Hydration, is a tool that can lazily load data that might be otherwise expensive to retrieve aggressively.

*   **Dehydration** - think of this as "make a pointer", you control the creation of pointers with `z.dehydrate(func, inputData)` (or `z.dehydrateFile(func, inputData)` for files). This usually happens in a trigger step.

*   **Hydration** - think of this as an automatic step that "consumes a pointer" and "returns some data", Zapier does this automatically behind the scenes. This usually happens in an action step.

> This is very common when [Stashing Files](#stashing-files) - but that isn't their only use!

The method `z.dehydrate(func, inputData)` has two required arguments:

*   `func` - the function to call to fetch the extra data. Can be any raw `function`, defined in the file doing the dehydration or imported from another part of your integration. You must also register the function in the integration's `hydrators` property. Note that since v10.1.0, the maximum payload size to pass to `z.dehydrate` / `z.dehydrateFile` is 6KB.

*   `inputData` - this is an object that contains things like a `path` or `id` - whatever you need to load data on the other side

*   A known limitation of hydration is a 5 minute cache if the hydration call is made with identical `inputData` within that timeframe. To workaround this cache for records triggering hydration in close succession, include a unique value in the `inputData`, for example a `timestamp` in addition to the record `id`.

> **Why do I need to register my functions?** Because of how JavaScript works with its module system, we need an explicit handle on the function that can be accessed from the App definition without trying to "automagically" (and sometimes incorrectly) infer code locations.

Here is an example that pulls in extra data for a movie:

```js
const getMovieDetails = async (z, bundle) => {
  const url = `https://example.com/movies/${bundle.inputData.id}.json`;
  const response = await z.request(url);

  // reponse.throwForStatus() if you're using core v9 or older

  return response.data; // or response.json if you're using core v9 or older
};

const movieList = async (z, bundle) => {
  const response = await z.request("https://example.com/movies.json");

  // response.throwForStatus() if you're using core v9 or older

  return response.data.map((movie) => {
    // so maybe /movies.json is thin content but /movies/:id.json has more
    // details we want...
    movie.details = z.dehydrate(getMovieDetails, { id: movie.id });
    return movie;
  });
};

const App = {
  version: require("./package.json").version,
  platformVersion: require("zapier-platform-core").version,

  // don't forget to register hydrators here!
  // it can be imported from any module
  hydrators: {
    getMovieDetails,
  },

  triggers: {
    new_movie: {
      noun: "Movie",
      display: {
        label: "New Movie",
        description: "Triggers when a new Movie is added.",
      },
      operation: {
        perform: movieList,
      },
    },
  },
};

module.exports = App;
```

And in future steps of the Zap - if Zapier encounters a pointer as returned by `z.dehydrate(func, inputData)` - Zapier will tie it back to your integration and pull in the data lazily.

> **Why can't I just load the data immediately?** Isn't it easier? In some cases it can be - but imagine an API that returns 100 records when polling - doing 100x `GET /id.json` aggressive inline HTTP calls when 99% of the time Zapier doesn't *need* the data yet is wasteful.

### Merging Hydrated Data

As you've seen, the usual call to dehydrate will assign the result to an object property:

```js
movie.details = z.dehydrate(getMovieDetails, { id: movie.id });
```

In this example, all of the movie details will be located in the `details` property (e.g. `details.releaseDate`) after hydration occurs. But what if you want these results available at the top-level (e.g. `releaseDate`)? Zapier supports a specific keyword for this scenario:

```js
movie.$HOIST$ = z.dehydrate(getMovieDetails, { id: movie.id });
```

Using `$HOIST$` as the key will signal to Zapier that the results should be merged into the object containing the `$HOIST$` key. You can also use this to merge your hydrated data into a property containing "partial" data that exists before dehydration occurs:

```js
movie.details = {
  title: movie.title,
  $HOIST$: z.dehydrate(getMovieDetails, { id: movie.id }),
};
```

### File Dehydration

*Added in v7.3.0.*

The method `z.dehydrateFile(func, inputData)` allows you to download a file lazily. It takes the same arguments as `z.dehydrate(func, inputData)` does, but is recommended when the data is a file.

An example can be found in the [Stashing Files](#stashing-files) section.

What makes `z.dehydrateFile` different from `z.dehydrate` has to do with efficiency and when Zapier chooses to hydrate data. Knowing which pointers give us back files helps us delay downloading files until it's absolutely necessary. Not only will it help avoid unnecessary file downloads, it can also prevent errors if the file has a limited availability. (Stashing files, described below, can also help with that situation.)

A good example is when users are creating Zaps in the Zap Editor. If a pointer is made by `z.dehydrate`, the Zap Editor will hydrate the data immediately after pulling in samples. This allows users to map fields from the hydrated data into the subsequent steps of the Zap. If, however, the pointer is made by `z.dehydrateFile`, the Zap Editor will wait to hydrate the file, and will display a placeholder instead. There's nothing inside binary file data for users to map in the subsequent steps.

> `z.dehydrateFile(func, inputData)` was added in v7.3.0. We used to recommend using `z.dehydrate(func, inputData)` for files, but we now recommend changing it to `z.dehydrateFile(func, inputData)` for a better user experience.

## Stashing Files

It can be expensive to download and stream files or they can require complex handshakes to authorize downloads - so we provide a helpful stash routine that will take any `String`, `Buffer` or `Stream` and return a URL file pointer suitable for returning from triggers, searches, creates, etc.

The interface `z.stashFile(bufferStringStream, [knownLength], [filename], [contentType])` takes a single required argument - the extra three arguments will be automatically populated in most cases. Here's a full example:

```js
const content = "Hello world!";
const url = await z.stashFile(
  content,
  content.length,
  "hello.txt",
  "text/plain"
);
z.console.log(url);
// https://zapier-dev-files.s3.amazonaws.com/cli-platform/f75e2819-05e2-41d0-b70e-9f8272f9eebf
```

Most likely you'd want to stream from another URL - note the usage of `z.request({raw: true})`:

```js
const fileRequest = z.request({
  url: "https://example.com/file.pdf",
  raw: true,
});
const url = await z.stashFile(fileRequest); // knownLength and filename will be sniffed from the request. contentType will be binary/octet-stream
z.console.log(url);
// https://zapier-dev-files.s3.amazonaws.com/cli-platform/74bc623c-d94d-4cac-81f1-f71d7d517bc7
```

> Note: you should only be using `z.stashFile()` in a hydration method or a hook trigger's `perform` if you're sending over a short-lived URL to a file. Otherwise, it can be very expensive to stash dozens of files in a polling call - for example!

See a full example with dehydration/hydration wired in correctly:

```js
const stashPDFfunction = (z, bundle) => {
  // use standard auth to request the file
  const filePromise = z.request({
    url: bundle.inputData.downloadUrl,
    raw: true,
  });
  // and swap it for a stashed URL
  return z.stashFile(filePromise);
};

const pdfList = async (z, bundle) => {
  const response = await z.request("https://example.com/pdfs.json");

  // response.throwForStatus() if you're using core v9 or older

  // response.json.map if you're using core v9 or older
  return response.data.map((pdf) => {
    // Lazily convert a secret_download_url to a stashed url
    // zapier won't do this until we need it
    pdf.file = z.dehydrateFile(stashPDFfunction, {
      downloadUrl: pdf.secret_download_url,
    });
    delete pdf.secret_download_url;
    return pdf;
  });
};

const App = {
  version: require("./package.json").version,
  platformVersion: require("zapier-platform-core").version,

  hydrators: {
    stashPDF: stashPDFfunction,
  },

  triggers: {
    new_pdf: {
      noun: "PDF",
      display: {
        label: "New PDF",
        description: "Triggers when a new PDF is added.",
      },
      operation: {
        perform: pdfList,
      },
    },
  },
};

module.exports = App;
```

> To create a new integration for handling files, run `zapier init [your integration name] --template files`. You can also check out our working example integration [here](https://github.com/zapier/zapier-platform/tree/main/example-apps/files).

## Logging

To view the logs for your integration, use the `zapier logs` command.

There are three types of logs for a Zapier integration:

*   `http`: logged automatically by Zapier on HTTP requests

*   `bundle`: logged automatically on every method execution

*   `console`: manual logs via `z.console.log()` statements ([see below for details](#console-logging))

Note that by default, this command will only fetch logs associated to your user.

For advanced logging options, including the option to fetch logs for other users or specific integration versions, look at the help for the logs command:

```bash
zapier help logs
```

### Console Logging

To manually print a log statement in your code, use `z.console.log`:

```js
z.console.log("Here are the input fields", bundle.inputData);
```

The `z.console` object has all the same methods and works just like the Node.js [`Console`](https://nodejs.org/docs/latest-v14.x/api/console.html) class - the only difference is we'll log to our distributed datastore and you can view the logs via `zapier logs` (more below).

### Viewing Console Logs

To see your `z.console.log` logs, do:

```bash
zapier logs --type=console
```

### Viewing Bundle Logs

To see the bundle logs, do:

```bash
zapier logs --type=bundle
```

### HTTP Logging

If you are using [shorthand HTTP requests](#shorthand-http-requests) or the `z.request()` method that we provide, HTTP logging is handled automatically for you. For example:

```js
z.request("https://57b20fb546b57d1100a3c405.mockapi.io/api/recipes").then(
  (res) => {
    // do whatever you like, this request is already getting logged! :-D
    return res;
  }
);
```

HTTP logging will often work with other methods of making requests as well, but if you're using another method and having trouble seeing logs, try using `z.request()`.

### Viewing HTTP Logs

To see the HTTP logs, do:

```bash
zapier logs --type=http
```

To see detailed HTTP logs, including data such as headers and request and response bodies, do:

```bash
zapier logs --type=http --detailed
```

## Error Handling

APIs are not always available. Users do not always input data correctly to
formulate valid requests. Thus, it is a good idea to write integrations defensively and
plan for 4xx and 5xx responses from APIs. Without proper handling, errors often
have incomprehensible messages for end users, or possibly go uncaught.

Zapier provides a couple of tools to help with error handling. First is the
`afterResponse` middleware ([docs](#using-http-middleware)), which provides a hook for
processing all responses from HTTP calls. Second is `response.throwForStatus()`
([docs](#http-response-object)), which throws an error if the response status indicates
an error (status >= 400). Since v10.0.0, we automatically call this method before returning the
response, unless you set `skipThrowForStatus` on the request or response object. The
last tool is the collection of errors in `z.errors` ([docs](/platform/build-cli/core#z-errors)), which control
the behavior of Zaps when various kinds of errors occur.

### General Errors

Errors due to a misconfiguration in a user's Zap should be handled in your integration
by throwing `z.errors.Error` with a user-friendly message and optional error and
status code. Typically, this will be prettifying 4xx responses or APIs that return
errors as 200s with a payload that describes the error.

Example: `throw new z.errors.Error('Contact name is too long.', 'InvalidData', 400);`

> `z.errors.Error` was added in v9.3.0. If you're on an older version of `zapier-platform-core`, throw a standard JavaScript `Error` instead, such as `throw new Error('A user-friendly message')`.

A couple best practices to keep in mind:

*   Elaborate on terse messages. "not\_authenticated" -> "Your API Key is invalid. Please reconnect your account."

*   If the error calls out a specific field, surface that information to the user. "Provided data is invalid" -> "Contact name is invalid"

*   If the error provides details about why a field is invalid, add that in too! "Contact name is invalid" -> "Contact name is too long"

*   The second, optional argument should be a code that a computer could use to identify the type of error.

*   The last, optional argument should be the HTTP status code, if any.

The code and status can be used by us to provide relevant troubleshooting to the
user when we communicate the error.

Note that if a Zap raises too many error messages it will be automatically
turned off, so only use these if the scenario is truly an error that needs to
be fixed.

### Halting Execution

Any operation can be interrupted or "halted" (not success, not error, but
stopped for some specific reason) with a `HaltedError`. You might find yourself
using this error in cases where a required pre-condition is not met. For instance,
in a create to add an email address to a list where duplicates are not allowed,
you would want to throw a `HaltedError` if the Zap attempted to add a duplicate.
This would indicate failure, but it would be treated as a soft failure.

Unlike throwing `z.errors.Error`, a Zap will never by turned off when this error is thrown
(even if it is raised more often than not).

Example: `throw new z.errors.HaltedError('Your reason.');`

### Stale Authentication Credentials

For integrations that require manual refresh of authorization on a regular basis, Zapier
provides a mechanism to notify users of expired credentials. With the
`ExpiredAuthError`, the current operation is interrupted and a predefined email
is sent out asking the user to refresh the credentials. While the auth is
disconnected, Zap runs will not be executed, to prevent more calls with expired
credentials. (The runs will be
[Held](https://help.zapier.com/hc/en-us/articles/8496291148685-View-and-manage-your-Zap-history#held-0-3),
and the user will be able to replay them after reconnecting.)

Example: `throw new z.errors.ExpiredAuthError('You must manually reconnect this auth.');`

For integrations that use OAuth2 with `autoRefresh: true` or Session Auth, `core` injects
a built-in `afterResponse` middleware that throws an error when the response status
is 401. The error will signal Zapier to refresh the credentials and then retry the
failed operation. You can also throw this error manually if your server doesn't use the 401 status or you want to trigger an auth refresh even if the credentials aren't stale.

Example: `throw new z.errors.RefreshAuthError();`

### Handling Throttled Requests

Since v11.2.0, there are two types of errors that can cause Zapier to throttle an operation and retry at a later time.
This is useful if the API you're interfacing with reports it is receiving too many requests, often indicated by
receiving a response status code of 429.

If a response receives a status code of 429 and is not caught, Zapier will re-attempt the operation after a delay.
The delay can be customized by the server response containing a specific
[Retry-After](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header in your error response or with a specified time delay in seconds using a `ThrottledError`:

```js
const yourAfterResponse = (resp) => {
  if (resp.status === 429) {
    throw new z.errors.ThrottledError("message here", 60); // Zapier will retry in 60 seconds
  }
  return resp;
};
```

Instead of a user’s Zap erroring and halting, the request will be repeatedly retried at the specified time.

For throttled requests that occur during processing of a webhook trigger's perform, before results are produced; there is a max retry delay of 300 seconds and a default delay of 60 seconds if none is specified. For webhook processing only, if a request during the retry attempt is also throttled, it will not be re-attempted again.

## Testing

There are several ways to test your Zapier integration:

*   You can use the `zapier invoke` command to invoke a trigger, search, create, or an auth operation locally.

*   You can write unit tests for your Zapier integration that run locally, outside of the Zapier editor.

*   You can run these tests in a CI tool like [Travis](https://travis-ci.com/).

### Using `zapier invoke` Command

*Added in v15.17.0.*

The `zapier invoke <ACTION_TYPE> <ACTION_KEY>` CLI command emulates how the Zapier production environment would invoke your integration. Since it runs code locally, it's a fast way to debug and test interactively without needing to deploy the code to Zapier.

Its general execution flow involves calling `operation.inputFields` of an action, resolving the input data to the expected types, and then calling the `operation.perform` method.

`zapier invoke --help` should be self-explanatory, but here's a quick rundown:

```bash
# Initialize auth data in .env file
zapier invoke auth start

# Refresh auth data (for OAuth2 or Session auth)
zapier invoke auth refresh

# Test your auth data in .env
zapier invoke auth test
zapier invoke auth label

# Invoke a polling trigger
zapier invoke trigger new_recipe

# Invoke a create action
zapier invoke create add_recipe --inputData '{"name": "Pancakes"}'
zapier invoke create add_recipe --inputData @file.json
```

### Writing Unit Tests

From v10 of `zapier-platform-cli`, we recommend using the [Jest](https://jestjs.io/) testing framework. After running `zapier init` you should find an example test to start from in the `test` directory.

> Note: On v9, the recommendation was [Mocha](https://mochajs.org/). You can still use Mocha if you prefer.

```js
/* globals describe, expect, test */

const zapier = require("zapier-platform-core");

// createAppTester() makes it easier to test your integration. It takes your raw app
// definition, and returns a function that will test you integration.
const App = require("../index");
const appTester = zapier.createAppTester(App);

// Inject the vars from the .env file to process.env. Do this if you have a .env
// file.
zapier.tools.env.inject();

describe("triggers", () => {
  test("new recipe", async () => {
    const bundle = {
      inputData: {
        style: "mediterranean",
      },
    };

    const results = await appTester(
      App.triggers.recipe.operation.perform,
      bundle
    );
    expect(results.length).toBeGreaterThan(1);

    const firstRecipe = results[0];
    expect(firstRecipe.id).toBe(1);
    expect(firstRecipe.name).toBe("Baked Falafel");
  });
});
```

### Using the `z` Object in Tests

Introduced in `core@11.1.0`, `appTester` can now run arbitrary functions:

```js
/* globals describe, expect, test */

const zapier = require("zapier-platform-core");

const App = require("../index");
const appTester = zapier.createAppTester(App);

describe("triggers", () => {
  test("new recipe", async () => {
    const adHocResult = await appTester(
      // your in-line function takes the same [z, bundle] arguments as normal
      async (z, bundle) => {
        // requests are made using your integration's actual middleware
        // make sure to pass the normal `bundle` arg to `appTester` if your requests need auth
        const response = await z.request(
          "https://example.com/some/setup/method",
          {
            params: {
              numItems: bundle.inputData.someValue,
            },
          }
        );

        return {
          // you can use all the functions on the `z` object
          someHash: z.hash("md5", "mySecret"),
          data: response.data,
        };
      },
      {
        // you must provide auth data for authenticated requests
        // (just like running a normal trigger)
        authData: { token: "some-api-key" },
        // put arbitrary function params in `inputData`
        inputData: {
          someValue: 3,
        },
      }
    );

    expect(adHocResult.someHash).toEqual("a5beb6624e092adf7be31176c3079e64");
    expect(adHocResult.data).toEqual({ whatever: true });

    // ... rest of test
  });
});
```

### Mocking Requests

It's useful to test your code without actually hitting any external services. [Nock](https://github.com/node-nock/nock) is a Node.js utility that intercepts requests before they ever leave your computer. You can specify a response code, body, headers, and more. It works out of the box with `z.request` by setting up your `nock` before calling `appTester`.

```js
/* globals describe, expect, test */

const zapier = require("zapier-platform-core");

const App = require("../index");
const appTester = zapier.createAppTester(App);

const nock = require("nock");

describe("triggers", () => {
  test("new recipe", async () => {
    const bundle = {
      inputData: {
        style: "mediterranean",
      },
    };

    // mocks the next request that matches this url and querystring
    nock("https://example.com/api")
      .get("/recipes")
      .query(bundle.inputData)
      .reply(200, [
        { name: "name 1", directions: "directions 1", id: 1 },
        { name: "name 2", directions: "directions 2", id: 2 },
      ]);

    const results = await appTester(
      App.triggers.recipe.operation.perform,
      bundle
    );

    expect(results.length).toBeGreaterThan(1);

    const firstRecipe = results[0];
    expect(firstRecipe.id).toBe(1);
    expect(firstRecipe.name).toBe("name 1");
  });
});
```

Here's more info about nock and its usage in the [README](https://github.com/node-nock/nock/blob/master/README.md).

### Running Unit Tests

To run all your tests do:

```bash
zapier test
```

> You can also go direct with `npm test` or `node_modules/.bin/jest`.

### Testing & Environment Variables

The best way to store sensitive values (like API keys, OAuth secrets, or passwords) is in an `.env` (or `.environment`, see below note) file ([learn more](https://github.com/motdotla/dotenv#faq)). Then, you can include the following before your tests run:

```js
const zapier = require("zapier-platform-core");
zapier.tools.env.inject(); // inject() can take a filename; defaults to ".env"

// now process.env has all the values in your .env file
```

> `.env` is the new recommended name for the environment file since v5.1.0. The old name `.environment` is deprecated but will continue to work for backward compatibility.

> Remember: **NEVER** add your secrets file to version control!

Additionally, you can provide them dynamically at runtime:

```bash
CLIENT_ID=1234 CLIENT_SECRET=abcd zapier test
```

Or, `export` them explicitly and place them into the environment:

```bash
export CLIENT_ID=1234
export CLIENT_SECRET=abcd
zapier test
```

### Testing in Your CI

Whether you use Travis, Circle, Jenkins, or another service, we aim to make it painless to test in an automated environment.

Behind the scenes `zapier test` does a standard `npm test`, which could be [Jest](https://jestjs.io/) or [Mocha](https://mochajs.org/), based on your project setup.

This makes it straightforward to integrate into your testing interface. For example, if you want to test with [Travis CI](https://travis-ci.com/), the `.travis.yml` would look something like this:

```yaml
language: node_js
node_js:
  - "v18"
before_script: npm install -g zapier-platform-cli
script: CLIENT_ID=1234 CLIENT_SECRET=abcd zapier test
```

You can substitute `zapier test` with `npm test`, or a direct call to `node_modules/.bin/jest`. We recommend putting environment variables directly into the configuration screens Jenkins, Travis, or other services provide.

Alternatively to reading the deploy key from root (the default location), you may set the `ZAPIER_DEPLOY_KEY` environment variable to run privileged commands without the human input needed for `zapier login`. We suggest encrypting your deploy key in the manner your CI provides (such as [these instructions](https://docs.travis-ci.com/user/environment-variables/#Defining-encrypted-variables-in-.travis.yml), for Travis).

### Debugging Tests

Sometimes tests aren't enough, and you may want to step through your code and set breakpoints. The testing suite is a regular Node.js process, so debugging it doesn't take anything special. Because we recommend `jest` for testing, these instructions will outline steps for debugging w/ jest, but other test runners will work similarly. You can also refer to [Jest's own docs on the subject](https://jestjs.io/docs/en/troubleshooting#tests-are-failing-and-you-dont-know-why).

To start, add the following line to the `scripts` section of your `package.json`:

```
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand"
```

This will tell `node` to inspect the `jest` processes, which is exactly what we need.

Next, add a `debugger;` statement somewhere in your code, probably in a `perform` method:

```js
// triggers on a new pizza with a certain tag
const perform = async (z, bundle) => {
  const response = await z.request({
    url: "https://jsonplaceholder.typicode.com/posts",
    params: {
      tag: bundle.inputData.tagName,
    },
  });
  debugger;
  // this should return an array of objects
  return response.data;
};
```

This creates a *breakpoint* while `inspect`ing, or a starting point for our manual inspection.

Next, you'll need an inspection client. The most available one is probably the Google Chrome browser, but there are [lots of options](https://nodejs.org/en/docs/guides/debugging-getting-started/#inspector-clients). We'll use Chrome for this example. In your terminal (and in your integration's root directory), run `yarn test:debug` (or `npm run test:debug`). You should see the following:

```
% yarn test:debug
yarn run v1.22.10
$ node --inspect-brk node_modules/.bin/jest --runInBand
Debugger listening on ws://127.0.0.1:9229/5edaab3c-a1d3-45e4-b374-0536095c559b
For help, see: https://nodejs.org/en/docs/inspector
```

Now in Chrome, go to chrome://inspect. Make sure `Discover Network Targets` is checked and you should see a path to your `jest` file on your local machine:

![](https://cdn.zappy.app/e2836d2950e1f8a03e3621a22452c3cd.png)

Click `inspect`. A new window will open. Next, click the little blue arrow in the top right to actually run the code:

![](https://cdn.zappy.app/a64e7963a7090e9730d9c8e7b3595a6a.png)

After a few seconds, you'll see your code, the `debugger` statement, and info about the current environment on the right panel. You should see familiar data in the `Locals` section, such as the `response` variable, and the `z` object.

![](https://cdn.zappy.app/4bfdfe079a344ab7aced64ad7728bc6a.png)

Debugging combined with thorough unit tests will hopefully equip you in keeping your Zapier integration in smooth working order.

## Using `npm` Modules

Use `npm` [modules](https://docs.npmjs.com/) just like you would use them in any other node app, for example:

```bash
npm install --save jwt
```

And then `package.json` will be updated, and you can use them like anything else:

```js
const jwt = require("jwt");
```

During the `zapier build` or `zapier push` step - we'll copy all your code to a temporary folder and do a fresh re-install of modules.

> Note: If your package isn't being pushed correctly (IE: you get "Error: Cannot find module 'whatever'" in production), try adding the `--disable-dependency-detection` flag to `zapier push`.

> Note 2: You can also try adding a `includeInBuild` array property (with paths to include, which will be evaluated to RegExp, with a case insensitive flag) to your `.zapierapprc` file, to make it look like:

```json
{
  "id": 1,
  "key": "App1",
  "includeInBuild": ["test.txt", "testing.json"]
}
```

> Warning: Do not use compiled libraries unless you run your build on the AWS AMI `ami-4fffc834`, or follow the Docker instructions below.

## Building Native Packages with Docker

Unfortunately if you are developing on a macOS or Windows box you won't be able to build native libraries locally. If you try and push locally build native modules, you'll get runtime errors during usage. However, you can use Docker and Docker Compose to do this in a pinch. Make sure you have all the necessary Docker programs installed and follow along.

First, create your `Dockerfile`:

```Dockerfile
FROM amazonlinux:2017.03.1.20170812

RUN yum install zip findutils wget gcc44 gcc-c++ libgcc44 cmake -y

RUN wget https://nodejs.org/dist/v8.10.0/node-v8.10.0.tar.gz && \
    tar -zxvf node-v8.10.0.tar.gz && \
    cd node-v8.10.0 && \
    ./configure && \
    make && \
    make install && \
    cd .. && \
    rm -rf node-v8.10.0 node-v8.10.0.tar.gz

RUN npm i -g zapier-platform-cli

WORKDIR /app
```

And finally, create your `docker-compose.yml` file:

```yml
version: "3.4"

services:
  pusher:
    build: .
    volumes:
      - .:/app
      - node_modules:/app/node_modules:delegated
      - ~/.zapierrc:/root/.zapierrc
    command: 'bash -c "npm i && zapier push"'
    environment:
      ZAPIER_DEPLOY_KEY: ${ZAPIER_DEPLOY_KEY}

volumes:
  node_modules:
```

> Note: Watch out for your `package-lock.json` file, if it exists for local install it might incorrectly pin a native version.

Now you should be able to run `docker-compose run pusher` and see the build and push successfully complete!

## Using Transpilers

If you would like to use a transpiler like `babel`, you can add a script named `_zapier-build` to your `package.json`, which will be run during `zapier build`,
`zapier push`, and `zapier upload`. See the following example:

```json
{
  "scripts": {
    "zapier-dev": "babel src --out-dir lib --watch",
    "_zapier-build": "babel src --out-dir lib"
  }
}
```

Then, you can have your fancy ES7 code in `src/*` and a root `index.js` like this:

```js
module.exports = require("./lib");
```

And work with commands like this:

```bash
# watch and recompile
npm run zapier-dev

# tests should work fine
zapier test

# every build ensures a fresh build
zapier push
```

There are a lot of details left out - check out the full example integration [here](https://github.com/zapier/zapier-platform/tree/main/example-apps/babel).

> We recommend using `zapier init .` to create an integration - you’ll be presented with a list of currently available example templates to start with.

## Command Line Tab Completion

Introduced in v9.1.0, the `zapier autocomplete` command shows instructions for generating command line autocomplete.

Follow those instructions to enable completion for `zapier` commands and flags!

## The Zapier Platform Packages

The Zapier Platform consists of 3 npm packages that are released simultaneously.

*   [`zapier-platform-cli`](https://github.com/zapier/zapier-platform/tree/main/packages/cli) is the code that powers the `zapier` command. You use it most commonly with the `test`, `scaffold`, and `push` commands. It's installed with `npm install -g zapier-platform-cli` and does not correspond to a particular integration.

*   [`zapier-platform-core`](https://github.com/zapier/zapier-platform/tree/main/packages/core) is what allows your integration to interact with Zapier. It holds the `z` object and integration tester code. Your integration depends on a specific version of `zapier-platform-core` in the `package.json` file. It's installed via `npm install` along with the rest of your integrations's dependencies.

*   [`zapier-platform-schema`](https://github.com/zapier/zapier-platform/tree/main/packages/schema) enforces integration structure behind the scenes. It's a dependency of `core`, so it will be installed automatically.

To learn more about the structure of the code (especially if you're interested in contributing), check out the `ARCHITECTURE.md` file(s).

### Updating These Packages

The Zapier platform and its tools are under active development. While you don't need to install every release, we release new versions because they are better than the last. We do our best to adhere to [Semantic Versioning](https://semver.org/) wherein we won't break your code unless there's a `major` release. Otherwise, we're just fixing bugs (`patch`) and adding features (`minor`).

Broadly speaking, all releases will continue to work indefinitely. While you never *have* to upgrade your integration's `zapier-platform-core` dependency, we recommend keeping an eye on the [changelog](https://github.com/zapier/zapier-platform/blob/main/CHANGELOG.md) to see what new features and bug fixes are available.

For more info about which Node versions are supported, see [the faq](#how-do-i-manually-set-the-nodejs-version-to-run-my-integration-with).

The most recently released version of `cli` and `core` is **15.18.1**. You can see the versions you're working with by running `zapier -v`.

To update `cli`, run `npm install -g zapier-platform-cli`.

To update the version of `core` your integration depends on, set the `zapier-platform-core` dependency in your `package.json` to a version listed [here](https://www.npmjs.com/package/zapier-platform-core?activeTab=versions) and reinstall your dependencies (either `yarn` or `npm install`).

For maximum compatibility, keep the versions of `cli` and `core` in sync.


# Action

Every Zap starts with a single trigger that watches for new or updated data, starting the user's workflow. Action steps then make use of that data.

Zapier actions create or update a single item in your app through API calls that include multiple details from user customized [input fields](/platform/build/add-fields).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/57f28534d180f2a642ebe0be2e236c32.png" alt="Zapier Action Visual Builder" />
</Frame>

Zaps can have one or more actions.

There are two types of actions to select.

## 1. Create actions

Most Zapier integrations should at a minimum include create actions to let users add items to their app automatically. Common actions by app category [here](/platform/quickstart/must-have-triggers-and-actions) should be used for inspiration when building your app.

*Create* actions in Zaps can create new items in an app or update existing items. The output returned should be an object containing individual fields that will be parsed for mapping into subsequent Zap steps.

The [output returned](/platform/build/response-types) by a *create* should be an object containing individual fields about the item that was created such as IDs, details about the new item including a link if possible, and any other useful data about the record. Do not return just a `success` message.

Unsucessful actions should return `4xx` errors. If your API returns a `2xx` error, add custom code to your API call to replace it with a correct error.

Update actions should be separate from create actions.

Actions may create multiple items if needed, using the same data, though you will likely need to customize the API call code to create multiple items at once. Only do this for linked items, such as if an app stores customers and customer addresses separately. If the multiple items that need to be created are top-level, complex items in your app, they should be separate actions within Zapier. You can then link the two with a drop-down menu in the action to select the paired item, add a search action for users to find the specific item they need, and then let them match the items with the [*Use a Custom Value* option](https://help.zapier.com/hc/en-us/articles/8496241696141) in Zapier.

## 2. Search actions

*Search* actions find existing items in an app and can optionally be paired with *create* actions to [add a new item](/platform/build/search-or-create) if the search does not return a result.

Search actions let users do more with the data they've already added to your app; such as avoiding adding duplicate items or look up info about an item, for example weather, conversion, and contact lookup, to use in a subsequent step.

Most useful searches return one individual item that will likely be needed in another Zap step.

The [output returned](/platform/build/response-types) by a *search* should be a JSON-formatted array sorted with the best match first. Only the first item will be returned. For no match found, a `200` with an empty array must be returned. If your API returns a `404` error for searches without results, add custom code to your API call to replace it with an empty array.

## 3. Delete actions

Zapier recommends careful consideration of action steps that fully delete or remove data. To prevent data loss, action steps should only add or update data.

If you are considering adding a delete action to your app, consider alternative actions for items such as deactivating, unsubscribing, or canceling, instead of deleting items completely.

If you do add a delete action, make sure to include a `Copy` field to clarify to users that the action is irreversible once the API request is made.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add input fields to triggers and actions

When building in the Platform UI, you'll use the Input Designer to create the form users will input data into, to send to your app's API.

The Input Designer works similarly to other form builder tools, building a form that lives inside the Platform UI. Add fields to your form for each bit of data your app needs from users. Use the same name for items as used in your app's UI. Configure each field's settings, then reorder them to match the logical order users would add or view data in your app.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/524b9300c044809eb13eb732d7a50f9d.png" />
</Frame>

Actions require an input form, as they always need a way for users to send data to your app's API to find, update, or create a new object. An input form is optional for triggers.

In this guide we will cover:

*   Add an input field to a trigger or action
*   Set field options
*   Reorder input fields
*   Remove input fields

## Add an input field to a trigger or action

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section in the left sidebar, click on your **trigger** or **action**.
4.  Click the **Input Designer** tab.
5.  For triggers, click **Add User Input Field**. For actions, click **Add** and select **Input Field**.
6.  In the Form editor, add in details about your input field:

*   **Key**: A unique identifier for the field, without spaces, ideally with the same key as your API, such as `first_name`.
*   **Label**: A user friendly name for the field, such as `First Name`.
*   **Help Text**: (optional) A 20 character or longer description that appears under the field label, with [Markdown](https://zapier.com/blog/beginner-ultimate-guide-markdown/) formatting. Do not include redundant help text in input fields that repeats the name of the field. Use field help text to tell users what to do, for example “Choose the directory to watch for new files”. Always use active voice.
*   **Type**: From the dropdown menu, select type of data you want user's to enter. Learn more in [field definitions and types](/platform/build/field-definitions):
    *   String
    *   Text
    *   Integer
    *   Number
    *   Boolean
    *   DateTime
    *   Password
    *   Dictionary
*   **Default Text**: (optional) Value to include in the field if the user leaves it blank; only include if this value would work for API requests made to every user's account.
*   **Options** (optional):
    *   Select the **Required** checkbox to make it mandatory for users to add data into this input field.
    *   Select **Allows Multiples** checkbox if you want users to add multiple enteries into the same input field.
    *   Select **Alters Dynamic Fields** to have Zapier automatically recompute any dynamic fields any time this field is changed.
    *   Select **Dropdown** 7. Once you've finished adding details for your input field, click **Save**.

## Setting field options

### Required

An email app like MailChimp requires an email address to add a new email subscription, and a calendar app like Google Calendar requires an event title, date, and time to add new events.

Check the *Required* option on those fields if your trigger or action step requires any data to make the API request. Zapier will show a red `(required)` label beside the field name in the Zap editor, and will not let users complete the Zap step without adding data to that field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f8d0fd74582dc7c5997248516ad50d4f.png" />
</Frame>

Include a description on required fields to let users know exactly what type of data they should add to this field. Never mark fields as required if the integration could work without them.

### Allows multiples

If users could add multiple entries in the same field, check the *Allows Multiples* option.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/bf4c2a6ad55c12f6e3079e50f5d38e6c.png" />
</Frame>

That will add another entry row to allow the user to input another entry for that field. An [array containing a comma separated list of entries](/images/95938b473edfee13663161ee3c8e5ea4.png) is sent in the API request. Never ask users to type in a comma separated list, rather use this functionality.

### Alters dynamic fields

For each [dynamic field](/platform/build/dynamic-field) in your integration, Zapier runs code to decide whether to show a field or what to show in a field.

Check the *Alters Dynamic Fields* option, to have Zapier automatically recompute any dynamic fields in your Zapier integration anytime this field is changed. Do not check the *Alters Dynamic Fields* option unless the field is needed for your integrations' dynamic fields.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f89ed93d7233d0473b435156b31a1a8a.png" />
</Frame>

Only dropdowns support *Alters Dynamic Fields*.

### Dropdown

#### Static Dropdown

To offer users pre-set options to choose from in a field, set your field type as `String`, then check the *Dropdown* option.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/4f65bd28634d7709cac0ba84f2fa73d3.png" />
</Frame>

You'll see the default *Static* selected, or *Dynamic*. To add a Static menu of choices, type the options in a comma separated list, with quotes around each item and square brackets around the set, such as:

`["one", "two","three"]`

Enter the fields as used in your API, as Zapier will pass the exact value users select to your app. Zapier will capitalize each item in your dropdown menu in the Zap Editor, and will add spaces instead of any underscores, so an option like `first_name` would show in the menu as `First Name` to users.

**Static Dropdown with Key Value Pairs**

If your API requires different values for the field than the text you want to show to users inside the dropdown menu in Zapier, make a key value pair that includes the value to send to your API, the sample value to show users ([should be the same as the value](https://github.com/zapier/zapier-platform/blob/master/packages/schema/docs/build/schema.md#fieldchoicewithlabelschema)), and a user-friendly label.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/101618614c22bd4bd453c365db753db9.png" />
</Frame>

To do that, add each menu item inside an object (curly brackets); with the sample, value, and label comma separated. List the item first and the value second, both wrapped in quotes. Separate each menu item with commas, and wrap the whole set in an array (square brackets).

For example, if your API expects a value of `1` or `2`, but `1` actually means `pork` and `2` actually means `fish` to a user, you could use the following code to add the dropdown menu pictured:

```JSON
[
  {
    "sample": "1",
    "value": "1",
    "label": "Pork"
  },
  {
    "sample": "2",
    "value": "2",
    "label": "Fish"
  }
]
```

Alternatively, you can also use the syntax of `value:label`, which shows to users as follows:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b1892fb8ea92e7d21ec0b356f7e3330b.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/886425bb6592ab9e4141b15e3cc001ec.png" />
</Frame>

#### Dynamic Dropdown

If users need to select data from their account in your app — such as a project, folder, team member, or other user-specific detail with a corresponding ID — then you would use a dynamic dropdown. For dynamic dropdowns, Zapier first fetches data from your API and then displays it in a menu. Never make users type in an ID number, rather use this functionality or [add a search action](/platform/build/search) to find the ID number automatically.

The best way to make a dynamic dropdown is to use a dedicated trigger to fetch the values for the menu.

**1. Build a trigger to fetch dynamic dropdown data**

Create a new trigger, with a key, name, and noun. This trigger is usually configured to not be seen by users but you may wish to include a description for your internal team's awareness. In the *Visibility in Editor* field, select `Hidden` to hide this trigger from your app's trigger list in Zapier.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/36d19e02a566c61c3df00e7b6987daf1.png" />
</Frame>

You can also use an existing, visible trigger to power a dynamic dropdown if applicable.

Skip the *Input Designer* tab, as the dynamic dropdown cannot require any user input.

Select the *API Configuration* tab, and add the API call where Zapier can fetch the data from your API. For standard Zapier triggers, you would use an API call that fetches new or updated items. For dynamic dropdowns, instead use an API call that pulls in a list of the items that the user can select from.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e7dd5bf7e40caa03989e754ca518d14c.png" />
</Frame>

API calls will usually require additional configuration to pull in data in the order that makes most sense in your menu. You may want to sort options in the order they were added or updated, or want to have the API fetch more items at once than the default. Set these parameters from the *Show Options* menu.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/81be9d8ede3b19a8d4b5f58125575076.png" />
</Frame>

If your API supports pagination, you can allow users to load additional data in the menu by checking the *Support Paging* box. The first API call might pull in 20 items; if the user requests additional items, Zapier would call the API again and request the second page for the next 20 items. *per\_page* and *limit* are common parameters to indicate how many items to pull (controlling the page size). Confirm which parameter to use from your API's documentation.

Customize the pagination using [Code Mode](/platform/build/code-mode). Learn more about [how to use pagination in triggers](/platform/build/trigger#how-to-use-pagination).

Zapier shows the data in the dropdown menu in the order your API sends it to Zapier. If your API sends the data in alphabetical order, or numerical order, it will show as such in your drop-down menu. If your API call supports sorting, include the sorting parameter in your API call that would return data in the order you want it to show in your drop-down.

Define the fields from this hidden trigger that you need to use in the dynamic dropdown input field. To do so, test your trigger and identify the output fields needed, adding them to the *Output Fields* list at the end of your settings page. Include at least a field with the data that Zapier needs to send to your API in the action (for example `id`), along with a field that includes a user-friendly `name` for the data in that field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/3023ea97191fbcf90222dcf42a46035d.png" />
</Frame>

**2. Add an input field with dynamic fields**

To use the data from the hidden trigger you've configured, add a new input field to the trigger/action you're working on, and set the label, key, and other details as normal. Check the *Dropdown* box and select the *Dynamic* toggle. Choose the hidden trigger you've configured for this menu in the *Dropdown Source* option.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f2a8c7071bf4e96e5a44073999b4ccf5.png" />
</Frame>

Select the field with the data your API needs for this action in the *Field Name* menu, and the field with a human-friendly name for the data in the *Field Label* menu. The [preview will indicate the presence of the field](/images/4780bb34f2f3d24062d2eb556ed1e3a9.png), but you will need to use your trigger/action in a Zap to test the menu and pull in real data.

When this trigger/action is selected in a Zap, the user will see a dropdown as Zapier polls your API for the data from that hidden trigger, parse the entries and extract the fields you specified, showing them in a user-friendly dropdown menu. The human-friendly name will be in larger, darker text, and the value to be sent to the API in smaller, lighter text.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/afacb9fb44654908ec3d9e6803b728ee.png" />
</Frame>

It is important to provide the API value (example `id`) for users to know what type of data the field expects. Users can also choose to [enter a custom value](/images/f72a12759a3b3b391025f6500f6c7904.png)
and map data from other Zap steps into this field. Being able to see what type of value to map is extremely helpful.

**3. Add search to a dynamic field (optional)**

Dynamic Dropdown menus can optionally include an additional *Add a Search Step* button beside the dropdown menu. This lets users dynamically select the correct item from a dynamic field based on input from previous Zap steps.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/51ef24ac9ee90dd393e695ec75601532.png" />
</Frame>

You'll need to add a [Search Action](/platform/build/action#how-to-add-a-search-action) to find the items used in this dropdown menu. Then check the *Add a search to this field* option under the dynamic dropdown you've built, choose that action, and enter the ID of the field from that trigger that Zapier needs to pass with this API call (which should include the same data as the *Field Name* you selected before for the dynamic menu).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/3f6203d975653bcd004dccb5f15424ed.png" />
</Frame>

When users click or add a search step in their Zap, Zapier will add a new search step before this action step.

<video width="100%" controls>
  <source src="https://cdn.zappy.app/b6a7e1ac915b668e12c53b0d08f8330c.mp4" type="video/mp4" />
</video>

This allows the user to enter the details to search for the item they need, and Zapier will automatically map the correct output value from that search to this dynamic dropdown field [as a custom value](https://help.zapier.com/hc/en-us/articles/8496241696141-Add-custom-values-to-dropdown-menu-fields-in-Zaps#01H7FR09FBWT481ZJ77VVR0HBR).

## Reorder input fields

Reordering input fields in triggers or actions can help improve readability and usability.

List the most important, required fields first, with less important, optional fields near the bottom. Have related fields (such as first and last name) near each other. Ordering fields in Zapier similar to the order of fields in any input forms in your app will increase ease of use.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/08bf5a61391edb66c9a5f5bad37e2889.png" />
</Frame>

In your trigger or action settings:

1.  Click the **Input Designer** tab.
2.  In the *Sort* column, click the **up**

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/7b756b685e0e7f9759e8f7e1ed700aca.png" />
</Frame>

or **down** <Frame> <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f596f26128d01efe179c4340f0d17f84.png" /> </Frame> arrow to move the fields to the order you want in the Form Editor screen.
3\.  The preview on the right shows how the finished form looks to users inside Zapier.
4\.  A pop-up message will appear to confirm your changes have been saved.

## Remove input fields

Make sure to delete only unnecessary fields, as a previous version of the input form cannot be restored. You cannot remove input fields from public integrations; you must [create a new version of your integration](/platform/manage/versions) before changing input fields and [consider the impacts of the change](/platform/manage/planning-changes#changing-form-field-keys).

In your trigger or action settings:

1.  Click the **Input Designer** tab.
2.  Click the **gear icon** <Frame> <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/1397c67b31689803185dca4e04858e37.png" /> </Frame> beside the input field you want to delete.
3.  Click **Delete**.
4.  Click **Confirm** to remove the input field from your integration.
5.  A pop-up message will appear to confirm your changes have been saved.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add authentication with API Key

API Key authentication passes along a user-entered API Key with every API call. In your Zapier integration using API Key authentication, the API key—and optionally any other data your API needs—is included every time a Zap step runs.

<Frame caption="_Example API Key auth screen for users inside Zapier_">
  <img src="https://cdn.zapier.com/storage/photos/19467b7d1852276b766b373373fd069c.png" />
</Frame>

Use API Key authentication if your API primarily uses an API key to identify accounts, especially with apps for weather, maps, content verification, file conversion, and other data tools that require a key for access to the service but do not contain user-specific content.

Since API Key authentication allows you to create a custom input form, you can use it for any custom authentication type with username and password-based logins that don't fit other authentication scheme types.

## 1. Build input form

*   Open the *Authentication* tab in Zapier visual builder and select *API key*.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/283f4595d4e25caee8256b2727eebb6d.png" />
</Frame>

*   Add authentication input fields where users will enter their API key and any other required authentication details. Check your API documentation for what fields are required, including user or account names, domains, and more. Note any details users may need on how to find that data in your app. API keys especially are often hidden under settings menus and you'll need to include those details in your input form's help text.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8de37a192b7d50162a7a115281d4a388.png" />
</Frame>

*   Click the *Add Fields* button and fill in the details for your field. Add the most commonly needed fields first, in the order users expect, as you cannot reorder fields once added.

*   Add the required *Key*, the name your API uses to reference this field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d122ac64a68b926bacd5b4e0954ead2c.png" />
</Frame>

*   Fill in the optional fields, as appropriate, especially the *Label*:

– **Label**: A human-friendly name for this field that will be shown to users in the authentication form.

– **Required? (checkbox)**: Check if this field is required for successful authentication.

– **Type**: All input fields use the `string` text field by default; select `password` instead if you would like to obscure the data as users enter it.

– **Help Text**: Include a direct URL formatted with [Markdown](https://zapier.com/blog/beginner-ultimate-guide-markdown/) where users can obtain their API key from your app. If there is no direct link, include as clear of directions as possible to help users find the API key.

– **Input Format**: (optional) Help users figure out exactly what piece of data you need them to enter. For example, for [a subdomain](/platform/build/subdomain-validation), [https://.yourdomain.com/](https://.yourdomain.com/).

– **Default Value**: Include a value for this field to be used as a fallback. For optional fields, the default value is set on initial connection creation and used in the API call instead of missing or null values every time the Zap runs. For required fields, this value is used during connection creation, but not when the Zap runs (Zapier raises an error for missing/null values instead).

*   Input fields marked as password and all authentication fields with sensitive, private data such as API keys from API Key auth are automatically censored at runtime. These values are stored in the Auth bundle and used in API calls, but are replaced in Zapier's logs with a censored value like this `:censored:6:82a3be9927:`. Due to this, it is not possible to view the exact tokens or keys in Zapier's logs. To verify that the same token as was returned by the authentication, is being used in subsequent API calls; you can compare censored value characters, for example `:censored:6:82a3be9927:` would have the same value ending in 9927 when used in a subsequent call.

*   Computed fields are not applicable to API Key authentication and are only used with OAuth v2 and Session Auth.

*   Each input field is listed with its label, key, type, and required status in your authentication settings. Click the field to edit it, or click the gear icon and select *Delete* to remove a field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b27d364f2dd6ef2c1701c8b094a7ada0.png" />
</Frame>

*   Once you've added all the input fields to your authentication form, select *Continue*

## 2. Add a Test API Request

*   Add an API call to your API that requires no configuration, typically a `/user` or `/me` call. Add the URL for the API call, and set the call type, typically a `GET`. This will test the user-entered API key and any other credentials to ensure it enables a successful API call to your app.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/c4c58979ddcf7eb8a462ac5ff7a37348.png" />
</Frame>

*   The API key and any additional input fields are automatically included in the URL Params and the HTTP Headers. Click *Show Options* to remove the details where they are not needed. It is typically not recommended to pass any sensitive information such as the API key in the URL Params. Passing it through the headers or even the body is preferable.

*   To customize the test API request, select *Switch to Code Mode* and write custom JavaScript code to handle your test API call and the response parsing as needed. The first time you click the toggle, Zapier will convert your API call to code. If you switch back to Form Mode though, Zapier will not convert your code changes to the Form mode, nor will any subsequent changes in the form be added to your code.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b5f336f8d8642f04d99d584f04c4e334.png" />
</Frame>

## 3. Configure a Connection Label

Review [connection label documentation](/platform/build/connection-label) to optionally differentiate the app accounts users connect.

## 4. Test your authentication

Connect a valid user account to [test authentication](/platform/build/test-auth).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Authentication

Connecting an app to Zapier starts with authentication. Users select an app they wish to use in their Zap, authenticating their account with that app to allow Zapier to access their data.

Zapier will have access to the account until the authorization expires, is revoked, or credentials are changed. Zapier will automatically refresh OAuth v2 and session authentications when refresh token functionality is enabled in the integration.

Once users authenticate an app account to Zapier, they can use any of that app's triggers/actions in their Zaps without authenticating again. Users would authenticate another connection if they wish to use additional accounts from an app with Zapier, for example if they have a work and personal account in one app.

Zapier integration builders define how Zapier connects to your app to authenticate users, adding an API call where Zapier tests the account authentication.

All Zapier integrations that can access or add private data for users require authentication. The only apps that don't require authentication include data feeds (such as news or weather updates) or utilities (such as file format conversion tools or public search engines). If you're building an integration for any app that stores private data and requires an account to use, your integration will require authentication.

## Zapier Supported Authentication Schemes

Zapier supports the following five authentication schemes in the Platform UI, each with their own settings:

<CardGroup cols={3}>
  <Card title="API Key" icon="key" href="/build/apikeyauth" horizontal />

  <Card title="OAuth v2" icon="lock" href="/build/oauth" horizontal />

  <Card title="Session Auth" icon="user-clock" href="/build/sessionauth" horizontal />

  <Card title="Basic Auth" icon="user-lock" href="/build/basicauth" horizontal />

  <Card title="Digest Auth" icon="shield-halved" href="/build/digestauth" horizontal />
</CardGroup>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/37e7829169eb2e07278d512c174cd708.png" />
</Frame>

Where possible, OAuth v2 authentication is the preferred scheme to simplify a user's account connection and minimize set up time. During the authentication flow via Zapier, a familiar popup window appears from your app to select their account or log in, then verify the connection. This fits the flow most modern apps use for integration authentication.

API Key authentication is next best. Users must be able to obtain their API key from your app without human intervention. Your integration won't be [approved for publishing](/platform/quickstart/private-vs-public-integrations) if your service requires users to email or call your team in order to receive an API key or access to your API.

Basic authentication, while acceptable, is the least appropriate authentication type to use for a third party service like Zapier, as users must type their account credentials directly into Zapier's UI.

For more custom authentication schemes, switch to the [Platform CLI](/platform/manage/export-cli).

## How to Remove or Change Type of Authentication Scheme

You cannot change an integration's authentication scheme directly. First, remove the existing integration's authentication scheme, then add a new authentication scheme.

> **Note:** You can only do this for a (new) integration version that has not yet been promoted and has less than 5 active users, since this will break connected accounts for the version. If an integration's authentication scheme needs to be changed, clone a new major version and add the new authentication. [Learn more](/platform/manage/versions)

To remove a Zapier integration's authentication scheme in the Platform UI, open the *Authentication* page. Click the gear icon beside the existing authentication scheme, click *Delete*, then confirm to remove the authentication.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a616ced2f22bdf0873b0f910fc238424.png" />
</Frame>

Then add your app's new authentication scheme to the Zapier integration instead.

> **Note:** Again, to not break connected accounts, you can normally not migrate existing users' Zaps and connected accounts to a new version that has a different authentication scheme. For public integrations that meet certain conditions, we can provide support to migrate connected accounts between authentication schemes. [Learn more](/platform/manage/auth-scheme)

## Common Authentication Error Messages

When the test API call to verify users' credentials is unsuccessful, an error message shows in the Test section of your Zapier integration. Zapier shows a simplified error message in the *Response* tab by default.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f6e4616d8e457f2ccf8dd872b2a15aac.png" />
</Frame>

The original API response with the full error message is shown in the *HTTP* tab under *Response Content*.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/2598ca338518d55cb41cebc2116bd1af.png" />
</Frame>

The most common errors include:

### 404

The standard HTTP 404 `Not Found` error is commonly returned when:

*   Test API endpoint URL is incorrect
*   Test API call method is incorrect

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/4b268ddfa326ee23cb902d05acc6ac10.png" />
</Frame>

Verify both the API endpoint URL and the call method (typically `GET`). Ensure both are set to what your API expects, then click the *Save & Continue* button, and click the *Test Connected Account* button again.

### 401 or 403

The standard HTTP 401 `Unauthorized` or HTTP 403 `Forbidden` error is commonly returned when:

*   User account credentials are incorrect, expired, or revoked

Try authenticating your app user account with Zapier again. Click *Connect an Account*, add credentials for an active account on the app, then try the test again.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e8dd16ccd395d9c466d81ce669510296.png" />
</Frame>

### 400

The standard HTTP 400 `Bad Request` error is often returned when:

*   OAuth v2 Client ID and/or Secret are incorrect or expired
*   Some other part of your request is malformed, particularly a token exchange request

Check the full error message from the error or Zapier's testing logs to see if it lists why the call failed, then correct that part of your authentication flow. Verify each part of your authentication flow is entered correctly, including the request headers, URL parameters, and request body for each part of your authentication flow.

<Frame>
  <img src="https://cdn.zapier.com/storage/photos/91579ed613b77fb803ff52fb900b093b.png" />
</Frame>

### Error Parsing Response

The *Error Parsing Response* error is commonly returned when:

*   API returns non-standard and especially non-JSON output
*   Test API endpoint URL is incorrect

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/7dc661d47076c1114b2581289646de48.png" />
</Frame>

Verify the test API URL is entered correctly. If a normal webpage URL is entered in the test field, the site will return its raw HTML content to Zapier and that will likely result in this error. If you do change the URL, click *Save & Continue*, then test your connection again.

If your API call is correct and returning data in a format [Zapier does not expect](https://zapier.github.io/visual-builder/build/faq/#what-response-type-does-zapier-expect), you will need to switch to [Code mode](/platform/build/code-mode) and add custom parsing for your API response. Under the *Test* API call in the top of your app's Authentication settings, click *Switch to Code Mode*, then add custom JavaScript code to parse your API response.

### Authentication Failed Task Timed Out

The *Authentication Failed* error, often including *Task Timed Out*, is commonly returned when:

*   The API request does not return a response to Zapier within 30 seconds
*   The API request is formatted incorrectly and the server does not respond with an error code

Check your Zapier test logs to see if it shows which URL timed out, then verify you've entered the correct URL in all of your integration authentication settings. Finally, check the API provider to see if their site or API are temporarily down.

If the request seems to be successful but the task still times out, your API call may be taking too long to respond, or could be returning more data than Zapier can parse within the time limit. Use a testing API call in authentication that returns as little data as possible, such as a `/me` call that returns the connected user's account data. Or, if your API supports pagination and/or filtering, enable that and have the API return only the most recent result. Then test again to ensure the call works successfully.

<Frame>
  <img src="https://cdn.zapier.com/storage/photos/2bc7541d4859785d23a64dc5caceec22.png" />
</Frame>

### 500

The HTTP 500 error is the default, unformatted error that may be returned without specifying what went wrong or why. If you encounter this error, check the API endpoint URL that gave the error, and verify your API call is configured correctly with the expected URL params, HTTP headers, and Request Body.

<Frame>
  <img src="https://cdn.zapier.com/storage/photos/22eb6cbc2c965dc196a3646511deeb7d.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add authentication with Basic Authentication

APIs using Basic Authentication will authenticate users with a username and password. In your Zapier integration using Basic Auth, Zapier includes the username and password credentials in the API request bundle every time Zapier polls an API endpoint for new data or posts new data to an API endpoint.

<Frame caption="Example Basic Auth screen for users inside Zapier">
  <img src="https://cdn.zapier.com/storage/photos/8987788036a5a70072c9e75c4911ff6a.png" />
</Frame>

Use Basic Auth if your API requires a username and password or other basic fields, needs no special configuration, and specifically if your API leverages “[HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)”. For further customization of your login flow or to request additional data from users, [API Key authentication](https://platform.zapier.com/docs/apikey) may be a better fit.

## 1. Build an input form

*   Open the *Authentication* tab in Zapier visual builder and select *Basic Auth*.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a8f5698b7c1fd2556eb6b6dc0a983155.png" />
</Frame>

*   The pre-built input form for Basic Authentication includes a username and password field already.

*   Add additional fields if your API documentation requires it by selecting *Add Fields* and fill in the details for your field. Add the most commonly needed fields first, in the order users expect, as you cannot reorder fields once added.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/c17e6838cf27ed5704f561c75625864f.png" />
</Frame>

*   Add the required *Key*, the name your API uses to reference this field.

*   Fill in the optional fields, as appropriate, especially the *Label*:

– **Label**: A human-friendly name for this field that will be shown to users in the authentication form.

– **Required? (checkbox)**: Check if this field is required for successful authentication.

– **Type**: All input fields use the `string` text field by default; select `password` instead if you would like to obscure the data as users enter it.

– **Help Text**: Include details to assist users in authenticating with your app, especially if they may be unsure where to find the data needed within your app. Format text with [Markdown](https://zapier.com/blog/beginner-ultimate-guide-markdown/), and include a hyperlink if needed.

– **Input Format**: (optional) Help users figure out exactly what piece of data you need them to enter. For example, for [a subdomain](/platform/build/subdomain-validation), [https://.yourdomain.com/](https://.yourdomain.com/).

– **Default Value**: Include a value for this field to be used as a fallback. For optional fields, the default value is set on initial connection creation and used in the API call instead of missing or null values every time the Zap runs. For required fields, this value is used during connection creation, but not when the Zap runs (Zapier raises an error for missing/null values instead).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f4346b3456ea0080862db2eae7108050.png" />
</Frame>

*   Input fields marked as password and all authentication fields with sensitive, private data such as both username and password from Basic Auth are automatically censored at runtime. These values are stored in the Auth bundle and used in API calls, but are replaced in Zapier's logs with a censored value like this `:censored:6:82a3be9927:`. Due to this, it is not possible to view the exact tokens or keys in Zapier's logs. To verify that the same token as was returned by the authentication, is being used in subsequent API calls; you can compare censored value characters, for example `:censored:6:82a3be9927:` would have the same value ending in 9927 when used in a subsequent call.

*   Computed fields are not applicable to Basic Authentication and are only used with OAuth v2 and Session Auth.

*   Each input field is listed with its label, key, type, and required status in your authentication settings. Click the field to edit it, or click the gear icon and select *Delete* to remove a field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a207e9be179e401dadfa9d5422e4df5c.png" />
</Frame>

*   Once you've added all the input fields to your authentication form, select *Continue*

## 2. Add a Test API Request

*   Add an API call to your API that requires no configuration, typically a `/user` or `/me` call. Add the URL for the API call, and set the call type, typically a `GET`. This will test the user-entered credentials to ensure it enables a successful API call to your app.

*   The username and password input fields are automatically included in the URL Params and the HTTP Headers. Click *Show Options* to remove the details where they are not needed. It is typically not recommended to pass any sensitive information such as the password in the URL Params. Passing it through the headers or even the body is preferable.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a910a8114e18b545a93bf1e2e735e5a1.png" />
</Frame>

*   To customize the test API request, select *Switch to Code Mode* and write custom JavaScript code to handle your test API call and the response parsing as needed. The first time you click the toggle, Zapier will convert your API call to code. If you switch back to Form Mode though, Zapier will not convert your code changes to the Form Mode, nor will any subsequent changes in the form be added to your code.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6ec17f1acd3def0addad6dfc9167acec.png" />
</Frame>

## 3. Configure a Connection Label

Review [connection label documentation](/platform/build/connection-label) to optionally differentiate the app accounts users connect.

## 4. Test your authentication

Connect a valid user account to [test authentication](/platform/build/test-auth).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Reference user-entered details with data bundles

Zapier stores data from users' authentication and input forms for API calls in the `bundle` object. You can reference that data in your integration using `{{bundle.bundleName.field}}` text in API requests and connection labels, replacing `bundleName` with the bundle name and `field` with the input field key or API response field key you need.

You can also reference bundles in custom code if you switch to Code Mode for a request, using the same name but without the curly brackets, for example `return bundle.bundleName.field;` to have JavaScript code return a specific field.

If an API response includes a nested field, you can reference it as `field.nestedfield`, for example `{{bundle.inputData.data.name}}` to reference a `name` nested field inside the `data` field.

Zapier integrations include the following bundles:

*   authData
*   inputData
*   meta
*   rawRequest and cleanedRequest
*   outputData
*   targetURL
*   subscribeData
*   process.env

## authData

Reference with: `{{bundle.authData.field}}`

Includes data users enter into the authentication input form, including the `username` field for [basic authentication](/platform/build/basicauth) and any other field added to the authentication input form with other authentication methods. Additionally, with OAuth v2, session authentication, and digest authentication, `authData` includes all data returned by the Token Exchange Endpoint url, referenced with the following, replacing `field` with the field name from your API response: `{{bundle.authData.field}}`

For example, the Access Token value will often be accessed via `{{bundle.authData.access_token}}` or `{{bundle.authData.accessToken}}`.

However, the `authData` bundle does not support nested objects: all values returned from authentication functions need to be at the top level. This bundle also does not include values provided by intermediate steps in OAuth v2 or session authentication flows, like `redirect_uri`.

Commonly used authData fields include:

*   Username: `{{bundle.authData.username}}`
*   Password: `{{bundle.authData.password}}`
*   Access Token: `{{bundle.authData.access_token}}` or `{{bundle.authData.accessToken}}`

## inputData

Reference with: `{{bundle.inputData.field}}`

In authentication configuration, including connection labels, `inputData` contains the fields returned from the test API call, as well as some values provided by intermediate steps in more complex auth flows, such as the OAuth v2 `redirect_uri`. Values returned by the test API call are typically used to add a [connection label](./auth#label) to new integration connections.

In triggers and actions, `inputData` contains the data that users enter into the input forms for this particular run of the trigger or action. The `{{curlies}}` (mapped fields from previous Zap steps) are rendered with their raw data from previous steps.

If you want the input field data with the original `{{curlies}}` and not the data from previous steps, use `{{bundle.inputDataRaw.field}}` instead. This is less common.

Commonly used inputData fields include:

*   Zapier Redirect URI: `{{bundle.inputData.redirect_uri}}`
*   Authorization Code: `{{bundle.inputData.code}}`
*   ID of selected triggering event: `{{bundle.inputData.id}}`

## meta

Reference with: `{{bundle.meta.field}}`

`bundle.meta` contains information on what the user is doing. It has the following options for `field`:

| 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](#dedup)                                                                                                                                   |
| `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](#paging) to uniquely identify which page of results should be returned                                                                                                                                                                                                                                       |
| `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 |

## rawRequest and cleanedRequest

<Info>
  **Note:** `bundle.rawRequest` and `bundle.cleanedRequest` are only available in the `perform` for webhooks, `getAccessToken` for OAuth v2 and `performResume` in callback actions.
</Info>

Reference with: `{{bundle.rawRequest}}` or `{{bundle.cleanedRequest.field}}`

It includes the raw or cleaned information, respectively, from the HTTP request that triggered the `perform` method, or from the user's browser request that triggers the `getAccessToken` call from OAuth v2 authentication. You can reference individual fields with `cleanedRequest`.

Use `bundle.rawRequest` if you need access to header data. 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.

## outputData

Reference with: `{{bundle.outputData}}`

<Info>
  **Note:** `bundle.outputData` is only available in the `performResume` in a callback action - read about that advanced functionality [here](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#zgeneratecallbackurl).
</Info>

Using `bundle.outputData` will return a whatever data you originally returned in the `perform`, allowing you to mix that with `bundle.rawRequest` or `bundle.cleanedRequest`.

## targetUrl

Referenced with: `{{bundle.targetUrl}}`

In triggers using REST hooks, this returns the URL your app should send data to when the triggering event occurs, such as `https://hooks.zapier.com/1234/abcd`.

## subscribeData

Referenced with: `{{bundle.subscribeData}}`

In triggers using REST hooks, this includes the data returned by your API from the `performSubscribe` function. This should include all information needed to send a `DELETE` request to your server to stop sending webhook data to Zapier when the user turns off a Zap.

## process.env

Reference with: `{{process.env.field}}`

Commonly used process.env fields include:

*   Client Secret: `{{process.env.CLIENT_SECRET}}`
*   Client ID: `{{process.env.CLIENT_ID}}`

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add an instant trigger using REST Hooks in Zapier Platform CLI

REST Hooks are an alternative to polling. The main differences are allowing your customers' Zaps to trigger instantly; and avoiding polling triggers' numerous - and sometimes unnecessary - requests to your API's endpoints to check for new data.

REST Hook triggers are marked as *Instant* [in the Zap editor](https://cdn.zappy.app/f510859bf90c0e341bc94997a75f9626.png).

When building in the [Platform CLI](/platform/quickstart/ui-vs-cli), use the [example implementation](https://github.com/zapier/zapier-platform/tree/main/example-apps/rest-hooks) for guidance.

With a REST Hook trigger, Zapier subscribes to your server using a unique URL per activated Zap and your app sends a payload of data back to the unique URL to trigger that particular Zap whenever that trigger event occurs in your app.

REST Hooks differ from static webhooks. With static webhooks, your customer needs to manually copy and paste a specific URL per Zap to connect your app and their Zap together, whereas with REST Hooks this is all handled automatically for them between Zapier and your app.

Zapier does not support Identity Confirmation for webhook subscriptions.

## Prerequisites

*   Your app supports REST Hooks - webhook subscriptions that can be manipulated through a REST API.
*   A way to store the unique URLs we send you, such as a database.
*   A way to send payloads in JSON format to each stored URL, when a specific event happens that you want to trigger the associated Zap on.
*   One or more endpoint(s) to receive subscribe and unsubscribe payloads (these can be the same endpoint(s) or separate ones)
*   If your webhook subscriptions expire, make sure the subscribe endpoint returns an `expiration_date` property containing an ISO8601 date. The platform will automatically attempt to resubscribe after the expiration date.

When a Zap is turned on, the `subscribeHook` function is called with a payload of data that includes the unique URL to send data to in order to trigger the Zap. Store this URL, associating it with an id and use it whenever you want to trigger this Zap. Return that id in the response back to Zapier to be used later in the Unsubscribe.

When the Zap is turned off, the `unsubscribeHook` function is called to notify your app to delete the unique URL previously stored for this Zap.

## 1. Write the `subscribeHook` Function

The first function to implement is the `subscribeHook` function which provides the URL you will use to trigger the Zap. Include the following parameters:

*   `url` should be `bundle.targetUrl` which is an URL Zapier automatically generates for each activated Zap.
*   Any other data you want to send from any [inputFields](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#input-fields)

```js
const subscribeHook = (z, bundle) => {
  const data = {
    url: bundle.targetUrl,
    field: bundle.inputData.field,
    // etc
  };

  const options = {
    url: 'subscription endpoint url',
    method: 'POST',
    body: data
  };

  // make the request and parse the response - this does not include any error handling.
  return z.request(options)
    .then((response) => response.data);
}
```

This function is called in the `performSubscribe` method on the module for the Trigger:

```js
module.exports = {
  // Other data about the Trigger, e.g. key, name, etc. omitted
  performSubscribe: subscribeHook
};
```

This function will run every time a Zap is turned on.

## 2. Write the `unsubscribeHook` Function

The next function to implement is the `unsubscribeHook` function which will be called when a Zap is turned off. This notifies your servers to delete the URL you've been storing and stop sending payloads to it. If your API continues posting notification payloads to the Zap after it has unsubscribed, you can expect to see a 410 response from Zapier.

`bundle.subscribeData.id` is required — this is the ID of the hook in question. You could assign it to a `hookID` variable or similar.

Include the following parameters:

```js
const unsubscribeHook = (z, bundle) => {
  // bundle.subscribeData contains the parsed response JSON from the subscribe request.
  const hookId = bundle.subscribeData.id

  const options = {
    url: `unsubscription endpoint url/${hookId}`,
    method: 'DELETE'
  }

  return z.request(options)
    .then((response) => response.data);
};
```

This function is called in the `performUnsubscribe` method on the module for the Trigger:

```js
module.exports = {
  // Other data about the Trigger, e.g. key, name, etc. omitted
  performUnsubscribe: unsubscribeHook
};
```

A REST Hook trigger missing a Subscribe or Unsubscribe function, is presented to users as a [Static Webhook](https://cdn.zappy.app/3b35908a6a0c232087b5da807cf9d6fb.png). Static hooks are [not supported in public integrations](/platform/publish/integration-checks-reference#d017---static-hook-is-discouraged), but they could be used if the integration intends to remain private.

## 3. Write the `perform` Function

The next function to implement is the `perform` function which is called each time your app delivers a notification payload to Zapier. This is what actually triggers the Zap. Typically, you would simply return the cleaned request in an array. The data returned by the request [must be an array](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#return-types).

```js
const parseHookPayload = (z, bundle) => {
  // bundle.cleanedRequest will include the parsed JSON object (if it's not a
  // test poll) and also a .querystring property with the URL's query string.
  const payload = {
    id: bundle.cleanedRequest.id,
    name: bundle.cleanedRequest.name,
    directions: bundle.cleanedRequest.directions,
    style: bundle.cleanedRequest.style,
    authorId: bundle.cleanedRequest.authorId,
    createdAt: bundle.cleanedRequest.createdAt
  };

  return [payload];
};
```

followed by:

```js
module.exports = {
  // Other data about the Trigger, e.g. key, name, etc. omitted
  perform: parseHookPayload
};
```

## 4. Write the `performList` Function

The final function to implement is the `performList` function, used when testing the Trigger to collect sample data in the Zap editor. Though optional, not defining a performList is a sub-optimal experience for users and is [required for public apps](/platform/quickstart/private-vs-public-integrations).

Most commonly this is a GET request to an endpoint of your API which will provide a response from the user's account with the exact same schema as the data delivered via webhook when a trigger event happens.

Response data returned must be an array, even if the array contains only one object. It must have the exact same schema as the data delivered via webhook when a trigger event happens otherwise fields mapped from this sample in subsequent steps of a Zap will break when it runs live.

```js
const getFallbackSample = (z, bundle) => {
  // For the test poll, you should get some real data, to aid the setup process.
  const options = {
    url: 'sample endpoint here',
    method: 'GET',
    params: {
      style: bundle.inputData.style
    }
  };

  return z.request(options)
    .then((response) => response.data);
};
```

followed by:

```js
module.exports = {
  // Other data about the Trigger, e.g. key, name, etc. omitted
  performList: getFallbackSample
};
```

## 5. Test your API Request

To test the REST Hook trigger, [build a Zap in the editor](/platform/build/test-triggers-actions).

## 6. Define sample and outputFields

Define sample data and output fields following [the guide](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#output-fields). You can see an example of this [here](https://github.com/zapier/zapier-platform-example-app-rest-hooks/blob/master/triggers/recipe.js#L102-L122).

## BasicHookOperationSchema

### `subscribeHook`

Function that sends a payload of data to an endpoint you control. You need to grab the `targetUrl` parameter and store it securely as this URL is used to send data from your app to Zapier when the triggering event occurs, and thus trigger the Zap. You can see an example of this [here](https://github.com/zapier/zapier-platform-example-app-rest-hooks/blob/master/triggers/recipe.js#L1-L22).

### `unsubscribeHook`

Function that calls an endpoint you control with a payload of data, including the `targetUrl` parameter. You need to deactivate or delete the URL from your storage completely as the Zap associated will no longer trigger when requests are made to it. If your API continues posting notification payloads to the Zap after it has unsubscribed, you can expect to see a 410 response from Zapier. You can see an example of this [here](https://github.com/zapier/zapier-platform-example-app-rest-hooks/blob/master/triggers/recipe.js#L24-L39).

### `perform`

This should be a function that processes the inbound webhook request. No `HTTP` request has to made here. As seen in the example app [here](https://github.com/zapier/zapier-platform-example-app-rest-hooks/blob/master/triggers/recipe.js#L41-L54), this can simply take the data from `bundle.cleanedRequest` and build a new object that is returned within an array. It's important to return this object within an array as specified [here](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#return-types).

### `performList`

Testing a REST Hook trigger in the Zap editor will only call `performList`, not `perform`. `performList` ideally should make a `HTTP` request to an endpoint that can return sample data to be mapped into subsequent steps of the user's Zap. You can see an example of this [here](https://github.com/zapier/zapier-platform-example-app-rest-hooks/blob/master/triggers/recipe.js#L56-L67). Required for public apps and highly recommended for private apps to avoid user confusion when setting up a Zap.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Use Code Mode to refine your API call



<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/1fa2f12c1c41a49f3d7aeff4d2706f7c.png" />
</Frame>

In the Platform UI, when building your authentication, triggers and actions, you will create each component of your integration using Form Mode. However if you need to add further customization to your API calls, you can use Code Mode.

You can use Code Mode to:

*   Transform your API response into JSON format
*   Add user authentication details and input form data to the API call
*   Use ‘z' object library to customize your API call
*   Improve error response handling

## Getting started with Code Mode

To use Code Mode, Zapier recommends for users to have an understanding of Javascript and making [HTTP requests](/platform/reference/cli-docs#making-http-requests).

Code Mode is available to use in the API request settings:

*   For authentications, Code Mode is in the *Configure a Test Request & Connection Label* setting. Note, for OAuth v2, Code Mode is in the *OAuth v2 Endpoint Configuration* setting.
*   For triggers and actions, Code Mode is in the *API Configuration* setting.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/554f0c395459b37fd34312566feac891.png" />
</Frame>

Changes made in Code Mode are not saved automatically. Once you have added the code you want, click **Save & Continue**.

## Capabilities of Code Mode

### Use `z` object to customize your API call

You can write JavaScript code, using Zapier's default code as a base or writing custom code. Use the `z` object for Zapier specific features, including `z.console` to write to the console log, `z.JSON` to parse JSON and `z.errors` to handle errors. Learn more in [Zapier's CLI Z Object docs](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#z-object).

### Add user authentication details and input form data

You can use Zapier bundles to access authentication data, data from user input forms and request data. Learn more in [Zapier data bundles](https://platform.zapier.com/docs/advanced#bundle).

### Importing libraries

You can import from Node's standard library with `z.require`, for example, `z.require('querystring')` or `z.require('crypto')`. Zapier strongly recommend you keep it simple when coding in Platform UI. Building and testing complex code is better suited with the Platform CLI.

NPM modules are not supported within the Platform UI. You'd need to export your project to [Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md) and use `npm` to add additional libraries.

### Performance considerations

Each trigger and action has a 30 second time limit. To ensure that Zaps run smoothly, keep your custom code as lightweight and efficient as possible. If your code takes longer than 30 seconds to run, it will time out and user's Zaps will error. We also can't guarantee that all imported libraries will be supported within our runtime environment.

Here are some specific things you can do to improve the performance of your custom code:

*   Use efficient algorithms and data structures.
*   Avoid unneccessary loops and recursions.
*   Optimize your code for the specific task it is performing.
*   Avoid using imported libraries that are not essential to triggers or actions.

## Switching between Form Mode to Code Mode

When you switch to Code Mode, Zapier uses your code when making API calls. Any previous settings from Form Mode will not be discarded. This is because Form Mode and Code Mode cannot be used together.

If you switch back to Form Mode, click **Switch to Form Mode**. Your previous Form Mode settings will be restored. Zapier will save the code you entered in Code Mode so you can use it again if you switch back to Code Mode.

## Code Mode resources

Here are some resources that will be helpful when using the code mode:

<CardGroup cols={3}>
  <Card title="HTTP Request Options" icon="gear" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#http-request-options" horizontal />

  <Card title="HTTP Response Object" icon="file-code" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#http-response-object" horizontal />

  <Card title="HTTP Requests" icon="server" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#manual-http-requests" horizontal />

  <Card title="Dynamic Dropdowns" icon="list" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#dynamic-dropdowns" horizontal />

  <Card title="Return Types" icon="code" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#return-types" horizontal />

  <Card title="Bundle Object" icon="box" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#bundle-object" horizontal />

  <Card title="Environment Variables" icon="leaf" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#accessing-environment-variables" horizontal />

  <Card title="Placeholders vs Template Literals" icon="code" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#when-to-use-placeholders-or-curlies" horizontal />

  <Card title="Error Handling" icon="triangle-exclamation" href="https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#error-handling" horizontal />

  <Card title="Error Response Handling" icon="bug" href="https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#error-response-handling" horizontal />

  <Card title="Schema Docs" icon="book" horizontal>
    [14.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-schema@14.1.2/packages/schema/docs/build/schema.md)

    [13.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-schema@13.0.0/packages/schema/docs/build/schema.md)
    [12.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-schema%4012.2.1/packages/schema/docs/build/schema.md)
  </Card>
</CardGroup>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Use computed fields in OAuth or Session Authentication

When adding a field in your integration's authentication configuration, Zapier offers two field type options; field and computed field. The field option allows users to enter account information needed for authentication.

The computed field option stores values returned by your integration's authentication response which you can then reference in subsequent API calls. You must maintain the same field key as your API uses for this field.

The computed field option only supports session and OAuth v2 authentication.

## Prerequisites

*   Understanding of [Platform UI authentication configuration](/platform/build/auth)
*   Familiarity with your API's authentication

## Steps

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section in the left sidebar, click your **authentication**.
4.  Click **Save**.
5.  Click **Add Fields**.
6.  Under *Field Type*, select **Computed Fields**.
7.  Select the **Is this field required? Check if yes** checkbox. Zapier use your computed fields in the response data. This means that any omission of the fields marked as computed fields in the authentication response would cause Zapier to display an error.
8.  Complete the rest of the relevant fields. Learn more in [Input Designer](/platform/build/field-definitions).
9.  Click **Save**.
10. You can then reference the field in any subsequent API call from the input bundle. In [Code Mode](/platform/build/code-mode), you replace `field` with your field key in this text `{{bundle.authData.field}}`

<Frame>
  <img src="https://cdn.zappy.app/9ac5ede3a88928989a3f91590a611282.png" alt="Code example shows how a computed field appears in the Zapier Platform" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Compute a field from the data of the Test API call

Zapier doesn't store the responses from the test API call for OAuth v2 and session authentication. Using computed fields, you can use data from a test API call later in your Zapier integration.

## Prerequisites

*   Knowledge of working with APIs
*   Understanding of computed field concept
*   Familiarity with JavaScript

## Steps

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section in the left sidebar, click your **authentication**.
    *   For Session authentication:
        *   Click **Configure a Token Exchange Request**
        *   Click **Switch to Code Mode**.
    *   For OAuth v2 authentication:
        *   Click **Add OAuth v2 Endpoint Configuration**.
        *   Click **Switch to Code Mode**.
4.  Add **custom JavaScript code** to instruct Zapier to call both the URLs necessary for the authorization process and your test API call.
5.  Click **Save & Continue**.
6.  The *Test Request* section remains unchanged.
7.  Return [computed fields](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#computed-fields) for data returned from either API call so you can reference those fields in subsequent steps as `bundle.authData.field` where `field` is the field's name from your test API call response.

Upon completion, you will be able to use data from a test API call as a computed field in later stages of your Zapier integration.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add a connection label

Zapier users can authenticate multiple accounts for any app. By default, every new app account added to Zapier is identified by the app's name, followed by a number (#2, #3, …) for accounts connected after the first.

A connection label in the integration authentication options adds optional text as the name of each account connection to help users distinguish between their authenticated accounts.

<Frame>
  <img src="https://cdn.zappy.app/b2612dc2bf60454eea4ae37335638bf5.png" />
</Frame>

## Prerequisites

*   A configured authentication scheme in the Platform UI

## Steps

1.  Enter your desired value into the *Connection Label* field in the authentication configuration, as the name of the field, surrounded by double curly braces

2.  Zapier always includes the app's name in each account label, so it is redundant to add any hard coded app name into your connection label.

3.  Instead, add either:

    *   Data that users enter in the authentication form

    *   Output fields from your app's authentication test API call

    For example, if your auth configuration includes a `username` field, your connection label could look like:

    `{{username}}`

    Alternatively, you can use an output field from your test API call in the Connection Label. If you aren't sure what fields will be returned, you'll want to test your authentication first, and return to the connection label.

    After connecting an account, check the *Response* tab to see the fields returned from the test request.

4.  Field keys from the authentication form and the test request are both pulled up to the top level.

    You can also refer to the fields with `{{bundle.authData.field}}` for input fields from the authentication form, replacing `field` with your input field key. Use `{{bundle.inputData.field}}` for fields returned from the test request, replacing `field` with the field key from the API response.

    If you need to use a nested field from your API call results, reference it as `field.nestedfield`. For example, if your test API response includes a `name` field nested under `details`, you would reference it as:

    `{{bundle.inputData.details.name}}`

    Any field your authentication test API call returns can be used in the connection label. The best fields to use are usernames, domain names (if your app is self-hosted), account numbers, email addresses, or other identifiable but not fully private data. Never use passwords, API keys, or other critical, private info in connection labels.

5.  Customize your connection label further with JavaScript code as needed.

    Custom code for connection labels is best used for:

    *   Using code to manipulate data from an auth or test call before using it in a connection label, such as to format a number or date

    *   Logging additional data

    *   Making a new API call to access data to use in the connection label

    Select *Edit Code* to switch to *\[Code Mode]\(link to [https://coda.io/d/Overhaul-platform-docs\_dRIxVbcnE\_L/Use-Code-Mode-to-refine-your-API-call\_suwCV#\_luwtY](https://coda.io/d/Overhaul-platform-docs_dRIxVbcnE_L/Use-Code-Mode-to-refine-your-API-call_suwCV#_luwtY))*. When\_Code Mode\_ is enabled, Zapier will only use the code to create your connection label, and will ignore any text that was in the *Connection Label* field in the regular string mode.

    To switch back to the regular string field, click the *Delete Code* button to delete your custom connection label code and revert to using the text entered in the *Connection Label* field.

    When writing code for the connection label, fields are not pulled up to the top level of the `bundle` object - use `bundle.authData` for auth input fields, or `bundle.inputData` for fields returned from the test API request. For example, your code might look like this:

```bash
return bundle.authData.username;
```

```bash
This is equivalent to using `{{username}}` in the regular string mode.

Add custom data to Zapier's console log with the `z.console.log` function to assist with testing and monitoring your app authentication.
```

<Frame>
  <img src="https://cdn.zappy.app/c099e1aaa7a6bbe1674cc830aed68573.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add a create action



## 1. Add the action settings

*   Open the *Actions* tab in Zapier's Platform UI from the sidebar on the left, and select **Add Action**, selecting your action type. New actions are *create* type by default, and they add new data or update existing data to your app.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/c845778e65b58839d1fac151d805bb55.png" alt="Zapier visual builder action settings" />
</Frame>

<Info>
  **Note**: You cannot change an action type once you click *Save and Continue* on a new action. If you need to change the action type, delete the action and recreate it.
</Info>

*   On the Settings page, specify the following:

– **Key**: A unique identifier for this action, used to reference the action inside Zapier. Does not need to be the same identifier as used in your API. Not shown to users.

– **Name**: A human friendly plain text name for this action, typically with a verb such as *Add* or *Create* followed by the name of the item this action will create in your app. The title-case name is shown inside the Zap editor and on Zapier's app directory marketing pages.

– **Noun**: A single noun that describes what this action creates, used by Zapier to auto-generate text in Zaps about your action.

– **Description**: A plain text sentence that describes what the action does and when it should be used. Shown inside the Zap editor and on Zapier's app directory marketing pages. Starts with the phrase “Creates a new”.

– **Visibility Options**: An option to select when this action will be shown. *Shown* is chosen by default. Choose `Hidden` if this action should not be shown to users. `Hidden` is usually selected if you build a *create* action solely to [pair with a search action](/platform/build/search-or-create) but do not want it used on its own.

*   Click on the *Save and Continue* button.

## 2. Complete the Input Designer

On the *Input Designer* page, add user [input fields](/platform/build/add-fields) for this action. All action steps *must* include an input form for Zapier to gather the data needed to create or find items in your app. Add at least one input field to your action.

Before building your action's input form, list each piece of data your app needs to create a new item. For example, if building an action to send an email, fields for the email address, subject, and email body would be needed. Your action will likely have several required fields, along with others that are optional, such as for tags or other details.

Add action fields for each piece of data your app needs to create or find this item in your app. Add the fields in the order they're listed in your app, with *required* fields first, for the best user experience.

## 3. Set up the API Configuration

The final page of building your action tells Zapier how to send the data to your API.

A `POST` call populates for *create* actions by default, sending a single item to the provided API endpoint. Zapier then expects a response with an object containing a single item, to be evaluated by [isPlainObject](https://lodash.com/docs#isPlainObject) and parsed into individual fields for use in subsequent Zap steps.

Select the correct API call if your app expects something other than the default, then paste the URL for your API call in the box under *API Endpoint*. Zapier will include each of your input form fields in the *Request Body* automatically.

If your API call expects input data in the core URL, reference any input field's key with the following text, replacing `key` with your field key:

`{{bundle.inputData.key}}`

The defaults on all other settings work for most basic API calls. If you need to configure more options, click *Show Options* to add URL Params, HTTP Headers, set your action to omit empty parameters, or customize the request body. Alternately, switch to [Code Mode](/platform/build/code-mode) to write custom JavaScript code for your action.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/1b33f697838f7a4e160d4aa8ef3c6d93.png" alt="Zapier action API configuration" />
</Frame>

## 4. Test your API request

Configure test data to [test the *create* action](/platform/build/test-triggers-actions). Note that testing a POST or PUT request will create or update the item in your app.

## 5. Define your output

Define sample data and output fields following [the guide](/platform/build/sample-data).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# How deduplication works in Zapier

Zapier automatically deduplicates incoming trigger data for your integration, so that Zaps do not run multiple times on the same data. Consider the following requirements for your “New Item” and “Updated Item” triggers to work as users expect.

*   Provide a unique primary key.
    *   By default, the field with the key `id` is used as the primary key.
    *   Alternatively, if you're using the CLI, you can choose other fields as the primary key by enabling `primary` in `outputFields`. See [more information](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#how-does-deduplication-work) in the CLI docs.
*   Sort reverse-chronologically by time created.

The API endpoint must list new or updated items in an array sorted in reverse chronological order.

[Polling](/platform/build/trigger) usually returns many results, most of which Zapier has seen before. Since a Zap should not trigger multiple times when an item in your app exists in multiple distinct polls, the data must be deduplicated.

For example, say your endpoint for new items returns a list of tasks:

```json
[
  {
    "id": 7,
    "created": "Mon, 25 Jun 2023 16:41:54 -0400",
    "list_id": 1,
    "description": "integrate our api with zapier",
    "complete": false
  },
  {
    "id": 6,
    "created": "Mon, 25 Jun 2023 16:41:45 -0400",
    "list_id": 1,
    "description": "get published in zapier library",
    "complete": false
  }
]
```

The following assumes you're using the default primary key, the `id` field. When a Zap is first turned on, Zapier makes an initial call to your API to retrieve existing data, and caches and stores each `id` field in our database. When the Zap is turned off, that list is cleared.

Active Zaps then [poll at an interval](https://help.zapier.com/hc/en-us/articles/8496181725453#01H8C8M008GXFT36C42W1157P0) (based on a [customer's plan](https://zapier.com/pricing)) and compare the `id`s to all those seen before, trigger on new items, and update the list of seen `id`s.

Now let's say the user created a new task:

```json
[
  {
    "id": 8,
    "created": "Mon, 25 Jun 2023 16:42:09 -0400",
    "list_id": 1,
    "description": "re-do our api to support webhooks",
    "complete": false
  },
  {
    "id": 7,
    "created": "Mon, 25 Jun 2023 16:41:54 -0400",
    "list_id": 1,
    "description": "integrate our api with zapier",
    "complete": false
  },
  {
    "id": 6,
    "created": "Mon, 25 Jun 2023 16:41:45 -0400",
    "list_id": 1,
    "description": "get published in zapier library",
    "complete": false
  }
]
```

On the next poll after the new task is created, your API returns all tasks, but only the first task with `id` equal to `8` will be seen as a new item. That particular JSON object will then trigger the user's Zap and complete any subsequent action steps the user has defined. The essence of deduplication is that the other `id`s in the poll, `6` and `7` will be ignored, since their `id`s have been seen in previous polls.

In order for deduplication to work, the `id` field should always be supplied and unique amongst all items in the result.

Your API must return results in reverse-chronological order to make sure new/updated items can be found on the first page of results, as Zapier polling triggers don't automatically fetch additional pages. If your API lists items in a different order by default, but allows for sorting, include an order or sorting field in your polling request to ensure newest records are returned on the first page of results. [Github is a great example](https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues) of an API that supports sorting on multiple fields in the `asc` or `desc` direction.

## Re-ordering returning items

If your API cannot order its results in reverse-chronological order, you can use [Code Mode](/platform/build/code-mode) to make additional requests, if necessary.

One possible scenario could be:

1.  Fetch the first page, containing the oldest items, but also the total number of pages.
2.  Fetch the last page and reverse the order of the items before returning results in an array.

When adding additional requests to your custom code, all requests and processing code for a trigger must [finish within 30 seconds](/platform/build/operating-constraints#timeouts-triggers). It is not recommended to attempt to fetch all pages of results.

If the items your API returns do not have an `id` field or you're adding an Updated Item trigger, you will use [Code Mode](/platform/build/code-mode) to modify the API response.

## Custom primary keys

In older, [legacy Zapier Web Builder apps](/platform/manage/versions-legacy), Zapier guessed what fields made a unique key if `id` wasn't present. It is now required that you define one or more fields as the primary key.

By default, Zapier uses the field with the key `id` as the primary key if no `outputFields` has `primary: true`. The `id` field would be required in this case. Otherwise, your trigger would run into an error at runtime. If your API's items have a differently-named unique field, adapt this code snippet to ensure this test passes:

```javascript
// ...
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;
});
```

Alternatively, if you're using the CLI, you can use non-id fields as the primary key. See [“How does deduplication work?”](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#how-does-deduplication-work) in the CLI docs for example code.

## Updated Item triggers

When triggering on updated items, you'll want to define the `id` field to be used for deduplication. Assuming your task API has an endpoint that can return tasks sorted by `updatedAt` in descending direction, here's example code to incorporate the `updatedAt` value with the `id`, so that Zapier recognizes a new update as a new item. Assuming that you have configured `options` with the appropriate API request URL and parameters:

```javascript
return z.request(options)
  .then((response) => {
    response.throwForStatus();
    const results = response.json;
    results.forEach(function(result) {
      result.originalId = result.id;
      result.id = result.id + '-' + result.updatedAt;
    });
    return results;
  });
```

Notice how the code preserves the original `id` value before setting `id` to a new combined value that is unique for every update of a task. This is useful to ensure that the original ID can still be used for other purposes, such as performing a search or associating records together.

Alternatively, if you're using the CLI, you can set `primary: true` for the `id` and `updatedAt` fields. See [“How does deduplication work?”](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#how-does-deduplication-work) in the CLI docs for example code.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add authentication with Digest Authentication

Digest Auth prompts users to enter their username and password, optionally along with any additional data your API requires for authentication. Zapier makes an unauthenticated API call to get the nonce from your server, and uses it to encrypt and pass the authentication data to your server with each API call.

<Frame>
  <img src="https://cdn.zapier.com/storage/photos/3c842632d017aa50ba6470201d02f416.png" />
</Frame>

Use Digest Auth if your API uses the [RFC 7616](https://tools.ietf.org/html/rfc7616) authentication standard, where users enter their username and password to be passed encrypted to your API with the nonce key your app sends to Zapier on the first API call.

## 1. Build an input form

*   Open the *Authentication* tab in Zapier visual builder and select *Digest Auth*.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/fccd6ab8ba9c837158907d39eef1f288.png" />
</Frame>

*   The pre-built input form includes username and password fields already.

*   Add additional fields if your API documentation requires it by selecting *Add Fields* and fill in the details for your field. Add the most commonly needed fields first, in the order users expect, as you cannot reorder fields once added.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/680696e6c2d79b837600c5d1e32c9d40.png" />
</Frame>

*   Add the required *Key*, the name your API uses to reference this field.

*   Fill in the optional fields, as appropriate, especially the *Label*:

    **Label**: A human-friendly name for this field. Enter what this value is called inside your app's UI.

    **Is this field required**: Check this box for the API key field, and any other fields your API requires to authenticate.

    **Type**: All input fields use the `string` text field by default; select `password` instead if you would like to obscure the data as users enter it.

    **Help Text**: Include details to assist users in authenticating with your app, especially if they may be unsure where to find the data needed within your app. Format text with [Markdown](https://zapier.com/blog/beginner-ultimate-guide-markdown/), and include a hyperlink if needed.

    **Default Value**: Include a value for this field to be used as a fallback. For optional fields, the default value is set on initial connection creation and used in the API call instead of missing or null values every time the Zap runs. For required fields, this value is used during connection creation, but not when the Zap runs (Zapier raises an error for missing/null values instead).

*   Input fields marked as password and all authentication fields with sensitive, private data such as both username and password from Digest Auth are automatically censored at runtime. These values are stored in the Auth bundle and used in API calls, but are replaced in Zapier's logs with a censored value like this `:censored:6:82a3be9927:`. Due to this, it is not possible to view the exact tokens or keys in Zapier's logs. To verify that the same token as was returned by the authentication, is being used in subsequent API calls; you can compare censored value characters, for example `:censored:6:82a3be9927:` would have the same value ending in 9927 when used in a subsequent call.

*   Computed fields are not applicable to Basic Authentication and are only used with OAuth v2 and Session Auth.

*   Each input field is listed with its label, key, type, and required status in your authentication settings. Click the field to edit it, or click the gear icon and select *Delete* to remove a field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/573a3eb19a884c44fd67b9fa421c3bf4.png" />
</Frame>

## 2. Add a Test API Request

*   Add an API call to your API that requires no configuration, typically a `/user` or `/me` call. Add the URL for the API call, and set the call type, typically a `GET`. This will test the user-entered credentials to ensure it enables a successful API call to your app.

*   The Digest input fields you configured earlier are automatically included in the URL Params and the HTTP Headers. Click *Show Options* to remove the details where they are not needed or add any custom URL params or HTTP headers your API requires. It is typically not recommended to pass any sensitive information such as the password in the URL Params. Passing it through the headers or even the body is preferable.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/503eeb14514aa854ae06ba956b6c572c.png" />
</Frame>

*   To customize the test API request, select *Switch to Code Mode* and write custom JavaScript code to handle your test API call and the response parsing as needed. The first time you click the toggle, Zapier will [convert your API call to code](/platform/build/code-mode). If you switch back to Form Mode though, Zapier will not convert your code changes to the Form Mode, nor will any subsequent changes in the form be added to your code.

## 3. Configure a Connection Label

Review [connection label documentation](/platform/build/connection-label) to optionally differentiate the app accounts users connect.

## 4. Test your authentication

Connect a valid user account to [test authentication](/platform/build/test-auth).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Send or receive dynamic user-defined fields through your API

Dynamic fields are a type of field built from an API call. Custom code runs to show fields based on other input field data. These are especially useful with project management apps, CRM apps, databases, and any other app where users can add custom, user-defined fields.

## Add a dynamic field

You can use dynamic fields, to retrieve fields in three different contexts:

*   Whenever the value of a field with `altersDynamicFields` is changed.
*   Whenever the *Set up* section for the action is opened in the Zap editor.
*   Whenever the *Refresh Fields* button is used on the action.

In the [Platform UI](https://zapier.com/app/developer):

1.  In the *Build* section in the left sidebar, click on your **action**.
2.  Click the **Input Designer** tab.
3.  Click **Add** and select **Dynamic Field**.
4.  A code box will appear. Add your **JavaScript code** to make an API call and fetch the fields from your app's API. Use Zapier's `z.request` to make the API call. Your code should return each custom field's key and name to Zapier in an array, so they display correctly in the Zap editor. Learn more about using [Z Object to call a function](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#z-object).
5.  Once you've added your JavaScript code, click **Save**.

<Info>
  **Note**:

  *   Do not rely on any input fields already having a value, since they won't have one the first time the Zap editor loads.
  *   Dynamic fields using `z.request` will not show in the Zap editor preview.
  *   Dynamic fields can only be added to actions, not triggers.
</Info>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8177dce9ee77e014b8fe65f4f6d6e5ba.png" />
</Frame>

## Create a dynamic field based on previous input field data

In the [Platform UI](https://zapier.com/app/developer):

1.  In the *Build* section in the left sidebar, click on your **action**.
2.  Click the **Input Designer** tab.
3.  Review your input field settings, you'll need at least one input field that has **Alters Dynamic Fields** selected. This is the field that Zapier will use to decide if the dynamic field should be shown.
4.  Click **Add** and select **Dynamic Field**.
5.  A code box will appear. Add your JavaScript code that evaluates data from previous fields (referencing `bundle.inputData.field_key` where `field_key` is replaced with the actual key of the prior input field that must be checked), including logic and details on another field to show depending on the value of the former.
6.  Once you've added your JavasScript code, click **Save**.

## Send dynamic field data to your API

In the [Platform UI](https://zapier.com/app/developer):

1.  In the *Build* section in the left sidebar, click on your **action**.
2.  Click the **API Configuration** tab.
3.  Click **Switch to Code Mode**
4.  In the dialog box, click **Switch to Code Mode**
5.  A code box will appear. Add your Javascript code, that include `body: { ...bundle.inputData }` to send every input field, including pre-defined or dynamic fields in the body of your API call to your app. You can also use `bundle.inputData.field_key` to include individual lists of dynamic fields.
6.  Once you've added your JavasScript code, click **Save**.

## Test a dynamic field

To test how your dynamic fields will appear in the Zap editor:

1.  [Create a new Zap](https://zapier.com/app/editor/).
2.  Add the action to your Zap that includes your dynamic field. This will allow for you to test how your action will appear to users.

If you want more details about the API calls being made, in the [Platform UI](https://zapier.com/app/developer):

*   In the *Manage* section in the left sidebar, click **Monitoring**.
*   You'll see a graph and be able to click on data points to view events and details to troubleshoot your setup further.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Use environment variables in your API call

Integrations can define environment variables that are available when the app's code executes. They are useful when you have data like an OAuth client ID and secret that you don't want to commit to source control. Environment variables can also be used as a way to toggle between a staging and production environment during app development and this would be recommended instead of the use of an independent integration for staging purposes.

Environment variables are defined on a per-version basis. Much like in local development environments such as the one in the [Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#environment), environment variables store key and value pairs outside of your app's API calls. Instead of hardcoding critical, secret values into your API authentication, trigger, and action calls, it's best to add them as environment variables in your integration, then reference the environment variable in your API calls.

If using OAuth v2 Authentication, the client ID and client secret fields are reserved environment variables. You'll need to use custom variable names for any other variables.

## 1. Add environment variables

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/52c80699c321084eaaf7b387c47bd438.png" alt="Add Zapier environment variables" />
</Frame>

To add environment variables:

*   Log into the [Platform UI](https://zapier.com/app/developer).
*   Select your **integration**.
*   In the *Build* section in the left sidebar, click **Advanced** . There you can add your environment variables including a key and a value for each.
*   Click **Add** to include additional environment variables, or click the `x` icon to remove variables if needed.
*   Once you've completed adding a key and value, click **Save**.

## 2. Reference environment variables

<Frame caption="Use environment variables in Zapier's API call forms or in custom code">
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/226591ede97cbcde75481b38fa0cd64f.png" alt="Use Zapier environment variable" />
</Frame>

Use environment variables in any Zapier integration API call through the form's *Show Options* link, selecting *Request Body*, *URL Params* or *HTTP Headers* as your API expects. Reference the environment variable you've configured under *Advanced*, in the form with the following text, replacing `YOUR_KEY` with your actual key: `{{process.env.YOUR_KEY}}` Zapier will then replace that variable with the value for that key from your *Advanced* settings and will use the correct value every time if you change it in the future. You can also reference environment variables in custom code if you switch your API call to code mode.

## 3. Change environment variables

<Frame caption="You can change environment variable values, but not the original keys">
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/2bbe32e1bcfd77c62a75fce5be6eb03a.png" alt="Edit Zapier Environment Variables" />
</Frame>

It is possible to change your environment variables in a new version of your integration, or when switching from dev to production endpoints for the release of your integration to the public.

To change an environment variable:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section in the left sidebar, click **Advanced**.
4.  Edit the text in the **Values** column with the new variable values. You cannot edit the *Key*. If you need to change a key and its value, first delete the old key, then add a new one instead.

## 4. Use environment variables for staging and production versions

We strongly recommend against the use of an independent integration for staging purposes. By utilising [version control](/platform/manage/versions) and environment variables instead, the deployment, integration and user management processes (eg. migration) will be significantly improved for your integration. It also helps [Developer Support](https://developer.zapier.com/contact) establish exactly what you're working on and identifying relevant logs.

It is conventional to reserve one version for testing/staging and one version for production, setting the applicable environment variables under *Advanced* in each.

If you want to be able to work with both environments in development in one version, one approach is the following:

*   Set environment variables for both domains you want to call
*   Set another environment variable for the domain you want to work with. For example, `key: ENVFLAG / value: staging`

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e2820a532b072401560d6c6afd7c90f8.png" />
</Frame>

*   Throughout the app, in [Code Mode](/platform/build/code-mode), check the value of the latter variable and conditionally reference the corresponding domain environment variable.

For example:

```js
let domain;

if (process.env.ENVFLAG === "staging") {
  domain = process.env.STAGING
} else {
  domain = process.env.PRODUCTION
}

const options = {
  url: domain // conditional, depending on envFlag
  method: 'POST',
  headers: {
    // headers
  },
  body: {
    // body
  }
}
```

Thus, during development, you can toggle the value of one environment variable to conditionally call the corresponding domain.

However, once a version is pushed to production, the variables are fixed, so in the case of a [public app](/platform/quickstart/private-vs-public-integrations) make sure to set the flag environment variable accordingly before promoting.

## 5. Allow users to select an environment

Certain apps need to allow users to select the domain during authentication. To do so, create an [input field in the authentication form](/platform/build/oauth#optional-input-form), allowing the user to pick which domain they want their connection to interact with.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8da73bce1eb9f7b20c018be357c765b1.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/10bd5195508ba9d9cc43e711504f96be.png" />
</Frame>

Reference the user's selection with `{{bundle.authData.env_url}}` throughout the integration to conditionally call the corresponding domain.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Error: An array is expected

When you add a polling trigger or search action to your integration, the Zapier platform [expects a bare array of new or found items returned](/platform/build/response-types), sorted in reverse chronological order. An API may instead return a result _object_ that contains the array of items the trigger/search needs.

## Error shown

For example, for a “Find Issue” search action with GitHub's API, we might start with a `https://api.github.com/repos/{{bundle.inputData.owner}}/ {{bundle.inputData.repo}}/issues/{{bundle.inputData.issue_number}}` request:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a5516cc30abee4f84a58ac5b7b3dfc76.png" />
</Frame>

When tested, Zapier will show an error message `Results must be an array, got: object,`

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/2461b0e7f49ecf5aed90d429a59ad2bf.png" />
</Frame>

Check the API response in the HTTP tab of the *[Test your API Request](/platform/build/test-triggers-actions)* section, and you'll see an *object* that contains the array of items we need was returned, not the array itself:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d569e3a05f643a9a199b5d85dc4a4fc2.png" />
</Frame>

## Solution

Instead, return that array of channels to Zapier. To do that switch to [Code Mode](/platform/build/code-mode) in your request. That will allow you to provide a JavaScript function to handle the request, and make needed changes to the structure or content of the result before returning data to the Zapier platform.

For this request, wrap the response with an array instead of the default `return results`, to have Zapier return an array of issues.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/3bec13fa502f47ff1e5f9bfded052b4d.png" />
</Frame>

> Remember: [Code Mode](/platform/build/code-mode) is a toggle; if you switch back to Form Mode your code will be ignored!

Now, retest the request and it should run successfully.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/af56c7fed5183aed462d2e7efbf78f8c.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Error: Got a non-object result, expected an object from create



## Error shown

When you add a create action to your integration, the Zapier platform [expects a single object to be returned](/platform/build/response-types), containing an `id` and details about the new item created. If an API returns a non-object result, the following error will show.

`CheckError: Invalid API Response: - Got a non-object result, expected an object from create`

## Solution

Instead, make sure your API returns an object to Zapier. To do that switch to [Code Mode](/platform/build/code-mode) in your request. That will allow you to provide a JavaScript function to handle the request, and make needed changes to the structure or content of the result before returning data to the Zapier platform.

If an array is returned, you would parse the response to return the object, without being enclosed in an array.

This could be by enclosing the `results` in a `{ }`, or reaching into the array and pull out the object that represents what you want to return to the user.

<Info>
  Remember: [Code Mode](/platform/build/code-mode) is a toggle; if you switch back to Form Mode your code will be ignored!
</Info>

Once you retest the create request, it should run successfully.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Error: Got a non-object result in the array, expected only objects

When using a REST Hook trigger, the data returned by the perform must be an array.

## Error shown

If an API returns a non-object result within the array, or an array of arrays, the following error will show.

`Got a non-object result in the array, expected only objects ( )`

The non-object result will be wrapped in the parentheses for the error message.

## Solution

If the data included in the webhook needs to be transformed, or includes multiple objects, you can add custom code to parse the response data in `bundle.cleanedRequest` within the Perform into an array of objects.

If your webhook already provides an array, remove the wrapping array that Zapier includes by default and simply return `bundle.cleanedRequest`.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/26459a11e1630fe8318c341bf598ab5a.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add error response handling

If your API returns responses with a status code above 400 that should not automatically throw an error then Zapier recommends enabling skipThrowForStatus.

This feature allows you to create custom error handling script for status codes above 400. Note that 401 status codes will throw a `RefreshAuthError` [regardless](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#user-content-error-response-handling).

To enable `skipThrowForStatus`:

## 1. Enable skipThrowForStatus

*   Log into the [Platform UI](https://zapier.com/app/developer).
*   Select your **integration**.
*   In the *Build* section in the left sidebar, click **Advanced**.
*   Click the **Settings** tab.
*   Click the **On** toggle next to *Enable skipThrowForStatus*.
*   Click **Save**.

## 2. Use Code Mode to add error handling script

You'll need to add error handling script to your authentication, triggers, actions that could encounter the error using [Code Mode](/platform/build/code-mode).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/9553266cb5a5ab7804d3f9ac1a9eed60.png" />
</Frame>

```js
return z.request(options).then((response) => {
  if (response.status === 404) {
    throw new z.errors.Error(
      "Insert error message to user here",
      "InvalidData",
      404
    );
  }
  return response.json;
});
```

Learn more about [general error handling in Zapier](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#general-errors).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Field types



Three types of fields exist for users to see in the Zap editor:

*   Input fields
*   Dynamic fields
*   Line item groups

## Input fields

Input fields are most commonly used and include 9 types that look and act differently in the Zap editor. Depending on the type, users can either enter plain text data, make a selection or map variables from previous triggers and actions. Zapier does not validate the data to ensure users added the correct item for that field type.

### String

The default input field, which accepts text input. Typically used to gather individual text values such as a name or email address.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b6abd9f3b9048e4a0b8010c79ac78ca7.png" />
</Frame>

### Text

Displays large, `<textarea>`-style entry box, accepts longer text input. Often used to enter notes.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6936573528e1c7dc72b33e5b8178aac7.png" />
</Frame>

### Integer

Accepts integer number values, shown with a `1 2 3` tag beside the field in Zaps. Typically used along with a dropdown type field to select ID numbers for objects in your app, also used to gather whole numbers.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/4c3d21a485c1329720209fa9ee1664f7.png" />
</Frame>

### Number

Accepts any numeric value, including decimal numbers, shown with a `1.0` tag beside the field in Zaps.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/0d149da9dd979e6aa0a628a1a4abf2b7.png" />
</Frame>

### Boolean

Allows users to select between `yes` and `no` values in a dropdown in their Zap to send a corresponding `1` or `0` in the request to your app's API.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/da36e8486e9aec5d685650be5c5b194d.png" />
</Frame>

### Datetime

Accepts both [precise and human-readable date-time values](https://help.zapier.com/hc/en-us/articles/8496259603341-Different-field-types-in-Zaps#date-time-fields-0-0). Sends an [ISO-formatted](https://www.iso.org/iso-8601-date-and-time-format.html) time string in the request to your app's API.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/68097e2927567b360f9fb9b65647369c.png" />
</Frame>

### Password

Displays entered characters as hidden, accepts text input, just like standard string fields. Does not accept input from previous steps.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/872c6d1ce250bb054460643701df646a.png" />
</Frame>

### Dictionary

Users enter their own value sets, with a plain text name followed by a String field to enter or map data from previous Zap steps. Click the + button on the right to add additional dictionary field entries.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/74689dc7f73a75c3cf5ab9b858f651b1.png" />
</Frame>

### Copy

Does not allow users enter data and is not passed to your app's API. Shows the Help Text field copy in a display-only formatted note, with no input field. Supports [Markdown](https://zapier.com/blog/beginner-ultimate-guide-markdown/). Typically used for important notices to users.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/eebc20fad3362197a0448edd8a52e27e.png" />
</Frame>

## Dynamic fields

<Warning>This feature is only available in the Platform CLI.</Warning>

Dynamic fields make a request to your app's API, showing the returned data in a dropdown menu in live time. Users select the object needed. Typically used for folders, projects, assignees, and other data that would need to be chosen from within your app.

## Line item groups

Line item groups allow users to input line items provided from the trigger or prior action. Those items will then be sent in the request as an array of individual objects. Users cannot input comma separated values into a line item field, they need to be provided from the trigger or a prior action in line item format for them to be sent this way. Typically used to add multiple lines of similar details to an app, for example in invoice and accounting apps.


# Use form mode to setup your API calls

In the Platform UI, when building your authentication, triggers and actions, the default setting under _API Configuration_ is to create each component of your integration using Form Mode.

## Capabilities

With Form Mode you can:

*   Add the API endpoint for the request to be made
*   Choose the API request type
*   Set any custom URL parameters, HTTP headers and the request body under *Show Options*.

By default, the configured authentication is included as HTTP Headers, which you can modify if needed.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e380a04c42f0332d57df4fd0d1532590.png" />
</Frame>

By default, any Input fields are included as URL Params. To instead include them within the API endpoint, use format \`\` where `fieldkey` is the key of the input field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/fdc8807e9b6d41427d937d1362f20fbd.png" />
</Frame>

When the request runs, Zapier parses JSON-encoded responses into individual output fields to use in subsequent Zap steps.

Form Mode is the simplest way to set up most API calls and options in your Platform UI integration's authentication, triggers, and actions.

## Refine the API call further

If your API calls need more customization than Form Mode offers or your API response is in a non-JSON format, you will need to write custom JavaScript code to handle your API call and/or response parsing. To do so, use [Code Mode](/platform/build/code-mode).

The first time you switch to Code Mode, Zapier copies everything entered in Form Mode, including any custom options added, and converts that to JavaScript code. It then changes the UI to include a code block, where you can add code for your API call.

Zapier uses the currently visible option when running each part of your integration. To check which mode and settings Zapier is using for each API call, open that part of your Zapier integration and visually check to see if the Form or Code Mode is visible.

To switch back to the Form Mode, click the *Switch to Form Mode* button to see the form options as they were when you *first switched*. Zapier will save the code you entered, but will not convert it back to the Form Mode nor use the custom code in your API Request.

If you then switch back to Code Mode again — you will see the last saved version of your code, and no changes you made in Form Mode will be refelcted in that code.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add a REST Hook trigger

Set up your REST Hook trigger in the Platform UI with the Settings, Input Designer and API Configuration tabs.

## Prerequisites

*   Your app supports REST Hooks - webhook subscriptions that can be manipulated through a REST API.
*   An endpoint/s to set up and remove the hook subscription exists for Zapier to send Subscribe and Unsubscribe API requests to with a unique subscription URL, the`bundle.targetUrl`, for each active Zap.
*   A separate endpoint exists that returns a list of sample items that would be expected to trigger the user's Zap. This is optional for apps remaining private and ***required*** for public apps.
*   If your webhook subscriptions expire, make sure the subscribe endpoint returns an `expiration_date` property containing an ISO8601 date. The platform will automatically attempt to resubscribe after the expiration date.

## 1. Add the trigger settings

*   Open the *Triggers* tab in Zapier's Platform UI and select **Add Trigger**.
*   On the Settings page, specify the following:

– **Key**: A unique identifier for this trigger to be referenced inside Zapier. This is not shown to users. This cannot be edited once saved.

– **Name**: A human-friendly name for this trigger, typically with an adjective such as *New or Updated* followed by the name of the item that the trigger watches for inside your app. The title-case name is shown inside the Zap editor and on Zapier's app directory marketing pages.

– **Noun**: A single noun that describes what this trigger watches for, used by Zapier to auto-generate text in Zaps about your trigger.

– **Description**: A plain text sentence that describes what the trigger does and when it should be used. Shown inside the Zap editor and on Zapier's app directory marketing pages. Starts with the phrase “Triggers when”.

– **Visibility in Editor**: An option to select when this trigger will be shown. *Shown* is chosen by default. Choose `Hidden` if this trigger should not be shown to users.`Hidden` is usually selected when the trigger is not ready to be used in the integration, or for polling triggers that power [dynamic dropdown](/platform/build/add-fields#dynamic-dropdown) fields.

– **Directions** is used for static webhooks only to describe how and where to copy-paste the static webhook URL for the trigger within your app. **Directions** will not show to users in other cases. Static webhooks are not permitted in public integrations.

*   Click on the **Save and continue** button.

## 2. Complete the Input Designer

On the Input Designer page, add user [input fields](/platform/build/add-fields) needed by your API to watch for the triggering item.

Trigger input fields allow users to enter filters, tags, and other details to filter through new or updated data at the endpoint.

If no input data is needed for this trigger's endpoint, continue.

## 3. Set up the API Configuration

On the API Configuration page, select **REST Hook** as the trigger type, and complete each section.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/0368aeae12e11ec59688d10a7ef69d8c.png" />
</Frame>

REST Hook triggers are marked as *Instant* [in the Zap editor](/images/f510859bf90c0e341bc94997a75f9626.png).

### Subscribe

This request, usually a POST, is performed when a user activates a Zap that starts with this REST Hook trigger. This is how a Zap makes a subscription request to your API to be notified (via webhook) of all trigger events with the given parameters going forward.

The subscribe request is **only** made when a Zap is turned on for the first time, or if an active Zap is paused and then unpaused. Note that [publishing a new Draft](https://help.zapier.com/hc/en-us/articles/8496260938125-Create-drafts-of-your-Zaps) would not always mean pausing/unpausing a Zap. Only if a new trigger subscription is required, would a new subscribe request be made. In cases of remapping action step fields (from existing trigger data), this doesn't require any change to the trigger itself as a datasource, so a new subscribe request would **not** be made when that Draft was published.

Click on the *Show Options* dropdown to add data to the Request Body or HTTP Headers that are needed by your API for a successful subscription. Note that you'll need to make sure the keys here match what your API expects.

Zapier includes a `targetUrl` when making this request. You need to store the targetUrl, usually in a database, and you'd typically associate it with an id. Return that id in the response back to Zapier to be used later in the Unsubscribe.

Your app's event system would determine which stored subscriptions should be invoked when an event occurs in your app, posting to the corresponding stored `targetUrl`.

The webhook URL can be accessed via `{{bundle.targetUrl}}`.

For example, for Gitlab's API `url` is used as the key for the `{{bundle.targetUrl}}` value that contains the webhook URL to send data to.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8b7941e3092850bd7edf331cb78b5659.png" />
</Frame>

If you need to customize the subscription request, select *Switch to Code Mode* and [write custom JavaScript code](/platform/build/code-mode) to handle the API call and the response parsing as needed.

It is recommended that a successful Subscribe response return a 201 status code. The data returned with the response should include any data needed to later the Unsubscribe request. This data will be stored in `bundle.subscribeData`.

### Unsubscribe

This request, usually a DELETE, is how Zapier notifies your API when it is no longer listening for trigger events, when the Zap is deactivated or deleted. If your API continues posting notification payloads to the Zap after it has unsubscribed, you can expect to see a 410 response from Zapier.

Click on the *Show Options* dropdown to add data to the Request Body or HTTP Headers that is needed by your API for a successful unsubscription. Note that you'll need to make sure the keys here match what your API expects. When Zapier sends the request to your API to unsubscribe the webhook, it can reference any data that was returned from your API during the Subscribe request and was stored in `bundle.subscribeData`.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/44615176b56966a90101067d719b09ad.png" />
</Frame>

A REST Hook trigger missing a Subscribe or Unsubscribe endpoint, is presented to users as a [Static Webhook](/images/3b35908a6a0c232087b5da807cf9d6fb.png). Static hooks are [not supported in public integrations](/platform/publish/integration-checks-reference#d017---static-hook-is-discouraged), but they could be used if the integration intends to remain private.

### Perform List

This request, usually a GET, is used to collect sample data in the Zap editor, typically prior to a Zap's activation. This will be used to fetch sample data when users are testing the trigger and mapping fields to their Zap's subsequent steps. Though optional, not defining a Perform List is a sub-optimal experience for users and is [required for public apps](/platform/quickstart/private-vs-public-integrations).

Most commonly this is a GET request to an endpoint of your API which will provide a response with the exact same schema as the data delivered via webhook when a trigger event happens.

Response data returned must be an array, even if the array contains only one object. It must have the exact same schema as the data delivered via webhook when a trigger event happens otherwise fields mapped from this sample in subsequent steps of a Zap will break when it runs live.

### Perform

The Perform function is called each time your app delivers a notification payload to Zapier. Use custom code to parse the webhook payload and modify it as required before returning it to the Zap as trigger data.

The data returned by the perform must be an array. By default, Zapier includes `return [bundle.cleanedRequest]` to return the data from the webhook as an array. If the data needs to be transformed, or includes multiple objects, you can add custom code to parse the response data in `bundle.cleanedRequest` within the Perform into an array of objects.

If your webhook already provides an array, you can remove the wrapping array and simply `return bundle.cleanedRequest`.

If, for architectural reasons, your webhook will receive some data that shouldn't trigger the Zap, include code for the Perform function to return an empty array in those cases, so the Zap won't run.

For data sent to Zapier via REST Hook, most requests will be successful and return a 200 status code with some request-tracking data. This indicates that Zapier has accepted the data, but it is still possible for errors to occur within the Zap if the structure of the provided data is unexpected.

### Best practices when sending data to a REST Hook trigger

*   Be mindful of Zapier's [rate limits](https://zapier.com/help/troubleshoot/behavior/rate-limits-and-throttling-in-zapier#step-4).
*   If your app receives a 410 response, that webhook subscription is no longer active, and you should stop sending data to it.
*   If your app receives repeated 4xx or 5xx failures from Zapier outside those error types, this can be handled at your discretion. You may choose to try again later, or to stop sending data and deactivate the hook.
*   To signal that your app has deactivated the hook for any reason, you can optionally send a *reverse unsubscribe* call to Zapier. This can allow users to manage their subscriptions from inside your app, or permit you to clean up after a user deletes their account or revokes credentials. The request should be of the form `DELETE <target_url>`, where `target_url` is the unique target URL that was provided when the subscription was created. This will pause the Zap within Zapier.

Once you've configured all the endpoints, click *Save API Request & Continue*

## 4. Test your API request

To test the REST Hook trigger, [build a Zap in the editor](/platform/build/test-triggers-actions).

## 5. Define your output

Define sample data and output fields following [the guide](/platform/build/sample-data).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Use hydration in Platform CLI

The best answer to this lives in our [CLI docs](https://docs.zapier.com/platform/reference/cli-docs#dehydration):

## What is dehydration & hydration?

<Info>
  Dehydration, and its counterpart hydration, is a tool that can lazily load data that might be otherwise expensive to retrieve aggressively.
</Info>

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).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/c3e87ca1ba18ce915d23dedf055e61af.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/914893706d089af76bb445b3d885908d.png" />
</Frame>

Here's how the integration looks in [Zapier's developer dashboard](https://developer.zapier.com/). Add an optional icon to it if you like.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/13df6356d05b5bb3e4533666bbfcc680.png" />
</Frame>

Next, we'll want to add a Zap. Open the [Zap editor](https://zapier.com/editor), and select your integration's trigger.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/50a4c0399eef2728b1f3e2b67f7fb916.png" />
</Frame>

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.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/c68b2539d72c6c72da2ba3c35c9cef8d.png" />
</Frame>

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.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/28ed4762db06d0dd95563a4480a8dc36.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/dd56ad225973829fbdc9fa1bcec5a2da.png" />
</Frame>

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):

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/97152cd0012be0e7c35385a4f4b3f50a.png" />
</Frame>

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!

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/81cac4d9b52b6a76194d5af91949bfef.png" />
</Frame>

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.*


# Hydration/dehydration limits

[File dehydration](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dehydration) is an extremely useful tool to remain within time and size constraints for Zapier triggers and actions. However, it does have its own limits.

## Constraint

There is a hard limit of 150MB on the size of dehydrated files. Depending on the complexity of the app, issues can also occur for files over \~100MB.

## Errors user will see if constraint is hit

*   In their Zap history, the user will see an error like `Runtime exited with error: signal: killed`.

## Best practice

If your integration regularly loads large files, provide checks on file size and don't perform hydration for files that are larger than \~100MB. Include messaging for users letting them know of the limit on file size in the trigger/action description or in a `Copy` field above the file input field.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add line item group field to actions

Input fields in Zapier add one item each time the Zap runs. But, if you want users to be able to add multiple items in a single Zap run, then this can be achieved by using a line item group. This group takes line items, which are comma-separated values, and adds each instance of the values to the app in a single Zap run.

<Info>
  **Tip**: Learn more about line items from Zapier's [guide](https://zapier.com/blog/formatter-line-item-automation/) and [help documentation](https://help.zapier.com/hc/en-us/articles/8496277737997).
</Info>

Line items can be challenging for users to work with as a concept, so make sure to consider what a user would be inputting and do not add line item group fields unless there is a clear use case for them.

## Before adding a line item group

Before adding a line item group to an action:

*   You should have already added an [action](/platform/build/action) where this line item group would be added.
*   Your app's API should be able to accept and work with line item values.

## Add a line item group

To add line item group to an action:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section in the left sidebar, click the **name of your action**.
4.  Click the Input Designer tab
5.  Click \*\*Add

<Frame>
  <img src="https://cdn.zapier.com/storage/photos/9f549e9320881f1dcda37fce73ace8ca.png" />
</Frame>

dropdown menu and select **Line Item Group**.
6\.  In the **Line Item Group Label** field, add a name for the Line Item Group. This should be something users would recognize as being a set of values where each one would be added individually.
7\.  In the **Line Item Group Key** add a key for the Line Item Group. This will let Zapier identify this group internally.
8\.  Click the **Add Line Item** button.
9\.  Add in the relevant fields as you would for a [input field](/platform/build/field-definitions#add-fields). Each field that you add here will be received as line item values. Note the *Allows Multiples* and *Alters Dynamic Field* options will not appear as those options cannot be used in line item fields as they are mutually exclusive.
10\.  After adding all the line item fields, click **Save**.

Your new Line Item groups will show up in the Zap editor as a separate block with the different line item input fields within:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/41f81ef42d5101767810a9ffc5626e95.png" />
</Frame>

Users will need to map line item values from earlier steps in their Zap into the fields within the line item group. They would be sent to your API as an array of individual objects. Users cannot input comma separated values into a line item field, they need to be provided from the trigger or a prior action in line item format for them to be sent this way.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/533efc7f7719fa85587f72b04204371f.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a7bad08984545eb39e96ed2782660737.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add authentication with OAuth v2

OAuth v2 authentication matches in appearance the login process users expect from most modern apps.

The user interaction with authenticating Zapier typically takes place in full on the app's own site, helping users easily connect accounts without needing to share account credentials or look up API keys.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/520541acfeeb0e0a812944e599852756.png" />
</Frame>

The user will see a familar window from your app showing either a login screen or if they're already logged in, an account selector where they don't need to enter credentials. Once they authorize Zapier access, the app will return an access token that Zapier uses in future API calls.

Zapier implements the “Authorization Code” [grant type](https://tools.ietf.org/html/rfc6749#section-1.3.1) when you choose OAuth v2 in the Platform UI. If your OAuth v2 implementation supports refresh tokens, you can optionally configure a “Refresh Token” [request](https://tools.ietf.org/html/rfc6749#section-1.5).

Platform UI does not currently support other grant types. If your integration requires a different OAuth v2 grant type, you'll need to use another supported authorization type with Zapier such as [Session](/platform/build/sessionauth) or [API Key](/platform/build/apikeyauth).

If your integration requires OAuth v1 authentication, use the [Platform CLI](/platform/quickstart/cli-tutorial) instead of Platform UI.

## 1. Configure the OAuth v2 components

*   Open the *Authentication* tab in Zapier visual builder and select *OAuth v2*.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e89518edce251293ff42ae0f186c0efb.png" />
</Frame>

### Optional input form

*   Most apps with OAuth v2 authentication do not need an input form. If your API requires data from the user before displaying the authorization URL, or requires URL details to create the authorization URL; such as account team name or site URL for self-hosted apps, or you need to store fields received from your server to use in subsequent API calls - you'll need an input form.

*   Add those additional fields by selecting *Add Fields* and fill in the details for any field requiring user input. This will show a form to users with the fields you've added before redirecting them to your authorization URL.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/3603f9715129250a93ecfcf78d4b5f17.png" />
</Frame>

*   Two types of fields are available when building an OAuth v2 input form. Standard Fields, work much like other form fields with Zapier's [input form](/platform/build/field-definitions) in triggers and actions. [Computed Fields](/platform/build/computed-fields) make sure specific fields are returned by your app's authentication API call response.

*   For standard fields to be input by the user, select the default *Field* type. Add the most commonly needed fields first, in the order users expect, as you cannot reorder fields once added. Fill in the options as appropriate:

– **Key**: The internal name for your field, used to reference this field in Zapier API calls. For convenience, use the same key as your API uses for this field. Note: `client_id` and `client_secret` are reserved and cannot be used as keys for input form fields.

– **Label**: A human-friendly name for this field that will be shown to users in the authentication form.

– **Required? (checkbox)**: Check if this field is required for successful authentication.

– **Type**: All input fields use the `string` text field by default; select `password` instead if you would like to obscure the data as users enter it.

– **Help Text**: Include details to assist users in authenticating with your app, especially if they may be unsure where to find the data needed within your app. Format text with [Markdown](https://zapier.com/blog/beginner-ultimate-guide-markdown/), and include a hyperlink if needed.

– **Input Format**: (optional) Help users figure out exactly what piece of data you need them to enter. For example, for [a subdomain](/platform/build/subdomain-validation), [https://.yourdomain.com/](https://.yourdomain.com/).

– **Default Value**: Include a value for this field to be used as a fallback. For optional fields, the default value is set on initial connection creation and used in the API call instead of missing or null values every time the Zap runs. For required fields, this value is used during connection creation, but not when the Zap runs (Zapier raises an error for missing/null values instead).

*   Input fields marked as password and all authentication fields with sensitive, private data such as both username and password from OAuth v2 are automatically censored at runtime. These values are stored in the Auth bundle and used in API calls, but are replaced in Zapier's logs with a censored value like this `:censored:6:82a3be9927:`. Due to this, it is not possible to view the exact tokens or keys in Zapier's logs. To verify that the same token as was returned by the authentication, is being used in subsequent API calls; you can compare censored value characters, for example `:censored:6:82a3be9927:` would have the same value ending in 9927 when used in a subsequent call.

*   Each input field is listed with its label, key, type, and required status in your authentication settings. Click the field to edit it, or click the gear icon and select *Delete* to remove a field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/573a3eb19a884c44fd67b9fa421c3bf4.png" />
</Frame>

*   For computed fields, available in OAuth v2 and Session Auth only, review [computed fields documentation](/platform/build/computed-fields).

### OAuth Redirect URL

*   Open your app's application, integration, or API settings, and add a new application for your OAuth integration with Zapier. From the Zapier Platform UI's Authentication *Copy your OAuth Redirect URL* section, copy the OAuth Redirect URL and add it to your application's integration settings.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b3e6b9ca7657ba02e4864ec28ab74951.png" />
</Frame>

*   To reference the redirect URL inside your Zapier integration, use the following code:

`{{bundle.inputData.redirect_uri}}`

### Add PKCE Support

*   Zapier provides built-in support for [PKCE](https://oauth.net/2/pkce/#credentials) (Proof Key for Code Exchange and pronounced “pick-see”), an extension to the authorization code flow that adds a layer of protection against security vulnerabilities. The code generation and exchange steps of the flow occur automatically by Zapier when enabled.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/124a3c00d8bc37dadd953f19451205a5.png" />
</Frame>

### Add Application Credentials to Zapier

*   In your application's settings, you'll receive credentials that Zapier will use to verify itself to your app — typically called a *Client ID* and *Client Secret*, though they may have a slightly different name. Copy that data from your app, then in Step 3 of your Zapier OAuth configuration, paste those items in their respective fields. Zapier will use that data along with the authorization URL to receive the request token.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/ba093465f009bf6b417d55b2166c4324.png" />
</Frame>

*   Click *Save & Continue* to save your progress so far.

*   Zapier automatically includes the Client ID and Secret in authentication API calls, but if you need to reference them in your integration's API calls or custom code, use the following codes: – Client Secret: `{{process.env.CLIENT_SECRET}}` – Client ID: `{{process.env.CLIENT_ID}}`

### Add OAuth Endpoint Configuration

*   Add your application's Authorization URL, where Zapier will redirect users to authenticate with your app. Copy this URL from your application or integration settings where you copied the client ID and secret previously, or from your app's API page.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/1920c552acfc0cc03089feb2e9ada029.png" />
</Frame>

*   By default, Zapier will pass the client ID, state, redirect URI, and a standard `code` response type as URL Params in the request to the authorization url. If you need to change that, click the *Show Options* button and add any additional call details needed.

> **Note**: The Oauth2 `state` param is a [standard security feature](https://auth0.com/docs/secure/attack-protection/state-parameters) that helps ensure that authorization requests are only coming from your servers. Most Oauth clients have support for this and will send back the `state` query param that the user brings to your app. The Zapier Platform performs this check and this required field cannot be disabled. The state parameter is automatically generated by Zapier in the background, and can be accessed at `bundle.inputData.state`. Since Zapier uses the `state` to verify that GET requests to your redirect URL truly come from your app, it needs to be generated by Zapier so that it can be validated later (once the user confirms that they'd like to grant Zapier permission to access their account in your app).

*   To optionally limit Zapier's scope to let it only access specific data from your app, add OAuth scopes in the *Scope* field with a comma- or space-separated list.

*   Add your app's Access Token Request URL from your API documentation, typically with a `POST` call.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/517f20c1a33012af96b18036332371eb.png" />
</Frame>

*   By default, Zapier will pass the client ID, client secret, authorization code, redirect URI, and a standard `authorization_code` grant type in the API request body with the access token request. If you need to change that, click the *Show Options* button and add any additional call details needed.

*   If your API supports automated token refresh, add your API's Refresh Token Request URL, and check the *Automatically Refresh Token* box. This enables users' Zaps to run in the background without interruptions as long as possible by refreshing expired access tokens automatically.

*   If your Access Token and Refresh Token requests don't return the tokens at the top level, use [Code Mode](/platform/build/code-mode) to modify the response so that the tokens are available at the top level. It is not possible to store an object with nested keys from the response.

*   Zapier will automatically include the access token in subsequent API requests, but if you need to manually add it, the access token is stored in the `authData` bundle and can be referenced with `{{bundle.authData.access_token}}` or `{{bundle.authData.accessToken}}`, depending on how your API's response references the access token.

## 2. Add a Test API Request

*   Add an API call to your API that requires no configuration, typically a `/user` or `/me` call. Add the URL for the API call, and set the call type, typically a `GET`. This will test the user-entered credentials to ensure it enables a successful API call to your app.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/48cdb444eab9a329023b3e991bfa0da9.png" />
</Frame>

*   The access token is included with the API call by default, as it will with all subsequent API calls, but if your API requires any additional configuration, click the *Show Options* button and add any options needed for a successful API call.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f9d47ff9bd4af168dcb4c4811a1e184d.png" />
</Frame>

*   To customize the test API request, select *Switch to Code Mode* and write custom JavaScript code to handle your test API call and the response parsing as needed. The first time you click the toggle, Zapier will [convert your API call to code](/platform/build/code-mode). If you switch back to Form Mode though, Zapier will not convert your code changes to the Form Mode, nor will any subsequent changes in the form be added to your code.

## 3. Configure a Connection Label

Review [connection label documentation](/platform/build/connection-label) to optionally differentiate the app accounts users connect.

## 4. Test your authentication

Connect a valid user account to [test authentication](/platform/build/test-auth).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Zapier operating constraints

Zapier offers a relatively unique run-time environment for your integration and its requests to your API. The environment is stateless and restricts both execution time and payload size to offer normalized reliability and running time. There are three distinct contexts of this run-time that your integration will need to consider.

*   Zap step setup and testing using the Zap editor
*   Zap startup - turning a Zap on
*   Zap step execution when a Zap runs

The following pages describe the operating constraints of these run-time modes, the errors your integration users could run into, and best practices. Developers in the Platform CLI development environment and those using [Code Mode](/platform/build/code-mode) in the Platform UI will find these pages most relevant.

Many errors can be viewed in your integration's log monitoring in the [Platform UI](/platform/build/test-monitoring), or if using the [Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#handling-throttled-requests), by using `zapier logs` command.

# Important Zapier constraints summary

| Time Limits                                                |            |
| ---------------------------------------------------------- | ---------- |
| Zap Editor test step: request(s) + scripting               | 30 seconds |
| Polling trigger: request(s) + scripting                    | 30 seconds |
| REST Hook trigger: ingest and payload processing scripting | 30 seconds |
| Create/search action: requests(s) + scripting              | 30 seconds |

| Size limits           |                      |
| --------------------- | -------------------- |
| Deduplication table   | 105,000 rows per Zap |
| Custom fields         | 1000                 |
| Webhook payload       | 10 MB                |
| HTTP response payload | 20 MB                |
| Downloaded files      | \~120 MB             |

| Throttles                                                                                                       |                             |
| --------------------------------------------------------------------------------------------------------------- | --------------------------- |
| Polling trigger                                                                                                 | Default: 100 new items/poll |
| REST Hook trigger                                                                                               | 10000 webhooks/5 minutes    |
|                                                                                                                 | 30 webhooks/second          |
| More on throttles [here.](https://help.zapier.com/hc/en-us/articles/8496181445261#h_01H91ED0PQ782E3B34ZRB5DXF7) |                             |

| TCP/IP                  |                                                                                                     |
| ----------------------- | --------------------------------------------------------------------------------------------------- |
| Integration assigned IP | [AWS-East](https://zapier.com/help/troubleshoot/behavior/cant-access-or-use-zapier-with-other-apps) |

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Use pagination in triggers

By default, Zapier triggers fetch new or recently updated data to start Zaps, and only need to find the most recently added items. Triggers can also be used to populate [dynamic dropdown fields](/platform/build/add-fields#dynamic-dropdown), and there they need to find all possible items to populate the field.

These triggers used for populating dynamic dropdown fields will often find dozens or hundreds of items. Many APIs let you split the results into pages. The first API call will return the first set of results — often 20 to 100. The items from this result would be seen as options in the dynamic dropdown field. If you want additional entries, you can make a new API call requesting page 2 and get the next set of results, iterating through the pages until the API has sent every possible option.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f59291e5a74d1977648850f84513e33e.png" />
</Frame>

## 1. Enable pagination

*   Log into the [Platform UI](https://zapier.com/app/developer).
*   Select your **integration**.
*   In the *Build* section in the left sidebar, click on the trigger that should power the dynamic dropdown field. This can be an existing trigger or a new trigger you set as *Hidden* if its only purpose is to power the dynamic dropdown.
*   Go to the **API Configuration** tab
*   In the **Configure your API Request** section, select the **Support Paging** checkbox, which should only be applied to triggers built to power dynamic dropdown menus. This enables Zapier's `bundle.meta.page` value which tracks the pages Zapier has loaded, along with a Load more option in the user-facing Zapier editor's dropdown menus.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/4680b6dd27b3db71b8364177e351fdd5.png" />
</Frame>

*   In the API Endpoint section, click *Show Options* to include `bundle.meta.page` in your API call as Zapier does not include that automatically, then add a new URL Param for your API's paging option (or optionally add it to your HTTP Headers if your API expects the paging value there). Use the page request field name from your API on the left, and `{{bundle.meta.page}}` on the right to have Zapier pull in the correct page value.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/2492cb37ec953861cceaf243c0625285.png" />
</Frame>

Note that Zapier's `bundle.meta.page` value uses zero-based numbering. The first time Zapier fetches data from your API, it uses a page value of 0, followed by 1 the second time, and so on. If your API expects the first API call to request page 1, with 2 for the second page and so on, you'll need to [switch to code mode](/platform/build/code-mode) and add `+ 1` to `bundle.meta.page`. So pagination in the code would look like the below (substituting `page` with the correct field name your API uses for pagination):

`'page': bundle.meta.page + 1`

![Zapier pagination code mode](https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8e7923caa73ff68ea6061d61ad37e451.png)

*   Test the API Request in the Platform UI and check the HTTP tab to see the request Zapier sent your app. Zapier should show a `page=0` value (or the correct term for pages in your API) under the Request Params header by default, or `page=1` if you're customizing the page requests to start at 1.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/78b76bad2188b7594325c2c6bb85f121.png" />
</Frame>

## 2. Test pagination

To test your paginating trigger:

*   Build an action in the Platform UI that uses this trigger in a dynamic dropdown.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d7d14e885062e466c4bcbbab8dcfd535.png" />
</Frame>

*   Make a new Zap in the user-facing Zap editor that uses your action with the dynamic dropdown field. Click the dropdown field, scroll to the end, and click the **Load More** button. Repeat until it loads the last options, which will show a *We couldn't find any more choices* prompt.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b2d1a5bf597c95f8615ca009ee7d66c6.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add a polling trigger

Set up your polling trigger in the Platform UI with the Settings, Input Designer and API Configuration tabs.

## Prerequisites

*   Understanding of the [concept of deduplication](/platform/build/deduplication) and familiarity with how it is [explained to users](https://zapier.com/help/create/basics/data-deduplication-in-zaps)

## 1. Add the trigger settings

*   Open the *Triggers* tab in Zapier's Platform UI and select **Add Trigger**.
*   On the Settings page, specify the following:

– **Key**: A unique identifier for this trigger to be referenced inside Zapier. This is not shown to users. This cannot be edited once saved.

– **Name**: A human-friendly name for this trigger, typically with an adjective such as *New or Updated* followed by the name of the item that the trigger watches for inside your app. The title-case name is shown inside the Zap editor and on Zapier's app directory marketing pages.

– **Noun**: A single noun that describes what this trigger watches for, used by Zapier to auto-generate text in Zaps about your trigger.

– **Description**: A plain text sentence that describes what the trigger does and when it should be used. Shown inside the Zap editor and on Zapier's app directory marketing pages. Starts with the phrase “Triggers when”.

– **Visibility in Editor**: An option to select when this trigger will be shown. *Shown* is chosen by default. Choose `Hidden` if this trigger should not be shown to users. `Hidden` is usually selected when the trigger is not ready to be used in the integration, or for polling triggers that power [dynamic dropdown](/platform/build/add-fields#dynamic-dropdown) fields.

– **Directions** is used for [static webhooks](/platform/publish/integration-checks-reference#d017---static-hook-is-discouraged) only to describe how and where to copy-paste the static webhook URL for the trigger within your app. **Directions** will not show to users in other cases. Static webhooks are not permitted in public integrations.

*   Click on the **Save and continue** button.

## 2. Complete the Input Designer

On the Input Designer page, add user [input fields](/platform/build/add-fields) needed by your API to watch for the triggering item.

Trigger input fields allow users to enter filters, tags, and other details to filter through new or updated data at the endpoint.

If no input data is needed for this trigger's endpoint, continue.

## 3. Set up the API Configuration

Platform UI selects a *Polling* trigger type by default.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/0f08230cffa8a3a568d4847e35e42d0c.png" />
</Frame>

Enter your API URL in the *API Endpoint* field. If your API URL requires specific data in the URL path, enter the following text in the URL where your API expects that data, replacing `key` with the input field key containing the relevant input field you created in the previous step:

`{{bundle.inputData.key}}`

Otherwise, Zapier will automatically include any input field data with the API call as URL parameters (for GET requests), or in the request body as JSON (for POST requests).

If your API requires any additional data to return the new or updated items in the [expected response type](/platform/build/response-types) of an array sorted in reverse chronological order, add it using the *Show Options* button to expose more detailed request configuration. Alternatively select *Switch to Code Mode* to [further customize the API call](/platform/build/code-mode) in JavaScript code.

Only if you plan to use this trigger to power dynamic dropdown menus in other Zap steps (such as to find users, projects, folders, and other app data often used to create new items), and if your API call can paginate data, select *Support Paging* (see [more details on dropdowns](/platform/build/add-fields#dynamic-dropdown) below).

Once you've added your trigger settings, click *Save API Request & Continue*.

## 4. Test your API request

Configure test data to [test the polling trigger](/platform/build/test-triggers-actions).

## 5. Define your output

Define sample data and output fields following [the guide](/platform/build/sample-data).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Reduce requests to your API



## Trigger/action runs in a Zap

### Constraint

Each time a Zap runs and your integration is invoked, it can mean multiple requests to your API endpoints, depending on what your trigger/action seeks to accomplish. This can mean hundreds of requests/hour for your polling triggers based on a [Zapier customer's plan](https://zapier.com/pricing), or thousands based on a Zap with an active REST Hook Trigger from another app and an action in your app.

For triggers/actions that include files in the output, each time a Zap step requests a file from your API, it will be accessed and downloaded from the relevant endpoint.

### Best practice

One way to reduce that API load is via the Zapier platform [dehydration feature](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dehydration). By putting any secondary requests behind a dehydration pointer, Zapier will only make this request once, although it might see the same records again and again based on the Zap's polling cycle.

For file outputs, implementing dehydration means the file will only be accessed and downloaded when a later Zap step asks for it.

To make this even more efficient, you can [stash the file](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#stashing-files) at Zapier. Rather than provide the file to the requesting step, Zapier will stash the file (under a dehydrated URL), so that only one request will ever be made for the file from your API.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Reorder or remove action

Whenever a user selects your app's integration in a Zapier action step, they'll see every _create_ and _search_ action in your integration. 

## Reordering actions

Zapier shows *create* actions first, followed by *search* actions. Within the Create and Search sections, actions are listed in alphabetical order and this order cannot be changed.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/5d3c2e8f9f6cf0f6daadd3ee97fa5e80.png" alt="Actions inside Zapier" />
</Frame>

If you don't want an action to be shown, you can change the action's visibility at any time.

To change an actions's visibility:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section on the left sidebar, click **Actions**.
4.  Click on the action whose visibility you wish to change.
5.  Scroll to the bottom of the page to the **Visibility in Editor** and select `Hidden` if you want to keep users from being able to select the action
6.  Users with that action selected in their existing Zaps would continue to be able to use it, but if they edit the Zap and select a different action, they will not be able to select the `Hidden` action again.

## Removing actions

You may want to remove an action your app no longer supports. Deleted actions cannot be restored.

If you remove an action from a live Zapier integration, this will break existing Zaps. As such, before removing an action, always [create a new major version](/platform/manage/versions) of your integration, hide the action in the new version, to allow users to [manually switch to a new action](/platform/manage/change-keys) without breaking their Zaps. Monitor integration usage by action from the Dashboard to only remove actions with no usage.

To remove an action:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section on the left sidebar, click **Actions**.
4.  Click on the ellipses for the action you wish to remove, and click **Delete**.
5.  On the confirmation prompt, click **Delete**

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/809321da0cd78edef6f464eb1f803855.png" alt="Remove Zapier action" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Reorder or remove triggers

Triggers are listed in alphabetical order in the Zap editor and this order cannot be changed.

## Reordering triggers

You can, however, change a trigger's visibility to control if it is shown to users or not.

To change a trigger's visibility:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section on the left sidebar, click **Triggers**.
4.  Click on the trigger whose visibility you wish to change.
5.  Scroll to the bottom of the page to the **Visibility in Editor** and select `Hidden` if you want to keep users from being able to select the trigger.
6.  Users with that trigger selected in their existing Zaps would continue to be able to use it, but if they edit the Zap and select a different trigger, they will not be able to select the `Hidden` trigger again.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/51c3b8911b5384ddf03b9dbfffd40050.png" />
</Frame>

## Removing triggers

You may want to remove a trigger that your app no longer supports, or fully rebuild a new one in place of the previous one.

> Note: It is best practice to not remove a trigger that has been used in a live integration version. If a trigger is in use, it is recommended to [hide it rather than deleting it](/platform/manage/planning-changes#updates-to-triggeractionsearch-keys). Only remove unused triggers from staging or development versions.

To remove a trigger:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section on the left sidebar, click **Triggers**.
4.  Click on the ellipses for the trigger you wish to remove, and click **Delete**.
5.  On the confirmation prompt, click **Delete**

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/fc65459abad8ac60504f6085078bb361.png" />
</Frame>

Deleted triggers cannot be restored.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add authentication fields to Request Template

The Request Template is a request editor that lets users set static values that apply to all requests made by this integration. Users can configure the URL params, HTTP headers and request body. This is the perfect place to set authentication fields.

<Frame>
  <img src="https://cdn.zappy.app/987abd7e65935ecb581fd356b45fb4bc.png" />
</Frame>

Adding input fields to the Authentication Fields also writes the fields to the Request Template.

<Frame>
  <img src="https://cdn.zappy.app/8cfafbc546e1693fa1e517ccc14a96ab.png" />
</Frame>

By adding fields to the Request Template, they will also show up in the request builder of trigger and actions. The applicable fields for authorization in triggers and actions under Options would then be disabled with help text. You'll need to access the Request Template under Advanced Settings to make changes to them.

<Frame>
  <img src="https://cdn.zappy.app/a3ba1e8593946e7e99a361afe291050e.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Response types Zapier expects

With every API call, Zapier expects the response data to be returned in a specific response type. This can vary depending on what part of your integration you're working on. Use the table below to identify the correct response type to use

| Method                     | Response type | Notes                                                                                                                                                                                      |
| -------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Authentication**         | Object        | Single JSON-formatted object with any specific fields for auth scheme.                                                                                                                     |
| **Authentication testing** | Object        | Object                                                                                                                                                                                     |
| **Trigger**                | Array         | JSON-formatted array with the results in reverse chronological order. Results are parsed and passed through [deduplication](/platform/build/deduplication). Empty arrays will not trigger. |
| **Create action**          | Object        | Individual fields within object are parsed for mapping into subsequent Zap steps.                                                                                                          |
| **Search action**          | Array         | JSON-formatted array sorted with the best match first. Only the first item will be returned. For no match found, return a `200` with an empty array.                                       |

<Info>
  **Note**:

  *   If your app's API call does not return a response, Zapier will show a timeout error.
  *   If you use line items in your integration, note that not all Zapier integrations support line items. Use the recommended response type in the table to reduce users possibility of needing to reformat this data to use in their Zaps. Learn more on [how line items work in Zaps](https://help.zapier.com/hc/en-us/articles/8496277737997).
</Info>

## Return your data as line items for search or create actions

Line items are used to present multiple items associated with a single transaction, such as an order or an invoice. To return line items in the data for your users in their search or create actions, you'll need to return the multiple items you want to show as an array of objects under a descriptive key. This may be as part of another object (like items in an invoice) or as multiple top-level items.

For example, a Create Order action returning an order with multiple items might look like this:

```js
order = {
  name: 'Zap Zaplar',
  total_cost: 25.96,
  items: [
    { name: 'Zapier T-Shirt', unit_price: 11.99, quantity: 3, line_amount: 35.97, category: 'shirts' },
    { name: 'Orange Widget', unit_price: 7.99, quantity: 10, line_amount: 79.90, category: 'widgets' },
    { name:'Stuff', unit_price: 2.99, quantity: 7, line_amount: 20.93, category: 'stuff' },
    { name: 'Allbird Shoes', unit_price: 2.99, quantity: 7, line_amount: 20.93, category: 'shoes' },
  ],
  zip: 01002
}
```

While a Find Users search action could return multiple items under an object key within an array, like this:

```js
result = [{
  users: [
      { name: 'Zap Zaplar', age: 12, city: 'Columbia', region: 'Missouri' },
      { name: 'Orange Crush', age: 28, city: 'West Ocean City', region: 'Maryland' },
      { name: 'Lego Brick', age: 91, city: 'Billund', region: 'Denmark' },
    ],
  }];
```

A standard search returns only the inner array of users, and only the first user would be provided as a final result. Returning line items instead means that the first result return is the object containing all the user details within it.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Output data, defining sample data and output fields

This guide will explain what output data, sample data and output fields are and how to modify them in your triggers or actions.

## Output data

Each Zap step must return data to Zapier to use in subsequent steps. By default, the output data is the direct response from your API—but in some cases, you may need to customize the response data to make it work well with Zapier. Here are general principles for output data from your Zap steps:

**Separate data where possible**, to make it as widely usable as possible in subsequent Zap steps. Names should be split into separate first/last or given/surname pairs, along optionally with a full name field. Addresses should be separated into their individual components.

**Format Date-time values in [ISO 8601](http://www.cl.cam.ac.uk/~mgk25/iso-time.html#date) standard including time zone offset**, even for UTC times. Avoid UNIX or Epoch timestamps. Date responses may be modified in your API call custom code if your API returns dates in different formats. Example acceptable date-time values include:

*   `2023-12-15T01:15:13Z` (or `-0000` instead of `Z`)
*   `2023-12-01T12:32:01-0800`
*   `2023-12-01T12:32:01-08:00`
*   `2023-12-13` (for date-only values)

**Optionally include an additional human-friendly date** especially for scheduling or calendar app integrations where the date is important for users.

**Set boolean values as `true` or `false`**. Do not use `1` and `0` for boolean values.

**Include the value name and ID in lists and dropdown menus** to help users know which item to choose.

**Consider removing non-necessary fields that may seem confusing to users** in your API call's custom code.

## Sample data

Sample data gives Zapier example data if users don't test the trigger or action. Though optional, it is especially important for triggers and also useful in actions.

In the Zap editor, Zapier will attempt to retrieve or create existing data to test triggers and actions. With triggers, Zapier will try to fetch recently added or updated items during the test. If the connected account doesn't have any data for this item (polling triggers) or the Perform List is not defined (REST Hook triggers), the user will see an error that no items are available.

Users can also opt to skip those test steps. In both cases, Zapier shows the sample data instead, to allow users to map fields correctly in subsequent Zap steps.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/05e9be8a62a663f42ab25cf2b17591b8.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/42353c3702ca94af6000e3efb926a3f2.png" />
</Frame>

Sample data must be JSON-formatted and use the same field names as your app's API. Either click the *Use Response from Test Data* button to import the fields your app sent to Zapier in the previous test, or add your own JSON-formatted fields. No personally identifiable data should be included, and the copy must be safe for work.

Only include fields that are present every time a Zap runs. If a field is provided in the sample, it can be mapped into a field in a later action by any user.

If that mapped field is then not available when a user's Zap runs, the action field will be empty, causing errors or unexpected results for users. For example, suppose your sample data looks like this:

```JSON
{
  "id": 1,
  "first_name": "Jane",
  "last_name": "Suarez",
  "email_address": "janesz@example.com",
  "job_title": "Executive Director"
}
```

A user might map the `job_title` information into a required field in another app, such as a CRM. Then, when the Zap runs, `job_title` is only included in the live result if it happens to be available, and the data the Zap receives looks like this:

```JSON
{
  "id": 5,
  "first_name": "Jacob",
  "last_name": "Giotto",
  "email_address": "jacob@example.com"
}
```

The user's Zap run will error when the request to the CRM's API attempts to add the person, because `job_title` is a required field in the CRM but there's no data in it. To avoid this, there are several [integration checks](/platform/publish/integration-checks-reference) that require sample and live Zap run data to match.

## Output fields

Output fields give your API's response data user-friendly labels in subsequent Zap steps.

By default, Zapier uses a basic human-friendly transformation of field names, capitalizing words and adding spaces instead of underscores. You can customize this further with Output Fields.

## How to modify your sample data and output fields

In your trigger or action settings:

1.  Click *Step 3 Define your Output* to expand this section.
2.  In the *Sample Data* field, add in your **JSON formatted sample data output**.
3.  Click **Generate Output Field Definitions**.
4.  In **Output fields** you'll see a table of fields with keys on the left, and field types on the right. Add a **human-friendly name** for each field in the center Label column, and select the **field type** in the right hand column.
5.  Click **Save Output & Finish**.

For example, if you use GitHub's API to watch for new issues, the API calls the issue name `title`. Users may expect that field to be called *Issue* or *Issue Title*, so you could define the Output Field as having the name *Issue Title*, rather than the default transformation of “Title”.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/293b09e9593b591de3c735988f1a5f19.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add a search action



## 1. Add the action settings

*   Open the *Actions* tab in Zapier's Platform UI from the sidebar on the left, and select **Add Action**, selecting your action type. New actions are *create* type by default, and add new data or update existing data to your app.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/3e47ad8a26c30fb761fdd60390d8705e.png" alt="Zapier Search action settings" />
</Frame>

<Info>
  **Note**: You cannot change an action type once you click *Save and Continue* on a new action. If you need to change the action type, delete the action and recreate it.
</Info>

*   On the Settings page, specify the following:

– **Key**: A unique identifier for this action, used to reference the action inside Zapier. Does not need to be the same identifier as used in your API. Not shown to users.

– **Name**: A human friendly plain text name for this action, typically with a verb such as *Find* or *Search* followed by the name of the item this action will find in your app. The title-case name is shown inside the Zap editor and on Zapier's app directory marketing pages.

– **Noun**: A single noun that describes what this action searches, used by Zapier to auto-generate text in Zaps about your action.

– **Description**: A plain text sentence that describes what the action does and when it should be used. Shown inside the Zap editor and on Zapier's app directory marketing pages. Starts with the phrase “Finds a”.

– **Visibility Options**: An option to select when this action will be shown. *Shown* is chosen by default.

*   At the bottom of the settings page, you'll see an option to pair your *search* with a *create* action. That lets your [action also create an item](/platform/build/search-or-create) if the search does not return any results.

*   Click on the *Save and Continue* button.

## 2. Complete the Input Designer

On the *Input Designer* page, add user [input fields](/platform/build/add-fields) for this action. All action steps *must* include an input form for Zapier to gather the data needed to create or find items in your app. Add at least one input field to your action.

Before building your action's input form, list each piece of data your app needs to find an item. Most search actions only include a single input field, sometimes along with a drop-down menu to select filter data.

## 3. Set up the API Configuration

In the final *API Configuration* page, add the API endpoint where Zapier will send the search request to.

A `GET` call is used for search actions by default, and sends the data from the input form to your API endpoint.

Zapier expects an array response with 0 or more items. If more than 1 item is returned in the array, Zapier shows only the first match, and parses it into individual fields for use in subsequent Zap steps.

If you prefer your search to return multiple results, return the set of results as an array of objects ([line items](https://help.zapier.com/hc/en-us/articles/8496277737997)) under a descriptive key. However, using the standard approach is recommended, because not all integrations support line items directly, so users may need to take additional actions to reformat this data for use in their Zaps, depending which app they pair your *search* with.

If you'd still like to offer your users a *search* that returns multiple matches, it is recommended to consider offering both a single item *search* and multiple item *search* - for example a `Find Record` that returns a single result and a `Find Record(s)` that could return multiple results.

For no match found, a `200` with an empty array must be returned to ensure the *search* step behaves as expected in the Zap editor. A *search* that returns no results is still considered a successful action step and [should not return an error](/platform/build/response-types).

If you need to parse the response from the endpoint into the expected response type, switch to [Code Mode](/platform/build/code-mode) to write custom JavaScript code for your action.

## 4. Test your API request

Configure test data to [test the *search* action](/platform/build/test-triggers-actions). Testing a GET request would be expected to return the item from the endpoint.

## 5. Define your output

Define sample data and output fields following [the guide](/platform/build/sample-data).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add a search or create action

When adding a _search_ action type, you'll see the option to _Pair an existing search and a create to enable “Find or Create” functionality_ in the _Settings_ page. This embeds the _create_ inside the _search_ step to find or create items in one step of the Zap.

## Add a *create* action

*   Add a relevant *create* action to your integration. If your *search* action is looking for contacts, say, you would need a *Add New Contact* action to pair with it. Open your integration's *Actions* page in a new tab and add a new *create* action if your integration does not have an appropriate one already.

## Configure the *search* action

*   Back in your new *search* action's settings, check the *Pair an existing search and a create* box

*   Select the relevant action from the *Create Action* menu and add a new label that Zapier will show on this step if users choose to have the action create new items as well.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/41334f793dfce14d21e6c35361179721.png" />
</Frame>

*   When users use the search action in a Zap, Zapier will show your core *search* action settings that you set in the *Input Designer* by default. Then, if users click the checkbox to create an item if nothing is found, Zapier will load the *create* action's input fields inside the search action so users can fill both out.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/32ba8f867d02cf60cb6c344a0447b5a5.png" />
</Frame>

*   Configure the rest of your search action as normal, including the [Test API Request](/platform/build/test-triggers-actions) and [Output](/platform/build/sample-data) sections.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add authentication with Session Authentication

Session authentication has elements of Basic authentication — where Zapier requests a username and password, and OAuth v2 — where Zapier redirects users to the app's site to allow access. User credentials are exchanged for a token used to authenticate subsequent API calls.

It is similar to cookie-based authentication in your browser, only here the “cookie” is an auth token stored by Zapier.

<Frame>
  <img src="https://cdn.zapier.com/storage/photos/7c7092a2311cf217298cb3e3f5735385.png" />
</Frame>

Use Session authentication with your Zapier integration if your API is designed for session-, cookie-, or token-based authentication.

## 1. Build an input form

*   Open the *Authentication* tab in Zapier visual builder and select *Session Auth*.

<Frame>
  <img src="https://cdn.zappy.app/2453d5aa12c5aa4fe02beae9d85f6786.png" />
</Frame>

*   Session auth does not include any default input fields. Add the fields required by your API by selecting *Add Fields* and fill in the details for each field. Add the most commonly needed fields first, in the order users expect, as you cannot reorder fields once added.

*   Two types of fields are available when building an Session input form. Standard Fields, work much like other form fields with Zapier's [input form](/platform/build/field-definitions) in triggers and actions. [Computed Fields](/platform/build/computed-fields) make sure specific fields are returned by your app's authentication API call response.

*   For each field, add the required *Key*, the name your API uses to reference this field.

*   Fill in the optional fields, as appropriate, especially the *Label*:

– **Label**: A human-friendly name for this field that will be shown to users in the authentication form.

– **Required? (checkbox)**: Check if this field is required for successful authentication.

– **Type**: All input fields use the `string` text field by default; select `password` instead if you would like to obscure the data as users enter it.

– **Help Text**: Include details to assist users in authenticating with your app, especially if they may be unsure where to find the data needed within your app. Format text with [Markdown](https://zapier.com/blog/beginner-ultimate-guide-markdown/), and include a hyperlink if needed.

– **Input Format**: (optional) Help users figure out exactly what piece of data you need them to enter. For example, for [a subdomain](/platform/build/subdomain-validation), [https://.yourdomain.com/](https://.yourdomain.com/).

– **Default Value**: Include a value for this field to be used as a fallback. For optional fields, the default value is set on initial connection creation and used in the API call instead of missing or null values every time the Zap runs. For required fields, this value is used during connection creation, but not when the Zap runs (Zapier raises an error for missing/null values instead).

*   Input fields marked as password and all authentication fields with sensitive, private data such as both username and password from Session Auth are automatically censored at runtime. These values are stored in the Auth bundle and used in API calls, but are replaced in Zapier's logs with a censored value like this `:censored:6:82a3be9927:`. Due to this, it is not possible to view the exact tokens or keys in Zapier's logs. To verify that the same token as was returned by the authentication, is being used in subsequent API calls; you can compare censored value characters, for example `:censored:6:82a3be9927:` would have the same value ending in 9927 when used in a subsequent call.

*   Each input field is listed with its label, key, type, and required status in your authentication settings. Click the field to edit it, or click the gear icon and select *Delete* to remove a field.

*   For computed fields, available in OAuth v2 and Session Auth only, review [computed fields documentation](/platform/build/computed-fields).

## 2. Add a Token Exchange Request

*   Add the token exchange request URL and select the HTTP method. Zapier will automatically include the data from the input fields in the API request body. If your API expects the data in the URL Params or HTTP headers instead, or requires additional data, click *Show Options* and add the details your API call needs. It is typically not recommended to pass any sensitive information such as the password in the URL Params. Passing it through the headers or the body is preferable.

<Frame>
  <img src="https://cdn.zappy.app/70908a4341146b3df38c9a3169f68cfb.png" />
</Frame>

*   To customize the token exchange request, select *Switch to Code Mode* and write custom JavaScript code to handle the API call and the response parsing as needed. The first time you click the toggle, Zapier will [convert your API call to code](/platform/build/code-mode). If you switch back to Form Mode though, Zapier will not convert your code changes to the Form Mode, nor will any subsequent changes in the form be added to your code.

*   If your token exchange request doesn't return the token and/or any [computed fields](/platform/build/computed-fields) at the top level, also use [Code Mode](/platform/build/code-mode) to modify the response so those fields are available at the top level. It is not possible to store an object with nested keys from the response.

*   All values will be referenced via `{{bundle.authData.field}}`, where `field` is the key in the response.

## 3. Add a Test API Request

*   Add an API call to your API that requires no configuration, typically a `/user` or `/me` call. Add the URL for the API call, and set the call type, typically a `GET`. This will test the user-entered credentials to ensure it enables a successful API call to your app.

*   Session Auth doesn't include a defined standard for how access tokens are included in subsequent API calls, so unlike other authenticaton methods, Zapier doesn't include the access token by default. You'll need to add it in the test request and in subsequent Trigger and Action step API calls.

*   Click *Show Options*, then add the access token to your API call's URL Params or HTTP Headers as needed. It is typically not recommended to pass any sensitive information such as the password in the URL Params. Passing it through the headers or the body is preferable. The Access Token will be in the `bundle.authData`, and typically be referenced as `{{bundle.authData.access_token}}`, `{{bundle.authData.sessionToken}}`, or a similar field, depending on how your token exchange response includes the token.

<Frame>
  <img src="https://cdn.zappy.app/bf9711293b10af85200fb8d7bfe21e39.png" />
</Frame>

## 4. Configure a Connection Label

Review [connection label documentation](/platform/build/connection-label) to optionally differentiate the app accounts users connect.

## 5. Test your authentication

Connect a valid user account to [test authentication](/platform/build/test-auth).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Enable static IP address connection for customers

To enable customers to access your integration by static IP address, all outbound traffic from Zapier to your integration will need to be routed through a smaller set of consistent IP addresses. Any app owner/developer can request to enable the static IP address feature on a private or public app.

## Traffic from Zapier

Traffic from Zapier uses the following IP addresses:

*   44.214.195.64/28 (us-east-1)
*   18.246.81.208/28 (us-west-2)

Enabling this feature will increase the volume of requests to your server received from these IP addresses. This means this change would affect all users of your integration who are on a Zapier Teams, Company, or Enterprise plan.

Review our documentation on how the [static IP address functions within Zapier](https://help.zapier.com/hc/en-us/articles/15406083674509-Use-a-static-IP-address-to-connect-to-Zapier).

## How to enable static IP address connection

1.  **Consult with your Security team:** They can confirm if a static IP address is permitted within your network security policies and advise on any potential conflicts with existing security measures.

2.  **[Contact Zapier Support](https://developer.zapier.com/contact)**: They can help you enable static IP addresses for any public or private app integration you are an [Admin](/platform/manage/add-team) for. The turnaround time is typically within 1 business day.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Validate domain and subdomain input fields during authentication

When adding a subdomain input field, commonly used in OAuth implementations, additional validation is strongly recommended to prevent a potential security vulnerability. If not taken into account, an attacker could utilize a maliciously constructed subdomain field (like `attacker-domain.com/`) in order to redirect OAuth connection requests to that attacker-controlled domain (because `attacker-domain.com/.your-domain.com` resolves to the attacker's domain instead of the expected one). Taking the following steps prevents the potential for an attacker to access your integration's sensitive authentication information, such as the OAuth client ID or secret.

## Prerequisites

*   An authentication method that uses pre-configured tokens or secret values (for example, OAuth 2)
*   User is able to input a domain or subdomain when authenticating within Zapier
*   Your integration stores sensitive authentication details (in environment variables, for example) which are used as part of the authentication process

## Steps

1.  If your integration allows for the user to provide a domain, validate the input against an allow-list of trusted domains.

2.  If your integration allows for the user to provide a subdomain, add conditional validation for the subdomain string whenever you include the value in your OAuth HTTP requests. This change will prevent potential exploitation of the subdomain vulnerability.

### Handle subdomain validation in Platform UI

*   Update the *Access Token Request* and related sections under the *OAuth v2 Endpoint Configuration* options, using the [Code Mode](/platform/build/code-mode) editor.
*   Example code for handling subdomain validation in integrations built using the Platform UI, via Code Mode:

```js
// --- UPDATE: add your validation for the subdomain field before using it ---
if (!/^[a-z0-9-]+$/.test(bundle.authData.yourSubdomainField)) {
 throw new Error(
   "Subdomain can only contain letters, numbers and dashes (-)."
 );
}

const options = {
 url: `https://${bundle.inputData.yourSubdomainField}.mydomain.com/oauth/access-token`,
 method: 'POST',
 json: {
   'code': bundle.inputData.code,
   'client_id': process.env.CLIENT_ID,
   'client_secret': process.env.CLIENT_SECRET,
   'grant_type': 'authorization_code',
 },
}

return z.request(options)
 .then((response) => {
   const results = response.json;
   return results.data;
 });
```

### Handle subdomain validation in Platform CLI

*   If you're using OAuth-based authentications, update the `getAccessToken` and optional `refreshAccessToken` configuration methods. If the integration uses [shorthand HTTP requests](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#shorthand-http-requests), switching to [manual HTTP requests](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#manual-http-requests) will allow you to perform this manual subdomain validation.
*   Example code for handling subdomain validation in integrations built using the Platform CLI:

```js
const refreshAccessToken = async (z, bundle) => {
 // --- UPDATE: add your validation for the subdomain field before using it ---
 if (!/^[a-z0-9-]+$/.test(bundle.authData.yourSubdomainField)) {
   throw new Error(
     "Subdomain can only contain letters, numbers and dashes (-)."
   );
 }

 const response = await z.request({
   url: `https://${bundle.authData.yourSubdomainField}.mydomain.com/oauth/token`,
   method: "POST",
   body: {
     client_id: process.env.CLIENT_ID,
     client_secret: process.env.CLIENT_SECRET,
     grant_type: "refresh_token",
     refresh_token: bundle.authData.refresh_token,
     redirect_uri: bundle.inputData.redirect_uri,
   },
 });

 return {
   access_token: response.data.access_token,
   refresh_token: response.data.refresh_token,
 };
};
```

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Test authentication

Testing a user's authentication is crucially important, as it is later used to test subsequent trigger and action steps when built.

## Prerequisites

*   Completed authentication configuration with your app's authentication scheme
*   Set of valid user credentials for your app - recommended to use a new account specifically for testing so you don't clutter your core app account with testing data.

## Steps

1.  In the final *Test your Authentication* step, enter user credentials
2.  Select *Test Authentication* to make the test API call you configured
3.  Successful authentication shows a green check and a *Request Successful* message at the top of the dialog.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/60ebf09a1442345cc5d86b44440eb11c.png" />
</Frame>

4.  The *Response* tab shows the JSON data response from your API, with each field and its data listed.
5.  The *Bundle* tab shows the data Zapier stores about your authentication. Values returned by the test API call are stored in the `inputData` [bundle](/platform/build/bundle#inputdata) and can be accessed by `{{bundle.inputData.field}}` where `field` is the field key from the API response, for use in the [connection label](/platform/build/connection-label) or trigger and action requests. However, responses from the test API call are not stored for subsequent requests in the case of OAuth v2 and session authentication unless you use [computed fields](/platform/build/computed-test-field).
6.  The *HTTP* tab shows the full request details for each API call Zapier made during the test.
7.  The *Console* tab shows any additional data your API calls logged in Zapier if you use custom code for any part of your authentication.
8.  Check each tab to ensure the expected data came through - it is possible to have an unsuccessful API call come through as successful if your API returns a message without an HTTP error code. Check the Response to make sure it includes the data expected from this API call, and if anything is incorrect, check the HTTP tab to help diagnose where things went wrong.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/dacbdf7561aa6d2a78e599f12b80a325.png" />
</Frame>

9.  Clean up and remove older testing accounts from your core Zapier account. Open your [Zapier *Connected Accounts*](https://zapier.com/app/connections) page, and find your app's name (you may need to use the search box or `CMD`+`F` (Mac) or `Ctrl`+`F` (PC)). Click the app name. Once on the app's connections page, identify the connection to remove and click the three dots, then *Delete*, and confirm the deletion. Repeat for each subsequent testing account you added to clean up your authentication list.

    Then refresh your integration page in the Platform UI, and you'll only see the authentications that were not deleted.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/943cd7b0ee2ada32492c834157a2eccb.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b31f5c7f712c1a585e727b729248615a.png" />
</Frame>

10. A caveat of testing in the Platform UI is that tokens are not saved in the Zapier database, so if you are testing the access/refresh token mechanism, make sure to test in a Zap in your account to avoid connections expiring or being marked as invalid.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Test and monitor your integration in your Zapier account

Testing inside the Platform UI is crucial during the building process. To ensure users can benefit from your integration's features, it is equally crucial to test your integration within the Zap editor. This is the best way to notice details that might have been overlooked while building your integration.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/4f69910e7b7d5fb9325d0e36579bca5a.png" />
</Frame>

## Prerequisites

*   Completed build of your Zapier integration
*   A [Zapier account](https://zapier.com/sign-up)
*   If you haven't used Zapier before, you'll want to learn the basics in our [Zapier Getting Started Guide](https://zapier.com/learn/zapier-quick-start-guide/).
*   Set of valid user credentials for your app - recommended to use a new account specifically for testing so you don't clutter your core app account with testing data.

## Steps

1.  Create a Zap in the [Zap editor](https://zapier.com/app/editor/), selecting your app in the *Choose App* selector.

2.  Your integration will show the name and logo set in *Integration Settings*, along with the integration's current version number and a *By Invite* tag. If your integration is a new version of an existing *Public* integration, look for the *Latest* tag, the latest version number and the *By Invite* tag to differentiate from existing, public integrations.

3.  Check each of the following as you setup Zaps:

    *   **Authentication**: Does your app account successfully connect? Zapier will show any testing accounts you already added, but try adding a new account here for a complete test. To remove connected accounts, you can delete connections from [Apps/Custom integrations](/images/c97ff65c5857c3ebfeb302ffa8454867.png).
    *   **Connection Label**: Does the Zap editor show the [expected connection label](/images/693c0b1c08dabf06ea515995eab636aa.png) on the new account, with the info you set when building your integration?
    *   **Trigger and Action List**: Do you see every trigger and action included in your integration? Is each trigger and action's name and description accurate and grammatically correct?
    *   **Fields**: Do triggers and actions show the input fields you expect? Are their names and labels accurate and grammatically correct? Do any links or formatting in descriptions work correctly? Do drop-down fields or those with multiple selectors work correctly?
    *   **Output**: When you run a Zap trigger or action's test, does the step return the fields and data you expect in the correct format? Do triggers show the most recently added item from your app, and do searches return appropriate results?
    *   **Input**: Open your app, and check each item that Zapier actions added or updated for the correct format and expected appearance in your app.
    *   **Automation**: Turn on Zaps with each of your integration's triggers and actions. Do they run correctly when the data they watch for is added or updated in your app? You can check your [Zap History](https://zapier.com/app/history) to make sure Zaps ran as expected for triggering events. Do your integration's actions successfully add and update items when those Zaps are triggered?

## Monitoring

Use the *Monitoring* page in the Platform UI to ensure that test Zaps and the expected requests are running without errors. Every request made to your API by your Zapier integration is shown.

Adjust the Chart Filter for the correct timeframe, then click on any data point in the chart to see the any error messages and logs which should help you troubleshoot further. You can also filter by log type and by user email.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8e7113b876e9dd37b71722fee763cf3e.png" />
</Frame>

To manually print a log statement you can see in Monitoring, use `z.console.log` in Code Mode:

`z.console.log('Here are the input fields', bundle.inputData);`

## User testing

Have internal team members and/or beta users test your integration. If you are going to submit your app for *Publishing* in the Zapier App Directory, you'll need at least 3 users with live Zaps. Each additional tester helps ensure that your app doesn't ship with usability problems or bugs.

Internal team members can be invited from *[Manage Team](/platform/manage/add-team)* as admins or collaborators.

Beta users external to your organization can be invited from *[Sharing](/platform/manage/sharing)*

The *Insights* section in the Platform UI provides usage statistics by trigger and action.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Testing Tools

The Zapier platform provides a set of tools to help inform and validate your integration before pushing changes out to users.

## Canary Testing

Canary testing is a way to test new changes temporarily with real users in production with the goal of validating changes to ship new changes with more confidence and reducing bugs. These users are not informed or aware of the changes, as this is usually done at random and in small subsets to obtain a sample. Builders should have careful monitoring in place to watch for errors and rollback when necessary.

### Prerequisites

*   Completed build of your Zapier integration, built from the CLI (as of Sep 23, 2024, this is a CLI only feature)
*   If you haven't used Zapier before, you'll want to learn the basics in our [Zapier Getting Started Guide](https://zapier.com/learn/zapier-quick-start-guide/).

You may want to use the canary tool when adding a new feature, or fixing a bug. For example, if you are planning to roll out a bug fix, you may want to test this out to see if the fix will work. The usual validation steps may include unit tests, and [setting up Zaps for validation](/platform/build/test-monitoring). Unit tests will ensure your integration code functionality matches what you intended to do. Setting up Zaps will ensure the trigger or action works with the inputs you provide. The canary tool ensures your changes work for many existing live Zaps, with different inputs and outputs.

[`zapier canary`](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#canarycreate) provides a new way to validate your integration and build confidence that the change can work for all different types of Zap set ups.

[`zapier canary:create`](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#canarycreate) allows you to set the version you want to test with, the version you want to replace with, the percentage of traffic, and a duration before the versions are rolled back.

[`zapier canary:list`](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#canarylist) allows you to see the active canary and see how much time is left.

[`zapier canary:delete`](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#canarydelete) You can choose to delete the canary test before the duration is expired in case something unexpected occurs.

### Best Practices

*   **Start Small**: Begin with a small percentage of traffic.
*   **Monitor Closely**: Use monitoring tools to track performance and errors.
*   **Communicate**: Inform your team about the canary test.

### Troubleshooting

*   **Issue: High Error Rate**: Rollback immediately and investigate the logs.
*   **Issue: Performance Degradation**: Reduce the traffic percentage or rollback.

### FAQ

<AccordionGroup>
  <Accordion title="Q: What happens if I don't rollback in time?">
    A: The system will automatically rollback to the previous version after the specified duration.
  </Accordion>

  <Accordion title="Q: Can I extend the duration of a canary test?">
    A: Yes, you can extend the duration by stopping the existing canary, and re-running the `zapier canary` command with a new duration.
  </Accordion>

  <Accordion title="Q: How do I monitor the canary test?">
    A: Use Zapier's monitoring tools and logs to keep track of the test performance. We don't currently have a way to isolate monitoring for canary, only the overall success or error pattern. Test
  </Accordion>
</AccordionGroup>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Test triggers or actions

Once authentication is tested, trigger and action steps are easy to test inside Zapier visual builder. Set up the trigger or action settings and API calls, then as the last step the familiar _Test Your API Response_ box appears. It will show any accounts you added to your integration previously during the authentication testing.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b8bd7bcad203fd0473c25a64dd2203c6.png" />
</Frame>

## Prerequisites

*   Completed authentication configuration with your app's authentication scheme
*   Set of valid user credentials for your app - recommended to use a new account specifically for testing so you don't clutter your core app account with testing data.
*   A successful authentication test

## Steps

1.  Within *API Configuration* for each trigger or action, access the *Test your API Request* section
2.  Fill in details for each of the input fields in the *Configure Test Data* form. Add data that will successfully work in this API call, similar to what you would use in a live Zap.
3.  Enter individual values in each field to add single objects. If you include commas in the field data, Zapier will turn that field into an array sent to your API. Select *Raw* to preview the JSON formatted data.
4.  Select *Test Your Request* to run the trigger or action step, verify it ran successfully and show the JSON results which you can explore as in the Authentication testing.
5.  If an error is returned, check the following:

    *   Authentication: Did your app's authentication work correctly in the authentication step? You can only test an integration once you've connected an app account to Zapier.
    *   Test Data: Did your test data include the details your app expects, such as actual dates in date fields or complete email addresses in email address fields?
    *   Input Field Keys: Did you use the same field keys in your input field as your API expects? Double-check that in the Input Designer, and change if needed.
    *   API Call Customization: Does your API expect something different than the standard API call details Zapier sets by default? You may need to use Code Mode if the options you need aren't available.
6.  When testing a [REST Hook trigger](/platform/build/hook-trigger), you will instead have to create and test a Zap in the Zap Editor as follows:

    *   Test the trigger to confirm that the Perform List works and provides live data from the app.
    *   Turn on the Zap to confirm that the subscription is successful.
    *   Perform the trigger event in the app to confirm that the webhook is sent to the Zap's webhook URL and triggers the Zap.
    *   Compare the data returned from the Perform (can be seen in the [Zap history](https://help.zapier.com/hc/en-us/articles/8496291148685-View-and-manage-your-Zap-history)) with the data returned from the Perform List and confirm that they are both in the same format and have the same information in them.
    *   Turn off the Zap to confirm that the unsubscription is successful. A successful unsubscription should delete the Zap's webhook URL from your app.

It is recommended that you check the logs in the [Monitoring](/platform/build/test-monitoring) component for feedback from your Zap testing.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Trigger



Every Zap has a single trigger. Triggers are how your app's users can start automated workflows whenever an item is added or updated in your app. New or updated contacts, database records, blog posts, subscribers, form entries and project tasks, are examples of items that can be used to trigger a Zap.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/024547d6df8622335ca65456c6d0a11c.png" />
</Frame>

Triggers only watch for new data and typically send no or little data to your app. They are often quicker to set up than Zapier [actions](/platform/build/action).

Design triggers around how users interact with your app, not based on which API endpoints are available.

The are two types of triggers.

## 1. Polling trigger

This trigger type periodically checks the provided API endpoint for new or updated data.

The update time or frequency of polling occurs per active Zap every 1 to 15 minutes, depending on the user's [Zapier plan level](https://zapier.com/app/pricing).

The API endpoint must list new or updated items in an array sorted in reverse chronological order. These are typically the most common API endpoints to read data from a platform. For new item triggers, the endpoint should list newly created items first; while for updated item triggers, recency of update is preferred. If your API lists items in a different order by default, but allows for sorting, include an order or sorting field in your API call to ensure newest records are returned on the [first page of results.](/platform/build/deduplication)

Include details in your trigger description to let users know which type of updates will run the trigger.

Zapier automatically deduplicates incoming polling trigger data, so that Zaps do not run multiple times on the same data. Known as deduplication, this is [explained to users as a concept](https://zapier.com/help/create/basics/data-deduplication-in-zaps). Ensure your polling triggers behave as users expect by following [deduplication guidelines](/platform/build/deduplication) for new item and updated item triggers.

## 2. REST Hook trigger

This trigger requires your app to support REST Hooks - webhook subscriptions that can be manipulated through a REST API. It runs in near realtime with your app pushing data to Zapier, running Zaps as soon as new data comes into your app instead of waiting for Zapier to fetch new data from your API on a polling frequency.

This method of triggering also prevents numerous - and sometimes unnecessary - requests from being made to your API's endpoints in polling requests.

A webhook subscription is created between Zapier and your app, and whenever the trigger event occurs in your app, a webhook is sent by your app to the unique webhook URL provided by Zapier for each active Zap started with that trigger.

Zapier supports immediate webhook handshake confirmations by echoing back the `X-Hook-Secret` header when present, but does not support additional identity verification steps beyond this.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Troubleshoot action payload size



## Testing action in Zap editor

### Constraint

When a user clicks **Test and Review** or **Retest and Review** in the Zap editor, the response payload must be less than 6MB.

### Errors user will see if constraint is hit

*   *“Scripting payload too large (‘n' bytes but max is 6291456bytes).”* - (‘n' is the number of bytes in the payload.)

### Best practice

If your API endpoint supports request filtering, one option is to provide input fields in the action/search so a user can decide what record field data they want to return. If that is not available, you might look at having multiple endpoints for this record data. Your integration could then provide multiple action/searches for the user so they can get a full record.

## Action runs in a Zap

### Constraint

An action/search response payload must be less than 6 MB.

### Errors user will see if constraint is hit

*   *“Scripting payload too large (‘n' bytes but max is 6291456bytes).”* - (‘n' is the number of bytes in the payload.)

### Best practice

Use the simplest available endpoint on your API for the basic record data, and use Zapier platform [dehydration](/platform/reference/cli-docs#dehydration) to request supplementary data from other endpoints. A dehydration pointer is created for each subsequent request, and this pointer will only be resolved if the Zap needs a hydrated property in a later step.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Troubleshoot action timeouts



## Testing action in Zap editor

### Constraint

When a user clicks **Test and Review** or **Retest and Review** in the Zap editor, the output of your `perform` must be returned within 30 seconds.

### Errors user will see if constraint is hit

*   *“The app did not respond in-time. It may or may not have completed successfully.”*

### Best practice

Timeouts can occur on your API endpoint, or within the `perform` method processing the response payload. To speed up the `perform`, check for expensive processing operations, and consider reducing `z.console.log` calls, especially in looping code.

## Action runs in a Zap

### Constraint

Each time a Zap step runs, the action/search's `perform` method must finish processing in 30 seconds. If the API request cannot consistently be finished within 30 seconds - for example, file format conversion, an error will show.

### Errors user will see if constraint is hit

*   An error in the Zap history of their Zap due to the request timing out

### Best practice

For longer-running requests, use the webhook-based callback service the Zapier platform provides. This allows your action to be performed asynchronously, and when finished, POST to the callback URL. More on using this method [here](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#zgeneratecallbackurl).

A user will then see the [Waiting/Delayed status](https://help.zapier.com/hc/en-us/articles/20505304170637-Review-Zap-run-statuses) in Zap history for that Zap step, until the POST is received to the callback url, upon which the task will resume.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Troubleshoot custom fields



## Configuring the trigger/action in Zap editor

### Constraint

If your trigger, action, or search supports retrieving custom fields from your API, these are limited to 1000 custom fields, the output of the method to retrieve the fields must be returned within 30 seconds and the response payload must be less than 6MB.

### Errors user will see if constraint is hit

*   Slow rendering of the step when it is added or edited in the Zap editor
*   Custom fields might not display
*   Not all output fields will be available for mapping in later steps
*   *“The app did not respond in-time. It may or may not have completed successfully.”*
*   *“Scripting payload too large (‘n' bytes but max is 6291456bytes).”* - (‘n' is the number of bytes in the payload.)

### Best practice

Here is an example of the way one integration works around all three of these constraints. [Hubspot](https://zapier.com/apps/hubspot/integrations) offers a CRM product, and users often have thousands of custom fields for Company records.

In the ***Create Company*** action, instead of presenting an overwhelming number of custom fields, they present a set of default fields that all companies have, then allow the user to select the other fields they might need from an ***Additional Properties to Retrieve*** dropdown menu field.

The fields chosen are then retrieved by the Zap editor for user editing. This helps in both making sure the request can be accomplished within the time and size limits, and making sure the user can easily find the custom fields important to their specific workflow.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/4ac409c47f194303c54adff79fcd693f.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Troubleshoot throttles



## Throttling by your API

### Constraint

Your API has request limits.

### Errors user will see if constraint is hit

*   If a trigger, user will receive an email with an error message about the trigger error
*   If an action, user will see an error in Zap history

### Best practice

Add a specific `Retry-After` header to your 429 response, or specify a timed delay in your error response using a special `ThrottledError`. Instead of a user's Zap erroring and halting, the request will be retried at the specified time.

More on the retry [here](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#handling-throttled-requests). The user will see a [Waiting/Scheduled](https://help.zapier.com/hc/en-us/articles/20505304170637-Review-Zap-run-statuses) message in Zap history instead of an error while the limit is still in place.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/0933736266259b11771a0eba0aff23ce.png" />
</Frame>

If implementing a `ThrottledError`, you could consider implementing a jitter for handling 429 errors, that could look something like this to randomize the frequency of the retries as well:

`throw new z.errors.ThrottledError('message here', 60 + Math.floor(Math.random() * 60))`

Keep in mind that adding custom error handling with `ThrottledError` would likely require a [new integration version](/platform/manage/versions), whereas adding to the headers could be implemented on your API's end.

## Webhook throttles by Zapier

### Constraint

Zapier's current webhook limits are [here](https://help.zapier.com/hc/en-us/articles/8496181445261#h_01H91ED0PQ56S166BSB7MJNZNT). A 429 response is returned if your integration exceeds these limits in number of webhooks sent to Zapier.

### Errors user will see if constraint is hit

*   User will receive an email with an error message about the trigger error

### Best practice

You should support a retry/back-off schedule to make sure the data is eventually received.

## Polling trigger throttles by Zapier

### Constraint

There is a default limit of 100 new items recognized per poll after deduplication. More on that [here](https://help.zapier.com/hc/en-us/articles/8496181445261-Rate-limits-and-throttling-in-Zapier#h_01H91ED0PQ1HK1G1YFTZ9NSPRN).

### Errors user will see if constraint is hit

*   The user will receive an email about held Zap runs, as well as a banner with the same information in their Zap history.

### Best practice

If your trigger will be returning > 100 new records consistently, consider converting your trigger to be REST Hook based. Webhook limits are higher (up to 10,000 requests in a 5 minute period).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Troubleshoot trigger payload size



## Testing trigger in Zap editor

### Constraint

When a user clicks **Test Trigger** in the Zap editor, the response payload must be less than 20MB.

### Errors user will see if constraint is hit

*   *“Scripting payload too large (‘n' bytes but max is xxx bytes).”* - (‘n' is the number of bytes in the payload.)

### Best practice

The Zap editor will only process three new records at a time for sample data, so one way of making sure your payload size is less than the limit is by limiting your results to three records.

To determine when the request is for sample data, use the bundle meta parameter `bundle.meta.isLoadingSample`. When that is set to `true`, the user is testing in the Zap editor, and your integration can respond with a limited payload. More on `bundle.meta` properties [here](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#bundlemeta).

## Turning Zap on

### Constraint

When a user toggles a Zap with a polling trigger “On”, Zapier does some additional initialization that must be accomplished in 30 seconds.

First, it tests the user's authentication to your service.

Then it uses the trigger's `perform` method to build a [deduplication table](/platform/build/deduplication) of records, so that the Zap will not run for existing records. The payload returned from this request must also be less than 20 MB.

### Errors user will see if constraint is hit

*   Users will receive an email with an error message that includes the text *“Your Zap could not be turned on”*.

### Best practice

There is a `bundle.meta` property that you can take advantage of here, `bundle.meta.isPopulatingDedupe`. When set to `true`, the Zap is being enabled, and you can use that to create a distinct request. The deduplication process only uses the `id` property of each record, so one option to consider for this request is filtering the fields you return to reduce record size.

More on `bundle.meta` properties [here](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#bundlemeta).

For those requests that might otherwise exceed timeout or size limits, you can make sure your filtered request is successful. Alternatively, if your filtered request has a lot of headroom in both time and size, you could instead use this to load more records into the Zap's deduplication table than you usually request in your `perform`. This is especially relevant for an API that might return an inadvertent older record in a later polling request.

**Example:** The [Salesforce](https://zapier.com/apps/salesforce/integrations) integration has an ***Updated Field on Record*** trigger that can trigger on older records that a user might not consider relevant for their running Zap. To help mitigate this, the integration uses a filtered request to download up to 104,000 records to the deduplication table during Zap startup.

## Trigger runs in a Zap

### Constraint

Each time a Zap executes, the trigger's response payload must be less than 20MB for a polling trigger and less than 10 MB for a REST Hook trigger.

### Errors user will see if constraint is hit

*   User will receive an email with an error message, usually with *“Trigger Partner Failure”* in the message text.

### Best practice

*   For polling triggers, if your API endpoint supports request filtering around number of records or datetime, using these to reduce the number of records returned
*   If filtering isn't an option, consider using the simplest available endpoint on your API for the basic record data, and use Zapier platform [dehydration](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dehydration) to request supplementary data. A dehydration pointer is created for each subsequent request, and this pointer will only be resolved if the Zap needs a hydrated property in a later step.
*   For REST Hook triggers, if your REST Hook subscription endpoint supports filtering, one option is to provide input fields in the trigger so a user can decide what record data they want to return.
*   Consider sending a notification REST Hook that includes minimal record data. You can then use additional API endpoints and Zapier platform [dehydration](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dehydration) to request the supplementary record data. A dehydration pointer is created for each subsequent request, and this pointer will only be resolved if the Zap needs a hydrated property in a later step.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Troubleshoot trigger timeouts



## Testing trigger in Zap editor

### Constraint

When a user clicks **Test Trigger** in the Zap editor, the output of your `perform` (polling) or `performList` (REST Hook) must be returned within 30 seconds.

### Errors user will see if constraint is hit

*   *“The app did not respond in-time. It may or may not have completed successfully.”*
*   *“Problem creating Sample: Our computers ran into a problem”*
*   *“We couldn't find any more x. Create a new x in your account and try again.”*

### Best practice

The Zap editor will only process three new records at a time for sample data, so one way of speeding up the response is by limiting returned results to three records when a trigger is tested.

To determine when the request is for sample data, use the bundle meta parameter `bundle.meta.isLoadingSample`. When that is set to `true`, the user is testing in the Zap editor, and your integration can respond with a limited payload. More on `bundle.meta` properties [here](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#bundlemeta).

## Trigger runs in a Zap

### Constraint

Each time a Zap executes, the trigger's `perform` method must finish processing in 30 seconds. Polling triggers run on an interval based on a [user's Zapier plan](https://zapier.com/pricing) (between 1 and 15 minutes). REST Hook triggers run on an inbound POST to their subscription URL.

### Errors user will see if constraint is hit

*   User will receive an email with an error message, usually with *“Trigger partner failure”* in the message text. An example of the email sent when the trigger errors due to a timeout:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/0247e40bb198cd439e63cdfb6cc58bdb.png" />
</Frame>

### Best practices

*   For polling triggers, if your API endpoint supports request filtering around number of records or datetime, use these to reduce the number of records returned
*   For both types of triggers, optimize the `perform` scripting for manipulating the payload
*   Use [console logging](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#console-logging) efficiently. It can help you determine where issues might lie, but too much console logging can cause timeouts due to logging overhead.
*   If you have multiple requests per record that are causing timeouts, use the Zapier platform [dehydration functions](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dehydration). Instead of making the request immediately, a dehydration pointer is created, and the request will only be made if the Zap needs a hydrated property in a later step.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Developer Platform Login





# Partner Solutions Documentation

Heads up, our [Embed docs](https://docs.api.zapier.com/guides/intro) have moved.

Our partner solutions are the easiest ways to embed Zapier and surface integrations within your product. Check out the [Partner Solutions documentation](https://docs.api.zapier.com/guides/intro) to learn about [plug-and-play elements](https://docs.api.zapier.com/guides/workflow-element/zap-templates-element), the [Workflow API](https://docs.api.zapier.com/api-reference/getting-started/authentication), [quick account creation](https://docs.api.zapier.com/guides/quac/intro-quac), and more.

Zapier's partner solutions are available for public integrations. To take advantage of our embed solution, first ensure your integration has been published to [Zapier's App Directory](https://zapier.com/apps). If your integration isn't yet published, learn more about [submitting your integration for review](/platform/publish/public-integration#4-submit-your-integration-for-app-review).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Welcome



### Explore our guides and start building a Zapier integration today

Welcome to the Zapier Developer Platform documentation! Whether you are a new or experienced integration builder, this is your go-to resource for learning how to create and manage powerful apps on the Zapier platform.

### What You'll Find Here

*   **Getting Started**: Learn the basics of building integrations, from understanding Zapier's architecture to setting up your first app using the **Visual Builder** or **CLI**.

*   **Visual Builder Guide**: If you're looking for a fast, no-code way to integrate your app with Zapier, the Visual Builder is the place to start. It's ideal for quick deployment and collaboration across teams, even without developer expertise.

*   **CLI Guide**: For more complex needs, dive into the **Zapier CLI**, which gives developers full control over the integration process, using custom code to handle authentication, triggers, actions, and searches.

*   **Best Practices**: Guidance on how to optimize your integrations for performance, user experience, and maintainability.

*   **Advanced Features**: Explore OAuth authentication, dynamic dropdowns, and other advanced options to provide a more customized experience in your integration.

*   **Deployment and Testing**: Once your integration is ready, follow the steps for deploying, testing, and promoting your app version to Zapier's marketplace of over 1 million users.

### Why Build on Zapier?

*   **Reach Millions of Users**: By integrating with Zapier, your app becomes part of a thriving ecosystem that connects with thousands of other services.
*   **Save Time with Automation**: Zapier simplifies repetitive tasks, letting your users focus on what matters most by automating workflows across tools.
*   **Fast and Flexible**: Use the Visual Builder for quick, no-code setups or the CLI for full control and customization.

### Quick Links

*   [Getting Started Tutorial](https://zapier.com/resources/guides/quick-start)
*   [Zapier Developer Platform](https://developer.zapier.com/)
*   [CLI Documentation](https://docs.zapier.com/platform/reference/cli-docs)

Ready to dive in? Choose your path—start building with the Visual Builder or master the CLI for complete customization.


# Active users retention

At Zapier, churn means a user used your integration in their Zaps 29 - 56 days ago, but hasn't run a successful task in one of those Zaps in the past 28 days. This user is considered to have churned from the integration. Maybe they switched to using a competing integration or their workflow had a more periodic or seasonal cadence.

But, it could also mean they got so frustrated with the experience of trying to get their Zap working and *keep* it working successfully - they turned it off, deleted it, and walked away.

Active users are the percentage of users who haven't churned.

## Key Zapier insights

Lowered active user retention rates don't necessarily mean poor integration health. Some apps lend themselves to use-cases with shorter lifespans than others. That said, spikes in churn rate not related to seasonal variations in usage could be indicators of a problem with something not functioning as expected.

Active user retention is not a leading indicator. In fact, it's quite a lagging one that may only start indicating a problem up to 28 days after it has been an issue. That doesn't deem it useless, we just have to know how to make full use of it.

## Best practices

Approaching active user retention with a long-term strategy can help maintain a consistently high level of retention:

*   [Embed](https://platform.zapier.com/embed/overview) the Zapier experience with copy-and-paste and customizable code within your platform to provide automation value directly to users. Embeds have proven to [reduce churn](https://platform.zapier.com/partner_success_stories/all) on Partners' platforms. Learn about embedding options in the [guide](https://learn.zapier.com/surface-your-zapier-integration-within-your-app).
*   [Share use cases](/platform/publish/partner-faq#tip-4-share-zapier-use-cases-in-your-onboarding) widely during your platform's onboarding process. Having multiple Zaps using your integration increases stickiness of users not only to the Zapier integration, but also to your platform.
*   Update the integration regularly with features as your platform evolves. [Invite stakeholders to your integration](/platform/manage/add-team) to give them admin or read-only access to insights, metrics, and feedback to prioritize and align improvements.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Invite team members to your integration

Integrations do not have a dedicated owner, instead they are managed by a team that can be modified as needed. Add team members to your integration to collaborate, contribute, and view analytics data for your integration on the Developer Platform. Your integration team can have up to 200 team members, regardless of whether your integration is Private or Public.

Team members added to your integration will be assigned one of the following roles:

*   **Admin**: Admins are granted read and write access to the integration. This role is ideal for developers and individuals actively involved in building and maintaining the integration.

*   **Collaborator**: Collaborators are granted read-only access to the integration. Collaborator access is recommended for product, leadership, partnership, marketing, and other teams seeking access to integration data for analysis and record-keeping without direct involvement in its creation and maintenance. While they cannot make direct changes to the integration, they can:

    *   View performance data
    *   View the integration history
    *   Review and comment on feature requests and bug reports
    *   Access tools to embed your integration throughout your site to drive user adoption

## Invite team members

To add team members to your integration, follow these steps:

1.  Log into the [Developer Platform](https://zapier.com/app/developer)
2.  Choose your integration
3.  In the *Manage* section on the left sidebar, select *Manage Team*
4.  Select *Invite Team Member*
5.  Enter the email address of the team member that you want to invite
6.  Select the role that you wish to assign to the team member
7.  A note will be included in the invitation email. You can either use the default message provided, or customize it with your own text
8.  Click *Send Invite*.

<Info>
  **Note**: Admins can invite both Admins and Collaborators, while Collaborators can only invite Collaborators.
</Info>

Once you click *Send Invite*, an email invitation will be sent to the invitee, requesting their acceptance. If the invitee does not already have a Zapier account associated with the invited email, they will need to create an account first. Upon accepting the invitation, they will gain access to the integration through their Zapier account on the Developer Platform.

## Self-serve Collaborator access

Depending on your [integration settings](https://cdn.zappy.app/b2637f3ad910c36c4e8c8224c349beee.png), users have the option to self-serve and join an integration team as Collaborators. This feature employs domain-name verification and is managed by integration Admins. Currently, this feature is only available for integrations listed in [Zapier's directory](https://zapier.com/apps).

### Enable or disable self-serve Collaborator access

To toggle the “Join an Integration as Collaborators” feature, please follow these steps:

1.  Log into the [Developer Platform](https://zapier.com/app/developer)
2.  Choose your integration
3.  In the *Manage* section on the left sidebar, select *Manage Team*
4.  Select *Settings*
5.  Click the toggle to enable or disable self-serve join as Collaborator

### Manage eligible email domains for self-serve Collaborator access

1.  Log into the [Developer Platform](https://zapier.com/app/developer)
2.  Choose your integration
3.  In the *Manage* section on the left sidebar, select *Manage Team*
4.  Select *Settings*
5.  Click “Edit email domains”
6.  Add/remove the domain(s) to be allowed

### Join an integration team via self-serve Collaborator access

1.  Log into the [Developer Platform](https://zapier.com/app/developer)
2.  Click “Get Access” under Join an Integration as Collaborators
3.  Search for and select your integration, then click “Submit”
4.  If the Zapier account you're using is associated with an approved domain, you'll receive an invitation to join via email

## View and remove team members

You can view a list of invited team members (both accepted and pending invites) from the *Manage Team* page. Admins have the ability to remove team members (both other Admins and Collaborators) from this page. Collaborators can only remove other Collaborators. You can also remove yourself from the team, regardless of your role. It's important to note that if you are removed as an Admin, you can regain Admin access only if invited by an existing Admin.

## Assign marketing and technical contacts

Admins have the ability to assign dedicated marketing and technical contacts on the integration team. Assigning these contacts allows us to better streamline outbound communication, and ensures the right contacts receive timely information. Please note that Zapier's support teams might reach out to the technical contact for technical support. E.g. we might contact this person so we can collaborate on trickier Customer Support tickets on Zaps that involve your integration.

### How to assign contacts

1.  Navigate to the “Manage team” page in the developer platform
2.  Click the “Settings” icon for the user you want to assign a role to
3.  Click either “Set marketing contact” or “Set technical contact”
4.  Users will be tagged with the role they are assigned

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6e7b628f49cccb820ea693b224619da8.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/473075ad407c94a93cbccd1504173268.png" />
</Frame>

### Notes

*   Each integration team can have only one designated marketing contact and one designated technical contact at a time
*   A single user can be labelled as both the technical and marketing contact, they do not need to be separate users
*   Users are not alerted when they are assigned or removed from a role
*   Only Admins can assign roles, Collaborators cannot assign roles
*   You can manually remove a role from a user by clicking the “Settings” icon and selecting “Revoke technical contact” or “Revoke marketing contact”
*   You do not need to remove a role from a user before assigning the role to another user
*   Assigning a role to a new user that's already been assigned to another user will cause the role to automatically be removed from the original user
*   Only integration team members that have accepted a role can be assigned a role (ie team members that are Invitation Pending cannot be assigned a role)
*   The user assigned as the marketing contact will be shown as the contact on private invite sharing pages

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/defb445b2a2807fd2c6d0e45dde346b5.png" />
</Frame>

## Manage email subscriptions relating to your integration

### General marketing updates

Zapier sends out general partner marketing updates related to your integrations and the Developer Platform. These emails include:

*   Monthly Partner Newsletter with integration data insights
*   Outreach about partnership and co-marketing opportunities

To control your subscription for these emails, visit the [email preferences page](https://developer.zapier.com/partner-settings/email) in the developer dashboard and opt in or out per each integration you are a team member of.

### Platform and Partner Program updates

All members of your integration's team will also receive Zapier Platform and Partner Program Updates. These emails include:

*   Alerts about user [Bugs and Feature Requests](/platform/manage/integration-insights)
*   Critical Platform and Program Updates

You cannot unsubscribe from Zapier Platform and Partner Program Updates unless an existing integration team member removes you from the integration team entirely. These emails deliver essential announcements about the Partner Program, Zapier Platform features, product updates, and compliance with integration quality standards to ensure you're always up-to-date with changes that could impact your use of the Zapier Platform.

<Info>
  **Note: An Admin can remove another Admin or Collaborator from the integration team. A Collaborator can only remove other Collaborators from the integration team.**
</Info>

### Opting team members into emails

If you want to ensure that your team members receive partner marketing emails:

1.  [Invite your team members](/platform/manage/add-team) to the integration team as either an Admin or Collaborator and ensure they accept the invite.
2.  Team members who accept the invite will automatically receive Platform and Partner Program Updates.
3.  If they want to receive General Partner Marketing updates, have them visit their [email preferences](https://developer.zapier.com/partner-settings/email) in the developer platform and ensure they're opted into emails for the integration you want them to receive alerts for.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# null

Zapier recognizes that temporary unavailability is sometimes inevitable for your API.

To accommodate these situations, we recommend configuring [custom error response handling](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#general-errors) to:

*   Present a meaningful, user-friendly message to users
*   Refrain from disabling Zaps due to error-ratios
*   Potentially reattempt the action after a given delay window

## 1. Default behavior

Zap runs that encounter a 5xx error from your API throw an exception and receive the “Stopped / Errored” status. This will [display an error message to the user](https://help.zapier.com/hc/en-us/articles/20505304170637-Review-Zap-run-statuses); allowing them to replay the Run.

If the user has [Autoreplay](https://help.zapier.com/hc/en-us/articles/8496241726989-Replay-failed-Zap-runs#how-autoreplay-works-0-4) enabled, then the errored steps will be retried on [a defined schedule](https://help.zapier.com/hc/en-us/articles/8496241726989-Replay-failed-Zap-runs#how-autoreplay-works-0-4). Only when all Autoreplay attempts have also failed will the Zap Run be assigned the “Stopped / Errored” status.

If [95% of a Zap's runs in the last 7 days are assigned the “Stopped / Errored” status](https://help.zapier.com/hc/en-us/articles/8496037690637-Troubleshoot-errors-in-Zapier#500-series-error-codes-0-3), the Zap will be paused automatically. The Zap will not run again until the user manually enables it.

## 2. Avoid automatic Zap disablement

### Platform CLI

When building in the [CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md), you can [make use of HTTP middleware](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#using-http-middleware) to implement [custom error response handling](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#error-handling).

This allows you to write a single script that applies across the entire integration to detect a specific error from the API, and act accordingly. For example, if the API's outage duration is known, you could catch 503 responses during the `afterResponse` middleware and throw a [`ThrottledError`](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#handling-throttled-requests) like this:

```js
const yourAfterResponse = (resp) => {
  if (resp.status === 503) {
    throw new z.errors.ThrottledError('Service is temporarily unavailable. Retrying in 60 seconds.', 60);  // Zapier will retry in 60 seconds
  }
  return resp;
};
```

> **Note:** You need to set [`skipThrowForStatus`](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#http-response-object) to `true` when invoking `z.request()`.

### Platform UI

When building in the [UI](https://developer.zapier.com/), custom error handling needs to be added to each individual element of the integration (authentication, triggers, actions) using [Code Mode](/platform/build/code-mode).

For example:

```js
const options = {
  url: `https://example.app/api/endpoint`,
  skipThrowForStatus: true,
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
  params: {},
  body: {}
}

return z.request(options)
  .then((response) => {
    if (response.status === 503){
      throw new z.errors.ThrottledError('Service is temporarily unavailable. Retrying in 60 seconds.', 60); // Zapier will retry in 60 seconds
    }
   const results = response.json;
   return results;
  });
```

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f9f04386f19f0e9f7f107ea8d0d6e3b7.png" />
</Frame>

> **Note:** Integrations built with the Zapier Platform UI can enable the *skipThrowForStatus* toggle under [Advanced/Settings](/platform/build/errors) to On to use [`skipThrowForStatus:true`](https://cdn.zappy.app/8ac6af91f6b27c4a473d566f1534b27e.png) on every request

## 3. Specify how long Zapier should wait before retrying

The `ThrottledError(message,delay)` method accepts two input parameters; a custom error message (string) and a delay expressed in seconds (integer).

Include a Retry-After header with responses provided by your API during downtime, to specify the amount of time until the service is expected to be back online and Zapier should retry the request. Your [custom error handling script](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#error-response-handling) can read this header and pass it to the delay parameter for `ThrottledError()`, like this:

```js
if(response.status === 503){
    const delay = response.getHeader('retry-after');
    const message = `Service is temporarily unavailable. Retrying in ${delay} seconds.`;
    throw new z.errors.ThrottledError(message,delay);
}
```

## 4. Scheduled API maintenance

For periods of scheduled maintenance, a status of an app (API) as “unhealthy” can be set between a specific start and end time on request. A “Scheduled Maintenance” message will then be posted to our [status page](https://status.zapier.com) for [example](https://status.zapier.com/incidents/njgw7lrhn5hs).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/2ae85a1cd6d00981ed30af8eede5938b.png" />
</Frame>

The app will also have a status of “unhealthy” on the [*App Status* tab.](https://status.zapier.com/#app-status)

During a set maintenance window, Zaps impacted by the downtime will be affected as follows:

**Actions/Searches:** Zap runs with affected actions/searches will have the status of “Playing” in the Zap History page. The action/search will be delayed until the incident resolves, or up to five times. On the fifth time, Zapier will attempt it regardless of the app status. If it fails, Zap runs will error and [normal manual replay mechanisms](https://help.zapier.com/hc/en-us/articles/8496241726989-Replay-failed-Zap-runs) can be used to try to replay any affected Zap runs.

**Triggers:** *most* Zaps with affected (polling) triggers won't run and so (for the most part) there will be no Zap runs in [Zap History](https://help.zapier.com/hc/en-us/articles/8496291148685-View-and-manage-your-Zap-history).

In more detail, Zapier will “skip” 90% of polls, allowing 10% through, so typically *some* runs will occur for *some* Zaps. After 15 minutes, all polling is re-enabled to check if the API is healthy again. If not, Zapier will again “skip” 90% of polls. This will be repeated until the API is healthy. Once the API is healthy, Zaps will be triggered and “catch up” on missed data, subject to normal polling limitations such as [pagination](/platform/build/trigger#how-to-use-pagination).

If Zap runs error users will be notified via email based on their [notification settings](https://help.zapier.com/hc/en-us/articles/8496289225229-Manage-notifications-when-errors-occur-in-Zaps).

If you would like to schedule a maintenance window for your app, please send across the following information to [Developer Support](https://developer.zapier.com/contact)

*   Start date and time (in UTC)
*   End date and time (in UTC)
*   Further details about how the API will respond during the downtime if called; eg. will it return an [HTTP 503](https://www.webfx.com/web-development/glossary/http-status-codes/what-is-a-503-status-code/) “Service Unavailable” status code or not respond at all.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Change authentication field keys



## Change scenario

You want to change the key of one or more [authentication input fields](/platform/build/basicauth#1-build-an-input-form) required when a user authenticates your app to Zapier.

## Impact to users

This is a **breaking change**.

Modifying the key of an existing authentication field constitutes a breaking change. Without adequate precautions, existing app connections may break as [migration](/platform/manage/migrate) is not possible. Users will need to establish a new connection to your integration and manually refresh each of their Zaps.

## Best practices

We strongly encourage you to **avoid changing authentication field keys** whenever possible.

### Workaround

If your API endpoint requires a different property for authentication, think about adjusting the property key rather than amending the form field input's key. This modification must be made in each trigger, action, search request, as well as in the authentication.

> NOTE: Form field input keys do not need to match directly with the properties your API expects.

For example, let's say you have a form field input with the key `API-KEY`and are sending the field's value to your API using the same property name of `API-KEY`.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/daef0487a89a2cbb17ec719cbbd50577.png" />
</Frame>

```bash
headers: {
  "API-KEY": bundle.authData.api_key; // original
}
```

Next, your API changes and expects the request property to be `X-API-KEY` instead. You can change the request property key (left) as needed. But still refer to the original form field input (right).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/bf4cdfe3183752e0e57707001260e9e3.png" />
</Frame>

```bash
headers: {
  "X-API-KEY": bundle.authData.api_key; // new - request key and field key can differ
}
```

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add required authentication field



## Change scenario

You'd like to add, remove, or change an optional input field to be required in your current authentication schema.

## Impact to users

This will cause a breaking change which will have the following significant effects to users:

*   Existing connected accounts will not work: All existing connected accounts will no longer be functional with your integration until users re-authenticate manually.
*   Manual updates are required for all Zaps: Zaps cannot be migrated due to a breaking change, users will have to edit each Zap individually before they can start running tasks again. For example, if a user has 20 Zaps set up with your integration, they will need to manually update each one of those Zaps.

## Best practices

*   **Add the field as optional:** Use field \[help text]\(/ and [custom error handling](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#error-handling) (if you're using our Code Mode or the CLI platform) to validate that the newly required field is provided, while keeping it set to optional. Also, consider using custom code to set the field's default value in your API call if left blank.
*   **Set a default value:** If possible, provide a default value for the required field that can be overwritten if necessary. This ensures that users who do not provide explicit values for these fields can continue to use your integration without issues.

For example in the case of updated API endpoints for geographical region or site domain, it is possible to account for an added **required** inputField with scripting to ensure existing authentications are backward compatible, allowing existing users to be migrated to the new version.

In this example code, the default URL for US region is updated when the user selects AU or CA when authenticating.

```js
let baseURL = "theUsApiBaseUrl";
switch (authData.region) {
  case "AU":
    baseURL = "theAuApiBaseUrl";
    break;
  case "CA":
    //other regions can be easily supported by adding cases like this
    baseURL = "theCaApiBaseUrl";
    break;
  default:
    console.log("Legacy credentials are in use, defaulting to US Base URL.");
}
```

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Change authentication type

If your API's authentication method changes, you would need to change the method Zapier uses to authenticate user accounts.

## Impact to users

Changing the authentication type (e.g., Basic Auth, API Key, or OAuth) of an integration is regarded as a breaking change. Notably, migration is impracticable since all pre-existing connected accounts would stop working if migrated. Users would need to make a new connection to your integration and manually modify each of their Zaps.

However, if your integration meets the following conditions, you can use the [contact form](https://developer.zapier.com/contact) to request support for migrating connected accounts between authentication types:

1.  Your integration is public.
2.  You have an API endpoint or can otherwise programmatically exchange data of the old type (e.g. an API key) for the new type (e.g. an OAuth2 access and refresh token).
3.  The API endpoints that the integration actions use, can work with both the old and new auth type, at least for a few months until the old type may be sunset.
4.  Exchanging e.g. an API key for an OAuth2 access token doesn't immediately invalidate the API key, and thus break other connected accounts that may still use it.

## Best practices

**Creating a New Version**

*   [Clone](/platform/manage/clone) your app, generating a new version.
*   [Remove](/platform/build/auth#how-to-remove-or-change-zapier-integration-authentication-scheme) the existing authentication method and incorporate the new one.
*   Once configured, [promote](/platform/manage/promote) this version, making it available for new users to select during the connection of your integration to Zapier.

**Managing Existing Users**

*   If users with existing authentications can retain their connection using the old method, enabling them to stick to the old version is recommended.
*   However, they will be prompted to form a new connection for any new Zaps since only the promoted version is available during a name-based app search.

**Deprecating legacy authentication scheme**

*   If existing authentications are set to be non-functional in the future, then [Deprecation](/platform/manage/deprecate) is required.
*   Be mindful that this can be notably disruptive for our mutual users and thus should be considered carefully.

> NOTE: this method is not possible with apps built in the legacy web builder. To update the authentication, you would need to update all triggers/actions/searches as well; as deleting the authentication method and re-adding it in the new builder would not be compatible with existing triggers/actions/searches built in the legacy web builder.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add integration branding in Platform CLI

When you make a new integration in Zapier CLI, you can add the app's name, description, and homepage to the `package.json` file.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/5373ba1686dbea3e9d942b56101a5b40.png" />
</Frame>

The rest of your app's branding needs to be added in Zapier's online Platform UI.

Focus first on building your integration. When you've added your app's core details, authentication, triggers, and actions, push your integration to Zapier with a `zapier push` command. Zapier will use the name you added in the CLI integration settings, along with a placeholder icon for your app.

Then, before launching your integration, add your app's branding via Zapier's Platform UI at [zapier.com/app/developer](https://zapier.com/app/developer). There you will see every Zapier integration you've built. The top *Integrations* section includes every app you've added via Zapier's Platform UI or CLI. Look for the integration you built with Zapier CLI and click its name.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/21501f70d3d15a341e6dc7ea90690ee6.png" />
</Frame>

You can't edit authentication, triggers, and actions in Zapier's Platform UI for integrations built with Zapier's CLI. But you can edit branding.

To do that, click the gear icon in the upper left hand corner near your app's name and placeholder icon. You can then edit your integration's name and description, and upload your app's icon (a square, transparent PNG at least 256x256px). Below that, you can set your app's category and other brand settings. Click *Update* to save your settings.

You can then make further changes to your integration in your Zapier CLI code and push them to Zapier anytime without affecting your branding. If you ever need to change your app icon or other branding again, just come back to the [Zapier Platform UI](https://zapier.com/app/developer) and edit it as before.

*→ Check [Zapier's app logo, name, and description guidelines](https://platform.zapier.com/partners/planning-guide#app-logo) to ensure your app's branding fits into the Zapier platform.*

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Changes to your API can impact your integration



## Change scenario

When making changes to your API, consider how these will affect your Zapier integration. API updates can vary from small to significant changes and can have varying effects on your users' Zaps.

## Impact to users

Though not exhaustive, here are some potential major impacts to users:

*   If a new version with breaking changes to authentication is promoted, users with existing Zaps will have to manually reconnect their account on Zapier.
*   If a new version with breaking changes to any trigger, action, or search is promoted, users with existing Zaps will have to manually upgrade *each* Zap using your integration. Keep in mind: power users can have tens to hundreds of Zaps using your integration.
*   Changes to a trigger, action, or search's response data can break or negatively affect the proceeding steps of the Zap.
*   Changes to response data in polling triggers can prevent Zaps from triggering on new records or cause them to trigger on old records. This can lead to missed data and undesirable results for your users.

## Best practices

To mitigate the impact of API changes on your Zapier users, consider the following best practices:

*   Maintain general backwards compatibility on existing endpoints. This ensures that the existing Zaps continue to work as expected, and your users won't need to modify them due to API changes.
*   For polling triggers - changing the number of records returned from the endpoint can significantly impact the functionality of the users' Zaps.
*   Implementing pagination on an endpoint can affect how much data is fetched and processed when a Zap polls that endpoint.
*   Changing the sort order of response data returned can impact Zap triggers. For polling triggers, ensure that the data is always sorted in reverse chronological order to maintain compatibility.

By adhering to these best practices, you can minimize disruptions to your Zapier users when updating or modifying your API. A well-managed API transition process ensures that your users continue to have a seamless and efficient Zapier integration experience.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Change trigger or action key



## Change scenario

In cases where a trigger/action/search's custom code needs to be rewritten or a new v2 is replacing an older v1.

## Impact to users

Deleting a trigger/action/search in a new version is a breaking change - which would prevent migration of your users to the new version. Updating an existing trigger/action/search's custom code would allow for migration but break users' Zaps if the output changes.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/65326a8f5fff0f9640c0d6fdc59dfa3b.png" />
</Frame>

The triggers, actions, and searches are identified by their **key**, such as `new_contact` or `create_post`, so if you remove that key from the app's definition, or change it (possible in CLI apps only), this message appears when you attempt to migrate.

## Best practices

*   If you have already renamed the **key** (possible in CLI apps only) for a trigger/action/search, you'll need to switch it back to the previous **key** to proceed with migrating users.
*   If you need to remove a trigger/action/search, change its visibility to **hidden** instead. Use the Visibility Options dropdown in the [UI](https://platform.zapier.com/polling-trigger#1-add-the-trigger-settings), or the `hidden` key in the [CLI](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#basicdisplayschema).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/73990d8049572347aea87fc304173ecc.png" />
</Frame>

Migrated Zaps that used the hidden trigger/action/search will now show it as Deprecated in the Zap editor, but will continue to function as long as the endpoints remain valid.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/61a2ac6095433d278bc412c2e59408fc.png" />
</Frame>

Once a user selects a different trigger/action/search when editing their Zap, they will not be able to retrieve the hidden one. New users will not see any `hidden` trigger/action/search as available for selection.

*   If you need to add a new trigger/action/search that replaces the hidden one, create a duplicate and give it a new **key** (such as appending `_v2` on the end), and keep the Name and Description the same if the functionality for a user is the same.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6231ec2b53271c7d83672f6ed232d0e3.png" />
</Frame>

*   This way existing Zaps continue to work once migrated with the previous (and now hidden) definition, and new Zaps will only be able to select the new definition.

*   In cases where the endpoint in the hidden trigger/action/search will be sunset and begin to return errors in the future, the impact to users would be as follows:

Once the API has been sunset, active Zaps (turned on) using the impacted trigger/action/search will produce errors when they run. Depending on [user's email notification settings](https://help.zapier.com/hc/en-us/articles/8496289225229), owners of these Zaps will be sent email notifications about these errors.

If those Zaps exceed the error ratio **and** users have not [overridden related settings in their Zaps](https://help.zapier.com/hc/en-us/articles/8496037690637-Troubleshoot-errors-in-Zapier#i-want-my-zap-to-continue-running-even-when-there-are-errors--0-6), those Zaps will be automatically turned off.

*   If you'd like to add custom errors within Zapier for the hidden trigger/action/search at the time of the endpoint sunset, you could consider the following:

Create a new version of the integration that immediately throws a `z.errors.Error` exception in the `perform` method of the impacted trigger/action/search. Learn more for apps maintained in the [UI](/platform/manage/planning-changes#custom-error-handling-in-the-ui) or the [CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#zerrors).

*Promote* and then *migrate* users to this new version as close to the sunset date as possible.

The benefits of this approach are:

*   Throwing an explicit exception will ensure impacted Zaps will hit the error ratio (and be turned off) at the earliest possible time.
*   You can add a user-friendly message to the exception that users will see in both Zaps Runs on the [Zap History](https://help.zapier.com/hc/en-us/articles/8496291148685-View-and-manage-your-Zap-history) and also in email error notifications, e.g. *This function has been deprecated and is no longer available.*
*   Here's an example of how a custom error message would be displayed on an action in the Zap History:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/50807aad2a2e2ecda9044a524dafba8c.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Update perform method for polling trigger



## Change scenario

It is generally safe to update a polling trigger's [perform method](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#basicpollingoperationschema) (Platform CLI) or [API Configuration](/platform/build/polling-trigger) (Platform UI), as long as the changes maintain backward compatibility in the returned response data. This means that the output fields and output data structure should remain consistent with previous version's.

## Impact to users

Updating a polling trigger's perform method or API Configuration might cause deduplication issues and cause old records to trigger a Zap if any of the following changes occur:

*   The [primary key](/platform/build/deduplication) (usually an `id` field) is changed or removed from the API response.
*   The perform method's endpoint is modified, resulting in fundamental changes to the primary key value.

For example, if the API or endpoint previously returned dedupe IDs as integers -1,2,3,4 - and is now updated to return alphanumeric values - 1-abc, 2-bcd, 3-cde, 4-def - records that were previously processed will fire again since the new IDs aren't saved in the deduplication table that Zapier references during each poll.

## Best practices

*   Ensure the primary key remains unchanged: If the API response changes the primary key, use [custom code](/platform/build/deduplication#custom-primary-keys) to maintain consistency and prevent deduplication issues.
*   Maintain reverse chronological order: the trigger should continue to return data in reverse chronological order to prevent unintended records triggering the Zap.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Change trigger from polling to REST Hook



## Change scenario

Your app needs to change how it notifies Zapier of new records - changing the type of trigger - from Zapier polling an app endpoint for new items to instead a REST Hook subscription where the app notifies Zapier of new records; or vice versa.

## Impact to users

This is a breaking change. Edits to an existing trigger's type will cause your users' Zaps to stop working and they would need to create new Zaps with the new trigger type, and manually re-create their actions. Using the [Duplicate feature](https://help.zapier.com/hc/en-us/articles/15408145778829-Duplicate-your-Zap) would help, but each Zap would need to be mapped individually with the new trigger.

## Best practices

*   Copy the trigger configuration into a new trigger and give it a new **key** (such as appending `_v2` on the end), change the type for this new trigger, and **hide** the previous trigger. This way existing Zaps continue to work with the previous (and now hidden) trigger definition, and new Zaps will use the new trigger definition.
*   This is the recommended path forward whether using the Platform UI or the Platform CLI, with the only difference being using the *Visibility: Hidden* toggle in the UI and setting the `hidden` key as `true` [in the CLI](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#basicdisplayschema).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Clone a version

Cloning allows you to duplicate an existing version of your integration. This is particularly useful when you want to introduce new features or fixes without altering the original integration. When a previous version of your integration has more than 5 active users, you will need to clone that version to make modifications.

> **Note**: The term “cloning” is specific to the Platform UI and is not used with Platform CLI. However, the concept is similar to updating the version number in your `package.json` file and running [`zapier push`](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#push) to create a new version you can access within your Zaps for testing.

## How to clone an integration version

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Manage* section in the left sidebar, click **Versions**.
4.  On your existing version, click the **three dots icon**

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/7ff6381b55b013ebfc2bdda0e4662676.png" />
</Frame>

5.  From the dropdown menu, select **Clone**.
6.  The *Clone Version* dialog box will appear. In the dropdown field, select which **version** you want to clone your existing version too.
    *   **Patch (e.g., 1.0.0 to 1.0.1)**: Ideal for backward-compatible changes such as bug fixes or updating help text.
    *   **Minor (e.g., 1.0.0 to 1.1.0)**: Use this for adding new functionalities that do not disrupt existing features, like creating a new trigger or action.
    *   **Major (e.g., 1.0.0 to 2.0.0)**: Choose this option for changes that are likely to break existing Zaps, like removing triggers or actions, altering authentication methods, or revamping the entire integration.
7.  Click **Clone**.
8.  A dialog box will appear confirming you've cloned your version.

Now you can make edits and improvements to your integration.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Deprecate or delete a version

Deprecation is an optional process that allows you to set a date from which a non-public version of your integration will no longer be updated. Deprecation is only recommended if the older integration version will eventually stop working, such as if the related API will be removed. Zapier is normally a “set it and forget it” experience for users, so use this feature carefully. Only if the older version will no longer function, should it be deprecated. Please note that deprecating a version is significantly more disruptive to our mutual users than migrating to the latest promoted version, or than leaving users on an older (now) private version if migration is not possible.

When users are left on an older private version, they will see a [prompt in the Zap editor](https://help.zapier.com/hc/en-us/articles/18755649454989-Update-to-the-latest-app-version-in-Zaps) to optionally encourage them to make the update, instead of any deprecation communications or Zap disruptions.

## What happens after setting a version to deprecate

### 1. Email notification

After setting the date, an initial email will be sent to users, informing them that they need to manually update their Zaps to continue using the new version. Zapier recommends supplementing these automated messages by also notifying your general user base through in-app announcements or email marketing campaigns. For privacy reasons, Zapier cannot provide you with an email list of users of your integration.

### 2. Deprecation date reached

*   Zaps that haven't been updated will be automatically paused.
*   A second email, titled “X Zaps paused over deprecated apps,” will be sent out to those users. Customizing this email is not possible.
*   The deprecated version will be labelled as “Deprecated” in the Zap editor, with a prompt for users to update to the latest version.

### 3. Post-deprecation

After the deprecation date, the version will still be visible in the Versions page for your future reference. Users will not be able to see the version once they select a different version in their Zaps. Although you can technically still invite users to the deprecated version, it's not recommended.

## Deprecate a version with Platform UI

To deprecate an integration version:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Manage* section in the left sidebar, click **Version**.
4.  Click the **three dot** icon in line with the integration version you want to deprecate.
5.  Click **Deprecate**.
6.  In the dialog box, add the **Deprecate Date**. You can set a deprecation date anytime between 2 weeks and 1 year from the current date.
7.  Click **Deprecate**.
8.  Click **Close**

## Deprecate a version with Platform CLI

To deprecate an integration version use the command `zapier deprecate`. Learn more about [Platform CLI commands](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#deprecate).

## Deleting deprecated versions

You have the option to delete versions entirely, but exercise this option with extreme caution. Deleting a version makes it permanently inaccessible, both to you and to your users. Deleted versions cannot be restored by Zapier Developer Support.

In most scenarios, it's advisable either to deprecate the version or leave it as a private version. Deletion should be a last resort, used only when you are certain that the version will never be needed again.

Deletion is also only possible when 0 users remain on a version. The count of users on the *Versions* page includes live Zaps only. If you're seeing the error `Unable to delete app version`, this can be caused by users on that version not included in the visible count (paused Zaps, app authentications for example).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/1722db1a2658ec77e47f5d4de58720ff.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Embed activation rates

Consider all the user clicks on Zap Templates surfaced in your embeds. The embed activation rate is the percentage of those Zaps that actually activated within 24 hours of creation, meaning the Zap ran at least one successful task. It measures the efefctiveness of the Zapier embeds in your product at converting user clicks on Zap Templates to Zap activations.

In your integration's *Insights*, along with activation rate, see a funnel-view of users who signed up for Zapier, created Zaps, enabled Zaps, and activated Zaps from Zap Templates in your embeds.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/896a5e4d56f2c86ba3909b669abf5d0c.png" />
</Frame>

A low activation doesn't necessarily mean something is broken. Both your integration and embed(s) could be implemented correctly and in good health, but you could still be seeing low activation rates. Embed insights can help identify where in their journey users are hitting challenges with activating, whether it's the Zapier sign up, Zap setup or enablement stage.

## Key Zapier insights

Embed insights and activation rates are strong indicators of user awareness and integration usability. If the click-to-sign-up ratio is really low, perhaps you haven't set sufficient context and expectations where the embed is placed for users to feel confident when they land on Zapier.com. If the create-to-enabled ratio is low, users could be having trouble with authenticating or field mapping once they're in the Zap editor creating a Zap from the template.

## Best practices

*   If there are no *Embed insights* in your Dashboard, there are no embeds associated with the integration. Check out the *Embed* tab of your integration's Dashboard or our [Partner Solutions](https://zapier.com/l/partner/solutions) to see how you can start embedding. Embed features are only available for public integrations.
*   We offer a variety of [embed tools](https://platform.zapier.com/embed/overview) as different options work best for different Partners and their platforms. Implement a combination of our partner tools and compare metrics to see which implementations are most successful for your use case.
*   Users are more likely to sign-up when they are aware of Zapier and the value we provide by integrating to your app. Give context of Zapier via a brief onboarding flow or link to help docs so users know what to expect.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Embed insights definitions

Embed features are available for public integrations.

All metrics can be filtered by *All Embeds* or [individual embeds](https://cdn.zappy.app/195883ab4fd7897224bcec00b7bf9b13.png), and the [last 7, 30, and 90 days](https://cdn.zappy.app/e3d334462ad532540710a7ce0d975942.png), hover over the metric to see the percentage and count figures.

Definitions for each of the metrics provided in the integration dashboard below:

| **Metric**                             | Definition                                                                   |
| -------------------------------------- | ---------------------------------------------------------------------------- |
| **Total conversion**                   | Percentage of users who clicked on Zap Templates and activated a Zap.        |
| **Users who clicked on Zap Templates** | Percentage and count of users who clicked on a Zap Template in an embed.     |
| **Users who signed up for Zapier**     | Percentage and count of users who signed up for a new account on Zapier.     |
| **Users who created Zaps**             | Percentage and count of users who started a Zap in the editor.               |
| **Users who enabled Zaps**             | Percentage and count of users who created and successfully published a Zap.  |
| **Users who activated Zaps**           | Percentage and count of users who created a Zap that successfully ran tasks. |

## Best practices

*   Test different iterations of embeds and compare rates to see which ones resonate most with your users. Do certain sets of Zap Templates garner more clicks and activations than others? Does enabling “App search” on the Full Zapier Experience, so users can see all the apps they can integrate with, increase activations?
*   Improve your Zap Templates with short, descriptive titles, and as much [prefilled field mapping](https://platform.zapier.com/embed/zap-editor#prefill-options) as possible, so users are immediately aware of the use case and can activate the Zap with the least amount of clicks.
*   Provide context with your embed through help docs or an onboarding flow guiding users on how to connect your app with others using Zapier to improve the *Users who signed up for Zapier* rate.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Improve error response handling

Errors from your API cause pain for users at two vital points:

*   During the Zap setup process, stopping them from enabling and activating their Zaps
*   During the Zap's runtime, once it has been turned on, preventing the Zap from completing all actions successfully

Zap runs that encounter an error response status code when making a request to your API throw an exception and receive the “Stopped / Errored” status. This will [display an error message to the user](https://help.zapier.com/hc/en-us/articles/20505304170637-Review-Zap-run-statuses) in their Zap History and they will be notified via email based on their [notification settings](https://help.zapier.com/hc/en-us/articles/8496289225229-Manage-notifications-when-errors-occur-in-Zaps).

Even if the `message` included in the response details an error, users should **never receive** a success/200 response if there was an error in the request as this will not show up as an error in the Zap history.

If [95% of a Zap's runs in the last 7 days are assigned the “Stopped / Errored” status](https://help.zapier.com/hc/en-us/articles/8496037690637-Troubleshoot-errors-in-Zapier#500-series-error-codes-0-3), the Zap will be automatically turned off. The Zap will not run again until the user manually enables it, so only return an error if the scenario is truly an error that needs to be fixed.

Monitor spikes in errors from the the *Monitoring* page in the Platform UI as leading indicators of problems users are facing within your integration.

The more descriptive and thorough [error handling](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#error-handling) your API and integration have in place, the easier it will be for users to resolve their own issues and for Zapier's support team to assist with debugging.

If you don't have control to make changes to the API itself, utilize custom error handling to improve your error messages:

*   Elaborate on briefly worded errors with user-friendly messaging.
*   When writing user-facing error messages however, keep the message below 250 characters total as Zapier truncates errors from integrations at 250 characters when displaying them to users.
*   Update “not\_authenticated” to “Your API Key is invalid. Please reconnect your account.”
*   Surface specific information to users regarding the field and why it's producing an error. This empowers users to fix Zap issues independently.
*   Instead of “Provided data is invalid”, return “Contact name is invalid”.
*   Improve “Contact name is invalid” with “Contact name exceeds character limit.”
*   Format the error to include a second, optional argument code machines can use to identify the type of error, and last, optional argument of a HTTP status code. `throw new z.errors.Error('Contact name exceeds character limit.', 'InvalidData', 400);`

Your app integration can use custom code to improve the user experience in Zapier by returning a user-friendly message and optional error and status code. Typically, this will be prettifying 4xx responses or APIs that return errors as 200s with a payload that describes the error. The code and status is used by Zapier to provide relevant troubleshooting to the user when communicating the error.

## Prerequisites

*   Documentation for the API you're working with that includes the response status codes per endpoint
*   Familiarity with [general error handling in Zapier](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#general-errors).

## 1. Custom errors in Platform CLI

Switching to the [Platform CLI](/platform/manage/export-cli), allows you to [make use of HTTP middleware](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#using-http-middleware) to implement any [custom error response handling](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#error-response-handling).

This will allow you to write a single script that applies across the entire integration to detect a specific error from the API, and show the relevant message to the user within Zapier.

## 2. Custom errors in Platform UI

When you build and maintain your app in the UI, custom error handling can still be implemented. The main difference is that you'll need to add it to each individual element of the integration (triggers, actions, searches, authentication) that could encounter the error.

In the UI, `skipThrowForStatus` is set from the [Advanced tab](/platform/build/errors). This allows for custom error handling for status codes above 400. Note that 401 status codes will throw a `RefreshAuthError` [regardless](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#user-content-error-response-handling).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8cafa443c6e5844d6881f2690e4f34c5.png" />
</Frame>

Once set, you can add [error handling](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#general-errors) when in Code Mode within the API Configuration for each trigger/action/search.

```js
return z.request(options).then((response) => {
  if (response.status === 404) {
    throw new z.errors.Error(
      "An error was returned. Help!",
      "InvalidData",
      404
    );
  }
  return response.json;
});
```

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/9553266cb5a5ab7804d3f9ac1a9eed60.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Essential tips for integrating quality health practices

Our shared customers rely on Zapier and your integration for business-critical workflows. Addressing feedback early and often ensures users have the best experience, both with Zapier's platform and yours. Follow these tips on how. 

## 1. Embrace early bug resolution

Early bug resolution prevents minor issues from escalating into significant problems, ensuring your users have a smooth and reliable experience.

## 2. Prioritize user-requested features

Requests for new features are invaluable insights into your user needs and pain points. Prioritizing these requests in your development roadmap can help enhance the functionality of your integration and significantly boost user retention and loyalty.

> Pro tip: Explore users' most valued and widely used features per app category! Head over to our [must-have triggers and actions](/platform/quickstart/must-have-triggers-and-actions) page for details.

## 3. Make Zapier an integral part of your product strategy

Your integration is a powerful tool for automating workflow, increasing productivity, and providing value to your users. Including it in your product expansion strategy and product roadmap planning, ensures it evolves in tandem with your core offerings.

> Pro tip: Read on how you can [prioritize user feedback](https://zapier.com/blog/prioritize-user-feedback-with-beamer/) in your product roadmap planning.

## 4. Spread the word about your integration's new features

Highlight new features and enhancements to your integration in your marketing materials. Adding these to newsletters, blogs, and in-product messages (IPMs), can help drive engagement and encourage users to explore new functionalities, leading to increased adoption and deeper integration into their daily workflows.

> Pro Tip: Get your integration in front of more users by embedding Zapier's [Partner Solutions](https://zapier.com/partner/solutions/plug-and-play) directly into your product.

## 5. Keep your integration fresh with regular updates

Regular updates signal to your users that you are committed to continuously improving their experience. Establish a cadence for updates that align with your sprint cycles.

> Pro tip: Add them to your calendar.

**Next Steps:**

*   Actively monitor user feedback
*   [Leave a comment](/platform/manage/user-feedback) on the Bugs & Feature Requests section inside your Partner Dashboard
*   When promoting a version, link any open bug reports and feature requests that the newly promoted version addresses
    *   *Only possible when promoting within the Platform UI*
*   Leverage [Quick Account Creation](https://platform.zapier.com/embed/quick-account-creation) to ease user signup friction and enhance security, through your embed placement
*   Make staying on top of your open bugs and feature requests a priority by leveraging [Zapier Issue Manager](/platform/manage/user-feedback#3-consider-zapier-issue-manager)

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Export integration to Platform CLI

The Zapier Platform CLI (Command Line Interface) is a toolset you install and run in your local development environment. It allows you to build, test, and manage your Zapier integration through JavaScript code and terminal commands.

Platform UI is the way for users with API experience that are more comfortable with a visual form editor to build integrations on Zapier.

The CLI is, on the other hand, the most powerful tool for professional developers and teams. Some of the advantages:

*   Access to all [features](/platform/quickstart/ui-vs-cli) of the Zapier Platform
*   Ability to better optimize your code. Move shared logic into modules. Leverage middleware to centralize request & response processing.
*   Easier team collaboration. You'll be able to check all the code for your Zapier integration into your team's source control repo.
*   Set up automated regression tests, to catch errors each time you push a change.

To export your existing Platform UI integration to Platform CLI:

## 1. Install and configure the Zapier CLI on your development environment

Follow the steps in the setup section in our [quickstart guide](/platform/reference/cli-docs#quick-setup-guide)

## 2. Run the `convert` command to create a CLI version of your project locally

Create a new directory for your Zapier project and from the command line `cd` into it. Then run:

`zapier convert {your integration id} . --version={integration version you want to convert}`

Your integration ID can be found in the browser location bar in the Platform UI:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/c5e0c7f17a5ab52d9661e134394e9cc7.png" />
</Frame>

Similarly, your version can be found there, and elsewhere throughout the Platform UI:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e31dd202b5e64bbcb13fc8b200518d17.png" />
</Frame>

Using this example, to create our project in the current directory our command would be:

`zapier convert 1234 . --version=1.0.0`

## 3. Explore your new CLI project and get familiar with the tool

Check out the resources available in the [CLI section](/platform/reference/cli-docs) of the docs to learn all about the Zapier CLI.

If you need a refresher on JavaScript, it's well worth the time to check out some of the many great resources available [online](https://javascript.info/). The Zapier Platform uses promises pretty heavily, so this is a good JavaScript topic to get familiar with.

## 4. Deploy

When you ran convert and created your new local CLI project, it was automatically associated with your Zapier integration (using the `.zapierapprc` file).

A couple of important notes before deploying:

*   When you push the CLI project to the server it will create a *new version* of your integration. If you haven't gotten familiar with how versions work you might take a moment and learn about those [here](/platform/manage/versions).
*   Take a look at the version number in your `package.json` file. When you created your project with the `convert` tool we automatically incremented the version you converted. You can change this to a different version number depending on your needs, but make sure a version with that number doesn't already exist in your integration. Run `zapier versions` from your project directory to see what's already been created.
*   Integrations converted with the `zapier convert` command will include `convertedByCLIVersion` in the `package.json` for informational purposes.

When you're ready to deploy your CLI version run:

`zapier push`

When that completes you'll be able to see the new version in the Platform UI in the Manage > Versions section, and will be able to make Zaps with your new CLI-built version.

You will not be able to edit your new CLI-built version from the *Build* section of the Platform UI, the attributes of this version will all [show a lock icon](https://cdn.zappy.app/f048ffc4c905bcbb332e344ce0ce52ba.png).

You can still use all the other functions of the Platform UI, though, including version management, embed management and your Partner Dashboard. Your earlier versions, created within the Platform UI, are also still available and can still be edited and used.

If you decide that the Platform CLI is not for you, you can delete your new version from Manage > Versions and continue where you left off in the Platform UI.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Export integration to Platform UI

The Zapier Platform UI is the easiest way for anyone with API experience to build Zapier integrations. It is for users more comfortable with a visual form editor.

Some advanced calls and response parsing can still be performed in the Platform UI when using [Code Mode](/platform/build/code-mode).

The Platform UI has the following advantages over the CLI:

*   A graphic user-interface with form-based editor.
*   A live form preview that shows how changes made to your integration would appear in the Zap editor.

If you have an integration in the Platform CLI that you would like to edit in the Platform UI instead, you can create a new version in the Platform UI.

When moving an app from CLI to the Platform UI, we cannot migrate users from previous versions or apps. Existing users will be able to continue using the version they're on, but must upgrade to the new version manually. Existing users would see this [prompt in the Zap editor](https://help.zapier.com/hc/en-us/articles/18755649454989-Update-to-the-latest-app-version-in-Zaps) to optionally encourage them to make the update.

Zapier does not automatically notify all existing users of a new version's availability, but new users would only see the currently promoted/Public version when searching for the app name. We also recommend announcing the changes/new integration version to your general user base via in-app or email marketing to encourage users to switch over.

To export your existing Platform CLI integration to Platform UI:

## 1. Contact Developer Support to create an empty version of your integration

Hiding an app and replacing it with a new app ID built in the preferred tool is [not supported](/platform/publish/integration-publishing-requirements#23-replacement-integration). To export your Platform CLI integration to the Platform UI, contact the [Developer Support](https://developer.zapier.com/contact) team to create an empty version associated with the same app ID.

## 2. Rebuild the integration from scratch

Rebuild the integration from scratch on this newly created empty version in the Platform UI and ensure that this new version has feature parity to the previous CLI version. This new version can then be maintained in the Platform UI by your team. For [Public apps](/platform/quickstart/private-vs-public-integrations), this avoids a repeat of the app review process; as well as retaining previous versions, associated bug reports and feature requests.

> Allow users to remain on the (now legacy) CLI version until they switch over manually, as long as the endpoints are functional. If [Deprecation](/platform/manage/deprecate) is necessary, please note this is significantly more disruptive to our mutual users and should be considered carefully.

When the conversion has been completed, you will be able to see this version and the previous CLI versions in the Platform UI's **Manage > Versions** section.

While you can see the previous CLI versions in the Platform UI, you would not be able edit those versions there. The previous CLI versions would show a lock icon on the various attributes under the *Build* section as they can only be edited from the CLI environment.

You can however, manage the integration, set up and manage embeds (for public integrations) and view the partner dashboard for the integration from the Platform UI.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Change input form field key



## Change scenario

You want to change the key of one or more form field inputs within a trigger, action, or search.

## Impact to users

Modifying the key of a form field input is a breaking change.

Unless precautions are taken, changing the key of an existing form field input will break the field's mapping within the step using it. The previously-mapped value will be dropped, resulting in missed data and/or errors.

## Best practices

*   Avoid changing form field input keys

If your API endpoint needs a different property in the request, consider changing the property key instead of modifying the form field input's key.

Form field input keys do not necessarily need to match the properties expected by your API.

Let's say you have a form field input with the key `first_name` (right) and are sending the field's value to your API using a property with the same name, `first_name` (left):

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/cf7d34d12c1a1c42f75a89815409d20e.png" />
</Frame>

```bash
body: {
  first_name: bundle.inputData.first_name; // original
}
```

Then, your API changes and expects the request property to be `firstname` (one word) instead. As shown below, you can change the request property key (left) as needed (`firstname`) while still referring to the form field input (right) based on its original key (`first_name`):

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b6b1776d964f52eed12d64ff264b8cac.png" />
</Frame>

```bash
body: {
  firstname: bundle.inputData.first_name; // new - request key and field key can differ
}
```

*   Handle both the old and new keys

If it's not possible to simply change a hardcoded request property's key:

We recommend that you design the trigger or action to handle both the old and new key

Using the same example above, let's say that instead of hardcoding your request properties, you were spreading `bundle.inputData` into the body of the API request, so there was a one-to-one relationship between field keys and request properties.

```bash
body: {
  ...bundle.inputData // original - request keys tied to field keys
}
```

Instead of changing form field input keys, you can use Code Mode (Platform UI) or code (Platform CLI) to modify the request.

For example, below, we create a new object, payload, with all the fields from `bundle.inputData` AND the updated property, `firstname`. Then we delete the old property, `first_name`, and send the updated object in the request:

```js
// copy bundle, add updated property
const payload = { ...bundle.inputData, firstname: bundle.inputData.first_name };

// delete old property
delete payload.first_name;

body = {
  ...payload, // send updated payload
};
```

With this approach, or one like it, you can change the request as needed without modifying field keys and breaking users' mappings.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Integration insights definitions

Integration quality on Zapier boils down to two main pillars: **Health** and **Depth**.

**Health** - does the integration allow users to set up Zaps and keep them running reliably? **Depth** - does the integration have all the functionality to allow users to automate what they want through their Zaps?

Definitions for each of the metrics provided in the integration dashboard below:

| **Metric**                         | Definition                                                                                                                                                                                                                                                                 | Filter by                                           |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| **New users**                      | Total number of new users using the integration over a selected period of time.  <br />  <br />A new user = first time a user creates and activates a Zap with this integration.                                                                                           | Last 7 days  <br />Last 30 days  <br />Last 90 days |
| **Bugs & feature requests**        | Total number of open bugs and feature requests for the integration.                                                                                                                                                                                                        |                                                     |
| **Daily active users**             | Number of users who had a Zap activated in a day.  <br />  <br />Activated = Zap is on and successfully ran at least one task.                                                                                                                                             | Last 7 days  <br />Last 30 days  <br />Last 90 days |
| **Monthly active users**           | Total number of users who had a Zap activated in a given month.  <br />  <br />Activated = Zap is on and successfully ran at least one task.                                                                                                                               | By year                                             |
| **Active Zaps**                    | Total number of active Zaps over a selected period of time.  <br />  <br />Active Zaps = number of Zaps currently on and using the integration.                                                                                                                            | Last 7 days  <br />Last 30 days  <br />Last 90 days |
| **Daily active Zaps**              | Number of active Zaps in a day.  <br />  <br />Active Zaps = number of Zaps currently on and using the integration.                                                                                                                                                        | Last 7 days  <br />Last 30 days  <br />Last 90 days |
| **Active users retention**         | The percentage of active users retained each month.  <br />  <br />Retention = users who have at least one successful task executed in a given month.                                                                                                                      |                                                     |
| **Zap activation rate by trigger** | For a given trigger, the percentage of Zaps using the trigger and the rate of the ones that activated within 24 hours of creation. The higher the rate, the better the trigger is performing.  <br />  <br />Activated = Zap is on and successfully ran at least one task. | Last 7 days  <br />Last 30 days  <br />Last 90 days |
| **Zap activation rate by action**  | For a given action, the percentage of Zaps using the action and the rate of the ones that activated within 24 hours of creation. The higher the rate, the better the action is performing.  <br />  <br />Activated = Zap is on and successfully ran at least one task.    | Last 7 days  <br />Last 30 days  <br />Last 90 days |
| **Usage by trigger**               | Number of current live Zaps (Zaps turned on), paused Zaps (Zaps turned off), and total Zaps for each trigger.                                                                                                                                                              | By integration version                              |
| **Usage by action**                | Number of current live Zaps (Zaps turned on), paused Zaps (Zaps turned off), and total Zaps for each action.                                                                                                                                                               | By integration version                              |

Public app integrations also have [visibility into embed insights](https://platform.zapier.com/embed/embed-insights).

## Best practices

*   Launching a co-marketing campaign or running ads? Track daily and monthly MAU over the launch period to track changes in usage.
*   Noticing certain triggers and actions with higher activations? Create additional Zap Templates to expand usage further since you know they are popular functionality.
*   Noticing certain triggers and actions with lower activation? Hop into the Zap Editor and test them out yourself. Are there any technical or usability issues you experience? How do other top apps in your category implement similar functionality?

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Migrate users to a new version

If this isn't the first time you've promoted your app - you might have users on older versions.

If you have made minor, non-breaking changes to improve your integration, you can migrate users to the latest version. When you migrate users to your latest version, it will update all of their Zaps, including Zaps turned on.

## What's the difference between minor and major changes?

Minor changes allow Zaps to continue to work as normal. Minor changes need to be compatible with both the previous and new version.

When migrating users to a new version, only active Zaps (not [draft](https://help.zapier.com/hc/en-us/articles/8496260938125-Create-drafts-of-your-Zaps)) are migrated. In some cases, after migration, users that have drafts with an older version of the app may edit that draft and publish the draft, thereby publishing a Zap with an older version of the app.

Zapier recommends migrating a small portion of your existing users to the new integration version to make sure everything is working as expected. Monitor the logs in the Monitoring tab, and migrate the remainder of your users to the new version when ready.

Major changes would cause active Zaps to error and potentially turn off. They would require users to manually update the Zap in order to get it working again. Learn more about [breaking changes to your integration](/platform/manage/planning-changes), best practices and the user impacts.

Zapier recommends not to attempt to migrate users for major changes. **Migration is not required unless the older version will no longer function.** If necessary, you can [deprecate the version](/platform/manage/versions#deprecating-versions) to prompt users to [manually update to the latest integration version](https://help.zapier.com/hc/en-us/articles/18755649454989-Update-to-the-latest-app-version-in-Zaps). Please note that deprecating a version is significantly more disruptive to our mutual users than migrating to the latest promoted version, or than leaving users on an older (now) private version when migration is not possible.

When users are left on an older private version, they will see a [prompt in the Zap editor](https://help.zapier.com/hc/en-us/articles/18755649454989-Update-to-the-latest-app-version-in-Zaps) to encourage them to make the update themselves.

## Migrate users to new version with Platform UI

In the [Platform UI](https://zapier.com/app/developer):

1.  In the *Manage* section in the left sidebar, click your **Versions**.
2.  On your existing version, click the **three dots icon**

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/7ff6381b55b013ebfc2bdda0e4662676.png" />
</Frame>

3.  Click **Migrate**
4.  The *Versioning* sidebar will appear on the right hand side. You'll need to specify:
    *   **From Version**. Select which version
    *   **To Version**. Select the new version you wish to migrate new users too.
5.  In the *Which users(s) to migrate* field, select either:
    *   **Percentage**. This will allow you to migrate users, based on percentage between 5% to 100%. This cautious approach helps ensure that minor updates haven't inadvertently caused any issues.
        *   Select a **percentage**
        *   Click **Migrate**.
    *   **Email**. To migrate users one at a time, by email. **Note**: migrating a single user will only migrate Zaps that are private for that user. Zaps that are [shared across the team](https://help.zapier.com/hc/en-us/articles/8496277647629), [shared app connections](https://help.zapier.com/hc/en-us/articles/8496326497037-Share-app-connections-with-your-team), or in a [team/enterprise account](https://help.zapier.com/hc/en-us/articles/22330977078157-Collaborate-with-members-of-your-Team-or-Enterprise-account) will **not** be migrated with this method.
        *   In the *Email* field, add the **user's email address** attached to their Zapier account.
        *   Click **Migrate**.
6.  To migrate a user's Zaps, Private & Shared, within all accounts for which the specified user is a member (including Team and Enterprise accounts) you can use the `migrate` command with the `--account` flag if [working in the CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#migrate). Be cautious as this can break shared Zaps for other users.
7.  The *Versioning sidebar* will update to show *Update status:Estimating*. Once the migration has been completed, the *Versioning sidebar* will disappear.

Once you're confident that the new version works well, you can go ahead and migrate the rest of your users.

## Migrate users to new version with Platform CLI

For Platform CLI users, it's possible to perform full or partial migrations using the [zapier migrate command](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#migrate).

**Examples**

```bash
# migrate 100% of your users between version 1.0.0 over to 1.0.1
zapier migrate 1.0.0 1.0.1

# migrate 15% of your users between version 1.0.1 over to 2.0.0
zapier migrate 1.0.1 2.0.0 15

# migrate the specific user user@example.com between version 2.0.0 to 2.0.1
zapier migrate 2.0.0 2.0.1 --user=user@example.com
```

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Change output data response



## Change scenario

Certain changes made to a trigger, action, or search's output data structure can be breaking changes for users. Even if the output field key(s) remain the same, significant changes to the data structure or format returned from one or more of these fields can affect users' Zaps.

Examples of potential breaking change scenarios:

*   An output field representing datetime is updated to return data in ISO-8601 format instead of full date format (Thursday, April 10, 2023 6:30:00 AM)
*   A `results` field in a search now returns a list of all search results instead of the single, last created result
*   A previously comma-separated list value is updated to return line-items

## Impact to users

Updated output fields from your integration steps can impact the way subsequent actions run in a user's Zap when they are mapped to input fields in those actions.

For example, if a user has set up a Formatter step to parse out the day of the week from the `added_date` field before adding it to a CRM, changing the date format to ISO-8601 will cause the Formatter to process unexpected values, which may lead to issues in the user's Zap.

Another example is a user's Zap configured with a search that always returns a single result based on the latest created date in the `results` field. If the search is updated to return all results, subsequent actions in the Zap may not be set up to handle multiple results, leading to potential problems.

## Best practices

*   Use custom code to add your newly formatted data to the existing output data. By doing this, you ensure that users who have already set up their Zaps with the previous data structure will not experience issues due to the changes.
*   *For example:* Add a `created_date_ISO` field with the ISO-8601 formatted created date to the output data, instead of updating the current `created_date` value to be in ISO-8601 format. Define clear [output field labels](/platform/build/sample-data) to help users differentiate the fields; updates to field labels are non-breaking.
*   Add an optional, boolean input field that users can toggle to choose between returning old and the new data structure. This way, users can decide which data structure best suits their needs for existing and new Zaps. Make sure to set a default value for the input field to automatically return data as your previous version did.
*   If you're looking to update a search to return multiple or all search results, instead of the default one result, create a new, separate search for the purpose. This way, users can choose which one to use without encountering issues in their existing Zaps. Differentiate the searches by pluralizing the search: “Find Lead” vs “Find Lead(s)”.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Change output field key



## Change scenario

You may want to update or remove existing output field keys in your Zapier integration. This can happen when you need to clean up output data, remove unnecessary fields, or due to changes in your API's response data.

## Impact to users

If a user has mapped one of the affected fields to an input field in a subsequent step of their Zap, the change can affect that step and potentially the entire Zap's functionality. Imagine if you updated the output field key or removed an output field altogether that a user had mapped to a required field in the next step of their Zap - the step would now error every time.

## Best practices

To minimize disruptions to your users, follow these best practices when updating or removing existing output field keys:

*   Remove extraneous fields carefully: If you're cleaning up output data and removing non-essential fields (such as HTTP request data like ‘request type' or ‘request URL'), you should be able to safely remove them. Be cautious and avoid removing fields that users might have mapped to required fields in subsequent Zap actions.
*   Maintain backward compatibility: If you're using the Zapier Platform CLI, you can use custom code to ensure backward compatibility with updated response data from your API. Modify the perform methods in your code appropriately to handle these changes.
*   Update the static sample: When you have made any changes to output fields, make sure to update the [static sample data](/platform/build/sample-data) accordingly. This ensures that the sample data used for testing and Zap setup by users remains accurate.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Planning and implementing integration changes

Before making updates to your integration, it's important to consider the potential impact on user migration and existing Zaps. Ensuring your API and Zapier integration remains backwards compatible is crucial to avoid disruption to users. However, we acknowledge certain changes are sometimes necessary and unavoidable. In such cases, consider the best practice for implementation.

## Effects of Different Changes (Versioning Matrix)

The matrix below illustrates the impact of different changes on your users. For public integrations, this will affect promotion and whether migration is possible. Refer to our best practices to facilitate the upgrade process for yourself and your users.

Columns:

*   Add: Adding a net new component
*   Update: Making a change to an existing component
*   Replace: Deleting/deprecating an existing component and adding a new one in its place
*   Delete/Deprecate: Removing an existing component completely

Matrix Key:

*   Breaking Change: A modification to the integration which renders existing Zaps incompatible with the new version
*   Depends: A modification which may render existing Zaps incompatible with the new version, depending on the implementation
*   <p> <Icon icon="check" /> : A modification to the integration which renders existing Zaps compatible with the new version</p>
*   \-: Not applicable

Common changes that can affect the ability to migrate users are detailed in the documentation linked in the matrix. Look for your change and pay attention to the recommended best practices.

Several change scenarios are validated by the platform when you try to *Migrate* after a version promotion, but always be aware of the effects of any changes you make before you even begin implementing those changes.

Change scenarios marked as *Depends* with no linked best practice can vary widely across integrations, so a generalized best practice is not provided; reach out to [support](https://developer.zapier.com/contact) if you need help with your specific change scenario.

| Integration Change                                        | Add                                               | Update                                             | Replace                                         | Delete/Deprecate                               | Validated by platform? |
| --------------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------- | ---------------------------------------------- | ---------------------- |
| **AUTHENTICATION CHANGES**                                |                                                   |                                                    |                                                 |                                                |                        |
| **Authentication schemes**                                | [BREAKING CHANGE](/platform/manage/auth-scheme)   | -                                                  | [BREAKING CHANGE](/platform/manage/auth-scheme) | -                                              | <Icon icon="check" />  |
| **Authentication fields - required**                      | [BREAKING CHANGE](/platform/manage/auth-required) | [Depends](/platform/manage/auth-required)          | -                                               | <Icon icon="check" />                          | -                      |
| **Authentication fields - optional**                      | <Icon icon="check" />                             | <Icon icon="check" />                              | -                                               | <Icon icon="check" />                          | -                      |
| **Authentication field key(s)**                           | -                                                 | [BREAKING CHANGE](/platform/manage/auth-keys)      | -                                               | -                                              | -                      |
| **Authentication - token request**                        | -                                                 | <Icon icon="check" />                              | -                                               | -                                              | -                      |
| **Authentication - test function**                        | -                                                 | <Icon icon="check" />                              | <Icon icon="check" />                           | -                                              | -                      |
| **TRIGGER/ACTION CHANGES**                                |                                                   |                                                    |                                                 |                                                |                        |
| **Trigger/Action - meta info (e.g.: label, description)** | -                                                 | <Icon icon="check" />                              | <Icon icon="check" />                           | -                                              | -                      |
| **Trigger/Action - key**                                  | -                                                 | [BREAKING CHANGE](/platform/manage/change-keys)    | [BREAKING CHANGE](/platform/manage/change-keys) | -                                              | <Icon icon="check" />  |
| **Trigger/Action - input field(s) - required**            | [Depends](/platform/manage/required-input)        | [Depends](/platform/manage/required-input)         | [Depends](/platform/manage/required-input)      | <Icon icon="check" />                          | -                      |
| **Trigger/Action - input field(s) - optional**            | <Icon icon="check" />                             | <Icon icon="check" />                              | <Icon icon="check" />                           | <Icon icon="check" />                          | -                      |
| **Trigger/Action - input field(s) - key**                 | -                                                 | [BREAKING CHANGE](/platform/manage/input-key)      | [BREAKING CHANGE](/platform/manage/input-key)   | -                                              | -                      |
| **Trigger/Action - input field(s) - field type**          | -                                                 | Depends                                            | Depends                                         | -                                              | -                      |
| **Trigger/Action - output data - key(s)**                 | <Icon icon="check" />                             | [BREAKING CHANGE](/platform/manage/output-key)     | [BREAKING CHANGE](/platform/manage/output-key)  | [BREAKING CHANGE](/platform/manage/output-key) | -                      |
| **Trigger/Action - output data - response structure**     | -                                                 | [BREAKING CHANGE](/platform/manage/output)         | [BREAKING CHANGE](/platform/manage/output)      | -                                              | -                      |
| **Trigger/Action - perform function**                     | -                                                 | Depends                                            | Depends                                         | -                                              | -                      |
| **Trigger type - polling to hook type**                   | -                                                 | [BREAKING CHANGE](/platform/manage/change-trigger) | -                                               | -                                              | <Icon icon="check" />  |
| **Trigger (polling) - perform function**                  | -                                                 | [Depends](/platform/manage/change-perform)         | [Depends](/platform/manage/change-perform)      | -                                              | -                      |
| **Trigger (hook) - perform list**                         | -                                                 | Depends                                            | Depends                                         | -                                              | -                      |
| **Trigger (hook) - performSubscribe**                     | -                                                 | <Icon icon="check" />                              | <Icon icon="check" />                           | -                                              | -                      |
| **Trigger (hook) - performUnsubscribe**                   | -                                                 | <Icon icon="check" />                              | <Icon icon="check" />                           | -                                              | -                      |
| **OTHER CHANGES**                                         |                                                   |                                                    |                                                 |                                                |                        |
| **Middleware**                                            | Depends                                           | Depends                                            | Depends                                         | Depends                                        | -                      |
| **Partner's API (overall)**                               | -                                                 | Depends                                            | Depends                                         | Depends                                        | -                      |
| **Product feature**                                       | <Icon icon="check" />                             | -                                                  | -                                               | -                                              | -                      |
| **Rebrand - (e.g. logo, app name)**                       | -                                                 | <Icon icon="check" />                              | -                                               | -                                              | -                      |
| **Export UI to CLI**                                      | -                                                 | [Depends](/platform/manage/export-cli)             | <Icon icon="check" />                           | -                                              | -                      |
| **Export CLI to UI**                                      | -                                                 | [Depends](/platform/manage/export-ui)              | -                                               | -                                              | -                      |
| **Edit Legacy Web Builder integration**                   | -                                                 | [Depends](/platform/manage/versions-legacy)        | [Depends](/platform/manage/versions-legacy)     | -                                              | -                      |

If your change is not listed, make sure to still consider whether it changes keys, requires users to provide *new* info, or revokes functionality.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Promote a version

After your integration has entered the beta or public status, you can set a new default version for public use. This process is called promoting a version.

Prior to promoting a version, run the [automated validation checks](/platform/publish/integration-checks-reference). All Errors and Publishing Tasks must be validated. Warnings are non-blocking and not strictly required to proceed as they would not prevent you from promoting a version, though we do recommend you review them for usability of your integration.

## Promote a version with Platform UI

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Manage* section in the left sidebar, click your **Versions**.
4.  On the version you want to promote, click the **three dots icon**

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/2ee11c82946187818a9622c4b4bf65bc.png" />
</Frame>

and **Promote**.
5\.  Fill in the [changelog form](/platform/manage/user-feedback#3-close-resolved-issues) and click **Promote**. Selecting specific features added and bugs fixed will automatically queue up the issue(s) to be reviewed by our internal team for closure.

> **Note**: If you have a private integration, you will not see the *Promote* option. Instead, you can [share your new version](/platform/manage/sharing) with users.

## Promote a version with Platform CLI

In the Platform CLI, you can run `zapier promote [version]` to make the specified version number the new public and default version. Learn more about [promoting a version using the Platform CLI](https://docs.zapier.com/platform/reference/cli-docs#promoting-an-integration-version) .

## What happens after you promote a version?

After successfully promoting a version:

*   **Zap templates update**: If there are no breaking changes, all Zap templates will be updated to use the new public version.
*   **New triggers and actions**: Any newly added triggers or actions will be displayed on your integration's public app page.
*   **User experience**: Users who select your integration for a new Zap will interact with the promoted version by default.

By following these steps, you can seamlessly promote a new version of your integration, ensuring that new users have access to the latest features and improvements.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add new required input field



## Change scenario

Your app's API endpoint adds an additional field for filtering records (trigger/search) or an additional property for created records (action).

## Impact to users

Adding a new **required** input field or making a *previously* optional input field now required, would break existing Zaps without a value for the field. There is no automated check for this when migrating so though it may seem possible, it will be a breaking change for migrated users.

## Best practices

Consider the following options:

*   Add new input fields as optional. Use a combination of custom error handling to throw an error if the field is empty and help text to explain the field is required.
*   Use [Code Mode](/platform/build/code-mode) (Platform UI) or code in `Perform` (Platform CLI) to set a default value for the inputField if users have it blank in their existing Zaps. This would only be successful if a universal default could apply to all users (would not work for custom fields for example).
*   Create a new trigger/action/search (T/A/S) with the required inputField; and hide the previous T/A/S. That way, existing Zaps will continue to work with the previous (and now hidden) trigger/action/search definition, and new Zaps will be forced to provide a value for the required field.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Share your integration

Once an integration is public, all users would have access to it when searching for an app's name in the Zap Editor, or in the [Zapier App Directory](https://zapier.com/apps).

For private integrations, there are two ways to share access with users:

## 1. Invite users with a public link

You can share a link with users to all versions of your integration.

*   You can't view a list of users that have accessed the public link.

*   You can't revoke a link invite, once shared. If you require control over who accesses your integration, we recommend inviting users by email.

*   [Log into the Platform UI](https://zapier.com/app/developer)

*   Select **My Integration**.

*   In the *Integrations* section, select your **integration**.

*   In the left sidebar, under *Manage* click **Sharing**.

*   <p>Click <Icon icon="clone" /> to copy the invite public link.</p>

*   You can access the public link in this way for integrations built in the Platform UI or the Platform CLI.

*   Generating a public sharing link for one specific version only is available in the [Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#sharing-an-app-version), but not the Platform UI.

Share the invite link with users via your website or other method. The link will take users to your integration landing page where they can follow the on-screen instructions to accept your invite to all versions of the integration.

## 2. Invite users by email

Inviting users by email gives you the option to select which version of your integration they can access. All users invited by email are tracked in the Sharing tab, allowing you to view and control who has access to your integration.

*   You can only invite 200 users by email.

*   [Log into the Platform UI](https://zapier.com/app/developer) or refer to the [Platform CLI documentation](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#sharing-an-app-version).

*   Select **My Integration**.

*   In the *Integrations* section, select your **integration**.

*   In the left sidebar, under *Manage* click **Sharing**.

*   In the *Email* field, insert the **user's email**.

*   (Optional) In the *Versions* dropdown menu, select which **version** of your integration you want your user to access.

*   Click **Invite**. This will instantly send the user an email from [notifications@mail.zapier.com](mailto:notifications@mail.zapier.com).

*   You can send invitations in this way for integrations built in the Platform UI or the Platform CLI.

The user's email will be added to the table in your Sharing settings. From there, you can track the status of their invite. They will need to accept the email invite in order to be able to access the integration.

## Revoke invites sent by email

Revoking invites will remove a user's access from that integration version, and all Zaps will immediately be paused. The user won't be able to use your app again until they're re-invited or it has gone public. In practice, this is not used often as it's very disruptive to users.

### Revoke public link invite

It's not possible to revoke a user's access to or change a public link invite once it has been shared.

### Revoke email invite

There are two ways to revoke email invites using either the Platform UI or Platform CLI.

#### Zapier Platform UI

1.  [Log into the Platform UI](https://zapier.com/app/developer)
2.  Select **My Integration**.
3.  In the *Integrations* section, select your **integration**.
4.  In the left sidebar, under *Manage* click **Sharing**.
5.  In the row for your user, click **Delete**.
6.  Click **Really?** to revoke the access for that user.

#### Zapier Platform CLI

> **Note**: This method will remove users from all versions of your integration.

1.  In your terminal, navigate to the directory of your integration (the directory with the `.zapierapprc` file).
2.  Run zapier `users:remove user@example.com`. Replace `user@example.com` with the email address that you'd like to revoke access for.
3.  The CLI will prompt you to confirm your revocation, type `Y` and click `Enter`.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Respond to user feedback and bugs

For public integrations, Zapier's Support team logs user requests and reported problems in Zapier's issue tracker, that your team can see from the _Bug & Feature Requests_ page in the Manage section.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b986eef73a1558ee3e121cf5d985339a.png" />
</Frame>

Problems with your public integration, authentication and API calls will be logged as `bug`, while new functionality users request for your app integration will be logged as `feature request`. You have the ability to filter open `bugs` and `feature requests` to view issues by individual Trigger or Action.

## 1. Monitor issues reported

*   Whenever a new issue is added or updated, Zapier will log it in the *Bugs & Feature Requests* page of your app's Developer Platform page.

*   Issue notifications are automatically sent to all confirmed Administrators and Collaborators on the integration team. If an Administrator or Collaborator is listed as `Invitation Pending`, they will need to accept the invite before they start to receive issue notifications.

*   Bug count is not as important as how many of your users are affected overall and what percentage of the app's overall monthly active users that impacts. Past history on closing bugs doesn't influence your app's current health score.

*   Affected user counts on issues typically underrepresent the actual totals, as only a small portion of all users facing an issue take the time to contact Zapier Support.

### Utilizing issue statuses

Maintaining accurate issue statuses during regular triage and review of your integration's issues is critical for keeping your integration healthy. Not only do statuses help your team track progress and build roadmaps, but they also signal your engagement and prompt reviews for fixed issues to the Zapier team.

<Frame caption="Click on &#x22;Enter Fullscreen&#x22; for a better demo experience.">
  <iframe src="https://on.driveway.app/guides/zEVVdGE/embed" width="100%" height="420px" />
</Frame>

Available statuses:

*   *Open*: The issue is open and no action has been taken.
*   *Planned*: Work has been committed to address the issue, but has not started.
*   *In Progress*: This issue is being actively worked on.
*   *Done*: Work is completed, and the updates for the issue have been promoted.
*   *Under review*: Zapier is reviewing the work completed on the issue.
*   *Rejected*: After review, Zapier deemed the issue resolution not complete.

Once an issue is resolved, update the issue status to `Done`. You will be prompted to select one of the following issue resolutions:

*   *Resolved*: Work is completed and updates have been promoted.
*   *Won't Do*: This issue won't be actioned (i.e. not on the roadmap, not technically possible, etc.). A comment explaining why is required.
*   *Duplicate*: The issue is a duplicate of another issue. Please note the duplicate issue in the required comment.
*   *Cannot Reproduce*: All attempts at reproducing this issue failed.

All issues marked `Done` are reviewed by the Zapier team for accuracy and completion. Any necessary explanation or feedback will be provided via comments on respective issues after the internal review. Please respond with questions or concerns on the appropriate issue to keep discussions clear.

Plus, any of the statuses above can sync right into your preferred issue-tracking tool thanks to the [Zapier Issue Manager integration](/platform/manage/user-feedback#4-consider-zapier-issue-manager-zim).

## 2. Respond to issues in a timely manner

*   Reply to the Zapier team about the issue within the *Bugs & Feature Requests* page to keep communications consistent for both your team and ours. These issues and comments are not visible to affected users; and users will only be notified when an issue is verified as closed/resolved by Zapier Support.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/2f7af76046895c4b4f053a1b1ce9781d.png" />
</Frame>

*   Lowering your count of issues will help improve your integration's [health score](/platform/publish/partner-program#integration-health-score) and increase your tier in the [Partner Program](https://zapier.com/platform/partner-program). The more you maintain a bug-free, quality integration, the more essential your product becomes to your users' workflows. This sets you up to reduce churn, drive account upgrades, and increase customer lifetime value.

*   Regardless of the size of your integration's user base, keeping the percent of monthly active users affected by open bugs under double figures (and ideally at 0) is recommended for a healthy integration.

*   Allocate ongoing resources in your team's product roadmap for the maintenance of your Zapier integration to avoid surprise work or gaps in functionality. Consider a [Zapier Expert](https://zapier.com/experts/matchmaking) to help you fix one-off bugs or maintain your integration.

*   Keep issue statuses up-to-date as you regularly review, triage, and address your integration's issues. See the [`Utilizing issue statuses` section](/platform/manage/user-feedback#utilizing-issue-statuses) below for more details and best practices.

### Dormant Issues

Any reported issue with no updates from you, Zapier Support, or end users for a year will now be automatically closed. This keeps your workspace current and focused, making it simpler to stay on top of active issues.

## 3. Close resolved issues

*   Bugs and feature requests require review and verification from Zapier's Support team before they can be closed. When an issue is closed, email notifications are sent out to all affected users on the issue, notifying them of the update.

*   Whenever a new version of your integration is promoted via the UI, you'll be prompted with a changelog form asking you to identify which feature requests or bugs were resolved and to provide a user-facing description of the changes. Issues identified in the changelog will automatically be queued for review by our Support team and closed once resolution is confirmed. Promoting via the CLI doesn't currently support changelogs.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8afba17ecfb25faf6b87597e2cd54387.png" />
</Frame>

*   You can also request issues to be closed manually by updating their statuses to `Done`. Make sure updates are promoted to the latest version of your integration, and users are migrated if possible before marking issues before marking issues as `Done`, as it will be reviewed by a Zapier support member. See the [`Utilizing issue statuses` section](/platform/manage/user-feedback#utilizing-issue-statuses) below for more details and best practices.

## 4. Consider Zapier Issue Manager (ZIM)

*   If you prefer syncing and managing issues from your own issue-tracking tools (such as Jira or Trello), create Zaps using Zapier Issue Manager.

*   Zapier Issue Manager is available to all *Beta* and *Public* Zapier partners who own and maintain their own integration. To request access, submit the request form below.

[Click here to request access to Zapier Issue Manager](https://zim.zapier.app/request)

Zapier Issue Manager offers the following for building Zaps:

### Triggers

*   New Issue
*   Issue Closed
*   New Comment
*   Affected User Added
*   Issue Reopened

### Actions

*   Create Comment
*   List Issues
*   Find Issue
*   Close Issue
*   Update Work Status
*   Generate Report

Here are some Zap templates to help you get started using Zapier Issue Manager.

## 5. Monitor integration insights

*   [See all the metrics tracked](/platform/manage/integration-insights) in this table, or access them for any integration you are an Admin or Collaborator on from the *Dashboard* tab of the Platform UI. Insights include important data on both the integration's growth and usage, such as monthly active users, retention rates, and Zap usage by triggers and actions.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d7a53ee12f8fb94a44edbc0f8e3195ea.png" />
</Frame>

*   Filter growth metrics by the last X number of days to identify trends and changes in user activity in correlation to marketing initiatives, integration changes, and product updates like [embedding Zapier](https://platform.zapier.com/embed/full-zapier-experience).

*   Usage details for each trigger and action will show which functionalities are the most popular and being effectively utilized.

*   Share access to the insights by [inviting collaborators](/platform/manage/add-team) to the integration without giving them permissions to make changes to it.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Versions

Versions in Developer Platform allow developers to create multiple iterations of their integration to experiment with and implement new features without affecting existing users. Each integration can have many versions, but only one version can have a public status at a one time.

Versions allow you to have:

*   **Seamless user experience**: Existing users have uninterrupted service, while new features are being tested and deployed.
*   **Incremental upgrades**: Developers can facilitate phased roll outs of new features, allowing for thorough testing and feedback collection before full deployment.
*   **Version management**: Developers have a structured approach to migrate users to updated versions and deprecate older versions when applicable.

## Managing versions in Platform UI

To manage your versions:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Manage* section in the left sidebar, click your **Versions**.

This page shows a list of all versions of the integration, along with status, number of active users and active Zaps on each. For public integrations will also show the changelogs input when a new version is promoted.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b443129368e61bdaa86c6f5a741fbe8a.png" />
</Frame>

Learn more on:

*   [Clone an version](/platform/manage/clone)
*   [Promote a version](/platform/manage/promote)
*   [Migrate users to a new version](/platform/manage/migrate)
*   [Deprecate or delete an version](/platform/manage/deprecate)

## Managing versions in Platform CLI

Integrations created with the Platform CLI cannot be edited in the Platform UI, however you can view the available versions in the Platform UI. You can also run the [`zapier versions`](https://github.com/zapier/zapier-platform/blob/master/packages/cli/docs/cli.md#versions) command to see the same information in your local terminal.

### What do I do if I am blocked from pushing new versions?

Zapier may fix bugs or add new features to your integration and release a new version.

In the event that Zapier has made changes to your integration, you will be unable to do the following until you update your local files by running `zapier pull`:

*   push to the promoted version
*   promote a new version
*   migrate from one version to another version

Run `zapier pull` to update your local files with the latest version and remove these restrictions. Any destructive file changes will prompt you with a confirmation dialog before continuing.

## Who can view your versions?

For public integrations, which are searchable in the Zap editor or in the app directory, a user who selects your integration in the Zap Editor will be using the current public version by default.

For both [private and public integrations](/platform/quickstart/private-vs-public-integrations), only team members added to the integration, or users with whom you have shared private versions with specifically, will see those versions as well.

## Editing versions

To make sure that existing Zaps can continue to work consistently, the Developer Platform only allows you to edit versions that have a private status and have fewer than 5 users. Versions that are public or have more than 5 users will show a warning message prompting you to clone the version instead.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8b5cbf5a37ce60ba89eb555c0429eca6.png" />
</Frame>

When making integration updates in newer versions, consider the potential impact on user migration and existing Zaps. Ensuring your API and app integration on Zapier remains backwards compatible is crucial to [avoid disruption to users](/platform/manage/planning-changes).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Manage a legacy integration



## Legacy API configuration settings

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/69285c90f9e37fc2803a06beb1395946.png" />
</Frame>

Converted legacy Web Builder integrations have slightly different API Configuration tabs for authentication, triggers, and actions then integrations (and new triggers or actions) built in Platform UI. In new integrations, each API field will let you set the API call settings in a form, or switch to [Code Mode](/platform/build/code-mode) to add custom JavaScript code for that call and its response parsing. In converted integrations, however, you will see static text fields for each API call URL. If you need to customize the API call or response bundle parsing, you will need to do so from the [Legacy Scripting](https://platform.zapier.com/legacy/scripting) settings.

## Where's my code?

To find your find your previous code:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section in the left sidebar, click your **Advanced**.
4.  Click the **Legacy Web Builder** tab.

Your converted integration looks a little different than if you'd started it from scratch in the Platform UI, but should feel pretty familiar (apart from versioning). One thing you may notice are sections where the configuration is a code block, and the code is calling `z.legacyScripting.run()`. To make integrations written in the old environment run without modification, in the new environment Zapier ‘wraps' your custom code and API configuration, and created a runtime library that emulates the behavior of the old platform. So each time you see this, you can recognize it as an artifact of this emulation - we're calling into the emulator to perform the operation we need using your original code and configuration.

Where you added custom scripting code, the `z.legacyScripting` will call these functions to drive your triggers, actions, and authentication transactions.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/79bb2a1ea31a301f286e07e209994dcd.png" />
</Frame>

You can edit this code as before, this time in a redesigned editor that does not take up your full screen. Be sure to click **Save** after editing your integration code, as Zapier does not auto-save changes.

> **Note**: Zapier recommends to understand [versioning model of the Platform UI](/platform/manage/versions) before making any code changes.

*→ Find detailed documentation on how to edit your integration's code in [Legacy Scripting](https://platform.zapier.com/legacy/scripting).*

## What is not included in Platform UI?

Platform UI includes most of the features you would expect from the legacy Web Builder, along with new features to help better manage your integration. In converted integrations, you will see some additional features to manage existing authentication, triggers, and actions. If you add new triggers or actions to your integration, though, these features will not be available in the new items, including:

*   **Scripting**: The legacy Web Builder combined all code into one Scripting page that let you add custom code for authentication, triggers, actions, and response data parsing. If you add new triggers or actions to your integration, though, use [Code Mode](/platform/build/code-mode) to customize your API call or response bundle parsing. Learn more in [legacy scripting](https://platform.zapier.com/legacy/scripting).
*   **Custom results fields**: The legacy Web Builder included an option in triggers and actions to make an API call that would fetch custom result field names. The Platform UI does not include this option for new triggers and actions, though it does let you continue to use this option in converted, existing triggers and actions. If you add new triggers or actions to your integration and need to fetch custom results fields, you will need to add that to the trigger or action's API call in the code mode.
*   **OAuth v1**: Platform UI does not support OAuth v1 for new integrations, and instead requires new integrations to use OAuth v2 authentication. You may continue to maintain existing OAuth v1 authentication in converted integrations, but if you make a new version of your integration or re-build it in Platform UI, you will need to use a newer authentication scheme.
*   **Advanced Editor**: The legacy Web Builder included an *Advanced Editor* option to edit your integration in JSON text instead of the web UI editor. That feature is no longer available in Platform UI, for new or converted integrations. If you would prefer to manage your integration in JSON text and code, the better option would be to export your integration to Platform CLI to manage it from your local development environment. Once your app has been converted to the Platform UI, you will have the option of [exporting your app to Platform CLI](/platform/manage/export-cli).
*   **Files**: The legacy Web Builder included some support for [file handling](/platform/reference/legacy-scripting#files). The Platform UI does not include capabilities for working with files. To work with files, [export your app to the CLI](/platform/manage/export-cli) after it is converted.

## Creating new triggers and actions

You'll be able to continue your development, creating new triggers and actions, in your converted Web Builder integration. When you create a new trigger or action, the interface will look a little bit different than those triggers and actions that were converted over from the legacy platform. It will look familiar to you. The big difference is when configuring an HTTP API request, you'll have many more options to customize headers and parameters without having to write custom code. And when you do need to use custom code, you'll do so in that trigger/actions's UI, rather than in a common script file. We'll talk about differences in the scripting environment next.

## Differences when writing custom scripting code

The following applies when you're building *new* triggers and actions, and working with custom scripting code. These are the idioms of the way operations are executed on the new platform.

> **Note**: If you're making changes to the code in the **Legacy Web Builder tab**, the following statements do not apply. That code is run in an “emulated” context and works just like the old environment.

### Code is scoped to the individual trigger or action

When you're writing custom scripting code in the new platform the scope of that code is local to that trigger or action. This makes it easier to understand when a request will be handled by custom code, or simply configuring a request for default request handling. It also means you can't easily share code between different triggers and actions. For that, have a look at moving to the Zapier CLI, which is geared toward teams, projects with custom code, projects that need 3rd party libraries, etc.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/27b0a08799b047fc9db1ed204d030377.gif" />
</Frame>

> **Tip**: A handy way to understand this is to have a look at the [schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md) of the integration definition you're building. [Here](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#basicpollingoperationschema) you'll see the structure of the definition of a polling trigger. Notice that the `perform` field takes a request configuration object, or it simply takes a Javascript function. The Platform UI reflects this. When you configure an API interaction, you use the Form Mode to configure a request, or you use Code Mode to write a function to be called instead.

### No pre or post functions

In the legacy Web Builder, you could write custom code that ran before the request was made, or after the request returned, or you could take over complete control of making the request and returning data to Zapier. In Platform UI, there is only the ‘complete control' option. Your request can implement a `perform` function that takes a bundle as input and is expected to return data when the request handling is completed.

### Promise based API

A big difference in the API of the new environment is that the request API is asynchronous, using promises rather than synchronous requests or callbacks. For developers new to Javascript promises this can be a small stumbling block. We recommend spending a few minutes getting familiar with how promises work in general. The `z.request` library works very similarly to the promise-based Fetch API, so most articles and tutorials about that apply to working with custom code in Zapier's environment.

<CardGroup>
  <Card title="Using promises(MDN)" icon="link" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises" horizontal />

  <Card title="JavaScript Promises in Depth(Egghead.io)" icon="js" href="https://egghead.io/courses/javascript-promises-in-depth" horizontal />

  <Card title="Using Fetch(MDN)" icon="link" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" horizontal />

  <Card title="Re-introduction to JavaScript(MDN)" icon="js" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript" horizontal />
</CardGroup>

### No libraries included

The legacy Web Builder tool included several JavaScript libraries, such as Underscore and jQuery, in its context that you could use in your script code. Those are no longer available when writing scripting code in the Platform UI. You'll be able to use any JavaScript feature of Node.js (version 12 at the time of this writing, possibly v14 by the time you're reading this), including its standard library with `z.require`.

If you need, or prefer to use, a 3rd party Javascript library you'll want to switch to the Platform CLI where you can install modules from npm and reference them throughout your code.

#### Structure and naming of the Bundle Object is different

Field names and structure of the Bundle is slightly different between the Legacy Web Builder and the new platform. Learn more details in our [Bundle docs](/platform/reference/cli-docs#bundle-object).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Zap activation rates

Consider all of the Zaps that users try to create with your integration's triggers, actions, or searches. The Zap activation rate is the percentage of those Zaps that actually activated within 24 hours of creation, meaning the Zap ran at least one successful task.

A low activation doesn't necessarily mean something is broken. Maybe the user started to make a Zap, put the kettle on, forgot about what they were doing and didn't bother ever coming back to finish what they started.

It can also highlight that certain triggers or actions are proving challenging to set up in a Zap or are erroring when run. Perhaps your trigger sample doesn't return the custom fields the user is looking to map in the Zap or the user gets confused by unclear input fields while setting up their action; these are common reasons a user may abandon setting up their Zap.

## Key Zapier insights

Zap activation rates at the individual trigger and action level are a great leading indicator of performance and usability. Simplifying authentication is a vital first step. If users can't authenticate or get their Zaps enabled and activated, they stand little chance of ongoing success.

## Best practices

Give users the best chance to successfully activate their Zaps by making the integration as familiar and easy-to-use as possible:

*   Follow our [Integration publishing requirements](/platform/publish/integration-publishing-requirements) which provide detailed user experience best practices for building your public integration.
*   Create [Zap Templates](/platform/publish/zap-templates) for popular use cases to simplify the setup process and decrease room for user error.
*   Head to the Zap editor to create and test Zaps using your own integration. This gives you firsthand experience on how users interact with the integration.
*   Keep your app and Zapier integration parallel to maintain a consistent experience for mutual users.
*   Object and field names referenced in the integration should parallel names in your app. For example, don't name your trigger *New Lead* if they are referred to as *Contacts* in your app. This makes it hard for users to find the functionality they want.
*   Provide all available input fields in your app in the integration step, including custom fields.
*   Match the field types in your app to the integration. For example, if a field is a static dropdown in the app, implement it as the same type within the integration.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Add or modify integration branding and details

When creating a new integration in the Platform UI from the link `https://developer.zapier.com/app/new`, you'll be prompted to add the app name, description, homepage URL and logo.

## Platform UI

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d98fc9b3e4aa9f5813f3f8f4cff70c98.png" />
</Frame>

It is also required to complete the Intended Audience, your role and the app's category.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/4e7b51a2339a6ba997c9a0a1a74561aa.png" />
</Frame>

## Platform CLI

When creating a new integration in the Platform CLI, you can optionally add the app name, description, and homepage URL to the `package.json` file. The rest of your app's branding needs to be added in the Platform UI once you `zapier push` your integration for the first time.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/08131a9df7402a098098ce7de7ed8197.png" />
</Frame>

Build your integration locally first. Once you've added your app's core details, authentication, triggers, and actions, push your integration to Zapier with a `zapier push` command. Zapier will use the name you added in the CLI integration settings, along with a placeholder icon for your app.

Next, add your app's branding via the Platform UI at [developer.zapier.com/](https://developer.zapier.com/). There you will see every Zapier integration you've built. The *My integrations* section includes every app you've added via Zapier's Platform UI or CLI. Look for the integration you built with Zapier CLI and select.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/21501f70d3d15a341e6dc7ea90690ee6.png" />
</Frame>

Only the branding for your CLI-built app can be edited in the UI; authentication, triggers, and actions must be edited in your local environment and pushed to Zapier.

To edit branding, select the gear icon in the upper left hand corner near your app's name and placeholder icon. Edit your integration's name and description, and upload your app's logo (a square, transparent PNG at least 256x256px), meeting [requirements](/platform/publish/branding-guidelines). Below that, set your app's Intended Audience, your role and the app's category. Select *Save*.

## Modify or rebrand your integration

### Private integrations

For integrations in `private` status, branding can be updated anytime on the Integration Settings page. Access Integration Settings by clicking the gear icon to the right of the integration name.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/cf718800abff0d3a5905e92bb8c3bc90.png" />
</Frame>

### Public/Beta integrations

For integrations in `public` or `beta` status, branding changes need to be processed by our Partner Support team. To request branding changes, visit the Integration Settings page and click the form linked at the top of the page.

The app ID and your Zapier account email will automatically populate into the form. Provide only the details you want updated on your integration's directory page, and submit the form. You'll receive a confirmation email of your submission, and the Partner Support team will process the changes within 1 business day. You'll receive a second email confirming once the changes have been made.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/13cb72133173c8add77496c62d0a4079.png" />
</Frame>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# A Guide to Zapier Partner Program Benefits: Accelerate your integration's growth and reach

Zapier offers a variety of marketing and support benefits to partners. This cheat sheet is designed to help you understand when and how you can access each of these benefits as you unlock them.

## Accelerate your access to these benefits by embedding

**Unlock the benefits of the Zapier Partner Program faster by embedding your integration into your product via your help docs, integration pages, or blog posts.** There are two ways to do this:

1.  If you're in Beta, you'll exit Beta the day after we detect a signup that originates from your embed
2.  If you're already a Partner, you can access some of the benefits of the next tier faster if you meet these requirements:
    *   we detect a signup from an embed that originates on your site
    *   your integration has a Healthy or Exceptional health score
    *   your integration meets a minimum active user count (40 for early Silver benefits, 300 for early Gold benefits, 2,500 for early Platinum benefits)

Embedding your integration not only enhances your product's functionality but also fast-tracks your access to higher-tier benefits. It's our way of incentivizing partners to integrate with Zapier and reap the rewards sooner.

## Next steps

*   Review your integration tier on the Partner Program page in the developer dashboard
*   Plan benefit redemption around your integrations' marketing calendar (and keep track of the annual usage limit of each benefit)
*   Highlight your integration in your product and marketing assets with our [partner embedding tools](https://zapier.com/l/partner/solutions)

## Access to our Partner Support team

Access to integration best practices through our dedicated Support team.

*   **How to access this benefit:** Submit our [contact form](https://developer.zapier.com/contact)
*   **Eligible tiers:** Bronze+

## Complimentary Zapier account for testing integration updates

Access to a complimentary workspace with premium features, giving your integration team access to features to support the ongoing development and maintenance of your integration.

| Zapier Partner Sandbox |                                                                                                                      |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **Tier**               | Bronze+                                                                                                              |
| **How to redeem**      | Submit the form linked at the top of the “Manage Team” page for your eligible integration on the developer platform. |
| **Opportunity**        | Use Zapier Partner Sandbox to test integration updates before promoting them to users.                               |

## App directory listing lead generation button

A “Learn More” button ([example](https://cdn.zappy.app/8a9c2c36af184c011c6377dbd7b8d54c.png)) is added to your app's listing in the App Directory. This way, anyone who lands on this page can directly visit your site for more information on your product.

| Lead Generation Button |                                                                                                                                                                        |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Tier**               | Silver+                                                                                                                                                                |
| **How to redeem**      | Automatically redeemed for you. Customize this button with UTM parameters of your choice reaching out to our team at [partners@zapier.com](mailto:partners@zapier.com) |
| **Opportunity**        | Make it easy for potential users to explore your product with just one click through the “Learn More” button for enhanced lead generation.                             |

## Blog feature of one of your popular workflows

Showcase one of the most popular workflows between your app and others on the Zapier blog.

| Deep Dive Blog Post |                                                                                                                                                                                                               |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Tier**            | Silver+                                                                                                                                                                                                       |
| **How to redeem**   | Email us at [partners@zapier.com](mailto:partners@zapier.com) with the Zap Template ID and blurb. You can also provide one no-follow link related to the workflow which we will consider linking in the post. |
| **Opportunity**     | In 1-3 sentences, describe how a popular workflow solves a common automation goal for your users.                                                                                                             |
| **Frequency**       | One every 365 days (rolling)                                                                                                                                                                                  |
| **Best used**       | In tandem with your integration promotional efforts through your socials                                                                                                                                      |

## Community post inclusion of your integration updates

We showcase new triggers, actions, and searches added to your integration, as well as user-reported issues closed with a “Resolved” statues in a monthly Community post.

| Integration announcements on the Zapier Community |                                                                                                                                                                                                                  |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Tier**                                          | Silver+                                                                                                                                                                                                          |
| **How to redeem**                                 | Automatically redeemed for you. We capture new triggers, actions, and searches added to your integration, as well as user-reported issues closed with a “Resolved” status, and include them in a Community post. |
| **Opportunity**                                   | Actively requests feedback from our mutual users on these updates, fostering direct engagement with users and enhancing the overall user experience.                                                             |
| **Best used**                                     | Don't miss a chance to promote these updates with your users, and engage with users by responding to their comments.                                                                                             |

## Featured Integration in the App Directory

Boost your integration's exposure through a feature placement in our app directory ([example](https://cdn.zappy.app/c18cd47ae39d442aa2f6ea8aadc5cc82.png)).

| Feature Integration in our App Directory |                                                                                                                                                        |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Tier**                                 | Silver+                                                                                                                                                |
| **How to redeem**                        | Select a 1-week period where you anticipate high customer engagement and email [partners@zapier.com](mailto:partners@zapier.com) requesting to redeem. |
| **Opportunity**                          | Users are more likely to discover your integration when they're viewing your integration's category in our app directory.                              |
| **Frequency**                            | Once every 365 days (rolling)                                                                                                                          |

## Blog feature of your integration updates

We highlight new triggers, actions, and searches added to your integration in a monthly blog post (shared with a large Zapier audience).

| Integration Announcements on Zapier Blog |                                                                                                                                  |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Tier**                                 | Gold+                                                                                                                            |
| **How to redeem**                        | Automatically redeemed for you. We capture new triggers and actions added to your integration and promote them in the blog post. |
| **Opportunity**                          | If you add a feature to an existing trigger or action that you think customers will love, send us an email.                      |
| **Best used**                            | Don't miss a chance to promote these updates with your users by linking this announcement to a post on your networks.            |

## Standalone Feature Blog Post

Being featured on Zapier's blog means you benefit from our high web traffic and SEO ranking.

| Dedicated Blog Post on the Zapier Blog |                                                                                                                                                                                                            |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Tier**                               | Gold+                                                                                                                                                                                                      |
| **How to redeem**                      | Email [partners@zapier.com](mailto:partners@zapier.com) to redeem this benefit. Our team will write a blog post about your app aiming to bring in a bigger audience, ensuring high relevance and interest. |
| **Opportunity**                        | Reach a large, tech-savvy user base that is always on the lookout for new integrations to streamline their workflows.                                                                                      |
| **Frequency**                          | One lifetime (eligible for update every 2.5 years)                                                                                                                                                         |
| **Best used**                          | Spread the word about your integration by promoting your dedicated perk on your social media channels.                                                                                                     |

## Prioritized Marketing Inclusion

Get ahead with early access to Zapier-initiated co-marketing campaigns, giving your integration more exposure and engagement opportunities.

| Prioritized Marketing Inclusion |                                                                                                                      |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **Tier**                        | Platinum                                                                                                             |
| **How to redeem**               | Automatically included. We'll prioritize invites for Platinum Partners to participate in our co-marketing campaigns. |
| **Opportunity**                 | Boost your integration's visibility and user engagement by being featured in high-impact marketing efforts.          |
| **Best used**                   | Take advantage of early access to promote your integration effectively across multiple channels.                     |

## Priority Developer Support

Request dedicated support for your integration and product roadmap from a Zapier Solutions Engineer. Technical troubleshooting and code review are available.

| Priority Developer Support |                                                                                                                                |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **Tier**                   | Platinum                                                                                                                       |
| **How to redeem**          | Email [partners@zapier.com](mailto:partners@zapier.com) to redeem this benefit.                                                |
| **Opportunity**            | Use this tailored benefit to approach adding new features or fixing new bugs on your integration, based on your product goals. |

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Best practices for showcasing your integration

Sharing well-crafted content about your Zapier integration can help you improve user adoption, highlight key use cases, and simplify integration processes. Need some inspiration? The following examples show how some of our partners are effectively communicating their Zapier integrations across different platforms.

# Best practices for showcasing your integration

Sharing well-crafted content about your Zapier integration can help you improve user adoption, highlight key use cases, and simplify integration processes. Need some inspiration? The following examples show how some of our partners are effectively communicating their Zapier integrations across different platforms.

## Integration pages

A popular option for showcasing your integration is to create a dedicated, visually engaging landing page for Zapier on your website’s integrations directory.

To drive faster adoption, follow these best practices for your page:

*   **Link to customer stories** — Share customer success stories that reference how Zapier has saved your users time and effort.
*   **Add quotes** — Feature quotes that showcase how Zapier has driven meaningful improvements in users’ workflow efficiencies.
*   **Embed Zap templates** — Show the most relevant use cases for your customers, and embed related Zap templates directly onto the page with our [embed tools](https://zapier.com/developer-platform/embed-tools). That way, users can easily build Zaps—without ever having to leave the integration page.

*Examples*: [BrightHR](https://www.brighthr.com/ca/integrations/zapier/), [SwagUp](https://www.swagup.com/integrations/zapier), [TrueReview](https://www.truereview.co/app/zaps), [Pipedrive](https://www.pipedrive.com/en/marketplace/app/zapier/10f6b602cda330ef)

## Help documentation

Write help documentation with clear, step-by-step instructions for integrating Zapier with your app.

These best practices can help your docs stand out:

*   **Embed Zap templates** — Just like with your integration pages, embedding Zap templates directly in help docs can help reduce the friction of setup.
*   **Be detailed** — Guide users who might be completely new to automation with detailed instructions.
*   **Use visuals** — Keeping the docs visually engaging can complement your copy, building your users’ confidence even more.

*Examples*: [Jotform](https://www.jotform.com/help/how-to-use-zapier-with-jotform/), [Kajabi](https://help.kajabi.com/hc/en-us/articles/360037178613-How-to-Use-Zapier-With-Kajabi), [ClickSend SMS](https://help.clicksend.com/article/0to8xcbzq8-integrate-clicksend-with-zapier)

## Blog posts

Your blog is a fantastic space for communicating updates to your Zapier integration.

For the most engaging posts, apply these best practices:

*   **Explore new features**  — Announce new Zapier features in your posts and give users ideas about how they can apply those features to their work.
*   **Spotlight benefits** — Inspire users by directly referencing which apps they can connect to through Zapier and how those connections support their work.

*Example*: [Buffer](https://buffer.com/resources/buffer-zapier-integration/)

## Video tutorials

A well-made video can help break down complex ideas into easily digestible concepts—perfect for explaining Zapier to new users.

Here are two great starter videos you can create:

*   **Welcome to Zapier** — Create an introductory video that walks users through a high-level overview of Zapier to help get them started.
*   **Setup deep dive** — Break down the technical setup process in a thorough, engaging tutorial.

*Examples*: Adalo ([Intro video](https://www.youtube.com/watch?v=2IrXNEJkvcQ\&t=2s), [deep dive video](https://www.youtube.com/watch?v=nS4C7-7D14M\&t=1s))


# Integration branding guidelines

When creating your integration, you'll add your app’s name, logo, description, category, and primary brand color. Consistent branding is essential for helping users recognize and discover your app on Zapier.

The journey starts at the [Zapier app directory](https://zapier.com/apps/), where apps are ranked by popularity and category. Users search for new apps and integrations here.

<Frame caption="App Directory listings help users discover new ways to connect apps">
  <img src="https://cdn.zappy.app/13ce36334d97ddf10fa31c896d19a6de.png" />
</Frame>

Each integration gets its own app profile page on Zapier, where your branding stands out. Users can explore your app’s features and see how it works with other apps.

App profiles feature [Zap Templates](https://platform.zapier.com/publish/zap-templates)—pre-built workflows that users can set up with just a few clicks. These templates are also shown in the Zapier dashboard for connected apps.

Inside Zapier's account dashboard, the *Start with a template* section shows Zap Templates for apps users have connected to their accounts.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/4b85f4fa4dde1779ffcb11652db92814.png" />
</Frame>

In the Zap editor, your app’s logo and name appear when users select apps to connect.

<Frame caption="Zap Editor app selector">
  <img src="https://cdn.zappy.app/9a28eb87b287ee52b23c7aee61066207.png" />
</Frame>

Follow these guidelines to ensure consistent, effective branding for your Zapier integration:

## App name

Use the actual, unique name of your app with the same capitalization your company uses in your core branding. Trademark/copyright identifiers such as a TM suffix aren't allowed in the app name, though they can be added to the description.

Do not add adjectives or phrases to your app name, and only include your company name if the company and app name are always used together in your branding. Do not include the word "app" unless you always include that in your app’s branding.

Abbreviations like "FKA" (formerly known as), "AKA" (also known as), and "NKA" (now known as) are not allowed in integration titles. Your integration title must exactly match your app's current name. Requests to include abbreviations in the title during rebranding will not be accepted. For example, if your integration was originally titled "My Cool CRM" and rebrands to "My New Cool CRM", a request to change the title to "My New Cool CRM (fka My Cool CRM)" will result in the integration being titled as "My New Cool CRM".

**Example**:

*   `Evernote`, not `Evernote Note Taking App` or `EverNote`
*   `Google Sheets`, not `Sheets` or `Google Spreadsheets`
*   `App Name`, not `App Name's integration`

## App logo

The logo you provide will be used across Zapier’s platform, including on your app’s dedicated directory page. Please ensure the logo you provide meets the following requirements:

*   PNG, transparent
*   Minimum 512x512px (identical height/width measurements)
*   Minimum 72 DPI

This will ensure your logo displays properly throughout the site.

Please provide a bigger version if you have one available. If your icon is not square, make a square transparent image and center your icon inside the transparent square. Do not include the app name or other copy in the logo as it will not be legible at small sizes.

**Example**:

<Frame caption="Evernote icon on Zapier">
  <img src="https://cdn.zappy.app/e1ac0809ac62bfd74330479dc22c9038.png" />
</Frame>

Evernote's elephant icon is included in a transparent square rectangle.

<Frame caption="Todoist icon on Zapier">
  <img src="https://cdn.zappy.app/47de5413e9b17530628040488ddd868c.png" />
</Frame>

Todoist's icon includes transparent, rounded corners.

## App description copy

Write a short description (up to 140 characters) of your app’s core features and use cases in the form of "**Integration Name** is a....". The description should be less about selling the platform and more about what the platform actually does. Use proper English and full sentences. The copy should not include links or mentions of Zapier. Do not use flowery or overstated language, or make it appear your integration is associated with or endorsed by Zapier.

**Example**:

*   `Trello is a team collaboration tool to organize anything on a kanban board.`, not `Trello is the best project management tool.`
*   `Dropbox lets you store files online, sync them to all your devices, and share them easily.`, not `A file storage app.`

## Category

Select the category that best fits your app's core features and use case. If your app includes features from multiple categories, choose the category that best describes your app's primary use case today.

Can't find an existing category that represents your app? Let us know! Suggest a category you'd like to see added to our app directory by [submitting this form](https://integration-branding.zapier.app/suggest).

You may update your app's category in the future if needed, so do not select a category that fits your future ambitions for the app instead of its features today. Additionally, do not select a category that applies only to a secondary feature in your app or a narrow category that does not cover your app's broader focus.

### Available Categories

We offer a variety of categories for you to list your integration in. Each top-level category includes all integrations from its child categories. To maximize visibility for your integration, we recommend selecting a specific child category rather than a general parent category. Integrations within each category are ranked by popularity. By choosing a child category, your integration can achieve a higher ranking and greater visibility within that category.

*   Artificial Intelligence - Tools that offer AI features such as natural language processing, image classification, and more.
    *   AI Tools - Tools to unlock AI potential in your workflow.
*   Business Intelligence - Tools to gather, analyze, and visualize business data.
    *   Analytics - Tools to measure and report on success.
    *   Dashboards - Tools to view your data on a full-screen dashboard.
    *   Reviews - Tools to manage your reviews and ratings.
*   Commerce - Tools to facilitate all aspects of business transactions, both online and offline.
    *   Taxes - Tools for automating your taxes.
    *   Accounting - Tools for accounting and finance.
    *   eCommerce - Tools to sell your products online.
    *   Fundraising - Tools to set up fundraising events and projects.
    *   Payment Processing - Tools to process payments on your site or app.
    *   Proposal & Invoice Management - Tools to create and send proposals and invoices to clients.
*   Communication - Tools to streamline and enhance communication across various channels.
    *   Call Tracking - Tools to monitor calls and track data from non-digital marketing campaigns.
    *   Email - Tools to manage your personal and business email correspondence.
    *   Fax - Tools to send documents over fax.
    *   Notifications - Tools to get customized notifications on your computer or mobile devices.
    *   Phone & SMS - Tools to make calls and send SMS messages.
    *   Team Chat - Tools to help teams collaborate together online with real-time chat.
    *   Team Collaboration - Tools for business social networking, file sharing, and chat to help teams work together more effectively.
    *   Video Conferencing - Tools to host team video calls and webinars.
*   Content & Files - Tools to create, manage, and share various types of content and files.
    *   Documents - Tools to write, edit, and share text documents.
    *   File Management & Storage - Tools to organize, share, and sync files.
    *   Images & Design - Tools for creatives and those dealing with images.
    *   Notes - Tools to write down thoughts and organize them in notebooks.
    *   Transcription - Tools to transcribe audio into text.
    *   Video & Audio - Tools to store and share multimedia assets.
*   Human Resources - Tools to manage all aspects of human resources, from hiring and recruitment to employee management and development.
    *   Education - Tools to enhance learning and teaching experiences
    *   HR Talent & Recruitment - Tools to manage your hiring and human resource department.
*   Internet of Things - Tools to connect and manage IoT devices and services.
    *   Devices - Tools for connecting Internet of Things devices through Zapier.
    *   Printing - Tools to print your designs, either on your printer or on products such as t-shirts and stickers.
*   IT Operations - Tools to manage and optimize IT infrastructure and services.
    *   Databases - Tools for developers to store and manage data.
    *   Developer - Tools to build and maintain software, services, and websites.
    *   Online Courses - Tools to publish lessons and educational material.
    *   Security & Identity - Tools to manage security and identity/permissions
    *   Server Monitoring - Tools that monitor services and application metrics.
*   Lifestyle & Entertainment - Tools to enhance various aspects of your personal life and leisure activities.
    *   Fitness - Tools to track your workouts, food, and more.
    *   Gaming - Tools that are centered around video gaming.
    *   News & Lifestyle - Tools to keep you informed on news and lifestyle content.
*   Marketing - Tools to plan, execute, and measure marketing campaigns across various channels.
    *   Ads & Conversion - Tools to track and reach an audience online.
    *   Drip Emails - Tools to send automated email messages on a set schedule.
    *   Email Newsletters - Tools to send regular email updates and newsletters to your subscribers.
    *   Event Management - Tools to manage events and attendees.
    *   Marketing Automation - Tools to market products, track interests, and turn visitors into customers.
    *   Social Media Accounts - Tools to connect and share with others online.
    *   Social Media Marketing - Tools to automatically share posts on social networks.
    *   Transactional Email - Tools to send email messages through your application.
    *   URL Shortener - Tools to shorten URLs.
    *   Webinars - Tools for scheduling and holding webinars.
*   Productivity - Tools to enhance efficiency and organization in your personal and professional life.
    *   Bookmark Managers - Tools to save your favorite links to view and read later.
    *   Calendar - Tools to plan your events and schedule.
    *   Product Management - Tools to plan your product's lifecycle and roadmap.
    *   Project Management - Tools for managing projects.
    *   Spreadsheets - Tools to manage numbers and data.
    *   Task Management - Tools to manage your tasks on simple lists.
    *   Time Tracking Software - Tools to track time spent on work and projects.
*   Sales & CRM - Tools to manage and optimize your sales processes and customer relationships.
    *   Contact Management - Tools to keep track of the people you need to keep in touch with most.
    *   CRM (Customer Relationship Management) - Tools for customer relationship management.
    *   Forms & Surveys - Tools to build forms and gather data from your website or apps.
    *   Scheduling & Booking - Tools to schedule appointments and events.
    *   Signatures - Tools to manage and sign legal documents online.
*   Support - Tools to enhance customer satisfaction and provide effective assistance.
    *   Customer Appreciation - Tools that help you show appreciation to your customers.
    *   Customer Support - Tools to answer your customers' questions through email, chat, and documentation.
*   Website & App Building - Tools to create and customize websites and applications.
    *   App Builder - Tools to build a custom app with forms and databases.
    *   Website Builders - Tools to help manage content and build websites for your business.

**Example**:

*   *Gmail* is primarily an app to send and receive email messages, so it fits best in the `email` category alongside services like *Microsoft Office 365*, not in the `email newsletters` category with *Mailchimp*.
*   *Slack* is primarily a team communication tool for chat, so it fits best in the `team chat` category alongside apps like *Discord* and *ChatWork*, not in the `video calls` category even though it does include a video call tool as a secondary feature.
*   *Google Contacts* is primarily an address book that fits best with other `contacts` apps, while *HubSpot CRM* can manage contacts but also includes deals and contact tracking which makes it a better fit for the `CRM` category.

## Colors

When you release your Zapier integration to the public, Zapier requires your app's primary color. The primary color is the main color used in your app's logo or branding.

<Frame caption="Gmail primary colors in App Directory">
  <img src="https://cdn.zappy.app/bef595e339362f712a227679316d1357.png" />
</Frame>

Do not use pure white (`#FFFFFF`) as the color, as overlaid text would be unreadable. If your logo is black and white, use the next most common color from your branding.

Zapier uses the primary color as the background color in your app's Zapier App Directory listing, and may additionally use it in the Zapier app dashboard, Zapier blog marketing materials, and other parts of Zapier's app and content that promote your app's integrations.

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="Can my integration belong to more than 1 category?">
    Currently, we only allow 1 category per integration^. This is to maintain our categorization effectiveness, making it easier for users to find relevant integrations. Limiting integrations to 1 category also helps to improve your discoverability within the category.

    *^Additional categories are limited to App Families and AI Tools at Zapier's discretion*
  </Accordion>

  <Accordion title="Can I change my integration's category in the future?">
    Yes, you can. Submit the Rebrand Form located at the top of the Settings page for your integration in the [Developer platform](https://developer.zapier.com).

    <Frame>
      <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/45d5c80072008f65856e8304a6394866.png" />
    </Frame>
  </Accordion>

  <Accordion title="What's an App Family?">
    An app family refers to a collection of related integrations, typically developed by the same organization. Being added to an App Family is at the discretion of Zapier.
  </Accordion>

  <Accordion title="How can I become a Premium integration?">
    Being classified as a Premium app isn't something we currently offer to partner-owned integrations. The justification as to why a Zapier-owned integration might be classed as Premium varies, but is often due to an increased cost on our end to be able to offer that integration. Only users on a paid Zapier plan can use Premium integrations, which helps to ensure we can continue to invest in the development of that integration.
  </Accordion>

  <Accordion title="Can I suggest a new category?">
    [Submit this form](https://integration-branding.zapier.app/suggest) to suggest a new category. Our team will review all submissions for consideration. If we move forward with adding your suggestion as a category, we'll notify you via email.
  </Accordion>
</AccordionGroup>

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Integration build guidelines

Before publishing your integration on Zapier, it is essential to ensure that your integration is well-prepared to provide a seamless and efficient user experience. The following guidelines are designed to assist you in refining your integration before submitting it for review. Adhering to these guidelines will help enhance the functionality and user interaction with your integration and will provide you with the best value and opportunities to harness Zapier as a method of obtaining new users and most commonly, boosting the lifetime value of your current customers.

## Familiarization and testing

### Familiarize yourself with Zapier

If new to Zapier, start by signing up for a free account and experimenting with [popular integrations](https://zapier.com/apps). This will help you understand how Zapier works, how your users will set up Zaps, and how your integration can best fit into Zap workflows

### Identify key use cases

Focus on the primary functionalities of your application. Review similar integrations in [Zapier's App Directory](https://zapier.com/apps) and [recommended features](/platform/quickstart/must-have-triggers-and-actions) by app category for ideas on which items to include in your integration. Also, get feedback from your current customer base on their needs and wants from automation with your application.

### Thorough integration testing

*   **Internal Testing**: Develop and test Zaps using your integration's triggers, actions, and searches in your Zapier account. Ensure these are error-free and have successful runs in the Zap History. This gives you firsthand experience of the Zap setup process our mutual users will go through.
*   **External Testing**: [Share your integration](/platform/manage/sharing) with external users before submitting it for review to gather early feedback from them and make changes to the integration when necessary.

### Provision of demo accounts and resources

Provide access to a fully functional demo account and any necessary resources to facilitate the review process.

### Automatic validation checks

Review the [integration check reference documentation](/platform/publish/integration-checks-reference) while building your integration to pass the automatic validation when submitting for publishing. To publish your integration, all Errors and Publishing Tasks must be validated. Warnings are non-blocking and not strictly required to proceed as they would not prevent you from promoting a version, though we do recommend you review them for the usability of your integration.

## Integration information

### Comprehensive documentation and metadata

All integration information, including the name, logo, description, and category, should be complete, accurate, and adhere to [Zapier's branding standards](/platform/publish/branding-guidelines). If your app includes features from multiple categories, choose the category that best describes your app's primary use case today.

### Clarify non-obvious features

Detail any complex features in your publishing request notes, and include relevant documentation to support understanding and review.

### Consistency in style and branding

The terminology and features in your Zapier integration should be consistent with the terminology and features seen in your product's UI. We strive to give users a consistent experience throughout Zapier, so your integration should also be consistent with the style used in other popular Zapier integrations. Be sure to follow Zapier's integration branding and design requirements to ensure your integration suits the Zapier ecosystem well.

## Authentication

### Ease of connecting accounts

Simplify the authentication process, preferably using [OAuth v2](/platform/build/oauth). For non-OAuth v2 authentication schemes, users must be able to easily generate and retrieve API keys and/or other required credentials themselves without needing assistance from your support team.

### Help texts

Provide help texts for authentication fields with guidance on where users can find their credentials if you're not using Basic Auth. If possible, provide an embedded [link](https://www.markdownguide.org/basic-syntax/#links) using [Markdown](https://www.markdownguide.org/) in the help text straight to the page in the platform where users can find the credentials. For example, “Go to the [API details](https://my.xxxx.com/manage/api-details) screen from your Dashboard to find your API Key.“

### Input format

If authenticating to your app requires providing a value in a certain format (such as a sub-domain URL), you should specify this format with the Input Format option when adding the input field for this value.

### Handling invalid credentials

Return informative and user-friendly error messages for authentication issues. For example, “Your API Key does not appear to be valid.” is better than a generic 401 error message.

## Triggers

### Trigger design and copy

Any copy associated with a trigger should match the copy, text, and terminology used in your product's UI. This includes the trigger's name, description, input fields, help text, output fields, etc. For instance, since Dropbox calls directories “folders” in their product UI, the respective trigger in [their Zapier integration](https://zapier.com/apps/dropbox/integrations#triggers-and-actions) refers to “folders” instead of “directories”. If your API uses different terminology than your product's UI, match the UI as users are most familiar with this - not the API.

Additionally, each trigger must have:

*   a descriptive, titlecased name. For example, ‘New Lead', instead of ‘New lead'.

*   a descriptive key. For example, ‘new\_lead', instead of ‘trigger\_1'.

*   a descriptive noun that reflects the object that is being returned in the trigger. This typically is one word and should always be in the singular form. The noun is used around the Zapier platform and will be pluralized automatically to complete various messages to users such as ‘No new X found.‘

*   a concise description using our standard phrasing ‘Triggers when…“ and no more than a handful of additional words. Always the description with a period/full-stop. Do not include the name of your integration nor capitalize the noun in the description such as ‘Triggers when a new Lead is created in Example CRM.'

### Trigger input fields

[Trigger input fields](/platform/build/add-fields), if they exist, should be named appropriately and include help text if their purpose is ambiguous. Use appropriate naming, ordering, and field types. Avoid unnecessary complexity in ID fields and include helpful tooltips where necessary.

*   **ID fields**: Users should never be expected to type in an ID as this is error prone. Your integration should not have any trigger fields labeled ‘ ID', such as ‘Customer ID'. Instead, use Zapier's dropdown functionality to provide users with a user-friendly list of options.

*   **Help texts**: Don't be redundant with help text, and only include it if you have something different to say from the field name such as the expected format of the value or additional instructions that would aid users in knowing what value the field expects and sometimes, how they can get those values. Redundant help text teaches users not to read any help text at all, so important information can be missed. Use [Markdown](https://www.markdownguide.org/) to [format text](https://www.markdownguide.org/basic-syntax/#emphasis) and include [links](https://www.markdownguide.org/basic-syntax/#links) to more detailed help documentation created on your site where applicable.

*   **Optional trigger fields**: If a trigger field is optional, confirm the request does not throw an error in a null case where the user does not select an option.

*   **Field types**: Use the most appropriate [input field type](/platform/build/add-fields) for each of the input fields to show users what type of data to include. Note the Zap editor does not validate the data to ensure users added the correct item for that field type. If the field can accept multiple values, use our built in [‘List' property](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema) or ‘[Allow Multiples](/platform/build/add-fields#allows-multiples)' functionality rather than asking users to provide a comma-separated value in a text field.

*   **Ordering**: Put required trigger input fields at the top of the form with optional fields towards the bottom by importance.

### Static webhook

Triggers using static webhooks are not allowed in public Zapier integrations. Users can replicate this functionality in their Zaps via the [Webhooks by Zapier Integration](https://zapier.com/apps/webhook/integrations). Please consider including authentication and using REST Hooks to provide users with a more streamlined experience.

### Polling triggers

Live Zaps with [polling triggers](/platform/build/trigger) automatically poll the request URL for new data every 1 to 15 minutes, depending on the user's [Zapier plan](https://zapier.com/pricing). For an effective polling trigger, the following should be done:

*   Return results from the polling URL in reverse chronological order by created date, so the most recently created or updated object is returned first.

*   Return a sufficient amount of items from the polling endpoint. Generally, 100 items is plenty for most integrations, but there are cases where it's common for users to perform an action in your app that would trigger more than 100+ records at once. Note that a trigger with more than 100 new items returned in a poll will run into the [polling trigger throttle](/platform/build/operating-constraints#polling-trigger-throttle-zapier).

*   Use [pagination](/platform/build/pagination-trigger) on trigger results when being used to populate a dynamic dropdown, if large amounts of data will be returned.

*   Each result should contain a unique primary key (usually an id field) for [deduplication](/platform/build/deduplication) from the polling endpoint.

### REST hook

[REST hook-based triggers](/platform/build/trigger) are Zapier's preferred implementation of triggers since they fire immediately after the triggering action is performed.Our mutual users prefer REST hook triggers over polling triggers in their Zaps. They are labelled as [Instant triggers](https://help.zapier.com/hc/en-us/articles/8496244568589-Types-of-triggers-in-Zaps) to users For an effective REST hook trigger, the following should be done:

*   Hook-based triggers must allow multiple subscriptions without overwriting subscriptions. You can test this by setting up 2 Zaps with the same trigger, turning them On, and confirming both Zaps fire successfully when the triggering event is performed in your app.

*   Each REST Hook trigger must also have a polling URL defined in the Perform List. This allows users to easily pull in sample resources to set up their Zaps without leaving the Zap editor. Without a corresponding polling URL, users would have to navigate off of the Zap editor to the product to create or update a resource each time they set up a Zap. Ensure that the data returned from the polling URL follows the Polling Trigger requirements above.

*   Data returned from the polling URL should exactly match the data returned in the hook payload. Check that field keys from the polling URL and in the hook payload match in spelling, casing, and data structure.

### Response content

The response content is what is returned to Zapier by the trigger. The response data should include important keys in the most usable format. For example, it helps to return both the ID and pretty name of objects, any contact information, and links to the resource (ex: for a New Card in Trello trigger, a URL to link to card). Remove unnecessary fields that may seem confusing or add noise to users' Zap setup process. For example, if the response content includes information about the request itself and the input fields provided, please remove those fields from the returned response. Also, ensure that the response content meets the below guidelines

*   **Names**: Many actions in Zapier require names to be split into two fields - first/last or given/surname. This conflicts with some naming schemes around the world, but without separated name fields, your trigger may not be compatible with certain integrations. Always provide separate name fields, though feel free to return the full name as well if the response already includes this.

*   **Addresses**: Likewise with names, many actions in Zapier require address components in separate fields (street, street2, city, state, zip), instead of requiring the complete address in a single field. Always provide separated address fields, though feel free to return the complete address as well if the response already includes this.

*   **Date-times**: Date-time values are required to be in [ISO 8601 format](http://www.cl.cam.ac.uk/~mgk25/iso-time.html?utm_source=zapier.com\&utm_medium=referral\&utm_campaign=zapier#date) and should always include a time zone offset (even if it is UTC). This includes date-time values returned from the polling URL, in the hook payload, and your sample data. Avoid UNIX/Epoch timestamps. Date-time values may be modified in your API call custom code if your API returns dates in different formats. Example acceptable date-time values include:

    *   `2023-12-15T01:15:13Z` (or `-0000` instead of `Z`)
    *   `2023-12-01T12:32:01-0800`
    *   `2023-12-01T12:32:01-08:00`
    *   `2023-12-13` (for date-only values)

*   **Booleans**: Set boolean values as true or false. Do not use 1 and 0 or alternative representations for boolean values.

*   **Dropdown fields**: Return both the name and ID of the selected dropdown option to be used in subsequent steps of a Zap.

### Sample data

Each trigger requires [sample data](/platform/build/sample-data) for instances where no result is returned during Zap setup or the user chooses to skip testing. This could be because the API is temporarily down, or because the user has no existing data in their account to return. The sample data must:

*   Only represent one item, and not the entire response of the request if there are multiple items returned as a set. For example, \{“key”: “value”} instead of \[\{“key”: “value”}, \{“key”: “value2”}, … ].

*   Use representative data in the expected format the values will be returned. For example, don't set a ‘first name' key to a value of ‘string' or ‘1234'; set it to something like ‘Bob'.

*   Only return the keys that would be returned in every response for all users of the trigger. This means if there are custom fields that will be returned for some users and not others, please do not include these in the sample data.

### Output fields

[Output fields](/platform/build/sample-data) add user-friendly labels to your API's response to facilitate mapping fields during Zap setup for users. By default, Zapier will try to make a friendly version of the response by capitalizing words and replacing underscores with spaces, but this can be customized. Add a custom output field label when:

*   a field is abbreviated. For example, ‘LTV' should be labeled ‘Lifetime Value'.
*   a field has an ambiguous unit of measure. For example, ‘Duration' should be labeled ‘Duration (in seconds)'.
*   a field is represented by an ID.
*   in general, it is not instantly clear what the field or value represents.

### Update triggers

Don't offer a generic ‘Updated ' trigger, as these are often too general for users to use in their Zaps. Instead, think under what specific scenarios the user needs an update trigger. For example, instead of offering an ‘Updated Deal' trigger which triggers when anything changes on the deal, offer a ‘New Deal in Stage' or ‘Updated Deal Status' trigger that allows the user to specify which stage they want to monitor.

### Error messages

Users should never receive a success/200 response if there was an error in the request as this will not show up as an error in the Zap history. All error messages should be user-friendly and avoid technical jargon. Be as specific as possible to what caused the error.

## Actions

### Action design and copy

The copy for actions should match your product's UI. This includes the action's name, description, input fields, help text, etc. For instance, since Dropbox calls directories “folders” in their product UI, the Zapier integration should refer to “folders” in the respective action. If the API uses different terminology than your product's UI, match the UI - not the API.

Additionally, each action must have:

*   a descriptive, titlecased name. For example, ‘Create Lead', instead of ‘Create lead'.

*   a descriptive key. For example, ‘create\_lead', instead of ‘action\_1'.

*   a descriptive noun that reflects the object that is being created or updated in the action. This should typically be one word and should always be in the singular form. The noun is used around the Zapier platform and will be pluralized automatically to complete various messages to users such as ‘No new found.‘

*   a concise description starting with a plural form of the action verb, and ending in a period/full-stop. For example, the description for a ‘Create Lead' action could be ‘Creates a new lead.' Please do not include the name of the platform nor capitalize the noun such as ‘Creates a new Lead in XXX CRM.'

### Action input fields

All action steps must include [input fields](/platform/build/add-fields) to gather the data needed to create, update, or perform the intended action. These fields should be named appropriately and include help text if their purpose is ambiguous. Use appropriate naming, ordering, and field types. Avoid unnecessary complexity in ID fields and include helpful tooltips where necessary.

*   **ID fields**: Users should never be expected to manually type or map an internal ID to an action field. Your integration should not have input fields labeled ‘XXX ID'. Generally, trigger and action steps of other integrations do not return internal IDs for your specific platform to be used in an action field from your Zapier integration. Use Zapier's [dynamic dropdown](/platform/build/add-fields#dynamic-dropdown) and [Search Connector](/platform/build/add-fields#dynamic-dropdown:~:text=**%203.Add%20search%20to%20a%20dynamic%20field%20\(optional\)**)/[search-powered field](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#how-do-search-powered-fields-relate-to-dynamic-dropdowns-and-why-are-they-both-required-together) functionalities to provide users with an easier way to select or search for the appropriate resource by a more readily available value such as ‘name', ‘email address', ‘title', etc.

*   **Ordering**: Order action fields logically. If you are unsure, look at how the respective fields are ordered in your platform and mimic that since users will be familiar with that ordering. Required action fields should generally be listed first at the top of the Zap editor while all optional, lesser-used fields towards the bottom. Group related fields together. For example, first name and last name fields, as well as individual address component fields should be ordered consecutively. Do NOT use [line-item groups](/platform/build/line-items) or the ‘[children](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema)' field schema key to visually group fields; these are solely intended for line-item or array functionality.

*   **Required fields**: If a field is not required by your API, don't make it required in the Zap action. Parallel the required fields from your integration as closely as possible. For example, if ‘Email Address' is not required to create a lead in the platform, do not make the ‘Email Address' field required in the Zap editor. If the API specifies a non-important field as required, hard-code a default value so the field is not empty if one is not provided by the user. If the action step has a case where one field OR another field is required, set both fields as optional, add help text to both fields stating that one or the other is required, and throw a user-friendly error message if neither field is filled.

*   **Help texts**: Don't be redundant with help text, and only include it if you have something different to say from the field name such as the expected format of the value or additional instructions. For example, help text for an ‘Email Address' field stating ‘Contact's email address' is not necessary. Redundant help text teaches users not to read any help text at all, so important information can be missed. Use [Markdown](https://www.markdownguide.org/) to [format text](https://www.markdownguide.org/basic-syntax/#emphasis) and include [links](https://www.markdownguide.org/basic-syntax/#links) to more detailed help documentation created on your site where applicable.

*   **Default values**: Set [default values](/platform/build/add-fields) for input fields sparingly. If the API specifies an unimportant field as required, set a default value in case one is not provided by the user. If the field in the application is set to or shows a default value, set the same default value for the action field in the Zapier integration.

*   **Field types**: Use the most appropriate [input field type](/platform/build/add-fields) for each of the input fields to show users what type of data to include — note Zapier does not validate the data to ensure users added the correct item for that field type. If the field can accept multiple values, use our built-in [‘List' property](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema) or ‘[Allow Multiples](/platform/build/add-fields#allows-multiples)' functionality rather than asking users to provide a comma-separated list of values in a text field.

### Response content

Return information about the resource that was created, updated, or affected by the action, and not just a ‘success' message. At a minimum, return an ID, the name or title of the resource (when pertinent), and a link to the newly created, updated, or affected resource in the platform (if available). Additionally, return any other useful data about the resource users may need in subsequent actions of a multi-step Zap.

*   **Date-times**: Date-time values are required to be in standard [ISO 8601 format](http://www.cl.cam.ac.uk/~mgk25/iso-time.html?utm_source=zapier.com\&utm_medium=referral\&utm_campaign=zapier#date) and should always include a time zone offset (even if it is UTC). This includes date-time values returned in the response content and in your sample data. Avoid UNIX/Epoch timestamps. Date-time values may be modified in your API call custom code if your API returns dates in different formats. Example acceptable date-time values include:

    *   `2023-12-15T01:15:13Z` (or `-0000` instead of `Z`)
    *   `2023-12-01T12:32:01-0800`
    *   `2023-12-01T12:32:01-08:00`
    *   `2023-12-13` (for date-only values)

*   **Booleans**: Set boolean values as true or false. Do not use 1 and 0 or alternative representations for boolean values.

*   **Important Data**: The response data should include important fields in the most usable format. For example, it helps to return both the ID and pretty name of resources, any important information about the resource such as contact information, and a link to the resource in the platform (ex: Create Card in Trello action returns a URL link to the card created). Also, Returning a link to the newly created/updated/affected resource in the action's response data helps users link to the resource in subsequent steps of a multi-step Zap and confirms the new resource was created. This is also helpful for the Zapier support team when debugging issues.

*   **Unnecessary/Excess Data**: Remove non-necessary fields that may seem confusing or add unnecessary noise to users' Zap setup process from your API call's custom code. For example, if the response content includes information about the request itself and the input fields provided, please remove those fields from the returned response.

*   **Dropdown Fields**: Return both the name and ID of the selected dropdown option to be used in subsequent steps of the Zap.

### Sample data

The ‘Test' step on actions sends a real request on behalf of the connected account. Therefore, each action requires [sample data](/platform/build/sample-data) for instances when the user does not wish to actually run the action in their account to finish setting up the Zap. The sample data must:

*   Represent the expected return from the request when a record is successfully created or found.

*   Use representative data in the expected format the values will be returned. For example, don't set a ‘first name' key value to ‘string' or ‘1234'; set it to something like ‘Bob'.

*   Reflect the keys returned in every response for all users of the action. This means if there are custom fields that will be returned for some users and not others, please do not include these key:values in the sample data.

### Output fields

[Output Fields](/platform/build/sample-data) add user-friendly labels to your API's response data to facilitate mapping fields during Zap setup for users. By default, Zapier will try to make a friendly version of your API's response, capitalizing words, and replacing underscores with spaces, but they can also be customized. Add a custom output field label when:

*   a field is abbreviated. For example, ‘LTV' should be labeled ‘Lifetime Value'.
*   a field has an ambiguous unit of measure. For example, ‘Duration' should be labeled ‘Duration (in seconds)'.
*   a field is represented by an ID.
*   in general, it is not instantly clear what the field or value represents.

### Create or update functionality

If your integration must have a ‘Create-or-Update' functionality, implement a ‘Search-or-Create' with an ‘Update' action versus a ‘Search' with a ‘Create-or-Update' action or a standalone ‘Create-or-Update' action.

### Delete actions

Carefully consider delete actions that fully delete or remove data. To prevent data loss, action steps should preferably only add or update data.

If you have a valid use case for a delete action, consider alternative actions for items such as deactivating, unsubscribing, or canceling, instead of deleting items completely.

If you do add a delete action, make sure to include a Copy field to clarify to users that the action is irreversible once the API request is made.

### Error messages

Users should never receive a success/200 response if there was an error in the request as this will not show up as an error in the Zap history. All error messages should be user-friendly and avoid technical jargon. Be as specific as possible to what caused the error.

## Searches

### Find or create

Search actions can optionally create items if nothing is found in the search. If your integration has a ‘Create-or-Update' action, please implement a ‘Search-or-Create' with an ‘Update' action versus a ‘Search' with a ‘Create-or-Update' action or a standalone ‘Create-or-Update' action.

### Search design and copy

The copy for searches should match your product's UI. This includes search name, description, input fields, help text, etc. For instance, since Dropbox calls directories “folders” in their product UI, the Zapier integration should refer to “folders” in the respective search. If the API uses different terminology than your integration's UI, match the UI - not the API.

Additionally, each search must have:

*   a descriptive, titlecased name. Use ‘Find' over ‘Get'. For example, ‘Find Lead', instead of ‘Get Lead'.

*   a descriptive key. For example, ‘find\_lead', instead of ‘search\_1'.

*   a descriptive noun that reflects the object that will be returned. This should typically be one word and should always be in the singular form. The noun is used around the Zapier platform and will be pluralized automatically to complete various messages to users such as ‘No new found.‘

*   a concise description starting with a plural form of the verb, which typically is ‘Finds', and ending in a period/full-stop. For example, a valid description for a ‘Find Lead' search is ‘Finds a new lead.' Please do not include the name of the platform nor capitalize the noun such as ‘Finds a new Lead in XXX CRM.'

### Search input fields

[Search input fields](/platform/build/add-fields) should be named appropriately and include help text if their purpose is ambiguous.

*   **ID fields**: Users should never be expected to manually type or map an internal ID to a search field. Your integration should not have search fields labeled ‘XXX ID'. Trigger and action steps of other integrations do not return internal IDs for your specific app that could be used in an action field for your Zapier integration. If the user is using a trigger from your Zapier integration, the trigger should return all necessary information about the resource without the Zap needing a search step to retrieve additional, necessary information. Instead, provide search field options for more general information such as ‘name', ‘email address', ‘phone number', ‘title', etc. that is more readily available from all triggers.

*   **Ordering**: Put required search fields at the top of the form with optional fields towards the bottom by importance.

*   **Optional search fields**: If a search field is optional, confirm the request does not throw an error in a null case where the user does not select an option.

*   **Help texts**: Don't be redundant with help text, and only include it if you have something different to say from the field name such as the expected format of the value or additional instructions. Redundant help text teaches users not to read any help text at all, so important information can be missed. Use [Markdown](https://www.markdownguide.org/) to [format text](https://www.markdownguide.org/basic-syntax/#emphasis) and include [links](https://www.markdownguide.org/basic-syntax/#links) to more detailed help documentation created on your site where applicable.

*   **Field types**: Use the most appropriate [input field type](/platform/build/add-fields) for each of the input fields to show users what type of data to include — note Zapier does not validate the data to ensure users added the correct item for that field type. If the field can accept multiple values, use our built-in [‘List' property](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema) or ‘[Allow Multiples](/platform/build/add-fields#allows-multiples)' functionality rather than asking users to provide a comma-separated list of values in a text field.

### Response content

While “create” actions return a single object to Zapier; searches must return an array of objects. Each search must follow these requirements:

*   **No Result**: Do not raise an error if there are no search results to return. If your API returns a 404 for search misses, use custom code to [return a 200 with an empty array as the response](/platform/build/response-types).

*   **Names**: Many actions in Zapier require names to be split into two fields - first/last or given/surname. This conflicts with some naming schemes around the world, but without a separated name field, your action may not be compatible with certain integrations in subsequent steps of a user's Zap. Always provide separated name fields if applicable, though feel free to return the full name as well if the response already includes this.

*   **Addresses**: Likewise with names, most actions in Zapier require address components in separate fields (street, street2, city, state, zip), instead of requiring the complete address in a single field. Always provide separated address fields, though feel free to return the complete address as well if the response already includes this.

*   **Date-times**: Date-time values are required to be in standard [ISO 8601 format](http://www.cl.cam.ac.uk/~mgk25/iso-time.html?utm_source=zapier.com\&utm_medium=referral\&utm_campaign=zapier#date) and should always include a time zone offset (even if it is UTC). This includes date-time values returned in the response content and in your sample data. Avoid UNIX/Epoch timestamps. Date-time values may be modified in your API call custom code if your API returns dates in different formats. Example acceptable date-time values include:

    *   `2023-12-15T01:15:13Z` (or `-0000` instead of `Z`)
    *   `2023-12-01T12:32:01-0800`
    *   `2023-12-01T12:32:01-08:00`
    *   `2023-12-13` (for date-only values)

*   **Booleans**: Set boolean values as true or false. Do not use 1 and 0 or alternative representations for boolean values.

*   **Important Data**: The response data should include important fields in the most usable format. For example, it helps to return both the ID and pretty name of resources, any important information about the resource such as contact information, and a link to the resource in the platform (ex: Find Card in Trello search, returns a URL link to the found card).

*   **Unnecessary/Excess Data**: Remove unnecessary fields that may seem confusing or add noise to users' Zap setup process from your API call's custom code. For example, if the response content includes information about the request itself and the input fields provided, please remove those fields from the returned response.

### Sample data

Each search requires [sample data](/platform/build/sample-data) for instances where no result is returned during Zap setup. This could be because the API is temporarily down, or because no result is returned for the specific search the user tests. The sample data must:

*   Only represent one record, and not the entire return from the request if there are multiple records returned as a set. For example, \{“key”: “value”} instead of \[\{“key”: “value”}, \{“key”: “value2”}, … ].

*   Use representative data in the expected format the values will be returned. For example, don't set a ‘first name' key value to ‘string' or ‘1234'; set it to something like ‘Bob'.

*   Reflect keys available in every response for all users of the search. This means if there are custom fields that will be returned for some users and not others, please do not include these key:values in the sample data.

### Output fields

[Output fields](/platform/build/sample-data) add user-friendly labels to your API's response data to facilitate mapping fields during Zap setup for users. By default, Zapier will try to make a friendly version of your API's response, capitalizing words, and replacing underscores with spaces, but they can also be customized. Add a custom output field label when:

*   a field is abbreviated. For example, ‘LTV' should be labeled ‘Lifetime Value'.
*   a field has an ambiguous unit of measure. For example, ‘Duration' should be labeled ‘Duration (in seconds)'.
*   a field is represented by an ID.
*   in general, it is not instantly clear what the field or value represents.

## Error handling

APIs are not always available. Users do not always provide valid data for requests. Build a defensive and resilient integration. Plan for 4xx and 5xx responses. Without [proper handling](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#error-handling), errors can have incomprehensible messages, technical error codes, or remain unnoticed by users.

### General errors

Use `z.errors.Error` in situations where users misconfigure their Zaps and need to take action. Typically, this will be for prettifying `4xx` responses and APIs that return errors as 200 with a payload that describes the error with guidance on how to correct it.

*   If a Zap raises too many error messages it will be [automatically turned off](https://help.zapier.com/hc/en-us/articles/8496037690637-Troubleshoot-errors-in-Zapier#500-series-error-codes-0-3), so only throw these if the scenario is truly an error that needs to be fixed.

*   Elaborate on terse messages. ‘Your API Key is invalid.' is better than ‘not\_authenticated.'

*   If the error calls out a specific field, surface that information to the user. ‘Title is not valid.' is better than ‘Invalid Request.'

*   If the error provides details about why a field is invalid, surface that information to users. ‘Title is invalid. Please only use alphanumeric characters.' is better than ‘Title is invalid.‘

### Halting errors

Use `z.errors.HaltedError` in situations where a required pre-condition is not met. For instance, in an action to add an email address to a list where duplicates are not allowed, throw a `HaltedError` if the Zap attempted to add a duplicate. This indicates failure but is treated as a soft failure. Thus, unlike general errors, a Zap will never be turned off when this error is thrown even if it is raised more often than not.

### Stale authentication credentials

For integrations requiring a manual refresh of authorization regularly, Zapier provides `z.errors.ExpiredAuthError`, so the current operation is interrupted, the Zap is turned off (to prevent additional runs with expired credentials), and a predefined email is sent out informing the user to refresh the credentials. The runs will be [Held](https://help.zapier.com/hc/en-us/articles/20505304170637-Review-Zap-run-statuses), and the user will be able to replay them after reconnecting.

For integrations using OAuth2 with automatic refresh requests or Session Auth, use `z.errors.RefreshAuthError`. This signals Zapier to refresh the credentials and retry the failed operation.

## Code quality

### Environment variables

Do not hard code credentials such as API Keys, Client IDs, Client Secrets, etc. into your integration. Use [environment variables](/platform/build/env) instead.

### Structure

If you are using the [Zapier CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md) to build the integration, ensure files are logically broken out into respective directories and the code is easy to follow. We suggest putting triggers, actions, and searches each in their directories.

### Dependencies

If you are using the [Zapier CLI](/platform/reference/cli-docs), keep dependencies up to date. When notified that a dependency must be updated—for example, `zapier-platform-legacy-scripting-runner`, `zapier-platform-core`, or `zapier-platform-cli` —complete the update promptly or by the stated deadline. If requested updates are not completed by the stated deadline, Zapier reserves the right to make necessary updates on your behalf.

## Conclusion

While these guidelines are not exhaustive, they provide a robust framework to prepare your integration for a successful launch on Zapier.

Note that while not implementing these guidelines would not prevent your integration from being published, there are certain [requirements](/platform/publish/integration-publishing-requirements) that **must** be met before your integration can be approved for publishing.

For any specific questions or additional support, feel free to contact the Developer Support team at Zapier via the [contact form](https://developer.zapier.com/contact).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Integration check reference

Before you can submit your integration for publishing, it runs through a set of automated checks to ensure it's working properly and giving our users (and yours) the best possible experience.

To [publish your integration](/platform/publish/public-integration), all Errors and Publishing Tasks must be validated. Warnings are non-blocking and not strictly required to proceed as they would not prevent you from promoting a version, though we do recommend you review them for usability of your integration.

If your integration will remain private, it isn't *necessary* to pass the validation checks. You will be able to [share it with users](/platform/manage/sharing) as is. However, you can still select `Run Validation` from the *Version Overview* tab in the Platform UI or run the `zapier validate` command [in the Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#validate) to be notified of any issues and recommendations to better the user experience.

To help better address a check in communication, each check is given a unique ID, consisting of a capital letter and three digits, such as `D001`.

You don't need to know the implication of the initial capitial letter. But if you're curious, they are:

| Area                   | Description                                                                                                                                                                                                                                              |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **C**ompatibility      | Only apply when your integration is public, these checks verify how the new version is backward compatible with the currently public version. They ask questions like “would this change break existing Zaps, Zap Templates, or connected accounts?”     |
| **D**efinition         | Definition of the integration, including auth and trigger/search/action configurations. Some of these checks could block you from saving/pushing if the violation results in a broken trigger/search/action.                                             |
| **M**arketing          | Public-facing information, such as the app title, description, and logo. The intent of these rules is to give Zapier users a consistent style among texts and images across all public integrations. They're more likely to block you from going public. |
| Connected **A**ccounts | Connected accounts that are linked to your integration. We verify these to ensure the authentication is working.                                                                                                                                         |
| **S**tats              | Usage stats, such as the number of users your integration has. These are more likely to block you from going public.                                                                                                                                     |
| **T** - Zap History    | Data from runs in your Zap History, produced by live (enabled) Zaps. These are more likely to block you from going public. The “T” checks are named as such for historical reasons. Zapier now shows tasks in the Zap History.                           |
| **U**ser               | Things in the developer's (your) account, such as Terms of Service acceptance.                                                                                                                                                                           |
| **L**ifecycle          | The lifecyle state of your integration or its versions, such as the visibility (private, pending, or public) and the version state (deprecated, non-production, or production).                                                                          |
| **Z**ap                | Things related to Zaps, such as the trigger samples you pulled into the Zap editor.                                                                                                                                                                      |

When the checks are run, we'll give a brief blurb summarizing the violation (with a check ID) along with a link to this page. This will act as a full reference explaining each error and giving examples for each.

***

## A001 - A Connected Account Exists

To ensure you've tested auth, we require you to set up at least one connected account.

***

## C001 - Try To Avoid Hiding/Removing Public Triggers/Searches/Creates

During promotion, we compare features of your currently public version with your soon-to-be-public version.

When you hide/remove a trigger in a new version, Zap Templates that use that trigger become invalid. Try to avoid this when you can. The same applies for searches and creates.

***

## C002 - Try To Avoid Removing Input Fields

During promotion, we compare features of your currently public version with your soon-to-be-public version.

When you change/remove the `key` property of an `input_field`, Zap Templates that use that field become invalid. Try to avoid this when you can.

***

## C003 - Try To Avoid Removing Sample Data

During promotion, we compare features of your currently public version with your soon-to-be-public version.

When you change/remove the `key` property of an item in your `sample`, Zap Templates that use that field become invalid. Try to avoid this when you can.

***

## D001 - Asks for API Key in Action Instead of in Auth

Some integrations incorrectly have `api_key` or similar as an an action field in an action instead of centrally used in auth. This is worse because action fields typically aren't treated with the same level of security as auth fields are (e.g. scrubbed from logs) and aren't action specific. Additionally, the ability to test the validity of auth doesn't exist for action fields, so anything auth related should be put into an auth field instead.

***

## D002 - Provide Link in Help Text for Auth Fields

It's often not obvious where a user can find their API credentials for a service. If you use a pasted API key as an authentication method, it's strongly encouraged that you link the user to the page (or relevant help doc) that has their key.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```bash
API key is found on the "API Details" page in settings
```

<Icon icon="check" /> an example of a **correct** implementation:

```bash
Go to the [API Details](https://my.site.com/manage/api-details) screen from your
Website Dashboard to find your API Key.
```

***

## D003 - Connection Label Should Be Set

A Connection Label helps a customer remember which account they connected. It should be short and easily identifiable.

For both [Platform UI](/platform/build/connection-label) and [CLI](/platform/reference/cli-docs#connection-label), the connection label is a string. You can use any data returned by your test function.

For instance, if a successful run of the auth test returns the following data:

```bash
{
  "name": "Malcom Reynolds",
  "email": "youcanttaketheskyfromme@serenity.com",
  "job": "Captain"
}
```

Your connection label could be the following:

```bash
{{name}} - {{email}}
```

The most important role of the label is to uniquely identify the connection.

<Icon icon="xmark" /> an example of an **incorrect** connection label:

```bash
"Slack"
```

<Icon icon="check" /> an example of a **correct** connection label:

```bash
{{user}} @ {{team}}
```

***

## D004 - ID Fields Should Have Dynamic Dropdowns

We've found that instead of instructing users to paste an item `id` into Zapier, providing them with a [dynamic dropdown](/platform/reference/cli-docs#dynamic-dropdowns) greatly increases the likelihood of the user setting up Zaps correctly. Users will still be able to map custom fields, but this gets them started on the right foot.

Read more about implementing dynamic dropdowns below:

*   [Platform UI](/platform/build/add-fields#dynamic-dropdown)
*   [Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dynamic-dropdowns)

***

## D005 - Dynamic Dropdown Connects to a Non-Existing Trigger

[Dynamic dropdowns](/platform/reference/cli-docs#dynamic-dropdowns) allow you to connect an input field to an existing trigger. The dropdown won't work if the trigger key you specify doesn't exist.

***

## D006 - REST Hook Trigger Needs a Polling URL

When users are setting up a hook-based (aka instant) Trigger, it's important to have a polling fallback. For example, imagine a Zap that triggers on a new Slack message. If testing relies on the sending of a webhook, the test won't complete without the user sending an actual message in a Slack channel, which is disruptive.

Instead, during testing, the Perform List (`performList`) operation fetches a (real) recent message using the provided URL for polling and uses it as the test result. The polling URL in a REST Hook trigger is only used for testing during a Zap setup.

It's very important that the structure of an object from a webhook and from a poll are identical. Typically, this means modifying a poll result so that it looks like a hook. If a poll has fields that a hook doesn't, the user may map them to a later step, and when the Zap runs live, the value will be blank. This can cause errors or unexpected results for users.

Let's walk through an example. Say we have a `New Contact` REST Hook trigger. When a new contact is created, Zapier gets a webhook that looks like this:

```json
{
  "id": 1,
  "firstName": "Bruce",
  "lastName": "Wayne",
  "job": "Batman"
}
```

The accompanying polling URL would look something like `https://site.com/contacts/list` and return:

```json
{
  "results": [
    {
      "id": 1,
      "firstName": "Bruce",
      "lastName": "Wayne",
      "job": "Batman",
      "friends": [2, 3, 4]
    },
    {
      "id": 2,
      "firstName": "Alfred",
      "lastName": "Pennyworth",
      "job": "Butler",
      "friends": [1, 3]
    }
  ]
}
```

To match the polling result to the hook result, you would modify this response to remove the `friends` information, and return only the array of contacts, not the enclosing object.

The polling result is not used when a user skips testing the Zap step. In that case, Zapier uses the sample data.

See [Sample Data](/platform/build/sample-data) for more details on this.

***

## D007 - All URLs Should Be HTTPS

When handling customer data (which all Zapier functions do), it's strongly encouraged that all communication take place securely. Using SSL is a big part of that, so ensure your URLs have HTTPS as their protocol.

If you need help setting up an SSL certificate for your API, we suggest [Let's Encrypt](https://letsencrypt.org/).

<Icon icon="xmark" /> an example of an **incorrect** setup:

```bash
http://example.com/messages/subscribe
```

<Icon icon="check" /> an example of a **correct** implementation:

```bash
https://example.com/messages/subscribe
```

***

## D008 - Invalid Markdown Link

A valid markdown link consists of a pair of square brackets with the link text paired with a pair of parentheses that have the link itself. See the [markdown cheatsheet](https://www.markdownguide.org/basic-syntax/#links) for more info.

If you want to show a full link without actually linking to it, use backticks. This makes it clear to the user that they don't need to click the link, it's just used as an example. Any link used in plain text needs to either be a proper link or in backticks.

<Icon icon="xmark" /> examples of an **incorrect** implementation:

```bash
See [Google(https://google.com)
```

```bash
See https://google.com
```

<Icon icon="check" /> examples of a **correct** implementation:

```bash
See [Google](https://google.com)
```

```bash
See `https://google.com`
```

If you see this error, you should look through both the description for the trigger/action/search and the help text for any fields that might have bad links.

***

## D009 - Requires at Least One Search Field

When making a search step, it's important to have a field to search by. Common examples for searching for a user are by name, email, and username.

***

## D010 - Missing Primary Key Fields in Static Sample Data

For polling triggers, the deduper uses the primary key field(s) to decide if it's seen an object before. You can define one or more output fields as the primary key. Each field can be a string or a number. But it's important that the primary key is unique. If no fields are set as `primary`, the deduper will by default use the `id` field as the primary key.

Hooks are not deduped, so they're not required to have a primary key.

This check ensures the static samples in your integration definition contain the primary key fields. It's similar to `T002`. The difference is that `T002` validates the live polling results in the Zap History.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```js
{
  // The deduper uses `id` field by default if no output fields are `primary`.
  // So this sample should have an `id` field for it to pass.
  "sample": {
    "contact_id": 4,
    "contact_name": "David"
  }
}
```

```js
{
  // `contact_id` is set as `primary`, but it's missing in the sample
  "sample": {
    "id": 4,
    "contact_name": "David"
  },
  "outputFields": [
    { "key": "contact_id", "primary": true },
    { "key": "contact_name" }
  ]
}
```

<Icon icon="check" /> examples of a **correct** implementation:

```js
{
  "sample": {
    "id": 4,
    "contact_name": "David"
  }
}
```

```js
{
  // This example defines `contact_id` as the unique primary key.
  "sample": {
    "contact_id": 4,
    "contact_name": "David"
  },
  "outputFields": [
    { "key": "contact_id", "primary": true },
    { "key": "contact_name" }
  ]
}
```

```js
{
  // If multiple fields are unique together, you can set them as `primary`.
  "sample": {
    "repo": "zapier/zapier-platform",
    "number": 1234,
    "title": "Add this feature please"
  },
  "outputFields": [
    { "key": "repo", "primary": true },
    { "key": "number", "primary": true },
    { "key": "title" }
  ]
}
```

***

## D011 - Redundant Help Text

Help text is optional and meant to provide non-obvious information or links for the user. If the label and help text are the same, they are considered redundant.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```bash
{
  "label": "Subdomain",
  "help_text": "subdomain"
}
```

<Icon icon="check" /> an example of a **correct** implementation:

```bash
{
  "label": "Subdomain",
  "help_text": "Where you (and your users) can access your forms, e.g., https://<SUBDOMAIN>.typeform.com"
}
```

***

## D012 - Static Sample Is Required

When a user sets up a trigger or action (create or search), they need sample data to be returned in order to have fields available to map in the subsequent steps. If testing the trigger returns no live results, we use static sample data as a fallback.

It's very important that the data structure of the response from the actual trigger/action request and in the sample data are identical. Otherwise, users could map fields that don't exist in the live results, which results in a broken Zap.

See [Sample Data](/platform/build/sample-data) for more details on this.

***

## D013 - Connects to a Non-Existing Search

[Search-Powered Fields](/platform/reference/cli-docs#search-powered-fields) prompt users to set up a search step to populate the value of the field. It won't work if the search key you specify doesn't exist.

***

## D014 - Has a Search Connector, but No Dynamic Dropdown

By design, to get the “Add a Search Step” button to appear for users in the Zap editor, an action needs both a search connector and a (valid) dynamic dropdown. If you can't provide a valid dropdown, you can instead point to a dummy trigger that always returns an empty array.

<Icon icon="xmark" /> an example of an **incorrect** setup:

```json
{
  "key": "update_thing",
  "search": "thing.id"
}
```

<Icon icon="check" /> an example of a **correct** implementation:

```json
{
  "key": "update_thing",
  "search": "thing.id",
  "dynamic": "things.id.name"
}
```

***

## D015 - Search-Or-Create Connects to a Non-Existing Action/Search

The search or create key you specify in `searchOrCreates` must reference to an existing search or action. Otherwise, it won't work.

***

## D016 - Consists Only a Static Webhook

A REST Hook trigger missing a Subscribe or Unsubscribe endpoint, is presented to users as a [Static Webhook](https://cdn.zappy.app/3b35908a6a0c232087b5da807cf9d6fb.png). Static hooks are [not supported in public integrations](/platform/publish/integration-checks-reference#D017), but they could be used if the integration intends to remain private. However, Zapier doesn't allow integrations that are a single static hook with the availability of [Webhooks by Zapier](https://zapier.com/apps/webhook/integrations). To fix this, add more triggers/searches/actions.

***

## D017 - Static Hook Is Discouraged

When a REST Hook trigger is missing a Subscribe or Unsubscribe endpoint, it is presented to users as a [Static Webhook](https://cdn.zappy.app/3b35908a6a0c232087b5da807cf9d6fb.png). As static webhooks require manual intervention by the user to set up correctly, we no longer support adding new static webhook triggers to a public integration. Please set up Subscribe and Unsubscribe requests for this trigger, or use a Polling trigger type instead.

***

## D018 - Titlecased Label

In order to have a consistent style across trigger/action/search labels, they're required to be presented in title case. If you fail this check, a passing version of your label will be shown.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```bash
new contact
```

<Icon icon="check" /> an example of a **correct** implementation:

```bash
New Contact
```

***

## D021 - Trigger Description Requirements

Trigger descriptions must start with `Triggers when` and end with a `.`.

<Icon icon="xmark" /> examples of an **incorrect** implementation:

```bash
Whenever there's a new contact, this goes?
```

```bash
Triggers whenever there's a new contact.
```

<Icon icon="check" /> examples of a **correct** implementation:

```bash
Triggers when there's a new contact.
```

***

## D022 - Creates Should Try to Have Static Input Fields

When making Zap Templates, it's helpful to have input fields that are common for all users. Without any, it's hard to create templates. If possible, add some static input fields that all users will be able to use.

***

## D023 - ISO-8601 Date/Time Format in Static Sample

To ensure Zapier can correctly parse dates and times, you should always use ISO-8601 format to represent dates or times. Timezone info should also be present if it contains time.

This check is similiar to `T003`. This check validates the static samples in your integration definition, while `T003` validates the live results in the Zap History.

<Icon icon="xmark" /> examples of an **incorrect** implementation:

```bash
01 Aug 2023
```

```bash
01 Aug 2023 06:50:30
```

```bash
2023-08-01T06:50:30
```

<Icon icon="check" /> examples of a **correct** implementation:

```bash
2023-08-01
```

```
2023-08-01T06:50:30-0500
```

```
2023-09-15T09:59:59Z
```

***

## D024 - Static Sample Respects Output Field Definition

If you define output fields for a trigger/action/search, they should be consistent with the static sample data. The specific checks are:

*   “required” fields must be in the sample
*   field values in the sample match their field type

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```json
static sample: {"id": "1"}
output fields: [
    {"key":  "id", "type": "integer"},
    {"key": "email", "type": "string", "required": true}
]
```

<Icon icon="check" /> an example of a **correct** implementation:

```json
static sample: {"id": 1, "email": "john@example.com"}
output fields: [
    {"key":  "id", "type": "integer"},
    {"key": "email", "type": "string", "required": true}
]
```

***

## D025 - URLs Should Not Be Dangerous URIs

In order to help prevent reflective cross-site-scripting (XSS) attacks on Zapier customers, we require that URLs inside the app definition do not match potentially dangerous URI patterns which could be used to run malicious code.

Read more about XSS in the [OWASP Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).

<Icon icon="xmark" /> an example of an **incorrect** setup:

```bash
javascript:alert('XSS');//
```

<Icon icon="check" /> an example of a **correct** implementation:

```bash
https://example.com
```

***

## D026 - Manual domain validation recommended if using “inputFormat” or domain-related authentication fields

When utilizing authentication fields which allow a user to input their own domain or subdomain, we strongly recommend performing [manual validation](/platform/build/subdomain-validation) on the input data to ensure that it matches your expectations and filters out values which could be used to redirect users into unexpected domains.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```js
// No subdomain validation, trusting the user input
const response = await z.request({
  url: `https://${bundle.authData.yourSubdomainField}.mydomain.com/oauth/token`,
  // ...
});
```

<Icon icon="check" /> an example of a **correct** implementation:

```js
// Manual validation step to ensure the subdomain matches your requirements
if (!/^[a-z0-9-]+$/.test(bundle.authData.yourSubdomainField)) {
  throw new Error(
    "Subdomain can only contain letters, numbers and dashes (-)."
  );
}

const response = await z.request({
  url: `https://${bundle.authData.yourSubdomainField}.mydomain.com/oauth/token`,
  // ...
});
```

***

## L001 - Version Is Deprecated

You can't promote a deprecated version.

***

## L002 - Version Was Already Submitted

You can't submit a version you've already submitted. If your integration was pushed back and you want to resubmit for another review, you should make changes on a **new** version and submit that.

***

## L003 - Version Is Already Production

This could happen if you're attempting to promote a version that is already in production.

***

## L004 - Changes From Partners Are Blocked

This is necessary when Zapier team pushes changes to a partner-owned integration. Partners would have to coordinate with Zapier team via [partners@zapier.com](mailto:partners@zapier.com) to lift the restriction for subsequent changes from them.

***

## M001 - App Category Is Required

To correctly categorize your integration on Zapier, please choose the category that fits best your app. You can specify a category for your integration in the Integration Settings page.

***

## M002 - Description Is Invalid

Your app's description is a place to talk about your app, not ours! Even if we really like your app, you're not allowed to say “Zapier” in your app's description.

Additionally, it's discouraged that you talk about how this integration will “sync” anything, as the space is supposed to be about your app itself instead of the Zapier integration in particular.

Lastly, this section should be short and sweet. A brief description (roughly tweet-sized) is best. Specifically, we're looking for 1 - 3 sentences or at least 40 characters and a maximum of 140 characters.

<Icon icon="xmark" /> an example of an **incorrect** setup:

```bash
Google Translate enables Zapier users to translate text from one language into
another.
```

<Icon icon="check" /> an example of a **correct** implementation:

```bash
Google Translate is a service that translates text from one language into another.
```

***

## M003 - Role Must Be Employee or Contractor

For your integration to go public, you must be employed or hired by the company who makes the app used in the integration. Go to the Integration Settings page to select your role.

***

## M004 - Invalid Logo

Your app's logo will be used all over the site in square containers and in various sizes. To ensure it looks good at all sizes, the logo image must be:

*   a square PNG image
*   at least 256px by 256px in size
*   in RGBA mode so it can have a transparent background

To resize an image or convert an image to PNG, you can use this [tool](http://www.picresize.com/).

***

## M005 - Admin Team Member Email Domain Matches App Domain

To ensure that this integration is being submitted by the app owner we require that one of the Admin team members listed on the project have an email address with the same domain as your app's homepage URL (which must also be present). You can add the homepage URL at `https://developer.zapier.com/app/APP_ID/version/APP_VERSION/settings`. Collaborator team members with the same domain as the homepage do not meet this requirement.

***

## M006 - Homepage URL Must Be Present

Each app must have a homepage URL. You can add the homepage URL at `https://developer.zapier.com/app/APP_ID/version/APP_VERSION/settings`.

***

## M007 - Public Integration Already Exists

We only allow one public integration in our app directory for a given app. If a public integration with the same title already exists, we won't approve your submission to go public. If you're the owner of the existing public integration, create an updated version with any edits and promote that instead of submitting a new integration.

***

## S001 - 3 Users with a live Zap

To verify user demand, there should be at least 3 users who have a live Zap using this integration. “Live” means the Zaps are switched on with at least one successful [Zap run in recent history](https://help.zapier.com/hc/en-us/articles/8496291148685).

You can [invite others to test your integration](/platform/manage/sharing) before publication.

***

## S002 - One Live Zap for Each Trigger/Search/Action

To ensure any show-stopping bugs are worked out, every visible trigger/search/action of your integration should have a live Zap that demonstrates it works. “Live” means the Zaps are switched on with at least one successful [Zap run in recent history](https://help.zapier.com/hc/en-us/articles/8496291148685).

You can create a [new Zapier account](https://help.zapier.com/hc/en-us/articles/8496197192461) and [invite it to your integration](/platform/manage/sharing) if you need extra Zaps.

You can also [contact us](https://developer.zapier.com/contact) if you need a trial extension.

***

## S003 - Live Version Count Limit

You can't have more than 5 previous or current production versions with live Zaps. To continue, migrate users on older versions to a newer version.

***

## T001 - One Successful Zap Run for Each Trigger/Search/Action

There must be at least one successful Zap run for each visible trigger/action/search in your app.

To ensure you have run a live test of every visible trigger/action/search, create a Zap for each one, turn it on, and trigger a Zap run while it's on.

This check is performed using the [Zap History](https://zapier.com/app/history) for accounts belonging to the integration admins, so build your test Zaps in these accounts.

Learn more about the Zap History [here](https://help.zapier.com/hc/en-us/articles/8496291148685).

***

## T002 - Missing Primary Key Fields in Live Polling Results

For polling triggers, the deduper uses the primary key field(s) to decide if it's seen an object before. You can define one or more output fields as the primary key. Each field can be a string or a number. But it's important that the primary key is unique. If no fields are set as `primary`, the deduper will by default use the `id` field as the primary key.

Hooks are not deduped, so they're not required to have a primary key.

This check ensures the live polling results in the [Zap History](https://zapier.com/app/history) contain the primary key fields. It's similar to `D010`. The difference is that `D010` validates the static samples in your integration definition.

This check is performed using the [Zap History](https://zapier.com/app/history) for accounts belonging to the integration admins, so build your test Zaps in these accounts.

<Icon icon="xmark" /> examples of an **incorrect** implementation:

```json
{
  // The deduper uses `id` field by default if no output fields are `primary`.
  // So this sample should have an `id` field for it to pass.
  // Note that `<live_polling_result>` represents a polling result in Zap History,
  // not an actual key in your integration definition.
  "<live_polling_result>": {
    "contact_id": 4,
    "contact_name": "David"
  }
}
```

```json
{
  // `contact_id` is set as `primary`, but it's missing in the result
  "<live_polling_result>": {
    "id": 4,
    "contact_name": "David"
  },
  "outputFields": [
    { "key": "contact_id", "primary": true },
    { "key": "contact_name" }
  ]
}
```

<Icon icon="check" /> examples of a **correct** implementation:

```json
{
  "<live_result_result>": {
    "id": 4,
    "contact_name": "David"
  }
}
```

```json
{
  // This example defines `contact_id` as the unique primary key.
  "<live_polling_result>": {
    "contact_id": 4,
    "contact_name": "David"
  },
  "outputFields": [
    { "key": "contact_id", "primary": true },
    { "key": "contact_name" }
  ]
}
```

```json
{
  // If multiple fields are unique together, you can set them as `primary`.
  "<live_polling_result>": {
    "repo": "zapier/zapier-platform",
    "number": 1234,
    "title": "Add this feature please"
  },
  "outputFields": [
    { "key": "repo", "primary": true },
    { "key": "number", "primary": true },
    { "key": "title" }
  ]
}
```

***

## T003 - ISO-8601 Date/Time Format in Zap History

To ensure Zapier can correctly parse dates and times, you should always use ISO-8601 format to represent dates or times. Timezone info should also be present if it contains time.

This check is similiar to `D023`. This check validates the data in the [Zap History](https://zapier.com/app/history), while `D023` validates the static samples in your integration definition.

This check is performed using the [Zap History](https://zapier.com/app/history) for accounts belonging to the integration admins, so build your test Zaps in these accounts.

<Icon icon="xmark" /> examples of an **incorrect** implementation:

```bash
01 Aug 2023
```

```bash
01 Aug 2023 06:50:30
```

```bash
2023-08-01T06:50:30
```

<Icon icon="check" /> examples of a **correct** implementation:

```bash
2023-08-01
```

```bash
2023-08-01T06:50:30-0500
```

```bash
2023-09-15T09:59:59Z
```

***

## T004 - Static Sample Contains a Subset of Keys from Live Result

Static samples provide Zapier users and partners a way to preview and map fields for a trigger or action without actually making a request to your API. It's important that static samples reflect the real results from these calls as seen in the Zap History. Errors occur when a Zap uses a field from static sample that is not provided once the Zap is running.

This check requires the static sample you define for each trigger/action/search to contain a subset of the keys in the latest run in the [Zap History](https://zapier.com/app/history).

This check is performed using the [Zap History](https://zapier.com/app/history) for accounts belonging to the integration admins, so build your test Zaps in these accounts.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```json
static: {"id": 1, "email": "john@example.com"}
live: {"id": 2, "name": "Alice"}
```

<Icon icon="check" /> an example of a **correct** implementation:

```json
static: {"id": 1, "name": "John"}
live: {"id": 2, "name": "Alice", "email": "alice@example.com"}
```

See [Sample Data](/platform/build/sample-data) for more details on this.

***

## T005 - Live Trigger Result Respects Output Field Definition

This check takes the latest run from the [Zap History](https://zapier.com/app/history) and verifies whether the trigger result conforms to the output fields for the trigger in your integration (if defined). The specific checks are:

*   “required” fields must be in the trigger result
*   field values in the trigger result match their field type

This check is performed using the [Zap History](https://zapier.com/app/history) for accounts belonging to the integration admins, so build your test Zaps in these accounts.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```json
live result: {"id": "1"}
output fields: [
    {"key":  "id", "type": "integer"},
    {"key": "email", "type": "string", "required": true}
]
```

<Icon icon="check" /> an example of a **correct** implementation:

```json
live result: {"id": 1, "email": "john@example.com"}
output fields: [
    {"key":  "id", "type": "integer"},
    {"key": "email", "type": "string", "required": true}
]
```

See [Sample Data](/platform/build/sample-data) for more details on this.

***

## T006 - Polling Sample Contains a Subset of Keys from Live Result

For REST Hook triggers, we require you to provide a `Perform List` URL (check `D006`) so that users can retrieve a real data sample in the Zap editor. This is called a polling sample, and is created when you test the trigger in the Zap editor before turning it on.

Errors occur when a Zap uses a field from the polling sample that is not provided by the hook payload sent once the Zap is running.

To ensure this doesn't happen, this check compares the latest item in the [Zap History](https://zapier.com/app/history) with the selected polling sample in the corresponding Zap. For it to pass:

*   There must be a Zap that is using the trigger.
*   The Zap must have at least one Zap run (the most recent run will be used).
*   The trigger must have been tested in the Zap editor via the Perform List method to retrieve a polling sample.
*   The polling sample should have the same data keys, or a subset of keys, compared to those available in the Zap run data.

This check is performed using the [Zap History](https://zapier.com/app/history) for accounts belonging to the integration admins, so build your test Zaps in these accounts.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```json
polling sample: {"id": 1, "email": "john@example.com"}
live: {"id": 2, "name": "Alice"}
```

<Icon icon="check" /> an example of a **correct** implementation:

```json
polling sample: {"id": 1, "name": "John"}
live: {"id": 2, "name": "Alice", "email": "alice@example.com"}
```

See [Sample Data](/platform/build/sample-data) for more details on this.

***

## U001 - Developer Terms of Service

You must agree to the latest Developer Terms of Service in order to proceed. Go to [Developer Home](https://developer.zapier.com) to agree.

***

## Z001 - Polling Sample Respects Output Field Definition

For REST Hook triggers, we require you to provide a `Perform List` URL (check `D006`) so that users can pull a live sample in the Zap editor. This is called a polling sample.

This check takes the latest polling sample from a Zap with this trigger and verifies that the sample conforms to the output fields for the trigger in your integration (if defined). The specific checks are:

*   “required” fields must be in the polling sample
*   field values in the trigger result must match their field type

This check is performed using the Zaps in accounts belonging to the integration admins, so build your test Zaps in these accounts.

<Icon icon="xmark" /> an example of an **incorrect** implementation:

```json
polling sample: {"id": "1"}
output fields: [
    {"key":  "id", "type": "integer"},
    {"key": "email", "type": "string", "required": true}
]
```

<Icon icon="check" /> an example of a **correct** implementation:

```json
polling sample: {"id": 1, "email": "john@example.com"}
output fields: [
    {"key":  "id", "type": "integer"},
    {"key": "email", "type": "string", "required": true}
]
```

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Integration publishing requirements

We're excited you are creating an integration for the [Zapier Platform](https://zapier.com/developer-platform). We're here to help you understand our platform and its requirements so that you can successfully prepare your Zapier integration for publishing. Thousands of partners have built integrations on the Zapier Platform that enable our mutual users to set up Zaps as easily and quickly as possible.

When your Zapier integration meets these requirements, it will pass the review for the publishing process quickly and smoothly. The requirements help maintain quality and consistency for all integrations listed in our [App Directory](https://zapier.com/apps).

Please review these requirements carefully before submitting to ensure your integration is compliant. Integrations and their associated apps must meet all requirements to be published.

**Important**: integrations for prohibited apps will be removed from the platform. Integrations for restricted apps or that do not meet all requirements will be allowed to remain [private](/platform/quickstart/private-vs-public-integrations).

Work through each section and ask the [PublishBot](https://publishbot.zapier.app/) or write in via the [contact form](https://developer.zapier.com/contact) for any questions.

## Key considerations

*   Because the Zapier Platform is always changing and improving to keep up with the needs of our customers, this is a living document.

*   If you prefer your integration to be accessible to a limited group of users, or if the Zapier integration review requirements do not align with your needs, you might want to consider maintaining your integration as private. Learn more about [private vs public integrations](/platform/quickstart/private-vs-public-integrations).

*   We encourage diversity on the Zapier Platform, provided your integration respects users with varying viewpoints and ensures a high-quality user experience. Any integration found to contain or exhibit behavior deemed inappropriate, discriminatory, or crossing acceptable boundaries will be rejected.

*   We reserve the right to reject publishing requests or to remove users and integrations from the Zapier Platform if we believe these requirements, the [Platform Agreement](https://zapier.com/platform/tos), or our [Terms of Service](https://zapier.com/legal/terms-of-service) have not been met. This includes attempts to exploit the review process, illicitly acquire user data, or manipulate test users.

## Sections

<CardGroup cols={3}>
  <Card title="Prohibited Integrations" icon="ban" href="/publish/integration-publishing-requirements#1-prohibited-integrations" horizontal />

  <Card title="Restricted Integrations" icon="triangle-exclamation" href="/publish/integration-publishing-requirements#2-restricted-integrations" horizontal />

  <Card title="Ownership and Permissions" icon="user-shield" href="/publish/integration-publishing-requirements#3-ownership-and-permissions" horizontal />

  <Card title="Data Security and Privacy" icon="lock" href="/publish/integration-publishing-requirements#4-data-security-and-user-privacy" horizontal />

  <Card title="Quality and Functionality" icon="circle-check" href="/publish/integration-publishing-requirements#5-quality-and-functionality" horizontal />

  <Card title="Integration Listing" icon="list" href="/publish/integration-publishing-requirements#6-integration-listing" horizontal />

  <Card title="Support" icon="headset" href="/publish/integration-publishing-requirements#7-support" horizontal />

  <Card title="Submission Process" icon="paper-plane" href="/publish/integration-publishing-requirements#8-submission-process" horizontal />
</CardGroup>

## 1. Prohibited integrations

Integrations built for the following apps aren't permitted and will be removed from the Zapier Platform:

### 1.1 Apps from a restricted country

Apps/integrations must not be from companies in or under the control of any country against which the United States currently has sanctions, as per section 6(d)(iv) of the [Zapier Platform Agreement](https://zapier.com/developer-platform/tos).

### 1.2 Apps from a competitor

Apps/integrations must not be from companies we deem to be competing with Zapier, as per section 2(i) of the [Zapier Platform Agreement](https://zapier.com/developer-platform/tos).

### 1.3 Apps that do not meet Zapier or third-party agreements

Apps/integrations must comply with all Zapier and other third-party agreements, including but not limited to the [Zapier Platform Agreement](https://zapier.com/developer-platform/tos) and the [Zapier Terms of Service](https://zapier.com/legal/terms-of-service). Should we ask you to supply it, you must be ready to provide supporting documentation of permission from third parties.

### 1.4 Integrations that make financial transactions

Integrations must not facilitate any form of financial transaction, transfer of assets, or payment processing. This includes the transmission of ownership of any tokens or assets in the case of blockchain or cryptocurrency apps.

### 1.5 Apps that have misleading or malicious functionality

Apps/integrations must not mislead users. Do not create a Zapier integration that can be used to spam, phish, or send unsolicited messages to users. If you attempt to cheat the system (for example, by trying to trick the review process, steal user data, or fake real users) your integration will be removed from Zapier and you could be expelled from submitting any integrations in the future. False information and features, including inaccurate data or joke functionalities, such as fake phone calls or SMS are prohibited.

### 1.6 Apps that have objectionable content

Apps/integrations should not include content that is discriminatory, defamatory, offensive, mean-spirited, or insensitive with regard to gender, religion, sexual orientation, age, race, national/ethnic origin, or other targeted groups.

## 2. Restricted integrations

Integrations built for the following apps will not be published but will be allowed to remain [private](/platform/quickstart/private-vs-public-integrations):

### 2.1 Replacement integrations

The integration should not have an equivalent that has already been published in our [app directory](https://zapier.com/apps). Each published integration should represent a distinct, independent app to ensure the best experience for our mutual customers. If you're attempting to replace an existing public integration, please refer to the guide on [versions](/platform/manage/versions) and how to [plan changes](/platform/manage/planning-changes). Publishing separate integrations for the same app brand is not permitted.

### 2.2 Multiple integrations

Do not create separate integrations for functionality that share the same authentication method and interact with the same API, unless each integration is for a separate product or service (not feature) that is clearly marketed independently. Instead, consolidate the functionality into one, high-level integration.

## 3. Ownership and permissions

### 3.1 APIs

You own or have permission to use all APIs used by the integration. Details of those permissions are supplied on request.

The account owning the integration must have proper access to the associated platform and APIs, either by working for, being associated with, or being contracted by the company whose integration is in review. This is so we can reach out with questions or support issues during the review and after the integration is public. If it's not obvious to us that the owning account is associated with your integration's company (largely by email domain), we will request proof via an email from a matching domain as the platform or confirmation from someone at the company.

Scenarios in which integrations are permitted on the Zapier Platform by status, ownership, and intention:

| Private                                                                                                                                                                                                    | Public                                                                                                                                                                                                                                                     | Not Allowed                                                                                                                                        |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Anyone that accepts [Zapier Terms of Service](https://zapier.com/platform/tos) can build a private integration.                                                                                            | Integrations by developers who work for the company owning the API or have been contracted by the API owner can go public.                                                                                                                                 | Integrations by developers who are building on an API they own but for a competing product with Zapier.                                            |
| Integrations by developers building on a private API with no intention of going public, are permitted to remain private/invite-only indefinitely.                                                          | Integrations by developers who work for or third-party developers (preferably a [trusted developer](/platform/quickstart/trusted-developers)) hired by the company owning the API and intended to go public, are permitted as private before going public. | Integrations by developers who do not own the API used in the integration or who have not been given explicit permission by the owners of the API. |
| Integrations by developers who are building on a public API they do not own, and routing traffic to their own servers. Exceptions apply when building on an approved third-party API.                      |                                                                                                                                                                                                                                                            |                                                                                                                                                    |
| Integrations by developers building on a public API they do not own and intend to share with their team (within their company or organization, family and friends, etc.) are only permitted to be private. |                                                                                                                                                                                                                                                            |                                                                                                                                                    |
| Integrations by developers building on a public API they do not own and intend to sell access to the integration, are only permitted to be private.                                                        |                                                                                                                                                                                                                                                            |                                                                                                                                                    |
| Integrations by developers building with sandbox/test/dev environments or endpoints are only permitted to be private.                                                                                      |                                                                                                                                                                                                                                                            |                                                                                                                                                    |

### 3.2 Trademarks

You own or have permission to use all trademarks used in the integration. Details of those permissions are supplied on request.

Zapier trademarks can't be used in an integration name, but they can be used in the description and partner marketing material in accordance with our [brand guidelines](https://brand.zapier.com/).

## 4. Data security and user privacy

### 4.1 Integration handles data appropriately

The integration does not facilitate or encourage end users to transmit or receive sensitive personal data in violation of the [Zapier Terms of Service](https://zapier.com/legal/terms-of-service).

### 4.2 Integration uses HTTPS

For security reasons, all API endpoints used by an integration (including authentication and login pages) must utilize HTTPS instead of HTTP.

### 4.3 Integration stores credentials securely

Do not hard code credentials such as API Keys, Client IDs, Client Secrets, etc. into your integration. Use the appropriate section in the Platform UI (e.g. Application Credentials) or use [environment variables](/platform/build/env) instead.

## 5. Quality and functionality

### 5.1 App has been publicly launched

Your app has been fully launched to the public and isn't an invite-only or “beta” app.

### 5.2 App APIs are documented

Your app's API and all other non-Zapier APIs used in the integration have clear, accurate, up-to-date, documentation covering all API endpoints. Links to this documentation are supplied in your publishing submission.

### 5.3 Integration is production-ready

The integration has been fully tested and does not have any unexpected or unhandled errors. All integration triggers, actions, and searches have been tested using Zaps in a Zapier account. Every test Zap has been turned on and has at least one successful run in the [Zap history](https://zapier.com/app/history).

Do not delete these Zaps or Zap runs. We may ask you to share your test Zaps with us when you submit your integration for review.

Use the Monitoring page in the Platform UI to check integration events and ensure the integration does not have unexpected or unhandled errors.

For more information and guidance, refer to [Integration Build Guidelines](/platform/publish/integration-build-guidelines) and [Test and monitor your integration in your Zapier account](/platform/build/test-monitoring).

### 5.4 Integration uses production APIs

The integration uses production API endpoints. Integrations using sandbox/test/dev endpoints will not be published

### 5.5 Integration requests authentication credentials appropriately

Authentication credentials are only requested from users via the [Authentication](/platform/build/auth) configuration. Integrations that request authentication credentials to be entered in other configurations, such as triggers, actions, or searches, will not be published.

### 5.6 Integration has a valid connection label

[Connection labels](/platform/build/connection-label) are optional but can help users identify which account each [app connection](https://help.zapier.com/hc/en-us/articles/8496258785421-Connect-your-app-accounts-to-Zapier) uses. Suitable values include account name, email address, and name.

Sensitive information such as API keys, secrets, and lengthy ID values are not allowed. Do not hard code values such as the application's name or words like “success”, as these are already communicated in the UI.

### 5.7 Integration uses English language

Currently, the Zapier Platform supports only English. All user-facing text, such as the name and description of your integration, trigger/action/search names, field names, output field labels, help texts, and error messages, must be in English. However, data sent to and received from your API can be in your product's native language.

### 5.8 Integration follows naming conventions

Triggers, actions, searches, and input fields should be named according to platform conventions. For more information, refer to [Trigger Copy](/platform/publish/integration-build-guidelines#trigger-design-and-copy), [Trigger input fields](/platform/publish/integration-build-guidelines#trigger-input-fields), [Action Copy](/platform/publish/integration-build-guidelines#action-design-and-copy), [Action input fields](/platform/publish/integration-build-guidelines#action-input-fields), [Search Copy](/platform/publish/integration-build-guidelines#search-design-and-copy), and [Search input fields](/platform/publish/integration-build-guidelines#search-input-fields).

## 6. Integration listing

### 6.1 Integration name is unique and matches your application's core branding

Enter your application name exactly as it appears in your core branding. Search our [app directory](https://zapier.com/apps) to make sure your integration name is not already in use.

Do not include trademark or copyright identifiers like a TM suffix or words like “app” or “integration.” Do not include your website's domain (e.g., `.ai`) or your company name unless they are always used in your core branding when referring to your app.

### 6.2 Integration description follows convention

Enter a short description of your application's core features and use cases, that leads with your app name in the form of `<Integration Name> is a....`. The description should not include links or mentions of Zapier or other apps, or make it appear your integration is associated with or endorsed by Zapier. Focus less on selling the application, instead, provide a clear explanation of what your application actually does.

**Examples**:

*   `Trello is a team collaboration tool to organize anything on a kanban board.`, **not** `Trello is the best project management tool`.
*   `Dropbox lets you store files online, sync them to all your devices, and share them easily.`, **not** `A file storage app`.

### 6.3 Integration homepage URL correctly set

Enter the URL of your application's marketing website, not the application URL or login page, etc.

## 7. Support

### 7.1 Application has a valid test account

The submission includes a valid test account created in the application. The test account allows Zapier support staff to view your app's interface for troubleshooting and assisting our shared users once an integration is published.

*   The test account must be non-expiring and use this email address: [integration-testing@zapier.com](mailto:integration-testing@zapier.com).
*   Zapier support staff must be able to change the account password unless the account uses “passwordless authentication,” such as OTP or “magic link.”
*   Ensure all necessary features are enabled for testing Zap steps (triggers and actions). Include any required paid features and avoid trial limitations.
*   Provide a demo video or detailed documentation for any complex features. Supply all account credentials required to log in and configure the account appropriately, avoiding super admin access or internal-only features.

### 7.2 Integration has a qualifying team member

Integrations must have at least one [admin team member](/platform/manage/add-team) with an email address from the application or API's top-level domain, or the company owning the API used in the integration. Add new team members to an integration by following this guide: [Invite team members to your integration](/platform/manage/add-team).

## 8. Submission process

### 8.1 Repeated submissions

Submitting multiple integrations that are essentially the same delays the Zapier process and risks rejection of all submissions. Please avoid flooding the Zapier team with consecutive submissions of the same integration during the review process. Violating this policy may result in slower review times or further penalties.

### 8.2 Approval discretion

At our discretion, we reserve the right to reject publishing requests or to remove users and integrations from the Zapier Platform if we believe these requirements, the [Platform Agreement](https://zapier.com/platform/tos), or our [Terms of Service](https://zapier.com/legal/terms-of-service) have not been met. This includes attempts to exploit the review process, illicitly acquire user data, or manipulate test users

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Integration success strategies

With 7,000+ public integration partners on Zapier, use these 10 tried-and-true tactics from our top partners to skyrocket your growth and earn you more [benefits from the Partner Program](https://zapier.com/developer-platform/partner-program).

*   [Level up your team](#tip-1-level-up-your-team)
*   [Monitor your integration insights on growth and usage](#tip-2-monitor-your-integration-insights-on-growth-and-usage)
*   [Boost user adoption by seamlessly embedding Zapier](#tip-3-boost-user-adoption-by-seamlessly-embedding-zapier)
*   [Share Zapier use cases in your onboarding](#tip-4-share-zapier-use-cases-in-your-onboarding)
*   [Add new features to your Zapier integration](#tip-5-add-new-features-to-your-zapier-integration)
*   [Power up your app marketplace with Zapier-enabled listings](#tip-6-power-up-your-app-marketplace-with-zapier-enabled-listings)
*   [Suggest Zapier as an answer to common support questions](#tip-7-suggest-zapier-as-an-answer-to-common-support-questions)
*   [Point empty marketplace search results to Zapier](#tip-8-point-empty-marketplace-search-results-to-zapier)
*   [Surface Zapier in your website footer](#tip-9-surface-zapier-in-your-website-footer)
*   [Leverage marketing automation](#tip-10-leverage-marketing-automation)

## Level up your team

Make sure your sales and support teams say “yes!” when asked about an integration. Share these bite-sized [Zapier 101 videos](https://learn.zapier.com/) to get them up-to-speed on Zapier.

## Monitor your integration insights on growth and usage

Review your integration insights on a regular cadence to guide making decisions and [inform you on the quality](/platform/manage/integration-insights) of your integration. We routinely hear from our partners that Zapier users are higher-value, sticky users. So, it's important to keep a pulse on how the integration is doing.

**Clearbit case study:** Clearbit carefully tracks the success of their users and discovered Zapier users are 20% less likely to churn. Armed with this data, their team prioritizes promotion and development strategies that encourage more of their users to try integrations.

## Boost user adoption by seamlessly embedding Zapier

Keep users engaged inside your own platform by embedding the Zapier experience in your app. Help users easily discover, build, and manage Zaps with our prebuilt, plug-and-play [Full Zapier Experience](https://platform.zapier.com/embed/full-zapier-experience), or utilize our [Partner API](https://platform.zapier.com/embed/partner-api) to create a seamless, custom solution.

**Jotform case study:** Jotform makes it easy for users to connect their forms to other apps to send form responses. Within every form builder, users have the ability to search for integrations, discover popular use cases, and build and edit Zaps all without leaving Jotform.

<Frame>
  <img src="https://cdn.zappy.app/bc91c44477f97c9f7104b9ca690cd7cf.gif" />
</Frame>

> **Do this yourself by:**

> *   Using the [Full Zapier Experience](https://platform.zapier.com/embed/full-zapier-experience) or [Zap Templates](https://platform.zapier.com/embed/zap-templates) to embed popular Zaps in just a few minutes.
> *   Or designing a custom experience like Jotform did via our [Partner API](https://platform.zapier.com/embed/partner-api).

## Share Zapier use cases in your onboarding

From tool tips to testimonials, make sure your newest customers know it's possible to connect your tool to 7,000+ other web apps via Zapier.

**Base CRM case study:** Base CRM shares a customer story in their onboarding sequence to help new users picture automation magic in their own work. The result? Stickier users right from the get-go.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/9b1c3ac4d870456e5f4ceedc85159878.png" />
</Frame>

> **Do this yourself by:**

> *   Identifying a point in your onboarding sequence that makes sense to introduce Zapier to new users.
> *   Linking to a customer story, a detailed help doc, or even display your most popular Zaps with [a simple snippet of code](https://platform.zapier.com/embed/zap-templates).

## Add new features to your Zapier integration

As your product evolves, your integration should, too. An up-to-date, bug-free integration makes automation simple for users.

**Thinkific case study:** As Thinkific's feature set grows, they improve their Zapier integration, too. By promoting these updates in blog posts and product newsletters, existing users learn how to power up their workflows and new users discover Zapier automation.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/945be106295dfa438f9479275f492eff.png" />
</Frame>

> **Do this yourself by:**

> *   Making updates your users are asking for. Review feature requests by logging in to [the dashboard](https://developer.zapier.com/) with your developer credentials.
> *   Creating new [Zap templates](https://developer.zapier.com/zap-templates) to inspire use cases.
> *   Promoting the new Zapier features to your users in a blog post or email.

## Power up your app marketplace with Zapier-enabled listings

Chances are, your users are searching your marketplace for the apps they want to connect to. Close the loop on those high-intent searches with listings for the apps your users search for most, powered by Zapier.

**Any.do and Wufoo case studies:** Any.do uses our plug-and-play Full Zapier Experience to show off a huge library of integrations enabled by Zapier, while Wufoo powers its integration marketplace with our Partner API. This way, you're providing users and prospects the widest selection of apps to search through when they're looking for integrations to connect.

<Frame>
  <img src="https://cdn.zappy.app/1ba8ac90ca98401b0234f04ee188e8fd.gif" />
</Frame>

<Frame>
  <img src="https://cdn.zappy.app/5771591ec4d63017651c1b62849a0778.png" />
</Frame>

> **Do this yourself by:**

> *   Leveraging the [Full Zapier Experience](https://platform.zapier.com/embed/full-zapier-experience).
> *   Or utilizing [Zapier's Partner API](https://platform.zapier.com/embed/partner-api) to programmatically deploy and maintain your marketplace.

## Suggest Zapier as an answer to common support questions

What are your customers searching for help on? Our top partners think beyond a one-size-fits-all Zapier help doc. Whether it's how to export data to Google Sheets, or how to get custom alerts for new data, crafting custom Zapier tutorials helps you have answers at the ready.

**Trello case study:** Trello's team gets a lot of questions about how to import project data. [This help doc](https://help.trello.com/article/751-importing-data-into-trello) shows how easy it is to automatically import to Trello, and directs users to Zapier.

> **Do this yourself by:**

> *   Asking your support and sales teams about the questions they often receive. Can Zapier solve these problems?
> *   [Creating a pre-made Zap](/platform/publish/zap-templates) for a step-by-step solution to that common problem.
> *   Writing a tutorial with the pre-made Zap [embedded](https://platform.zapier.com/embed/zap-templates) in your help docs so readers can build the workflow without leaving your site.

## Point empty marketplace search results to Zapier

Make sure your users aren't left empty-handed. When search results in your marketplace turn up empty, point them to Zapier (chances are, we support their favorite app).

**Help Scout case study:** Help Scout prompts users to search for the integrations they want. When a search result comes up empty, a short snippet of default text suggests Zapier.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/c6e2f18f1054a28e75a975cd9c3f99f8.png" />
</Frame>

> **Do this yourself by:**

> *   Adding a search bar to your integrations directory.
> *   Displaying this sentence as default text on an empty search state, “Can't find what you're looking for? See if you can integrate with it via Zapier.”

## Surface Zapier in your website footer

Help your users find what they're looking for by linking to your Zapier integration from your site footer.

**Close.io case study:** Close.io helps users discover their Zapier integration by linking to Zapier topics from their footer. This helps new users find out about Zapier and keeps Close.io as a top Zapier integration.

## Leverage marketing automation

Zero in on the users most likely to adopt automation into their everyday work. Watch for users to hit key milestones in your app, like creating a new contact or exporting data. Then, suggest automation as a natural next step.

**Autopilot case study:** Autopilot celebrates when users activate a chat feature, then suggests Zapier as a solution for automated contact management.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/5a6b1f983bc431286a2e1501b50a8257.png" />
</Frame>

> **Do this yourself by:**

> *   Identifying key actions your users that can be boosted with automation.
> *   Using your marketing automation to message users who take that action.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Partner Program

The [Zapier Partner Program](https://zapier.com/developer-platform/partner-program) is a program for Zapier's [6,000+ integration partners](https://zapier.com/apps). It is designed to give all partners a clear path to success for their integrations and reward them with benefits along the way.

> **The Partner Program is only available to public integrations.**

As your integration gains more active users, and maintains a healthy health score, your integration will tier up in our Partner Program. Zapier's Partner Program consistes for 4 tiers:

*   Bronze
*   Silver
*   Gold
*   Platinum

Learn more about the [benefits are available in each tier](https://zapier.com/developer-platform/partner-program).

## How tiers are calculated

Your tier in the Zapier Partner Program is determined by your integration's number of active users and health score.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/ea9bb3a0229584725b56c474c1f7bf0e.png" />
</Frame>

Your tier in the Partner Program is evaluated on a fixed quarterly basis: at the beginning of January, April, July, and October. Your health score is calculated daily in response to your engagement with open bug reports and feature requests.

If at the beginning of a quarter you don't meet all requirements of a tier, you will move down to the next tier that you do qualify for.

Zapier won't externally share your tier in the Partner Program with anyone, including other partners and Zapier users. But if you'd like to, you're welcome to share your tier in the program with your users.

### Active users

The active users metric looks at how many people are actively using your Zapier integration this quarter. Since this number accumulates over the quarter, it is normal to see a drop at the beginning of each quarter compared to the end of last quarter.

To increase usage of your Zapier integration, try out [these 10 tactics](/platform/publish/partner-faq).

### Integration health score

The integration health score is calculated by looking at:

*   how many open feature requests have been reported for your integration
*   how many open bug reports have been reported for your integration
*   how responsive your team is to the open issues

Some feature requests and bugs impact your score more than others. For example, a feature request with 100 users voting for it will impact your health score more than a feature request with 1 vote.

Our goal for all integrations is for them to maintain a “healthy” health score. No tier of the Partner Program requires an “exceptional” health score.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/1e71ec2a31c0cdecf7880f4ac1f4915d.gif" />
</Frame>

To improve your health score, respond to and resolve your outstanding [feature requests and bugs](/platform/manage/user-feedback).

## View Partner Program metrics

You can view Partner Program metrics in two ways:

*   **Partner newsletter**: Opt in to our [partner newsletter](https://developer.zapier.com/partner-settings/email), and we'll email you monthly updates.
*   **Partner Program dashboard**: Log into the [developer platform](https://developer.zapier.com/) with your developer's credentials. In the left side bar, click **Partner Program** to view your metrics.

## How to monitor integration insights

As you launch and maintain your integration, monitor and review the insights in the dashboard on a regular cadence. Insights include data on the integration's growth and usage, such as monthly active users, retention rates, and Zap usage by triggers and actions. You can [see all the metrics tracked](/platform/manage/integration-insights) in this table, or access them for any integration you are an Admin or Collaborator on from the “Dashboard” tab of the developer platform.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d7a53ee12f8fb94a44edbc0f8e3195ea.png" />
</Frame>

You can filter most growth metrics by the last X number of days to identify trends and changes in user activity in correlation to marketing initiatives, integration changes, and product updates like [embedding Zapier](https://platform.zapier.com/embed/full-zapier-experience). Usage details for each trigger and action will show which functionalities are the most popular and being effectively utilized. You can easily and safely share access to the insights by [inviting collaborators](/platform/manage/add-team) to the integration without giving them permissions to make changes to it.

If you have any questions about the Partner Program, reach out to the Partner Support team via the [contact form](https://developer.zapier.com/contact).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Build your first public integration on Zapier

This guide gives an overview of the process to publishing a public integration.

## Benefits to building a public integration

*   Building a public integration on the Zapier Platform is free.
*   There are no fees for publishing an integration.
*   Users of your integration are responsible for their own Zapier plans and billing.
*   Partners do not incur fees as a result of the usage of their integration.
*   You'll get support from [Zapier's Partner Program](/platform/publish/partner-program) once your app has been successfully reviewed.

To see how your integration will show up on Zapier's website, you can browse the [app directory](https://zapier.com/apps). It's a great idea to explore similar apps to see what triggers and actions they provide for building Zapier workflows, called Zaps.

## 1. Before you start

Consider workflows your app will support, and what types of activities your users want to automate. Learn [how Zapier works](/platform/quickstart/how-zapier-works) and set up a few Zaps to get a sense of the user experience.

To design useful triggers and actions for your integration, consider how your users might need data from your app to run other parts of their business. Some of the most popular use cases for Zapier integrations include:

*   Sending follow-up emails or messages
*   Copying data from a bill or invoice into another system
*   Updating contact records in multiple databases
*   Creating or updating records in a project management tool

Learn more about [recommended integration features](/platform/quickstart/must-have-triggers-and-actions) by app category and how to design [popular app type integrations](/platform/quickstart/must-have-triggers-and-actions).

## 2. Build your integration

Building a Zapier integration means identifying the relevant APIs for your triggers and actions, and designing an intuitive experience for your users to select and map the data they need.

There are two ways to build an integration on Zapier's Platform:

*   The Platform UI lets you create a Zapier integration in your browser without code using API endpoint URLs. You can set any custom options your API may need, including custom URL params, HTTP headers, and request body items.
*   Zapier Platform CLI lets you build a Zapier integration in your local development environment, collaborate with version control and CI tools, and push new versions of your integration from the command line.

Both of these tools run on the same Zapier platform, so choose the one that fits your workflow the best.

Learn more about the difference between building with the [Platform UI and the CLI](/platform/quickstart/ui-vs-cli).

## 3. Test your integration

While you're building your integration, you can test your API requests within the Integration Builder. For developers building on Zapier Platform CLI, you can write unit tests that run locally, in a CI tool like [Travis](https://travis-ci.com/).

To get a sense of the user experience, it's recommended to test your integration within the Zap editor. [Create a new Zap](https://help.zapier.com/hc/en-us/articles/8496309697421) that uses your integration's triggers or actions to ensure they all work as expected. After you're done building, invite users to try your integration before making it available to a wider audience.

Learn more about testing your integration:

*   [Testing using Zapier Platform UI](/platform/build/test-auth)
*   [Testing using Zapier Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#testing)

## 4. Submit your integration for app review

After you've confirmed your integration is working as expected, you're almost ready to publish your app. To publish your integration, you need to submit your app for review by Zapier.

Before submitting your integration, review [Zapier's integration publishing requirements](/platform/publish/integration-publishing-requirements) or ask the [PublishBot](https://publishbot.zapier.app/) for a smoother app review process.

To submit your integration for app review:

1.  [Log into the Platform UI](https://zapier.com/app/developer)
2.  Select your **integration**.
3.  In *Integration Home*, click **Publish.**
4.  You'll need to complete the online form.
5.  Click **Submit for Review**.

After you've submitted your integration for review, one of our developers will reach out to you in 1 week or less with any oustanding requirements. In the meantime, Zapier will update your:

*   App version to **Pending**.
*   App status will remain as **Private**.

## 5. Beta phase

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/ac77593e876f7283613c91f4f7408666.png" />
</Frame>

When your integration is approved, Zapier will update your:

*   App status to **Beta**.
*   Integration will be added to [Zapier's app directory](https://zapier.com/apps) with a Beta tag.

Your integration will remain in beta for 90 days. Whilst in public beta, we recommend completing these tasks to improve your integration:

### Add help docs to Zapier's Help Center

The Zapier team provide frontline support for your integration, and in order to provide the best experience for your users, we also host help documentation about your integration in the [Zapier Help Center](https://help.zapier.com/hc/en-us). Help our Technical Writing team create accurate documentation by filling out [this form](https://eu.jotform.com/form/202233475923352). We can also include link out to documentation on your site to help customers find the information they are looking for.

Once submitted, it takes roughly 2 weeks to get help docs published in the Help Center.

> **Tip:** As you maintain your integration, make sure to use the same form to submit updates at any time to the Technical Writing team.

### Create Zap templates

As soon as your app is published and in beta you can begin creating and sharing [Zap Templates](/platform/publish/zap-templates). Zap Templates are readymade integrations or Zaps with the apps and core fields pre-selected. In a few clicks, they help people discover a use case, connect apps, and turn on the Zap.

Learn more about creating [Zap templates](/platform/publish/zap-templates).

### Embed Zapier in your product or website

The best way to ensure users are able to discover your Zapier integration is to surface your integration where users are looking at it. By embedding Zapier in your product, you can create end-to-end user experience, helping your customers discover available integrations within your product without having to leave your app.

Learn more about [embedding Zapier](https://platform.zapier.com/embed/embed-benefits).

### Grow active usage

During the beta stage of your integration, it's important to actively work on growing the usage of your app. By encouraging more people to use your integration, you'll be able to gather valuable insights and feedback early on, which will help you optimize it further.

Learn more strategy [tips for a successful integration](/platform/publish/partner-faq).

### Manage bugs and feature requests

With the Zapier Issue Manager, you have the ability to conveniently address bugs and new feature requests within your preferred issue-tracking tool. Regularly reviewing and taking action on these items will greatly contribute to enhancing the overall health score of your integration.

Learn more about [Zapier Issue Manager](/platform/manage/user-feedback).

### Exit Beta early by embedding your integration

When one signup for Zapier is detected from your implementation of a [Zapier embed tool](https://platform.zapier.com/embed/embed-benefits), you'll exit Beta the next business day and unlock Partner Program benefits. The simplest option is to feature your Zap templates in a launch announcement. Additionally, you can present a dynamic gallery of the 6,000+ applications that are now compatible with your app for creating workflows.

## 6. Public phase

After 90 days in public beta, your integration will become public:

*   Your app status will be updated to public, and the beta tag will be removed.
*   You're added to our [Partner Program](https://zapier.com/developer-platform/partner-program), where you can earn marketing and support benefits.

Learn more about [maintaining your Zapier integration](/platform/manage/user-feedback).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Create help documentation for your users

The Zapier team provide frontline support for your integration, and in order to provide the best experience for your users, help documentation about using your integration in Zaps is hosted in the [Zapier Help Center](https://help.zapier.com/hc/en-us).

## Zapier-hosted help documentation

Ensure the Zapier Technical Writing team can create accurate documentation by filling out [this form](https://eu.jotform.com/form/202233475923352). Once submitted, it takes roughly 2 weeks to get help docs published in the Help Center. Optionally include links out to documentation on your app's site to help customers find the information they are looking for.

As you maintain and add functionality to your integration, use the same form to submit updates at any time to the Technical Writing team. When adding Zapier logos on any help documentation or pages in your app, access the up-to-date branding assets [here](https://brand.zapier.com/).

## Self-hosted help documentation

Step-up your documentation game. Implementing these essential strategies and tips can ensure that your audience gets the assistance they need, seamlessly.

### Speak your user's language

*   Identify your integration's target audience and tailor your help docs to their level of technical expertise
*   Consider the common questions, pain points, and use cases your users might have

### Organize content effectively

*   Structure your help docs in a logical manner, with categories, subcategories, and search functionality for easy navigation
*   Use descriptive headings and bullet points to break down complex topics into bite-sized pieces
*   Create a table of contents for quick reference

[Check Buffer's simple yet effective way of breaking down topics.](https://support.buffer.com/article/814-buffer-api-support-zapier-pinterest-and-whats-possible-for-users)

### Keep help docs clear and concise

*   Use plain language and avoid technical jargon whenever possible
*   Break down complex concepts into simple steps and provide examples or screenshots to illustrate key points
*   Write in an active voice to keep the content engaging and easy to understand

### Leverage your user Community

*   Encourage community engagement from users who have successfully resolved similar issues
*   Community-contributed answers help leverage social proof. When users see that their peers have found solutions helpful, this adds authenticity and credibility to your help docs
*   Highlighting real user experiences demonstrates that your documentation isn't just theoretical— bolstering users' confidence in its effectiveness.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/7fe23e47e3167561307b76c49831c07d.png" />
</Frame>

### Anticipate user needs

*   Include troubleshooting guides, FAQs, and best practices to address common issues and optimize user experience
*   Link related articles together to provide a seamless learning experience

### Offer multiple formats

*   Formats such as text-based articles, video tutorials, and interactive demos will help cater to different learning preferences and experiences
*   Ensure that all formats are easily accessible and mobile-friendly for users on the go

[See how Notion offers a variety of tutorials and documentation in different formats.](https://www.notion.so/help)

### Empower user feedback

*   Allow users to rate the helpfulness of your articles and provide comments or suggestions for improvement
*   Monitor user feedback regularly and use it to prioritize updates and improvements to your help docs (and integration)

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6da7b22dad9f82b3543f3cd8f9e7d5dc.png" />
</Frame>

### Integrate help docs with support channels

*   Integrate your help docs with your customer support channels such as live chat or ticketing systems to provide seamless assistance to users
*   Optionally, link out your documentation to our [Zapier-hosted help docs](/platform/publish/user-help) to create a more robust experience

### Measure and analyze usage

*   Use analytics tools to track user engagement with your help docs, including page views, search queries, and time spent on each article
*   Use this data to identify areas for improvement and optimize your help docs for a better user experience

### Embed Zapier within your help docs

*   Give users the ability to easily and securely discover what they can accomplish with your integration through one of our [embed tools](https://zapier.com/partner/solutions/plug-and-play)
*   Partners often publish articles detailing workflows with a Zap template for that specific workflow embedded using our [Zap template element](https://platform.zapier.com/embed/zap-templates)
*   [Learn more about other embed benefits](https://platform.zapier.com/embed/embed-benefits)

[See how Taskade has embedded Zap templates using our Zap template element to help users discover automation inpsiration.](https://www.taskade.com/blog/taskade-zapier-integrations/)

Remember to regularly review and refine your content to ensure it remains accurate, relevant, and helpful to your users.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Zap templates

Zapier empowers apps to do together what they can't on their own. With a bit of inspiration and creativity, your users can pull dozens of apps together into unique workflows to get more done with your app in far less time.

**Note:** Only public integrations can be used in Zap templates. Zap templates do not currently support private integrations.

Zap templates are ready made Zaps with the apps and core fields pre-selected, for publicly available Zapier integrations. In a few clicks, they help people discover a use case, connect apps, and turn on the Zap. Zap templates are the fastest way for your users to automate workflows.

<CardGroup cols={2}>
  <Card title="Save new Gmain attachments to Google Drive" href="https://zapier.com/webintent/create-zap?template=192&utm_source=partner&utm_medium=embed&utm_campaign=wfe_custom&provider=&entry-point-location=partner_embed&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates">
    <Icon icon="google-drive" size={21} /> Google Drive <br /><Icon icon="envelope-circle-check" size={21} /> Gmail<br /> <Icon icon="filter" size={21} /> Filter by Zapier

    <br />

    [**Use this workflow**](https://zapier.com/webintent/create-zap?template=192\&utm_source=partner\&utm_medium=embed\&utm_campaign=wfe_custom\&provider=\&entry-point-location=partner_embed\&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates\&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates)
  </Card>

  <Card title="Collect new Typeform responses as rows on Google Sheets" href="https://zapier.com/webintent/create-zap?template=111&utm_source=partner&utm_medium=embed&utm_campaign=wfe_custom&provider=&entry-point-location=partner_embed&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates">
    <Icon icon="video" size={21} /> Typeform <br /><Icon icon="sheet-plastic" size={21} /> Google Sheets

    <br />

    [**Use this workflow**](https://zapier.com/webintent/create-zap?template=111\&utm_source=partner\&utm_medium=embed\&utm_campaign=wfe_custom\&provider=\&entry-point-location=partner_embed\&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates\&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates)
  </Card>

  <Card title="Share your new Instagram posts to your Facebook page" href="https://zapier.com/webintent/create-zap?template=162&utm_source=partner&utm_medium=embed&utm_campaign=wfe_custom&provider=&entry-point-location=partner_embed&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates">
    <Icon icon="instagram" size={21} /> Instagram <br /><Icon icon="facebook" size={21} /> Facebook

    <br />

    [**Use this workflow**](https://zapier.com/webintent/create-zap?template=162\&utm_source=partner\&utm_medium=embed\&utm_campaign=wfe_custom\&provider=\&entry-point-location=partner_embed\&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates\&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates)
  </Card>

  <Card title="Create new Trello cards from new Google Calendar events" href="https://zapier.com/webintent/create-zap?template=1495&utm_source=partner&utm_medium=embed&utm_campaign=wfe_custom&provider=&entry-point-location=partner_embed&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates">
    <Icon icon="calendar-days" size={21} /> Google Calendar <br /><Icon icon="trello" size={21} /> Trello

    <br />

    [**Use this workflow**](https://zapier.com/webintent/create-zap?template=1495\&utm_source=partner\&utm_medium=embed\&utm_campaign=wfe_custom\&provider=\&entry-point-location=partner_embed\&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates\&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates)
  </Card>

  <Card title="Get Slack notifications for new Tuypeform entries" href="https://zapier.com/webintent/create-zap?template=883&utm_source=partner&utm_medium=embed&utm_campaign=wfe_custom&provider=&entry-point-location=partner_embed&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates">
    <Icon icon="video" size={21} /> Typeform <br /><Icon icon="slack" size={21} /> Slack

    <br />

    [**Use this workflow**](https://zapier.com/webintent/create-zap?template=883\&utm_source=partner\&utm_medium=embed\&utm_campaign=wfe_custom\&provider=\&entry-point-location=partner_embed\&referer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates\&referrer=https%3A%2F%2Fplatform.zapier.com%2Fpublish%2Fzap-templates%3Futm_source%3Dpartner%26utm_medium%3Dembed%26utm_campaign%3Dwfe_custom%26referer%3Dhttps%253A%252F%252Fplatform.zapier.com%252Fpublish%252Fzap-templates)
  </Card>
</CardGroup>

They're also a great way to promote your app, as your Zap templates are featured in:

*   Zapier's app directory

*   Zapier's onboarding experience

*   In many of Zapier's 7,000+ integration partners' apps and sites

Zap templates can also be featured inside your site and content to help your users start using Zapier integrations. The more Zap templates you create and the more users they have, the more likely they are to be featured. This helps your Zapier integration gain popularity and rise the ranks of the [Zapier Partner Program](https://zapier.com/developer-platform/partner-program). To learn more about embedding Zap templates (and other experiences) into your app or website, see our dedicated [embed](https://platform.zapier.com/embed/overview) section.

> Expected time to create a Zap Template: 10 minutes.

<Frame caption="An example Zap Template that automatically saves Gmail attachments to Google Drive — a handy way to speed up email file management.">
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/0d6ffaf492b547878ef8c06924a714f1.gif" />
</Frame>

## How to build a Zap template

Zap templates take a few steps to build, similar to any other Zap. You first select the app and trigger you want to start the Zap, then add an action app and map the fields from the trigger app to the action — and optionally add additional steps. Then, add a title and description to help people quickly understand when to use your Zap.

To get started, go to Zapier's [Zap template creator](https://zapier.com/app/editor/zap-template/) or from the [Zap Template dashboard](https://developer.zapier.com/zap-templates), click *Create Zap Template*.

### 1. Add a Trigger Step

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/64e407ae2b8e5f3ecaf9bedbbacd11d4.png" />
</Frame>

Select the trigger app by searching for the app by name from the menu.

> **Note:** You can use Zapier integrations that have been launched publicly.

Choose the app's trigger from the dropdown that starts your Zap.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/bcf87bac5267b210d520cb97ac0e5612.png" />
</Frame>

You won't be able to auth an account when creating Zap templates. Instead, the Zap template creator will load the sample data available for the selected trigger. If there are fields you want to use that aren't available, it means the integration developer did not add those fields to the integration's sample data. You can still use the trigger event in a Zap template, though when setting up the Zap's action step(s), you might not be able to pre-map fields for your users. If the trigger includes options, fill in any fields or leave the defaults to let users add info to the Zap themselves. On the `Test trigger` pane, you can view the sample data available. Click *Continue* to move to the next step.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d90a46d02a04c72341c2534c3c77e353.png" />
</Frame>

### 2. Add an Action Step

Select the action app by searching for the app by name from the menu.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/ff77e3d0297f9a61bad2b6250472a059.png" />
</Frame>

> **Note:** You can use Zapier integrations that have been launched publicly.

Choose the app's action from the dropdown that you want to follow the trigger. Same as the trigger, the Zap template creator will load the sample data available for the selected action.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/667f2e42d0ad4b367f2b86fd1e00398b.png" />
</Frame>

> **Note**: If you use a search action, you will need to add a second action step to use the data from the trigger and search steps.

Now for the most crucial part of your Zap template: Map the input fields from the trigger app to the form fields in this action app. The action step will show the fields that are statically available. Dynamic fields that populate based on the input in another field will not be available. For the best Zap template experience, fill in as many form fields as possible to help users set up Zaps quickly.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/42933f0f6a425d029429dbe918b0a107.png" />
</Frame>

> **Note**: In addition to not showing dynamic fields, custom fields from apps (added by users) will not be available. This is because the Zap template creator is not authenticated with an app account to fetch that data. When users create a Zap from your template, they'll be able to leverage dynamic and custom fields.

For most form fields, you'll need to add input fields from the trigger to the appropriate form field. Click into the form field and then `Show all options` button to see every input field from previous steps. Select the input field that fits the action field best. For example, you might select Gmail's *Attachment* field to upload the attachment via Google Drive's *File* form field. Or you might add Stripe's *Email* field to Mailchimp's *Subscriber Email* form field to add customers to an email list.

> **Tip**: Use [Zapier's date and time syntax](https://help.zapier.com/hc/en-us/articles/8496275717261-Insert-the-time-your-Zap-runs-into-a-field) to modify dates and times in action form fields.

Most dropdown menus are used to select folders, projects, and other user-generated data and should be left blank by default. You can, however, select options for dropdowns for boolean yes/no fields if you're certain which option is best for all users of this Zap template.

Zap Template action forms include *required* and *optional* fields. When users set up your Zap Template, they will see the required fields by default, and *must* fill them in before turning on the Zap. Try to map as many required input fields as possible. Then, less-critical fields will often be marked as optional, and are hidden by default when users set up the Zap Template. If you know the best data to map to those fields, add them to make sure your users' Zaps include as much detail as possible.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e4222e2c4934485dd27454ef50f17ec0.png" />
</Frame>

> **Note**: Do not enter plain text into an action form field unless *every* user of this Zap Template would want the text included in the action.

Zapier then shows a `Test action` pane, similar to what users see when setting up Zaps. You won't be able to test your action steps when building a Zap template. The `Test action` pane will default to showing a successful test.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6e87adbfde77b00ae144b487c4185c20.png" />
</Frame>

### 3. (Optional) Add filters or additional action steps

Choose to add another step to your Zap, or finish and save this Zap template with only two steps.

Most Zap templates only need two steps, with a trigger to watch for data from an app and an action to do something with that data in another app. Sometimes, though, you need more steps for advanced workflows, including:

*   [Additional create actions](https://help.zapier.com/hc/en-us/articles/8496257774221-Set-up-your-Zap-action) to add additional automations to your workflow

*   [Filters](https://help.zapier.com/hc/en-us/articles/8496276332557) to watch for specific items from trigger or action step(s)

*   [Search actions](https://help.zapier.com/hc/en-us/articles/8496241402253) to find specific data from apps, recommended especially to find customers, tickets, projects, and more before creating new items with Zaps

> **Note**: You cannot build Paths in Zap templates at this time.

To add another search or create action to your Zap, click the *+* button where you want to insert the additional step. Then repeat step 2 and set up the additional action. To add a *delay* action, select the *Delay by Zapier* app when adding a new action.

> **Note**: Code, custom webhook, Looping and custom Formatter steps are not allowed in Zap Templates.

You might want to add a filter to your Zap template. For example, you could add it between the trigger and action step to have the Zap only run when specific data is received. [Learn more about filters](https://help.zapier.com/hc/en-us/articles/8496276332557-Add-conditions-to-Zaps-with-filters). You can define a filter in the Zap template, or leave it blank and let users customize the filter to their needs.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/23309a53e37648bd8547e0a7ec0c993e.png" />
</Frame>

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/db5c3975470b1b2ba7be62b0ceb244ac.png" />
</Frame>

> **Note**: Most Zap templates don't need filters, and most filters should be added by users later if required. However, including a filter in a Zap template can be useful if your Zap Template is only useful with a filter to remove extraneous data.

### 4. Add a title and description

The final step is to add a title and description, then submit your Zap template for review.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6f02ec2e5eac6cc7130ecfafb03a64dc.png" />
</Frame>

Zap templates need to showcase a use case and describe it effectively. They provide inspiration for how to automate popular apps and workflows. Your Zap template's description and title help illustrate that idea to users.

The title is the first thing people see. Zap templates embedded inside your app, blog posts and other content show the Zap's title and app icons. Titles need to fit in well in each environment and describe the use case at a glance.

Descriptions are what users see when they click the Zap template. They explain the use case in further detail and tell how the Zap works. The title grabs interest; the description gets people to invest the minute or three it takes to turn on the Zap.

#### How to write a Zap template title

Zap template titles clearly and briefly state the apps the Zap connects and the workflow it accomplishes. They include the trigger and action apps and the actions they perform. They use present tense, active voice, and sentence case.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/fc6854d9f356bca06d8879a50ef41036.png" />
</Frame>

Most Zap template titles read something like this: `Add new Gmail emails to Google Sheets as rows`. The title mentions what happens in the trigger app followed by what Zapier does in the action app. Some examples of good titles:

*   `Create Trello cards for new Wufoo form entries`

*   `Get Slack notifications for new Google Drive files in a folder`

*   `Subscribe new Gumroad customers to a MailChimp list`

*   `Save new liked SoundCloud tracks to Google Drive`

> **Tip**: Use your discretion whether to mention the action or trigger app first. The trigger app works best first in most cases, but sometimes it sounds awkward — if so, go with the action app first.

Follow these rules in your Zap template titles:

*   **Start with an appropriate verb**. Zap templates start with an action verb that describes what the Zap does in the action app, such as `Create`, `Add`, `Make`, `Insert`, `Update`, `Subscribe`, or `Get`. Use unique verbs when possible, and use the most appropriate verb for the action Zapier performs with the app. For example:

    *   Only use `Send` with messages, including emails and text messages

    *   Only use `Post` with social network or chat apps

    *   Only use `Log` or `Archive` with spreadsheet, database, or note-taking apps

*   **Use sentence case**. Capitalize the first letter in the title and app names when appropriate.

    *   **Right:** `Create Trello cards for new Wufoo form entries`

    *   **Wrong:** `Create Trello Cards For New Wufoo Form Entries`

*   **Use present tense and active voice**. Zaps automatically run for every new item in an app. The title should reflect that with active present tense.

    *   **Right:** `Get Slack notifications for new Google Drive files`

    *   **Wrong:** `Slack will notify you when a new Google Drive file is saved`

*   **Always mention *new* before trigger apps**. Zaps only run when new items are created in apps, and cannot take action for existing items. Emphasize that by including `new` in your title before the action app name.

*   **Make app items plural**. Zaps run on every new item in your trigger app, and will always create the related item in your action app(s). Always make the trigger and action items plural to emphasize that, such as `Google Sheets rows` or `Mailchimp subscribers`.

*   **Respect app name styles**. Make sure to use the same capitalization and spelling that the apps in your Zap use in their branding; double-check the app's site or [Zapier's integration list](https://zapier.com/apps).

*   **Never use `sync` or `automatic` in titles**. All Zaps run automatically, only work on new data, and can't sync data multiple ways. Avoid these terms in titles; sync is misleading, and automatic is redundant.

*   **Don't use personal pronouns**. Never use `you`, `we`, or `I` in Zap Template titles unless the trigger or action items specifically include those words.

#### How to write a Zap template description

Zap template descriptions share more detail about what the Zap does and scenarios in which to use it in two to four sentences. They tell readers what this Zap does for them and how it works.

Start your description with the user's problem or need. Then explain how Zapier meets that need, when the Zap will run, and what the Zap does when it runs. Explain the trigger and action items in plain language that show the workflow's value. Some good examples:

> Find yourself spending too much time adding event attendees to your CRM by hand? Now with the help of Zapier, the tedious work is done for you. This integration will add every new Eventbrite attendee to Zoho CRM as a new contact, saving you time for more important work.

> After someone fills out a form on your site, you'll want to hear about it or send them a follow-up email. This Zapier automation handles both gracefully, sending an email via Gmail to you or the form respondent whenever you get a new Typeform entry. You'll never have to send the same message over and over again.

> Expedient order processing makes for happy customers. Make sure you act on every new delivery with this Zapier automation. It will capture every new order placed on your Shopify store after being set up, creating a delivery task for it on Onfleet so your team can fulfill it without delay.

Keep these guidelines in mind:

*   **Use present tense and active voice** as in the Zap title.

*   **1 paragraph, 2-4 sentences** is enough for most Zap Template descriptions.

*   **Don't use Zapier-specific terminology** including Zap, Zap template, trigger, action, or terms that Zapier doesn't support, such as sync. Instead, use generic words, such as `integration` or `automation`, that are universally understood.

*   **Use same terms as the integrations themselves**. If an app calls the results of a form an “entry” don't call it a “submission,” or if an email app uses “tags,” don't refer to “folders.”

*   **Don't include links**. Do not include links of any kind, whether Zapier-owned or third-party owned.

*   **Include tips at the end**. If your Zap Template requires extra setup for filters or other steps, or if it doesn't cover all use cases users may expect, include a sentence at the end to clarify. Start the tip with `Note:`, and format the entire note in italics with [Markdown](https://zapier.com/blog/beginner-ultimate-guide-markdown/) formatting. For example: `*Note: Add the tag you want to the Zap's Filter step.*`

> **Note**: Always write unique descriptions for each Zap template — Zap templates that use the same descriptions but only replace app names, or those that duplicate the Zap template title, will be rejected.

***

Once you've added your Zap template title and description, click `Save Draft` under “Finish Up Your Zap Template”.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/fb9a10e41c4ba4d794dc758e6c970f7f.png" />
</Frame>

Now try using the Zap template to make sure it works as expected. Open your [Zap Template dashboard](https://zapier.com/zap-templates/), click the gear icon beside the Zap template you built, and select `Copy Link`. Open that link in a new tab or window, set up and turn on the Zap, and verify everything works correctly.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/c06a94f004e20748235d5de67efa2960.png" />
</Frame>

### 5. Submit Zap template for review

Finally, when your Zap template is ready for public release, click `Submit for Review` under “Finish Up Your Zap Template”. This will submit the Zap template to our team to review to ensure it works as expected and meets our standards.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/09d633673809c700778cfa945b63d4ba.png" />
</Frame>

We'll send you an automated email with the subject line *We've reviewed your Zap templates!*, typically within 2 weeks, after reviewing your Zap template(s). If your Zap template(s) passes the review, we will mark them as public to have them appear on your app's directory page and in our embed solutions.

If your Zap template is rejected, the automated email will provide a justification what's required to amend the template before re-submitting for review.

Then make some more Zap templates. The more you make, the easier it will be for users to start using your Zapier integration.

## Manual field mapping (custom pills)

When creating a Zap template, you typically map data between steps by selecting from a list of fields (or “pills”) that have been added as sample data by the integration developer. Field mapping is what allows data to carry from one step to another. Custom pills take this a step further by letting you map fields that you know are returned by an integration, but haven't been added as sample data. For example, if you wanted to map the field `eventType` between the trigger and step 2 of your Zap template, but `eventType` isn't available in the trigger's sample data, you could create a custom pill to pull this field dynamically. Here's how to create custom pills.

**Step 1: Identify the step position**

*   The trigger is in position 1, with subsequent steps numbered sequentially

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/ad95202cb4726f214d234f0711e6c1fe.png" />
</Frame>

**Step 2: Determine the field name**

*   Because the sample data doesn't include the field you want, we suggest setting up a live Zap replicating the Zap template you want to create

*   After publishing your Zap, let the Zap run live and then review its Zap history

*   From the Zap history, you can identify the exact name of the field you want to pull data from

*   In this example, we can see Google Calendar > New Event returns the field `eventType`

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d0025c224ba67f6470ef9e2629dce890.png" />
</Frame>

**Step 3: Combine step position and field name**

*   Once you have both the step position and field name, you can combine them to create a custom pill

*   Start with 2 open curly braces `{{`

*   Add the step position folllowed by 2 underscores `__`

*   Next, add the field name

*   Finally, finish with 2 closed curly braces `}}`

Example: the `eventType` field isn't returned in the sample data for Google Calendar > New Event. If you want to map that field in a Zap template, you can do so using the custom pill `{{1__eventType}}`.

**Step 4: Insert custom pill**

*   You can insert the custom pill into any field in any step that comes after the field that returns the data

**Additional Notes**

*   Custom pills work in both Zap templates and regular Zaps. [Learn more about creating custom pills in Zaps](https://community.zapier.com/featured-articles-65/how-to-manually-map-fields-that-do-not-appear-in-the-sample-data-aka-custom-pill-mapping-9738).

*   Custom pills will only work when a live Zap run returns the field used in the custom pill. If the field isn't returned during a live Zap run, fields where the custom pill has been mapped will remain empty

*   The methodology described above only works for top-level fields. If a field is nested under another field (or multiple fields), each nest would be separated by double underscores.

*   For example, if the trigger returned the following and you wanted to retrieve the response for question 3, your custom pill would look like this: `{{1__questions__3}}`

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a64282d282a776319c77cc73f7aa504f.png" />
</Frame>

By following these steps, you should be able to effectively add custom pills to your Zap Templates.

## Promote your Zap templates

It's not enough to turn your ideal workflows into Zap templates. You need to get them in front of your end-users. Zapier automatically promotes your Zap templates in our SEO, app directory, and on partner sites using our embed tools (where both your and their apps are used in the template). You can promote Zap templates further by embedding them into your site (user dashboards, blog posts, help articles) using our [Zap template element](https://platform.zapier.com/embed/zap-templates).

### Zapier app directory

Your published Zap templates will appear on your directory page. Zap templates are loosely ordered by popularity, helping users easily discover common use-cases. Users can also search for Zap templates that connect your app with another.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/f56ac65d2c36314bae02f21b00f983e4.png" />
</Frame>

## Manage your Zap templates

Over time, you'll likely make dozens of Zap templates. You can manage them — in draft, review, or publicly available — from your [Zap templates](https://zapier.com/zap-templates/) dashboard alongside Zapier's Developer Platform tools.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/35809b4d266dcfb55f1190cd8224ff1d.png" />
</Frame>

Filter through your Zap templates by status on the left sidebar, click a Zap template to edit it, or select the gear icon on the right of a Zap template to copy its public link, test it, or delete it.

If you have any Zap templates in your *Rejected* list, edit them to fix the issues then re-submit them. You cannot edit public Zap templates, but if you notice something that you need to change in your existing Zap Templates, please [submit our contact form](https://developer.zapier.com/contact) with the Zap template ID. We can set the Zap as *Draft* again so you can edit and re-submit it for review with any changes.

### Promoting new versions of your integration

When promoting a new version of your integration, all Zap templates using the integration and having no breaking changes with the existing version will be updated to use the promoted version. The following are considered breaking changes:

*   A trigger/action is hidden or deleted

*   A trigger/action key is changed

*   A trigger/action input field key is changed or removed

*   A trigger/action output field key is changed or removed

If breaking changes exist between the previous and newly promoted integration versions, existing Zap templates will not be automatically updated and will continue to use the previous version. This can result in an older version acquiring new users over time. Consider the user impacts of [changes made in new versions](/platform/manage/planning-changes).

### Deprecating versions of your integration

When deprecating a version of your integration, any Zap templates using the integration will automatically be marked as *Invalid* within 24 hours of the deprecation.

While invalid, the Zap template will not be *Public* until it is adjusted to use a non-deprecated version, re-submitted for review and approved. This ensures users won't use deprecated and broken Zap templates to make Zaps. If you're having trouble accessing *Invalid* templates, please [submit a ticket.](https://developer.zapier.com/contact)

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="Why do I see Zap templates on my app's directory page that don't appear on my &#x22;Zap Templates&#x22; page in the developer platform?">
    Your integration's directory page will show all published Zap templates that use your integration. The developer platform will only show Zap templates created by the currently logged in user.
  </Accordion>

  <Accordion title="Who can publish Zap templates using my integration?">
    Any user on Zapier is able to create and publish Zap templates using any integration that is in beta or public status. Zapier also identifies real-world usage and automatically generates Zap templates to help users discover use cases, enhance integration usage, and create more engaged users on both platforms. All Zap templates are subject to the same review process to ensure our quality standards are met.
  </Accordion>

  <Accordion title="Can I opt out of Zapier-generated Zap templates for my integration?">
    Currently, we do not offer the option for integrations to opt out of Zapier-generated Zap templates. These templates are designed based on real-world usage to enhance the end-user experience by highlighting commonly implemented workflows. They also aim to boost adoption for both Zapier and our integration partners.
  </Accordion>

  <Accordion title="Can we remove or hide Zap templates that include our competitors from our directory page?">
    Zap templates, once published on your integration's directory page, remain in place to ensure our ecosystem is open, inclusive, and offers a wide range of options to users. This includes showcasing integrations that might combine your services with those of competitors. While direct removal of these templates isn't aligned with our approach, we offer a proactive solution. By leveraging our [Zap Template embed tool](https://platform.zapier.com/embed/zap-templates), you have the power to highlight specific Zap templates you prefer, directly within your own platform. This empowers you to curate and showcase the integrations most relevant to your users' needs.
  </Accordion>

  <Accordion title="How can I make changes to a published Zap template?">
    Once a Zap template is published, you are unable to self-serve changes to it. To request changes, please submit our [contact form](https://developer.zapier.com/contact). When submitting the contact form, please ensure you provide the Zap template ID and the change you're requesting per Zap template. The Zap template ID can be found by clicking on the [3-dot icon](/images/227b0c1da48e72115ced60cdb8c06a97.png) in the developer platform, or [in the URL](/images/c913c363b267fd1c4c33b5395e092460.png) when viewing a Zap template landing page. Please note the following when requesting changes:

    *   Zap templates should work for the broadest audience. Try to avoid requesting changes that will exclude an audience segment from successfully using a Zap template

    *   Every partner on Zapier has their own style and tone. We will not update titles or descriptions of Zap templates created by other partners to better accommodate your preferences. While a title and description might not match your tone and style, it could very well for the partner that published the Zap template

        *   If you believe you found an error with a title or description (eg the title references the wrong action), we will review and consider making the correction

    *   When requesting a functional change to a Zap template created by another partner, clearly state the Zap templates current behavior and the expected behavior to justify the requested change
  </Accordion>

  <Accordion title="How can I re-order Zap templates on my directory page?">
    Our current sorting system prioritizes Zap templates based on popularity and user engagement, ensuring users who land on your integration's page on Zapier see the most helpful examples of real workflows. The sort order cannot be modified.
  </Accordion>

  <Accordion title="How can I exclude specific Zap templates on my directory page?">
    All published Zap templates that use your integration will appear on your directory page. You are unable to exclude published Zap templates from appearing on that page. Note when embedding Zap templates using the [Zap Template Element](https://platform.zapier.com/embed/zap-templates), you can specify which Zap templates to embed.
  </Accordion>

  <Accordion title="How can I view all Zap templates created by my integration team?">
    You cannot view Zap templates in the developer platform created by other users, even if you're on the same integration team. If required, we can help to transfer ownership of Zap templates created by your integration team members to a singular account. You can [submit our contact form](https://developer.zapier.com/contact) to learn more about this.
  </Accordion>

  <Accordion title="How can I unpublish a Zap template?">
    Zap templates are a key driver of traffic for your integration. Promoting Zap templates is a part of our SEO strategy, and they appear throughout our embed network. For these reasons, we err against unpublishing Zap templates where possible. For Zap templates you own, you can unpublishing them by [deleting them](/images/c0f9036c4b6f166b4c8289034558c201.png). If you wish to unpublish a Zap template without deleting it, you can request this by submitting our [contact form](https://developer.zapier.com/contact). Be sure to include the Zap template ID. When requesting to unpublish a Zap template created by another partner, please justify the request (eg duplicate Zap template, Zap template broken) for our consideration.
  </Accordion>

  <Accordion title="Why were my Zap templates rejected?">
    Zap templates can be rejected due to different reasons. The day after your Zap templates are rejected, you'll receive an email notification that will include feedback as to why a Zap template might have been rejected. Some common reasons for why Zap templates are rejected include:

    *   The title and/or description of the Zap template does not comply with our [style guide's](/platform/publish/zap-templates#4-add-a-title-and-description) uniqueness requirements

    *   Fields in the trigger step have not been correctly mapped to corresponding fields in the action step(s)

    *   The use case for the Zap template is considered too narrow or specific for general use

    *   A Zap template with a similar use case already exists

    *   The Zap template title and/or description was written in a language other than English

    *   The Zap template includes a code, custom webhook, or custom formatter step which is currently not allowed

    *   The Zap template contains hardcoded values such as phone numbers, emails, or IDs which should be dynamically mapped from the trigger or another action step

    *   Submitting a large number of Zap templates for an integration with a low active user count can lead to rejection. We aim to ensure that our review resources are allocated fairly across all partners. When a disproportionate amount of submissions comes from a single source, it negatively impacts the review process for others. To maintain a balanced and efficient review process, we enforce limits based on the size and usage of the app
  </Accordion>

  <Accordion title="Can I select which Zap templates to display in my embed?">
    Yes, but only through our [Zap Templates](https://platform.zapier.com/embed/zap-templates) embed solution. You can choose between Popular Zap templates or Specific Zap templates, to highlight to your users.
  </Accordion>

  <Accordion title="How many users are using a particular Zap Template">
    Although we don't provide specific data around how many users are using a particular Zap template, you can track the number of active Zaps as well as activation rates by Trigger or Action. This information is available within your developer dashboard on the Dashboard page. You can learn more about the insights avilable on the Dashboard page [here](/platform/manage/integration-insights). Zap templates are also loosely ordered by popularity on your integration's directory page.
  </Accordion>

  <Accordion title="How many Zap templates should my integration have?">
    The more the merrier! One Zap template isn't enough—you'll want to make Zap templates for each of your app's most popular use cases. Although there's not a set number, we recommend adding 5 to 10 Zap templates to your integration, for a start. Learn more about Building Zap templates [here](/platform/publish/zap-templates#how-to-build-a-zap-template).
  </Accordion>

  <Accordion title="Why did my Zap Templates stop working?">
    Common reasons your Zap template stopped working include breaking changes, versioning, migration, and authentication. Adding, updating, replacing, or deleting components can have various effects. Learn more about [planning and implementing integration changes](/platform/manage/planning-changes).
  </Accordion>

  <Accordion title="Which use cases should my Zap templates highlight?">
    Your triggers, actions, and searches should focus on the main use cases of your platform. Check similar integrations in [Zapier's App Directory](https://zapier.com/apps) and [recommended features](/platform/quickstart/must-have-triggers-and-actions) by app category for ideas on which items to include in your integration.
  </Accordion>

  <Accordion title="Can I add filters/other conditions to my Zap templates?">
    Yes. Most Zap templates only need a trigger and two action steps. However, we know sometimes you may need more steps for advanced workflows. Learn more [here](/platform/publish/zap-templates#3-optional-add-filters-or-additional-action-steps).
  </Accordion>

  <Accordion title="Do users need to have an account to create their own Zaps?">
    All users requires their own Zapier account in order to create Zaps, even if the Zap originates from a Zap template. The account the Zap is owned by will add and store authentications for each app used in the Zap. To help reduce friction for new users, you should pair your embed solution with our [Quick Account Creation](https://platform.zapier.com/embed/quick-account-creation). This seamless, accelerated sign-up feature allows first-time Zapier users to skip the standard sign-up procedure and onboarding survey.
  </Accordion>
</AccordionGroup>

***

*Need help?* [Tell us about your problem](https://developer.zapier.com/contact) *and we'll connect you with the right resource or contact support.*


# Zapier Partner Sandbox

Zapier Partner Sandbox is a workspace for people in your organization who are on your integration team.

## What is Zapier Partner Sandbox?

It offers qualifying partners several premium features for free. Zapier Partner Sandbox is designed to support the ongoing development of your integration. Features of Zapier Partner Sandbox include:

*   Unlimited Zaps
*   2,000 free tasks per month
    *   Actions for Zapier apps do not count towards task usage
*   5-minute update time for polling Zaps
*   Multi-step Zaps
*   5 Premium Integrations
*   Paths (3 paths wide by 3 paths deep)

Zapier Partner Sandbox does not include premium features for Zapier Tables or Zapier Interfaces.

## Who is eligible for Zapier Partner Sandbox?

Each Admin or Collaborator on a public integration team can request access to Zapier Partner Sandbox.

## How to apply for Zapier Partner Sandbox

Visit the “Manage Team” page in the developer platform for your qualifying integration, and submit the form linked at the top of the page.

## How to access Zapier Partner Sandbox

When your request is approved, you'll continue to log into Zapier using your existing account. Clicking the icon in the top-right corner (1) will allow you to switch between your personal Zapier account (2) and the Zapier Partner Sandbox workspace (3). Your personal account will show the name set on your profile, while the Zapier Partner Sandbox workspace will show a “ZPS” avatar and “App XXX”, where “XXX” is replaced by your app ID.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e68a9ed3f83d64375017a785eaf9e3c2.png" />
</Frame>

Zaps created in the Zapier Partner Sandbox workspace will have access to premium features. Your personal Zapier account will remain unchanged.

## FAQ

<AccordionGroup>
  <Accordion title="Can I use Zapier Partner Sandbox for commercial purposes?">
    Zapier Partner Sandbox is solely to be used for:

    *   Integration Development. The ongoing development of your integration.
    *   Sales Enablement/Demonstrations. Demonstrations of specific illustrative workflows that highlight the capabilities and benefits of your integration with Zapier. These demonstrations can be used as part of sales presentations or marketing materials to your prospective clients.

    If you require a Zapier plan for commercial purposes beyond what's stated above, you're required to upgrade your personal account to one of our [in-market plans](https://zapier.com/pricing). We reserve the right to revoke your access to Zapier Partner Sandbox if we suspect you are using the account for commercial purposes.
  </Accordion>

  <Accordion title="How can I remove my access to Zapier Partner Sandbox?">
    Write into [partners@zapier.com](mailto:partners@zapier.com) and we'll be happy to assist!
  </Accordion>

  <Accordion title="Can I request the value of Zapier Partner Sandbox as a credit towards an in-market plan?">
    Currently, we are not issuing credits instead of access to Zapier Partner Sandbox.
  </Accordion>

  <Accordion title="Can I modify who can access my integration's Zapier Partner Sandbox?">
    Qualifying users can request access by submitting the form linked at the top of the “Manage Team” page in the developer platform for their qualifying integration. You can also manually add other integration team members to your Zapier Partner Sandbox workspace. When manually adding users, they will receive an email requiring them to accept the Zapier Partner Sandbox Terms of Use. If the Terms of Use are not agreed to within 7 days, their access will be removed. If ineligible members are added to your Zapier Partner Sandbox (ie users that are not an Admin or Collaborator for your integration), their access will be removed within 1 business day.
  </Accordion>

  <Accordion title="Can I lose access to Zapier Partner Sandbox?">
    Your access to Zapier Partner Sandbox can be revoked under the following circumstances:

    *   If your integration reverts to private
    *   If you are removed from the integration team
    *   If we suspect Zapier Partner Sandbox is being used for commercial purposes, we reserve the right to revoke your access to Zapier Partner Sandbox

    Zapier reserves the right to modify, suspend, or discontinue this benefit at any time, with or without notice to you
  </Accordion>

  <Accordion title="Can I request access to Zapier Partner Sandbox before making my integration public?">
    No, Zapier Partner Sandbox is a benefit exclusively for partners with a public integration.
  </Accordion>

  <Accordion title="Who owns Zapier Partner Sandbox?">
    Zapier Partner Sandbox workspaces are owned and maintained by Zapier. These workspaces are created on a per-integration basis.
  </Accordion>

  <Accordion title="Can I move Zaps between my personal account and Zapier Partner Sandbox?">
    Yes. [Learn more about moving Zaps between accounts](https://help.zapier.com/hc/en-us/articles/8496275081357-Move-Zaps-between-accounts).
  </Accordion>

  <Accordion title="Can Zaps I created in Zapier Partner Sandbox be transferred to my personal account after I lose access to Zapier Partner Sandbox?">
    No, once your access to Zapier Partner Sandbox is removed, transferring Zaps to your personal account is impossible.
  </Accordion>

  <Accordion title="I am an Administrator or Collaborator for multiple public integrations. Can I request access to Zapier Partner Sandbox for each integration?">
    Yes, you can request access to Zapier Partner Sandbox for each qualifying integration. Visit the “Manage Team” page in the developer platform for each of your qualifying integrations, and submit the form linked at the top of the page.
  </Accordion>

  <Accordion title="Can I see who else from my integration team can access Zapier Partner Sandbox?">
    Yes. Visit the “Members” page for the Zapier Partner Sandbox account from the [settings page](https://zapier.com/app/settings/).
  </Accordion>

  <Accordion title="I've already been granted access to Zapier Partner Sandbox. Looking in the account settings, I see an organization owned by app\_xxx@zapierpartner.com. What is this and how can I access it?">
    The email address shown is the systematic owner of your integration's Zapier Partner Sandbox workspace. This is owned and maintained by Zapier. You are unable to log into Zapier using that account.
  </Accordion>

  <Accordion title="Can I share Zaps and/or folders in Zapier Partner Sandbox?">
    No, Zaps and folders you create in Zapier Partner Sandbox are only accessible to the user that created them.
  </Accordion>

  <Accordion title="What counts as a task when testing Zaps in Zapier Partner Sandbox?">
    Zapier's standard task usage policies apply to Zapier Partner Sandbox. You can [learn more about what counts as a task](https://help.zapier.com/hc/en-us/articles/8496196837261-How-is-usage-measured-in-Zapier).
  </Accordion>

  <Accordion title="Does each member added to Zapier Partner Sandbox have their own task limit?">
    No, the 2,000/month task limit applies to the Zapier Partner Sandbox workspace, not per user. Note that if you have access to multiple Zapier Partner Sandbox workspaces, each has its own task limit.
  </Accordion>

  <Accordion title="Can I pay to add additional tasks to Zapier Partner Sandbox?">
    Currently, Zapier Partner Sandbox does not support metered billing.
  </Accordion>

  <Accordion title="What happens if I exhaust the task allowance?">
    If you exceed the task allowance, you will need to wait for the task usage to reset at the start of the next billing cycle. When viewing the Zapier Partner Sandbox workspace, you can see how many days remain until the usage resets in the bottom-left corner.

    <Frame>
      <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6876f7a7b903b5d89cdbd08e3874437c.png" />
    </Frame>
  </Accordion>
</AccordionGroup>

## Terms of Use

Zapier Partner Sandbox is a benefit of our Developer Program which Zapier may, in its sole discretion, choose to make available to select partners. Because Zapier Partner Sandbox may be made available to you at no additional charge, you must agree to the following in order to use Zapier Partner Sandbox:

1.  Your use of Zapier Partner Sandbox is subject to Zapier's [Terms of Use](https://zapier.com/legal/website-terms-of-use)
2.  Zapier Partner Sandbox is a discretionary benefit intended to support (together, the “Purpose”): A. Integration Development. The ongoing development of your integration. B. Sales Enablement/Demonstrations. Demonstrations of specific illustrative workflows that highlight the capabilities and benefits of your integration with Zapier. These demonstrations can be used as part of sales presentations or marketing materials to your prospective clients, but as mentioned below in (6), production data should not be run through any such workflows.
3.  Zapier Partner Sandbox may only be used for the Purpose and not for any other purposes, including other commercial ones. If you need to use Zapier outside of the Purpose, including as a customer to create/develop automation workflows in support of your business (other than the demonstration ones referenced above), you must upgrade your personal account to a currently offered plan, which you may view at: [http://zapier.com/pricing](http://zapier.com/pricing).
4.  The Zapier Partner Sandbox workspace is owned and operated by Zapier, with access granted exclusively to qualifying users.
5.  In the event that Zapier determines that an account is being used outside of the Purpose, Zapier reserves all rights, including being able to revoke access to Zapier Partner Sandbox.
6.  Because Zapier Partner Sandbox may only be used for the Purpose, and the Zapier Partner Sandbox workspace is owned and operated by Zapier, you agree not to submit any production data through Zaps created within the Zapier Partner Sandbox workspace. You further agree that for program management purposes, the Zapier Partnerships team has access to Zaps created within the Zapier Partner Sandbox workspace and that you should not have the expectation of privacy that you would with a Zapier customer account.
7.  Zapier Partner Sandbox is provided on an “as-is” basis. Zapier reserves the right to modify, suspend, or discontinue this benefit at any time, with or without notice to you. Zapier may also update these Zapier Partner Sandbox terms at any time, and you can always find the most current version in our platform documentation.
8.  The provisions of Section 3(e) (Beta Release) are incorporated by reference. In particular, Zapier is not and will not be liable for any Zaps or data that run through the Zapier Partner Sandbox workspace. Users acknowledge and assume full responsibility for their Zaps and any associated data stored in the Zapier Partner Sandbox workspace.
9.  Zapier Partner Sandbox permits you to invite other users to the workspace. You may invite members of your integration team to the workspace (who will be asked to agree to these terms as well in order to maintain their access), but please do not add anyone who is not a member of your integration team. Zapier will be auditing membership in the workspace and any non-integration team members will be removed at Zapier's discretion.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Build your integration on Zapier

This guide will walk you through what steps you need to take to build an integration from start to finish. There are no fees to build an integration with Zapier.

You can choose to build with either [Zapier Platform UI or Zapier Platform CLI](/platform/quickstart/ui-vs-cli).

## 1. Prerequisites

*   The app you wish to integrate will need to have a [publicly-accessible API](https://zapier.com/learn/apis/)
*   Create a [Zapier account](https://zapier.com/sign-up)
*   If you haven't used Zapier before, learn the basics in our [Zapier Getting Started Guide](https://zapier.com/learn/zapier-quick-start-guide/)

## 2. Choose a developer tool

Select [one of the two ways](/platform/quickstart/ui-vs-cli) to build an integration on Zapier's Platform.

*   The Platform UI lets you create a Zapier integration in your browser without code using API endpoint URLs. You can set any custom options your API may need, including custom URL params, HTTP headers, and request body items.

*   Zapier Platform CLI lets you build a Zapier integration in your local development environment, collaborate with version control and CI tools, and push new versions of your integration from the command line.

Both of these tools run on the same Zapier platform, so choose the one that fits your workflow the best. You can try both methods out with the [Platform UI tutorial](/platform/quickstart/ui-tutorial) and the [Platform CLI tutorial](/platform/quickstart/cli-tutorial).

## 3. Create a new integration

Create and add details about your integration. Set your Intended audience.

*   [Create an integration using Zapier Platform UI](https://developer.zapier.com/app/new)
*   [Create an integration using Zapier Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#creating-a-local-app)

## 3. Add an authentication scheme

Configuring authentication allows users to input credentials to authenticate with your API.

*   [Authentication using Zapier Platform UI](/platform/build/auth)
*   [Authentication using Zapier Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#authentication)

    *   [Authentication schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#authenticationschema)

## 4. Configure triggers

Triggers are how your app's users can start automated workflows whenever an item is added or updated in your app. Use webhook subscriptions or polling API endpoints to create triggers.

*   [Configure a trigger using Zapier Platform UI](/platform/build/trigger)

*   [Configure a trigger using Zapier Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#triggerssearchescreates)

*   [Trigger schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#triggerschema)

## 5. Configure actions

Actions allow users to either create, search or update records in your app via your API.

*   [Create an action using Zapier Platform UI](/platform/build/action)
*   [Create an action using Zapier Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#triggerssearchescreates)

    *   [Search schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#searchschema)
    *   [Create schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#createschema)

## 6. Test your integration

While you're building your integration, you can test your API requests within the Platform UI. For developers building on Zapier Platform CLI, you can write unit tests that run locally, in a CI tool like [Travis](https://travis-ci.com/).

To get a sense of the user experience, it's recommended to test your integration within the Zap editor. [Create a new Zap](https://help.zapier.com/hc/en-us/articles/8496309697421) that uses your integration's triggers or actions to ensure they all work as expected. After you're done building, invite users to try your integration before making it available to a wider audience.

Learn more about testing your integration:

*   [Testing using Zapier Platform UI](/platform/build/test-auth)
*   [Testing using Zapier Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#testing)

## 7. Validate your integration

Run automated checks to identify errors and recommendations on how to improve the user experience of your private integration. These checks are required to pass for public integrations, and are recommended for a better user experience for private integrations.

*   [Run automated checks using Zapier Platform UI](/platform/publish/integration-checks-reference)
*   [Run automated checks using Zapier Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#validate)

## 8. Invite team members

You can assign different roles and permissions to team members who you're collaborating with on your private integration.

*   [Invite team members using Zapier Platform UI](/platform/manage/add-team)
*   [Invite team members using Zapier Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#sharing-an-app-version)

## 9. Publish

Once you've built your integration, Publish it!

*   [Share your integration](/platform/manage/sharing)

## 10. Conclusion

Once you've built your integration, continue to make further improvements and manage versions of your integration.

*   [Manage integration versions using Zapier Platform UI](/platform/manage/versions#managing-versions-in-platform-ui)
*   [Manage integration versions using Zapier Platform CLI](/platform/manage/versions#managing-versions-in-platform-cli)

<Tip>
  **Tip**: Learn from the Zapier team and other [Zapier Platform developers in our community forum](https://community.zapier.com/p/developer-zone).
</Tip>


# Platform CLI tutorial

This tutorial walks you through the process of building, testing, and pushing an example app to Zapier using Platform CLI. We'll use a mock API for recipes in this tutorial, but for production Zapier apps, you'd want to connect to a real API.

## Prerequisites

*   You'll need a [Zapier account](https://zapier.com/sign-up).

*   Your dev environment meets the [requirements for running Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#requirements) with the proper version of Node.js installed.

*   Install the Platform CLI tool

```bash
  # Install the CLI globally
  npm install -g zapier-platform-cli
```

To see a list of all the available commands in Platform CLI, try `zapier help`.

*   Next you'll need to identify yourself via the CLI.

```bash
  # Setup auth to Zapier's platform with a deploy key
  zapier login
```

Zapier writes your deploy key to `~/.zapierrc`. You'll want to keep that file safe and not check it into source control.

## 1. Start an integration

Use the `init` command to setup the [needed structure](https://github.com/zapier/zapier-platform/tree/main/example-apps). You'll be presented with a list of available templates to start with.

```bash
# Create a directory with the minimum required files and choose a template
zapier init example-app --template minimal
# Move into the new directory
cd example-app
```

Inside the directory, you'll see a few files. `package.json` is a typical requirements file of any Node.js application. It's pre-populated with a few dependencies, most notably the `zapier-platform-core`, which is what makes your app work with the Zapier Platform. There is also an `index.js` file and a test directory (more on those later). Before we go any further, we need to install the dependencies for our app:

```bash
npm install
```

### `index.js`

`index.js` is the entry point to your app. This is where the Platform will look to understand how your app will interact with Zapier. Open the app directory you created in your code editor of choice.

You may see the following in `index.js`:

*   a single `module.exports` definition which will be interpreted by Zapier
*   `triggers` will describe ways to trigger off of data in your app
*   `searches` will describe ways to find data in your app
*   `creates` will describe ways to create data in your app
*   `resources` are purely optional but convenient ways to describe CRUD-like objects in your app (see [an example resources app](https://github.com/zapier/zapier-platform/tree/main/example-apps/resource))
*   `beforeRequest` & `afterResponse` are hooks into the HTTP client to manipulate the request/response on every call

## 2. Add a trigger

Add a [**trigger**](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#triggerssearchescreates) configured to read data from a mock API:

```bash
zapier scaffold trigger recipe
```

The [scaffold](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#scaffold) creates a new file `recipe.js` in the `triggers` folder. When building your own integration, you'll likely name your JS files with nouns like `contact.js`, `lead.js` or `order.js`.

Open `triggers/recipe.js` (file created by `zapier scaffold trigger recipe`) and **replace** it with:

```js
const listRecipes = async (z, bundle) => {
  const response = await z.request('http://57b20fb546b57d1100a3c405.mockapi.io/api/recipes');
  return response.data;
};

module.exports = {
  key: 'recipe',
  noun: 'Recipe',
  display: {
    label: 'New Recipe',
    description: 'Triggers when a new recipe is created.'
  },
  operation: {
    perform: listRecipes,
    sample: {
      id: 1,
      createdAt: 1472069465,
      name: 'Best Spaghetti Ever',
      authorId: 1,
      directions: '1. Boil Noodles\n2.Serve with sauce',
      style: 'italian'
    },
  },
};
```

To try out the trigger locally, try running `zapier invoke`. The command will ask you which action you'd like to invoke. Now we only have one trigger `recipe`, so you can choose that one.

```
$ zapier invoke
? Which action type would you like to invoke? trigger
? Which "trigger" key would you like to invoke? recipe
✔ Invoking triggers.recipe.operation.inputFields
✔ Invoking triggers.recipe.operation.perform
[
  {
    "id": "1",
    "createdAt": 1471984289,
    "name": "name 1",
    "authorId": 63,
    "directions": "directions 1",
    "style": "style 1"
  },
  <omitted...>
]
```

Let's review the code. The function definition for `listRecipes` handles the API work, making the HTTP request and returning the parsed response data.

In JavaScript, `async/await` are syntactic sugar built on top of [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). Be sure to use `await` or `.then()` when you call asynchronous functions.

The `listRecipes` function receives two arguments, a `z` object and a `bundle` object.

*   The [Z Object](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#z-object) is a collection of utilities needed when working with APIs. In our snippet, we use `z.request` to make the HTTP call.
*   The [Bundle Object](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#bundle-object) contains any data needed to make API calls, like authentication credentials or data for a POST body. In our snippet the Bundle is not used, since we don't require any of those to make our simple GET request.

It is possible to accomplish the same tasks using alternate Node.js libraries, but it is preferable to use the `z` object as there are features built into these utilities that augment the Zapier experience like logging of HTTP calls and error handling.

In `module.exports`, we export some metadata plus our `listRecipes` function and a sample. We'll explain later how Zapier uses this metadata and exposes it to the end user. For now, know that it satisfies the minimum info required to define a trigger.

As well as defining the trigger, the `scaffold` command has done the following:

`index.js` file now includes:

```bash
const getRecipe = require('./triggers/recipe')
```

```js
module.exports = {
  // ...
  triggers: {
    [getRecipe.key]: getRecipe
  },
  // ...
};
```

In test/triggers folder, the `recipe.test.js` file was created:

```js
const App = require('../../index');
const appTester = zapier.createAppTester(App);
// read the `.env` file into the environment, if available
zapier.tools.env.inject();

describe('triggers.recipe', () => {
  it('should run', async () => {
    const bundle = { inputData: {} };

    const results = await appTester(App.triggers.recipe.operation.perform, bundle);
    expect(results).toBeDefined();
    // TODO: add more assertions
  });
});
```

You should be able to run the test with `zapier test` and see it pass:

```bash
$ zapier test
Validating project locally
No structural errors found during validation routine.
This project is structurally sound!

✔ Running integration checks ... 23 checks passed

Integration checks passed, no issues found.

Adding /Users/marinahand/.zapierrc to environment as ZAPIER_DEPLOY_KEY...
Running test suite with the following command:

 npm run --silent test --

PASS  test/triggers/recipe.test.js
PASS  test/triggers.test.js (7.52 s)

Test Suites: 2 passed, 2 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        9.429 s
Ran all test suites.
```

For this example, we'll stick to a single test, but you can see what multiple tests look like in [another of the example apps](https://github.com/zapier/zapier-platform/blob/main/example-apps/trigger/test/triggers.js).

## 3. Modify a trigger

To let users tweak the cuisine style of recipes they are triggering on, you can provide an input field a user can fill out.

In `triggers/recipe.js`, **replace** the file with:

```js
const listRecipes = async (z, bundle) => {
  const params = {};
  if (bundle.inputData.style) {
    params.style = bundle.inputData.style;
  }

  const requestOptions = {
    url: 'http://57b20fb546b57d1100a3c405.mockapi.io/api/recipes',
    params: params,
  };

  const response = await z.request(requestOptions);
  return response.data;
};

module.exports = {
  key: 'recipe',
  noun: 'Recipe',
  display: {
    label: 'New Recipe',
    description: 'Triggers when a new recipe is created.',
  },
  operation: {
    inputFields: [
      {
        key: 'style',
        type: 'string',
        helpText: 'Which styles of cuisine this should trigger on.',
        required: false
      },
    ],

    perform: listRecipes,
    sample: {
      id: 1,
      createdAt: 1472069465,
      name: 'Best Spagetti Ever',
      authorId: 1,
      directions: '1. Boil Noodles\n2.Serve with sauce',
      style: 'italian',
    },
    outputFields: [
      { key: 'id', label: 'ID' },
      { key: 'createdAt', label: 'Created At' },
      { key: 'name', label: 'Name' },
      { key: 'directions', label: 'Directions' },
      { key: 'authorId', label: 'Author ID' },
      { key: 'style', label: 'Style' },
    ],
  },
};
```

See how the input field key `"style"` was added in the following places:

*   In the `inputFields` on `operation` - this defines the field as exposed in the Zap Editor for the user to see. The field is not required, so it could be null.
*   In the `listRecipes` function - we use the provided style via the bundle `bundle.inputData.style`.

Since you are developing locally, rerun `zapier invoke` to verify everything still works. This time, let's skip those interactive prompts. Specify the action type (`trigger`), the key (`recipe`), and the input data in the command, and you'll see:

```
$ zapier invoke trigger recipe --inputData '{"style":"style 20"}'
✔ Invoking triggers.recipe.operation.inputFields
✔ Invoking triggers.recipe.operation.perform
[
  {
    "id": "20",
    "createdAt": 1578590011,
    "name": "name 20",
    "authorId": 90,
    "directions": "directions 20",
    "style": "style 20"
  }
]
```

Let's also tweak the test. In test/trigger, replace the `recipe.test.js` file with:

```js
const zapier = require('zapier-platform-core');
const App = require('../../index');

const appTester = zapier.createAppTester(App);

describe('triggers', () => {
  describe('new recipe trigger', () => {
    it('should load recipes', async () => {
      const bundle = {
        inputData: {
          style: 'style 2',
        },
      };

      const results = await appTester(
        App.triggers.recipe.operation.perform,
        bundle,
      );

      expect(results.length).toBeGreaterThan(1);

      const firstRecipe = results[0];
      expect(firstRecipe.name).toEqual('name 2');
      expect(firstRecipe.directions).toEqual('directions 2');
    });

    it('should load recipes without filters', async () => {
      const bundle = {};

      const results = await appTester(
        App.triggers.recipe.operation.perform,
        bundle,
      );

      expect(results.length).toBeGreaterThan(1);

      const firstRecipe = results[0];
      expect(firstRecipe.name).toEqual('name 1');
      expect(firstRecipe.directions).toEqual('directions 1');
    });
  });
});
```

You can run your test again and make sure everything still works:

```js
zapier test
```

## 4. Deploy an integration

To take your local app integration and make it available to use in a Zap, you'll need to push it to Zapier.

You can have many versions of an app, which simplifies making breaking changes and testing in the future. For now, you will push a single version.

Since this is your first time pushing your app version, you'll need to register your app with Zapier. Run the `zapier register` command and Zapier will ask you to provide a name and some details about your app. Follow the prompt and then you can push your app using `zapier push`:

Zapier will ask you to provide a name and some details about your app with the `zapier register` command. Follow the prompts and then push your app again.

```
$ zapier push
✔ Copying project to temp directory
✔ Installing project dependencies
✔ Applying entry point file
✔ Building app definition.json
✔ Validating project schema and style
✔ Zipping project and dependencies
✔ Testing build
✔ Cleaning up temp directory
✔ Uploading version 1.0.0
Push complete! Built build/build.zip and build/source.zip and uploaded them to Zapier.
```

Log in and visit the [Zap editor](https://zapier.com/app/editor) to create a Zap with your new integration.

For the trigger step, choose your app and the "New Recipe" trigger, where you can see the label and description that were defined in `triggers/recipe.js`.

In the Zap editor, see the input field `"style"` and fill it out. Run the trigger test and the `listRecipes` function associated with the trigger runs, making the API request and returning the result to Zapier.

Go back to your terminal. Use the `zapier logs --type http` command to see the HTTP requests Zapier made.

```
$ zapier logs --type http

The logs of your app "Example App" listed below.
┌────────┬────────┬────────────────────────────────────────────────────────┬───────────────┬─────────┬──────────────────────────────────────┬───────────────────────────┐
│ Status │ Method │ URL                                                    │ Querystring   │ Version │ Step                                 │ Timestamp                 │
├────────┼────────┼────────────────────────────────────────────────────────┼───────────────┼─────────┼──────────────────────────────────────┼───────────────────────────┤
│ 200    │ GET    │ http://57b20fb546b57d1100a3c405.mockapi.io/api/recipes │ style=italian │ 1.0.0   │ a9055e16-fc0d-4fb2-a3e6-9d442d1f70e8 │ 2016-09-13T15:11:30-05:00 │
└────────┴────────┴────────────────────────────────────────────────────────┴───────────────┴─────────┴──────────────────────────────────────┴───────────────────────────┘
  Most recent logs near the bottom.
```

## 5. Add authentication

Authentication is crucial to interacting with most APIs. Zapier supports a number of different [authentication schemes](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#authentication). In this example, you are going to set it up to include an API Key in a header.

For different types of authentication, see these example apps:

<CardGroup cols={3}>
  <Card title="OAuth 2" icon="key" href="https://github.com/zapier/zapier-platform/tree/master/example-apps/oauth2" horizontal />

  <Card title="Basic Auth" icon="lock" href="https://github.com/zapier/zapier-platform/tree/master/example-apps/basic-auth" horizontal />

  <Card title="Session Auth" icon="user-clock" href="https://github.com/zapier/zapier-platform/tree/master/example-apps/session-auth" horizontal />

  <Card title="API Key" icon="key" href="https://github.com/zapier/zapier-platform/tree/master/example-apps/custom-auth" horizontal />
</CardGroup>

Define the `authentication` section for the example app you've built. In `index.js`, **add** the following to `module.exports`:

```js
  authentication: {
    type: 'custom',
    fields: [{ key: 'apiKey', type: 'string' }],
    test: async (z, bundle) => {
      const response = await z.request('http://57b20fb546b57d1100a3c405.mockapi.io/api/me');
      return response.data;
    },
  },
```

The above snippet defines the two required properties of `authentication`:

*   `fields` is where we define our auth fields. This works similar to the `inputFields` of triggers. When users connect their account to Zapier, they'll be prompted to fill in this field, and the value they enter becomes available in the `bundle.authData`. Not every authentication type will include `inputFields`.
*   `test` is a function used during the account connection process to verify that the user entered valid credentials. The goal of the function is to make an authenticated API request whose response indicates if the credentials are correct. A profile in the connected app, such as a `/me` endpoint, is a common choice. If valid, the test function can return anything. On invalid credentials, the test needs to raise an error. Since `z.request()` already throws an error for non-2xx responses, we don't need to throw an error explicitly in this case.

Next, make sure the API key is included in all the requests your app makes.

In `index.js`, **edit** the file to include:

```js
// Add this helper function above `module.exports`: 
const addApiKeyToHeader = (request, z, bundle) => {
  request.headers['My-Auth-Header'] = bundle.authData.apiKey;
  return request;
};

// module.exports = ...
```

The helper function `addApiKeyToHeader`, adds the user-provided API key as a request header called `My-Auth-Header`. The name chosen is illustrative, the header would be whatever the API you are integrating with requires. Sometimes, an API would require it in a query parameter instead of a header, and/or encoded first. You'll want to reference the API documentation for the expected format.

Finally, to ensure the helper function takes effect, it needs to be registered in the app.

In `index.js`, **edit** `module.exports` to also include:

```js
  beforeRequest: [
    addApiKeyToHeader, // new line of code
  ],
```

`beforeRequest` is a list of functions that are called before every HTTP request that uses `z.request`. It's where you add headers, query params, or whatever is needed to be within *all outbound requests*. With this addition, every outgoing HTTP request made by `z.request` will now have the API key added in a header.

The `zapier invoke` command has `auth test` subcommand that essentially calls the `authentication.test` function for you to verify the credentials locally. But first, you need to initialize the auth credentials with the `auth start` subcommand:

```
$ zapier invoke auth start
The .env file does not exist or is empty. You may need to set some environment variables in there if your code uses process.env.
? apiKey | string: mock-api-key
Auth data appended to .env file. Run `zapier invoke auth test` to test it.
```

Next, run the `auth test` subcommand to verify the credentials. You'll know you have the right API key if you can see the response data from the `/me` endpoint:

To check progress, re-push the app.

```
$ zapier invoke auth test 
✔ Invoking authentication.test
[
  {
    "id": "1",
    "createdAt": 1473801403,
    "userName": "userName 1"
  }
]
```

Re-push the app to Zapier to deploy the changes:

```bash
zapier push
```

Go back to your Zap at [https://zapier.com/app/zaps](https://zapier.com/app/zaps). It will now have a new ["Account" item](https://cdn.zappy.app/9096a3bc1d6cd8147fb695bb460692b2.png) in your "New Recipe" trigger.

Add an account by entering any value you like for the API key, the mock API does not care.

As soon as you add the account, Zapier will run your app's `authentication.test` function to confirm the credentials are valid.

We can verify the header is present in the request by looking at the logs again.

```
$ zapier logs --type http --detailed --limit=1
The logs of your app "Example App" listed below.
┌─────────────────────┬────────────────────────────────────────────────────────────────────────────────────────┐
│ Status              │ 200                                                                                    │
│ Method              │ GET                                                                                    │
│ URL                 │ http://57b20fb546b57d1100a3c405.mockapi.io/api/me                                      │
│ Querystring         │                                                                                        │
│ Version             │ 1.0.0                                                                                  │
│ Step                │                                                                                        │
│ Timestamp           │ 2016-09-16T15:57:51-05:00                                                              │
│ Request Headers     │ user-agent: Zapier                                                                     │
│                     │ My-Auth-Header: :censored:6:b1af149262:                                                │
│ Request Body        │                                                                                        │
│ Response Headers    │ server: Cowboy                                                                         │
│                     │ connection: close                                                                      │
│                     │ x-powered-by: Express                                                                  │
│                     │ access-control-allow-origin: *                                                         │
│                     │ access-control-allow-methods: GET,PUT,POST,DELETE,OPTIONS                              │
│                     │ access-control-allow-headers: X-Requested-With,Content-Type,Cache-Control,access_token │
│                     │ content-type: application/json                                                         │
│                     │ content-length: 59                                                                     │
│                     │ etag: "-80491811"                                                                      │
│                     │ vary: Accept-Encoding                                                                  │
│                     │ date: Fri, 16 Sep 2016 20:57:51 GMT                                                    │
│                     │ via: 1.1 vegur                                                                         │
│ Response Body       │ [{"id":"1","createdAt":1473801403,"userName":"userName 1"}]                            │
└─────────────────────┴────────────────────────────────────────────────────────────────────────────────────────┘
```

You can see from the detailed log that the request included our auth header. The value appears as `:censored:6:b1af149262:`, which is intentional. Zapier does not log authentication credentials in plain text.

## 6. Invite team members to help manage your integration

Share your integration with your team of fellow developers so it shows up in their [developer dashboard](https://developer.zapier.com/).

From the Zapier CLI, run either of these:

*   `zapier team:add user@example.com admin` to invite a team member [as an admin](https://platform.zapier.com/manage/add-team) for the app.
*   `zapier team:add user@example.com collaborator` to invite a team member [as a collaborator](https://platform.zapier.com/manage/add-team) for the app.

Replace the example email address with the user you want to invite.

## 7. Share users to your integration

Once you have at least one trigger or action, you'll want to share your integration with some of your users. Early feedback helps make sure you're building functionality that will be used.

From the Zapier CLI, run `zapier users:add user@example.com 1.0.0` to email this user to let them view and use the app version 1.0.0 in their Zaps.

Replace the example email address with the user you want to invite. You'll see a confirmation prompt, type `Y`.

The alternative way to invite anyone to use your app in their Zaps is via a link to the app. Run `zapier users:links` to see a public sharing link per specific version or a link to all versions. The link looks something like `https://zapier.com/platform/public-invite/1/222dcd03aed943a8676dc80e2427a40d/`. You can put this in your help docs, post it to your website, add it to your email campaign, etc. Access shared via these links cannot be revoked once shared with your users.

## 8. Modify your integration

If you need to make any changes to your integration in [a new version](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#deploying-an-app-version). Versions helps to track and manage changes to your integration over time.

Public integrations or private integrations with more than 5 users **require** a new version to make any edits. It is still recommended to make changes for private integrations with fewer than 5 users in a new version as well and [consider Zapier's best practices](/platform/manage/planning-changes) when doing so.

Integrations created with the Platform CLI cannot be edited in the Platform UI. You can't add triggers or actions, edit code or configurations - these options will be grayed out in the UI.

The following details of your CLI integration can be managed from the UI if preferred:

*   Invite team members
*   Monitor integration usage
*   Submit your integration to be made publicly available
*   Promote a new integration version to public
*   Migrate users between versions

## Learn more

*   [Z object](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#z-object)
*   [Bundle object](https://github.com/zapier/zapier-platform/blob/master/packages/cli/README.md#bundle-object) for functionalities available within the `perform` functions
*   [Different authentication schemes and search/create implementations examples](https://github.com/zapier/zapier-platform/tree/main/example-apps)
*   [Interactive tutorial](https://developer.zapier.com/cli-guide/introduction) that uses the GitHub API (a little outdated)
*   About [different types of support](https://platform.zapier.com/quickstart/support) available to help you build your integration

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Get help with the Zapier Platform

Get help building your app integration through the following channels:

## Developer Support on the Zapier Platform

The Platform Support team is equipped to assist you with a variety of topics to ensure your development process is as smooth as possible. Here's how we can help:

*   **Integration Maintenance & Launch**: Guidance on updating your integrations and launching new versions through both our UI and CLI.
*   **Authentication, Trigger & Action Setup**: Assistance with configuring authentication to your API and configuring the triggers and actions that make your integrations run.
*   **Error Handling**: Advice on best practices for error messaging and handling, aiming for clarity and helpfulness to end-users.
*   **Log Troubleshooting**: Support in interpreting and troubleshooting logs to resolve issues swiftly.

The following areas are however beyond our support scope:

*   **Custom JavaScript Code**: We cannot provide support for writing or structuring your custom JavaScript code.
*   **API Behavior Debugging**: We're unable to offer assistance with debugging unpredictable behaviors from third-party APIs.
*   **Application-Specific Advice**: While we'd love to help with everything, we can't provide tailored suggestions for the specifics of your individual application.

We encourage developers to use our comprehensive documentation and community resources for topics that fall outside of our support boundaries. Our aim is to empower you with the tools and knowledge you need to excel on the Zapier Platform.

Reach out to our Platform Support team via the [contact form](https://developer.zapier.com/contact).

## Partnering with Zapier

For public integrations, the Partner Support team is here to help with:

*   **Integration Launch**: We're here to guide you with launching your integration in the [Zapier App Directory](https://zapier.com/apps).
*   **The Partner Program**: Any questions about [the program](https://zapier.com/platform/partner-program) and help redeeming perks.
*   **Branding and Page Updates**: Updating your integration's branding and directory page.
*   **Solutions for Embedding**: Any questions about [embedding Zapier](https://platform.zapier.com/embed/overview) in your app and Zap templates.

Reach out to our Partner Support team via the [contact form](https://developer.zapier.com/contact).

## Build Faster with Zapier Experts

If you're working to build [public](/platform/quickstart/private-vs-public-integrations#public-integrations) or [private](/platform/quickstart/private-vs-public-integrations#private-integrations) integrations on the Zapier Developer Platform but need additional technical expertise or your developers are focused on other priorities, consider partnering with a Zapier Expert. Our certified Zapier Experts can help you build integrations quickly and efficiently, ensuring your projects stay on track. Whether you need guidance building an integration or full end-to-end integration development, a Zapier Expert can provide the expertise and assistance you need.

Learn more about Zapier Experts who are [trusted integration developers](/platform/quickstart/trusted-developers), or [search the Zapier Expert Directory](https://zapier.com/experts?f_services-needed=building-a-public-or-private-zapier-integrationapi).

## Zapier's Developer Community

Zapier's Platform [community](https://community.zapier.com/p/developer-zone) is dedicated to helping developers with new and existing integrations. With a worldwide team of users, there's usually someone around to answer your questions. We'd love to hear about your experience building on Zapier.

## Zapier Support

If you have issues with a Zap or your Zapier account, reach out to Zapier Customer Support via the [contact form](https://zapier.com/app/contact-us). This lets the support team access technical information in your account to troubleshoot your problem. You'll receive an email response from a member of the support team regarding your issue.

## Zapier Help Center

[Zapier's Help Center](https://zapier.com/help) is a robust library of documents and step-by-step guides that explain how Zapier works, provide information about the apps you use on Zapier, and offer solutions for common issues that you may run into.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Zapier Glossary



*   **Zapier**: An integration platform that connects over 7,000+ web apps and APIs together into [multi-step, automated, customizable workflows](/platform/quickstart/how-zapier-works). When an event happens in one app, Zapier can tell another app to perform (or do) a particular action.
*   **Zap**: An [automated Zapier workflow](https://help.zapier.com/hc/en-us/articles/8496309697421) that connects your app to others that have built integrations. Each Zap consists of a trigger and one or more actions. When you turn your Zap on, it will run the action steps every time the trigger event occurs.
*   **Trigger**: The app integration event that [starts a Zap when new or updated data is added](/platform/build/trigger) to your app, either by Zapier polling a specific API endpoint to check for new data or via a notification REST webhook from your app that pushes new data to Zapier.
*   **Action**: The app integration step that [finds, creates, or updates data](/platform/build/action) in your app using a GET, PUT or POST request to your API using data provided when a Zap is triggered by an event in another app.
*   **Create action**: The app integration step that creates or updates a single item in your app through an API call.
*   **Search action**: The app integration step that finds existing data in your app, optionally with a create action to add an item if the search does not return a result.
*   **Task**: An action that a Zap successfully completes will count towards [task usage](https://help.zapier.com/hc/en-us/articles/8496196837261). The number of tasks a user has depends on their [Zapier pricing plan](https://zapier.com/pricing).
*   **Zap editor**: The [user interface](https://zapier.com/editor) where end users build their Zaps, adding triggers and actions built by app integration developers.
*   **Zap history**: An end user's [log of all the Zaps](https://help.zapier.com/hc/en-us/articles/8496291148685#h_01HD2KKYMTS7ASSEPXRMT57H63) that have run live, showing the data going in and out of each step, any error messages and task usage.
*   **Filter**: A built-in Zapier function that users can add to their Zap to [restrict it to run](https://help.zapier.com/hc/en-us/articles/8496276332557) only when certain conditional values contain specific data.
*   **Paths**: A built-in Zapier function that users can add to their Zap to combine Filters and Actions to perform different actions based on different conditions. [Paths use conditional, if/then logic](https://help.zapier.com/hc/en-us/articles/8496288555917): if A happens in your trigger app, then perform this action, if B happens, then perform this other action.
*   **Formatter**: A built-in Zapier function users can add to their Zap to [customize and manipulate](https://zapier.com/apps/formatter/help) text, number and date data that is output from triggers and actions.
*   **Delay**: A built-in Zapier function users can add to their Zap to [place it on hold](https://help.zapier.com/hc/en-us/articles/8496288754829-Add-delays-to-Zaps) for a specified amount of time before the remaining actions run, in essence scheduling the action.
*   **Input Fields**: [Individual data fields](https://help.zapier.com/hc/en-us/articles/8496259603341-Different-field-types-in-Zaps) are received from trigger and action steps in a Zap, and mapped into subsequent steps' by a user to send that data as inputs to those steps' destination API.
*   **Beta**: If your app is published in the Zapier App Directory, it will [remain in Beta](/platform/publish/public-integration#5-beta-phase) for 90 days to indicate its newer status to end users.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# How Zapier works

[Zapier is a tool](https://zapier.com/how-it-works) that helps you automate repetitive tasks between two or more apps, no code necessary. Our customers use Zapier to move information from one app to another automatically rather than manually. Each Zap you create starts with a trigger (something that happens in one app) and then one or more actions (something else that happens in another app).

In order to connect your app to the 7,000+ apps on the Zaper Platform to allow your users to build Zaps, your app will need to have a publicly-accessible API which you can use to build your Zapier integration.

For an intro to APIs in general, check out the [guide](https://zapier.com/learn/apis/).

When you're ready to get started, [the UI tutorial](/platform/quickstart/ui-tutorial) offers a great introduction to building your first Zapier integration.

## What do Zapier integrations include?

Zapier integrations are comprised of three core concepts: Authentication, Triggers and Actions.

Authentication connects an app to Zapier. Users authenticate their account with an app to allow Zapier to access their data in that app. Once a user authenticates, Zapier will use the same authentication credentials for every API call the integration makes on the user's behalf.

Triggers are how your app's users can start Zaps (automated workflows) whenever an item is added or updated in your app.

Actions are how your app's users can create new or update existing items in your app.

## How does building an integration work?

All new Zapier integrations are built using Zapier Platform v3, the latest version of our developer platform. You can build integrations using Zapier Platform UI, an online visual builder to create integrations in a form layout. Or, you can use Zapier Platform CLI, a command line interface to build integrations in JavaScript code from your local development environment.

Both interfaces use the same Zapier Platform and function the same internally, with some [differences to consider when building](/platform/quickstart/ui-vs-cli).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Private vs public integrations

When building an integration on the Zapier Platform, you must specify the intended audience.

The intended audience determines whether the integration will be private or public:

*   Public integration: For access to all Zapier customers and published in the [Zapier App Directory](https://zapier.com/apps).

*   Private integration: For personal, internal (e.g. with your team) or controlled-access use.

*   Building a private or public integration on the Zapier Platform is free.

*   There are no fees for sharing or publishing an integration.

*   Users of your integration are responsible for their own Zapier plans and billing.

*   Partners do not incur fees as a result of the usage of their integration.

Both private and public integrations can connect to Zapier's app directory of over [7,000+ integrations](https://zapier.com/apps). Depending on your intended audience, there will be different features available and limitations to what the integration can do.

> **Note**: The intended audience is different from your integration's status. All integrations, regardless of intent, start with a private status. Learn more in [Zapier integration publishing requirements](/platform/publish/integration-publishing-requirements).

## Public integrations

Public integrations are suitable for the following use cases:

*   Connect your app product to Zapier for public use.
*   Gain exposure from Zapier's 3 million+ users.

By creating a public integration, you'll have access to these features:

*   Join the [Zapier Partner Program](https://zapier.com/platform/partner-program).
*   Publish your integration in [Zapier's app directory](https://zapier.com/apps).
*   [Create Zap templates](/platform/publish/zap-templates).
*   [Embed Zapier in your product](https://platform.zapier.com/embed/overview).
*   [Embed Zap Templates in your product](https://platform.zapier.com/embed/zap-templates).
*   Use [Zapier Issue Manager](/platform/manage/user-feedback#3-consider-zapier-issue-manager) to manage bugs and feature requests.
*   Users of your integration can [share a template of their Zaps](https://help.zapier.com/hc/en-us/articles/8496292155405-Share-a-copy-of-your-Zap).
*   Users of your integration can use [Zapier's ChatGPT plugin](https://help.zapier.com/hc/en-us/articles/14058263394573).

### Limitations

If you're building a public integration, there are specific limitations to be aware of:

*   To create a public integration, you need to meet the [Integration Ownership criteria](/platform/publish/integration-publishing-requirements#21-integration-ownership).
*   You can have up to [100 admins and collaborators](/platform/manage/add-team).
*   You can't restrict who uses your public integration.

## Private integrations

Private integrations are suitable for the following use cases:

*   Connect your internal tool or system to Zapier.
*   Connect your app product to Zapier to exclusively allow your team to build workflows.
*   Create an integration for a staging or testing environment.

<Info>
  **Note**: We strongly recommend against the use of an independent private integration for staging purposes in the case of an existing public integration. Instead, use a private [version](/platform/manage/versions) for this purpose.
</Info>

By creating a private integration:

*   You control who has access to your integration.
*   Your private integration won't appear within [Zapier's app directory](https://zapier.com/apps).

### Limitations

If you're building a private integration, there are specific limitations to be aware of:

*   Rate limits depend on the Zapier plan of the integration owner. When using your private app in their Zaps, users' Zap runs will be [held](https://help.zapier.com/hc/en-us/articles/8496291148685-View-and-manage-your-Zap-history) if the cumulative calls across all users of your private integration exceed the following limits on the integration owner's plan:

    *   Free and Professional plans: 100 calls every 60 seconds.
    *   Team and Enterprise plans: 5000 calls every 60 seconds.
    *   To increase a private integration rate limit for your users, [upgrade your Zapier plan](https://help.zapier.com/hc/en-us/articles/8496277302157-Change-or-cancel-your-Zapier-plan).
    *   To request an increase in the rate limit for your private integration beyond 5000 calls, contact our [Sales team](https://zapier.com/l/contact-sales).
*   You can have up to [100 admins and collaborators](/platform/manage/add-team).
*   You can invite up to [100 users by email](/platform/manage/sharing#2-invite-users-by-email). There's no limit when [sharing access using a public link](/platform/manage/sharing#1-invite-users-with-a-public-link), but note this access cannot be revoked once a user accepts the invitation link.
*   [Embedding](https://platform.zapier.com/embed/overview) Zapier and [Zap templates](/platform/publish/zap-templates) are not supported.
*   [Sharing a template of Zaps](https://help.zapier.com/hc/en-us/articles/8496292155405-Share-a-copy-of-your-Zap) is not supported.
*   Your app won't appear as an option on [Zapier's ChatGPT plugin](https://help.zapier.com/hc/en-us/articles/14058263394573).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Recommended triggers and actions

Whether you’re just starting to scope out a new Zapier integration build or have successfully launched your app in the Zapier App Directory, it’s helpful to know what features users find the most valuable and are the most widely used across Zapier’s various [app categories](https://zapier.com/apps). Ensuring your integration covers the foundational triggers, actions, and searches applicable to your app will provide more utility to your users.

## Top Triggers, Actions, and Searches by App Category

The lists provided do not encompass every possible model or object specific to individual apps. Rather, they represent a more general overview of the most frequently observed ones in each category to serve as a guide to apply to your own app. Remember to maintain consistent terminology between your Zapier integration and app platform so users have a clear connection between the two.

Maybe your CRM platform, refers to new entries as *Contacts*. On another, they could be called *Leads*, *Clients*, *Persons*, or any term unique to the platform, such as *Hoomans* or *Zaperonis*. You may not find *Zaperonis* listed below, but if you see a similar concept like a *New Contact* trigger, you can apply the same logic with a *New Zaperoni* trigger for your integration.

If you don’t see a list for your specific category, there may not have sufficient integrations within it to surface best practices. Reach out to [Partner support](https://developer.zapier.com/contact) to request a category.

### Accounting

| Triggers                    | Actions                           | Searches                     | Search or Creates                      |
| --------------------------- | --------------------------------- | ---------------------------- | -------------------------------------- |
| New Payment                 | Create Customer/Contact/Client    | Find Invoice                 | Find or Create Customer/Client/Contact |
| New Invoice/Bill            | Create Invoice/Bill               | Find Customer/Client/Contact |                                        |
| New Expense                 | Send Invoice                      |                              |                                        |
| New Customer/Contact/Client | Create Payment/Sale/Sales Receipt |                              |                                        |
| New Quote/Estimate          |                                   |                              |                                        |

View [example apps](https://zapier.com/apps/categories/accounting).

### Ads & Conversions

| Triggers                  | Actions                             | Searches | Search or Creates |
| ------------------------- | ----------------------------------- | -------- | ----------------- |
| New Lead/Subscriber/Visit | \*Send Event/Conversion/Measurement |          |                   |
| New Form Response/Entry   | Add Contact/Email to List/Audience  |          |                   |

\*Be sure your actions cover all types of events or conversions your app supports, such as ‘offline’, ‘lead’, ‘funnel’, etc.

View [example apps](https://zapier.com/apps/categories/ads-conversion).

### AI Tools

| Triggers                                      | Actions                                         | Searches | Search or Creates |
| --------------------------------------------- | ----------------------------------------------- | -------- | ----------------- |
| (Chat) New Message                            | Send Prompt                                     |          |                   |
| New Transcription/Recording/Video/Image Ready | Request/Generate Image/Transcription/Video/etc. |          |                   |

View [example apps](https://zapier.com/apps/categories/ai-tools).

### Bookmark Managers

| Triggers           | Actions                | Searches | Search or Creates |
| ------------------ | ---------------------- | -------- | ----------------- |
| New Item/Bookmark  | Bookmark/Add Page/Item |          |                   |
| New Favorited Item |                        |          |                   |
| New Tagged Item    |                        |          |                   |

View [example apps](https://zapier.com/apps/categories/bookmarks).

### Calendar

| Triggers              | Actions               | Searches   | Search or Creates    |
| --------------------- | --------------------- | ---------- | -------------------- |
| New Event/Meeting     | Create Event          | Find Event | Find or Create Event |
| Event Started         | Add Attendee to Event |            |                      |
| Updated Event/Meeting |                       |            |                      |

View [example apps](https://zapier.com/apps/categories/calendar).

### Call Tracking

| Triggers              | Actions | Searches | Search or Creates |
| --------------------- | ------- | -------- | ----------------- |
| Call Completed        |         |          |                   |
| New Call/Call Started |         |          |                   |
| Call Tagged           |         |          |                   |

View [example apps](https://zapier.com/apps/categories/call-tracking).

### Contact Management

| Triggers                        | Actions                            | Searches                         | Search or Creates                          |
| ------------------------------- | ---------------------------------- | -------------------------------- | ------------------------------------------ |
| New Contact/Lead/Person/Company | Create Contact/Lead/Person/Company | Find Contact/Lead/Person/Company | Find or Create Contact/Lead/Person/Company |
| New Form Submission/Entry       | Add Contact to Group/Tag/List      |                                  |                                            |
|                                 | Update Contact/Lead/Person/Company |                                  |                                            |

View [example apps](https://zapier.com/apps/categories/contacts).

### Customer Support

| Triggers                         | Actions                            | Searches                         | Search or Creates                 |
| -------------------------------- | ---------------------------------- | -------------------------------- | --------------------------------- |
| Tag Added to Customer/User/Lead  | Create Ticket/Request/Conversation | Find Customer/User/Lead          | Find or Create Customer/User/Lead |
| New Ticket/Request               | Update Ticket/Request/Conversation | Find Ticket/Request/Conversation |                                   |
| New Customer/User/Lead           | Add/Remove Tag on User/Ticket/Lead |                                  |                                   |
| New Chat/Message/Conversation    | Send Message                       |                                  |                                   |
| New Closed/Finished Conversation | Update Customer/User/Lead/Company  |                                  |                                   |
|                                  | Add Comment/Note on Ticket/Request |                                  |                                   |
|                                  | Create Customer/User/Lead/Company  |                                  |                                   |

View [example apps](https://zapier.com/apps/categories/customer-support).

### CRM (Customer Relationship Management)

| Triggers                         | Actions                                          | Searches                         | Search or Creates                          |
| -------------------------------- | ------------------------------------------------ | -------------------------------- | ------------------------------------------ |
| \*New Record/Module Entry        | \*Create Record/Module Entry                     | \*Find Record/Module Entry       | \*Find or Create Record/Module Entry       |
| New Contact/Lead/Person/Company  | \*Update Record/Module Entry                     | Find Lead/Contact/Person/Company | Find or Create Lead/Contact/Person/Company |
| New Deal/Opportunity/Job/Inquiry | Create Contact/Lead/Person/Company               | Find Deal/Opportunity            | Find or Create Deal/Opportunity            |
| Tag Added or Removed             | Update Contact/Lead/Person/Company               |                                  |                                            |
| \*\*Updated Record/Module Entry  | Create Deal/Opportunity                          |                                  |                                            |
| Updated Deal/Opportunity Status  | Update Deal/Opportunity                          |                                  |                                            |
| Updated Lead Status              | Create Note                                      |                                  |                                            |
| Updated Project Status           | Add Contact/Lead to Campaign/Group               |                                  |                                            |
|                                  | Create Activity/Event                            |                                  |                                            |
|                                  | Add Tag to X (i.e. Contact, Record, Opportunity) |                                  |                                            |

\*Cover the most popular models of your platform at a minimum. You can create individual triggers, actions, and searches for each model such as, “New Customer”, “New Deal”, “Create Customer”, and “Create Deal”. Or create a singular trigger, action, and search that each support multiple models by requiring users to select which model they’re taking action on via an input field, such as “New Record” and “Create Record”.

\*\**Updated* triggers for CRMs are often most useful when users are able to specify which field(s) to watch for updates on, instead of triggering on any and all updates.

View [example apps](https://zapier.com/apps/categories/crm).

### Databases

| Triggers           | Actions           | Searches        | Search or Creates         |
| ------------------ | ----------------- | --------------- | ------------------------- |
| New Record/Row     | Create Record/Row | Find Record/Row | Find or Create Record/Row |
| Updated Record/Row | Update Record/Row |                 |                           |

\*Zapier searches automatically return only the first result in the response. To return multiple results, [return the set of results](/platform/build/response-types) as an array of objects under a descriptive key.

View [example apps](https://zapier.com/apps/categories/databases).

### Documents

| Triggers                  | Actions                         | Searches      | Search or Creates       |
| ------------------------- | ------------------------------- | ------------- | ----------------------- |
| New Document              | Create Document (from template) | Find Document | Find or Create Document |
| Document Completed/Signed | Add to Document                 |               |                         |
| Document Status Updated   |                                 |               |                         |
| Document Sent             |                                 |               |                         |

View [example apps](https://zapier.com/apps/categories/documents).

### Drip Email

| Triggers                | Actions                                                                 | Searches                              | Search or Creates                               |
| ----------------------- | ----------------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------- |
| New Subscriber/Prospect | Add/Remove Tag from Subscriber/Lead/Prospect/Contact                    | Find Subscriber/Lead/Prospect/Contact | Find or Create Subscriber/Lead/Prospect/Contact |
| New Tagged Subscriber   | Create Subscriber/Lead/Prospect/Contact                                 |                                       |                                                 |
| New Unsubscribe         | Update Subscriber/Lead/Prospect/Contact                                 |                                       |                                                 |
| New Reply               | Add Subscriber/Lead/Prospect/Contact to Sequence/Campaign/List/Audience |                                       |                                                 |

View [example apps](https://zapier.com/apps/categories/drip-emails).

### eCommerce

| Triggers                        | Actions         | Searches      | Search or Creates       |
| ------------------------------- | --------------- | ------------- | ----------------------- |
| New Order/Purchase/Sale         | Create Order    | Find Order    | Find or Create Customer |
| New Paid Order/Payment Received | Create Customer | Find Customer |                         |
| Abandoned Cart                  | Create Product  | Find Product  |                         |
| New Customer                    |                 |               |                         |

View [example apps](https://zapier.com/apps/categories/ecommerce).

### Email

| Triggers                         | Actions            | Searches   | Search or Creates      |
| -------------------------------- | ------------------ | ---------- | ---------------------- |
| New Email Matching Search        | Send Email         | Find Email | Find or Create Email   |
| New Email                        | Create Draft Email |            | Find or Create Contact |
| New Labeled/Tagged/Starred Email | Create Contact     |            |                        |
| New Attachment                   |                    |            |                        |
|                                  |                    |            |                        |

View [example apps](https://zapier.com/apps/categories/email).

### Email Newsletters

| Triggers                                    | Actions                                    | Searches                | Search or Creates                 |
| ------------------------------------------- | ------------------------------------------ | ----------------------- | --------------------------------- |
| New Subscriber/Contact in List/Audience/Tag | Create Subscriber/Contact                  | Find Subscriber/Contact | Find or Create Subscriber/Contact |
| New Subscriber/Contact                      | Send Email/Campaign                        |                         |                                   |
| Email Opened/Link Clicked                   | Add/Subscribe Contact to List/Audience/Tag |                         |                                   |
|                                             | Update Subscriber/Contact                  |                         |                                   |

View [example apps](https://zapier.com/apps/categories/email-newsletters).

### Event Management

| Triggers                  | Actions                        | Searches | Search or Creates |   |
| ------------------------- | ------------------------------ | -------- | ----------------- | - |
| New Registrant/RSVP       | Create Attendee/Add Registrant |          |                   |   |
| New Order/Booking/Ticket  | Create Event                   |          |                   |   |
| New Event                 | Create Event                   |          |                   |   |
| New Attendee/Contact/Lead |                                |          |                   |   |

View [example apps](https://zapier.com/apps/categories/event-management).

### File Management & Storage

| Triggers           | Actions       | Searches    | Search or Creates     |
| ------------------ | ------------- | ----------- | --------------------- |
| New File in Folder | Upload File   | Find File   | Find or Create Folder |
| New Folder         | Create Folder | Find Folder |                       |
| New File           | Create File   |             |                       |
| Updated File       | Move File     |             |                       |

View [example apps](https://zapier.com/apps/categories/files).

### Forms & Surveys

| Triggers                           | Actions                               | Searches                            | Search or Creates |
| ---------------------------------- | ------------------------------------- | ----------------------------------- | ----------------- |
| New Entry/Form Response/Submission | Create Entry/Form Response/Submission | Find Entry/Form Response/Submission |                   |
| New Lead                           | Send Form                             |                                     |                   |

View [example apps](https://zapier.com/apps/categories/forms).

### Fundraising

| Triggers                        | Actions                            | Searches                  | Search or Creates                   |
| ------------------------------- | ---------------------------------- | ------------------------- | ----------------------------------- |
| New Donation/Transaction/Pledge | Create Member/Donor/Contact        | Find Member/Donor/Contact | Find or Create Member/Donor/Contact |
| New Member/Donor/Contact        | Create Donation/Transaction/Pledge |                           |                                     |

View [example apps](https://zapier.com/apps/categories/fundraising).

### Marketing Automation

| Triggers                                       | Actions                                            | Searches                     | Search or Creates                      |
| ---------------------------------------------- | -------------------------------------------------- | ---------------------------- | -------------------------------------- |
| New Contact/Lead/Subscriber                    | Create Contact/Lead/Subscriber                     | Find Contact/Lead/Subscriber | Find or Create Contact/Lead/Subscriber |
| New Contact in List/Audience/Segment           | Create Opportunity/Deal/Order                      | Find Deal                    |                                        |
| Tag Added/Removed from Contact/Lead/Subscriber | Update Opportunity/Deal                            |                              |                                        |
| Updated Deal/Pipeline Stage                    | Update Contact/Lead/Subscriberv                    |                              |                                        |
| New Order/Purchase/Sale                        | Add Contact/Lead/Subscriber to Campaign/List/Group |                              |                                        |
| New Form Entry/Submission                      | Add/Remove Tag fro Contact/Lead/Subscriber         |                              |                                        |
| Updated Contact/Lead/Subscriber                |                                                    |                              |                                        |
|                                                |                                                    |                              |                                        |

View [example apps](https://zapier.com/apps/categories/marketing-automation).

### Notes

| Triggers                         | Actions        | Searches | Search or Creates |
| -------------------------------- | -------------- | -------- | ----------------- |
| New Note                         | Create Note    |          |                   |
| New Note in Notebook/Section/Tag | Append to Note |          |                   |

View [example apps](https://zapier.com/apps/categories/notes).

### Notifications

| Triggers                 | Actions                   | Searches | Search or Creates |
| ------------------------ | ------------------------- | -------- | ----------------- |
| New Notification/Message | Send Notification/Message |          |                   |

View [example apps](https://zapier.com/apps/categories/notifications).

### Online Courses

| Triggers                             | Actions                                           | Searches          | Search or Creates           |
| ------------------------------------ | ------------------------------------------------- | ----------------- | --------------------------- |
| New Order/Sale/Purchase/Subscription | Enroll User/Add User to Course                    | Find User/Student | Find or Create User/Student |
| New Enrollment                       | Create Credentials/Grant Access/Issue Credentials |                   |                             |
| Course/Lesson Completed              | Subscribe User to List/Add User to Group          |                   |                             |
| New User/Student                     | Unenroll User/Student                             |                   |                             |

View [example apps](https://zapier.com/apps/categories/it-operations-education).

### Payment Processing

| Triggers                                        | Actions                             | Searches                         | Search or Creates       |
| ----------------------------------------------- | ----------------------------------- | -------------------------------- | ----------------------- |
| New Payment or Payment Paid                     | Create Customer                     | Find Invoice/Payment/Transaction | Find or Create Customer |
| New Subscription/Order/Sale/Transaction/Invoice | Create Payment Link (if applicable) | Find Customer                    |                         |
| New Customer                                    | Update Customer                     | Find Subscription                |                         |
| Canceled Subscription/Order/Sale                |                                     |                                  |                         |
| Failed Payment                                  |                                     |                                  |                         |
| New Refund                                      |                                     |                                  |                         |

View [example apps](https://zapier.com/apps/categories/payment-processing).

### Phone & SMS

| Triggers                 | Actions           | Searches                       | Search or Creates      |
| ------------------------ | ----------------- | ------------------------------ | ---------------------- |
| New SMS/Message Received | Send SMS/Message  | Find Contact (by phone number) | Find or Create Contact |
| New Call                 | Create Contact    |                                |                        |
| New Recording            | Update Contact    |                                |                        |
| Call Completed           | Call Phone Number |                                |                        |

View [example apps](https://zapier.com/apps/categories/phone).

### Product Management

| Triggers                            | Actions                       | Searches | Search or Creates |
| ----------------------------------- | ----------------------------- | -------- | ----------------- |
| New Feedback/Form Submitted/Request | Create Note/Feature/Task/Idea |          |                   |
| New Note/Comment                    |                               |          |                   |

View [example apps](https://zapier.com/apps/categories/product-management).

### Project Management

| Triggers                         | Actions                             | Searches             | Search or Creates              |
| -------------------------------- | ----------------------------------- | -------------------- | ------------------------------ |
| New Task/Item/Issue              | Create Task/Item/Issue              | Find Task/Item/Issue | Find or Create Task/Item/Issue |
| Task/Item/Issue Status Changed   | Update Task/Item/Issue              | Find User            | Find or Create Project/Board   |
| New Label/Tag on Task/Item/Issue | Add Comment/Note to Task/Item/Issue | Find Project/Board   |                                |
| Completed Task/Item/Issue        |                                     |                      |                                |
| New Event/Activity               |                                     |                      |                                |
| Updated Task/Item/Issue          |                                     |                      |                                |

View [example apps](https://zapier.com/apps/categories/project-management).

### Proposal & Invoice Management

| Triggers                         | Actions                        | Searches                     | Search or Creates                  |
| -------------------------------- | ------------------------------ | ---------------------------- | ---------------------------------- |
| Proposal/Estimate/Quote Accepted | Create Client/Contact/Lead     | Find Client/Contact/Lead     | Find or Create Client/Contact/Lead |
| Proposal Signed                  | Create Proposal/Estimate/Quote | Find Proposal/Estimate/Quote |                                    |
| Proposal Won                     | Create Invoice                 |                              |                                    |
| Proposal Sent                    | Create Project                 |                              |                                    |
| New Proposal/Estimate/Quote      |                                |                              |                                    |

View [example apps](https://zapier.com/apps/categories/invoices).

### Scheduling & Booking

| Triggers                        | Actions                                | Searches                 | Search or Creates     |
| ------------------------------- | -------------------------------------- | ------------------------ | --------------------- |
| New Client/Customer             | Create Client/Customer                 | Find Booking/Appointment | Find or Create Client |
| New Booking/Appointment         | Create Booking/Appointment/Request/Job |                          |                       |
| New Cancellation                |                                        |                          |                       |
| Rescheduled Booking/Appointment |                                        |                          |                       |

View [example apps](https://zapier.com/apps/categories/scheduling).

### Server Monitoring

| Triggers                               | Actions               | Searches                     | Search or Creates |
| -------------------------------------- | --------------------- | ---------------------------- | ----------------- |
| New or Updated Ticket/Request/Incident | Create Alert          | Find Ticket/Request/Incident |                   |
| New Alert                              | Create Ticket/Request |                              |                   |

View [example apps](https://zapier.com/apps/categories/server-monitoring).

### Signatures

| Triggers                    | Actions                    | Searches               | Search or Creates |
| --------------------------- | -------------------------- | ---------------------- | ----------------- |
| Document/Contract Sent      | \*Create Document/Contract | Find Document/Contract |                   |
| Document/Contract Completed | Send Signature Request     |                        |                   |
| Document/Contract Signed    |                            |                        |                   |

\**Create Document/Contract* actions typically allow for users to select a template in the action’s setup.

View [example apps](https://zapier.com/apps/categories/signatures).

### Social Media Accounts

| Triggers                       | Actions                   | Searches | Search or Creates |
| ------------------------------ | ------------------------- | -------- | ----------------- |
| New Post/Message/Media (by me) | Create Post/Message/Media |          |                   |
| New Post/Message/Media         |                           |          |                   |

View [example apps](https://zapier.com/apps/categories/social).

### Spreadsheets

| Triggers    | Actions        | Searches                             | Search or Creates  |
| ----------- | -------------- | ------------------------------------ | ------------------ |
| New Row     | Create/Add Row | Find Row                             | Find or Create Row |
| Updated Row | Create Row(s)  | Find Row(s) (with line-item support) |                    |
|             | Update Row     |                                      |                    |

View [example apps](https://zapier.com/apps/categories/spreadsheets).

### Task Management

| Triggers | Actions     | Searches  | Search or Creates   |
| -------- | ----------- | --------- | ------------------- |
| New Task | Create Task | Find Task | Find or Create Task |
|          | Update Task |           |                     |

View [example apps](https://zapier.com/apps/categories/todo-lists).

### Team Chat

| Triggers                | Actions                     | Searches                             | Search or Creates |
| ----------------------- | --------------------------- | ------------------------------------ | ----------------- |
| New Message in Channel  | Send Channel Message        | Find User (by email, name, username) |                   |
| New Reaction on Message | Send Direct/Private Message |                                      |                   |

View [example apps](https://zapier.com/apps/categories/team-chat).

### Time Tracking Software

| Triggers                     | Actions           | Searches    | Search or Creates      |
| ---------------------------- | ----------------- | ----------- | ---------------------- |
| New Time Entry               | Start Time Entry  | Find Client | Find or Create Client  |
| New Time Entry/Timer Started | Create Time Entry |             | Find or Create Project |
|                              | Create Project    |             |                        |
|                              | Create Client     |             |                        |
|                              | Create Task       |             |                        |

View [example apps](https://zapier.com/apps/categories/time-tracking).

### Transactional Emails

| Triggers           | Actions      | Searches | Search or Creates |
| ------------------ | ------------ | -------- | ----------------- |
| Failed Delivery    | \*Send Email |          |                   |
| Bounced Email      |              |          |                   |
| Open/Clicked Event |              |          |                   |

\**Send Email* actions often allowed users to select and use a template in the action’s setup.

View [example apps](https://zapier.com/apps/categories/transactional-email).

### Transcription

| Triggers       | Actions                                   | Searches | Search or Creates |
| -------------- | ----------------------------------------- | -------- | ----------------- |
| New Transcript | Create Transcription/Upload Audio or File |          |                   |

View [example apps](https://zapier.com/apps/categories/transcription).

### Video & Audio

| Triggers                          | Actions                 | Searches   | Search or Creates |
| --------------------------------- | ----------------------- | ---------- | ----------------- |
| New Video (in Channel/Playlist)   | Upload Video            | Find Track |                   |
| New Track in Playlist/Saved Track | Add Listener/Subscriber |            |                   |
|                                   | Add Track to Playlist   |            |                   |

View [example apps](https://zapier.com/apps/categories/video).

### Video Conferencing

| Triggers       | Actions               | Searches | Search or Creates |
| -------------- | --------------------- | -------- | ----------------- |
| New Recording  | Create/Add Registrant |          |                   |
| New Registrant | Create Meeting        |          |                   |

View [example apps](https://zapier.com/apps/categories/video-calls).

### Webinars

| Triggers                             | Actions                        | Searches        | Search or Creates |
| ------------------------------------ | ------------------------------ | --------------- | ----------------- |
| New Registration/Registrant          | Create Registration/Registrant | Find Registrant |                   |
| New Attendee or Webinar/Event Joined | Register to an Event           | Find Session    |                   |
| Attendee Stays Until/% of time       |                                |                 |                   |

View [example apps](https://zapier.com/apps/categories/webinars).

### Website Builders

| Triggers                           | Actions                   | Searches | Search or Creates |
| ---------------------------------- | ------------------------- | -------- | ----------------- |
| New Form Submission/Review/Message | Create Post/Content       |          |                   |
| New Member/User/Lead               | Create/Invite User/Member |          |                   |
| New Post/Content                   |                           |          |                   |

View [example apps](https://zapier.com/apps/categories/cms).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we’ll connect you with the right resource or contact support.*


# Zapier trusted app developers

If you don't have the time/resources to dedicate towards building and launching your own integration, there are Zapier trusted developers who may be able to assist you. These are individuals/groups who have demonstrated their capabilities by satisfying our requirements for building apps on our Developer Platform, and we recommend as a substitute for building your integration in-house.

## Approved trusted Zapier app developers

<CardGroup cols={3}>
  <Card title="Left Hook" icon="bolt" href="https://lefthook.com/opportunities/zapier-public-app/" horizontal />

  <Card title="DataAutomation" icon="database" href="https://dataautomation.com/zapier-developer/" horizontal />

  <Card title="GetUWired" icon="plug" href="https://www.getuwired.com/zapier-partner-page/" horizontal />

  <Card title="Mercology" icon="chart-network" href="https://integration.mercology.net/" horizontal />

  <Card title="Connex Digital" icon="link" href="https://connex.digital/zapier-bpa/" horizontal />
</CardGroup>

If you're looking for help with Zap workflows and not building an app, contact one of our [Zapier Experts](https://zapier.com/experts/) who can help you out.

<Info>
  **Note :**

  *If you do work with one of our Zapier trusted developers to develop an app, make sure you develop a plan for continued support and maintenance, either using that developer, or with an in-house team who will take responsibility for the app.*
</Info>

## Requirements for Zapier trusted developers

At this time, we are not actively accepting new developers into the program, and we cannot provide a definitive timeline on whether we will do so in the future.

In order to qualify as a Zapier trusted developer, you have demonstrated your capabilities by satisfying the following requirements:

*   Develop and launch two Public apps that meet our Zapier integration branding and design guidelines  standards. This ensures that you're familiar with Zapier development, launch, promotion, and maintenance processes. You are responsible for finding clients who need Zapier integrations built.

*   The apps must have **sufficient complexity**, which includes a mix/quantity of triggers, actions, searches, and/or scripting. Barring this being present on the public apps, you must demonstrate your competence with these features in another way, perhaps on an invite only app.

*   You are responsive when handling maintenance needs (bug fixes and feature requests), including being receptive to us increasing standards on the Developer Platform and adding new features, or moving to a newer version of the Developer Platform.

*   In order to maintain your listing on this page, you must have continued involvement with the Developer Platform. This can take the form of new Public apps, or significant improvements or bug fixes to existing apps. Developers should have some significant activity every year to continue.

As a developer listed here, you may promote your status within your own channels (website, social media, etc). We will be happy to give feedback on that information, which we expect to appropriately reflect Zapier's features and your capabilities as a partner.

***

*Need help?* [Tell us about your problem](https://developer.zapier.com/contact) *and we'll connect you with the right resource or contact support.*


# Platform UI tutorial

This tutorial walks you through the process of building an integration on Zapier with authentication, a trigger and an action using the Platform UI.

## Prerequisites

*   You'll need a [Zapier account](https://zapier.com/sign-up).
*   If you haven't used Zapier before, you'll want to learn the basics in our [Zapier Getting Started Guide](https://zapier.com/learn/zapier-quick-start-guide/).
*   Familiarity with [APIs](https://zapier.com/resources/guides/apis)
*   Select an API with public documentation, whether your own app's API or an app you're familiar with as a user.

## 1. Start an integration

To add an integration:

*   Go to [https://developer.zapier.com/](https://developer.zapier.com/).
*   Log into your Zapier account.
*   Click **Start a Zapier Integration**.
*   Next, you will be ask to provide information about your integration:
    *   **Name**: Add the name of your integration.
    *   **Description**: Add a description the app in 140 characters or less.
    *   **Homepage URL**: Add the homepage URL of your app. The domain will be used as the email domain to allow team members to [join your integration as a collaborator](/platform/manage/add-team).
    *   **Logo**: Upload a square, transparent PNG image of your app logo. The image must be at least 256x256px in size, and doesn't include the app name. Learn more in [branding guidelines](/platform/publish/branding-guidelines).
    *   **Intended Audience**: From the dropdown menu, select if you are building a [public or private integration](/platform/quickstart/private-vs-public-integrations).
    *   **Role**: Select your relationship with the app you're integrating with Zapier from the dropdown menu.
    *   **Category**: Select how you would categorize your app from the dropdown menu.
*   Once you have completed adding details your integration, click **Create**.

## 2. Add authentication

[Authentication](/platform/build/auth) defines how Zapier's platform authenticates with the app's API you're connecting to, collecting and storing user data to allow access to their accounts. Zapier supports the following authentication schemes:

<CardGroup cols={3}>
  <Card title="API Key" icon="key" href="/build/apikeyauth" horizontal />

  <Card title="Basic Authentication" icon="lock" href="/build/basicauth" horizontal />

  <Card title="Digest" icon="shield" href="/build/digestauth" horizontal />

  <Card title="OAuth v2" icon="user-lock" href="/build/oauth" horizontal />

  <Card title="Session" icon="id-badge" href="/build/sessionauth" horizontal />
</CardGroup>

Refer to the API documentation for the app you're building with to choose the correct authentication scheme for your API. Add the authentication fields for the input form your users will see when connecting their app account to Zapier.

Move through each configuration step of your chosen authentication scheme, adding the relevant endpoint to be used as the authorization URL to exchange the credentials supplied by the user for authentication. Depending on your API's authentication method, you may also need to configure Zapier-provided application credentials in your app's developer settings.

## 3. Add a trigger

[Triggers](/platform/build/trigger) allow users to start Zapier workflows when something is added or updated in the app.

Add settings for how your trigger displays to users, any optional input fields, and connect the app's API in the API Configuration tab.

If the API you're building with supports REST Hooks, you can configure your trigger to create a unique subscription from every Zap to your app that will run in near real-time and send events to Zapier as they occur in your app.

Alternatively, configure a polling trigger that automatically polls the URL you've provided for new data every 1 to 15 minutes, depending on the user's Zapier plan. An appropriate polling endpoint will return results in reverse chronological order to make sure new or updated items can be found on the first page of the results. Polling an endpoint most commonly uses a GET request.

The default *Form Mode* allows you to add API endpoint URLs, choose the API call type, and include any authentication details and input form data with the API call.

The optional toggle into [*Code Mode*](/platform/build/code-mode) converts everything entered in the API request form to JavaScript code and is an advanced feature beyond the scope of this tutorial.

## 4. Add an action

[Actions](/platform/build/action) allow users to create, search, or update records in your app through your API. In general, delete actions are not recommended for integrations.

Add settings for how your action displays to users, and at least one input field and connect the app's API in the API Configuration tab.

Refer to the API documentation for your app to choose a frequently used record with an available endpoint and add a create action that will add an record in your app when tested. Creating a new record most commonly uses a POST request.

## 5. Test your integration

When building an integration, it's important to test the functionality of your authentication, triggers, and actions in the Platform UI and also in the [Zap editor](https://zapier.com/editor/). In the Zap editor, you'll see how users of your integration will create Zaps using your triggers, and actions.

Learn more about [testing your integration](/platform/build/test-auth).

## 6. (Optional) Validate your integration

[Run automated checks](/platform/publish/integration-checks-reference) to identify errors and recommendations on how to improve the user experience of your integration.

These checks are only required to pass if you are building a public integration, but they are also useful to identify suggestions for future updates to any integration.

## 7. (Optional) Invite team members

Integrations are not owned by individuals, but rather by teams. You can invite team members to collaborate with in the Platform UI as either an admin or a collaborator.

Learn more about [adding team members](/platform/manage/add-team) to your integration.

## 8. (Optional) Share your integration

Once you have at least one trigger or action, you'll want to share your integration with some users. Early feedback helps make sure you're building functionality that will be used.

You can [share your integration with users](/platform/manage/sharing) by a public link or by email.

Note: [Public integrations](/platform/quickstart/private-vs-public-integrations) can be accessed by any Zapier user and doesn't require users to be specially invited.

## 9. Modify your integration

If you need to make any changes to your integrations, you can do so by creating [a new version](/platform/manage/versions). Versions helps to track and manage changes to your integration over time.

Public integrations or private integrations with more than 5 users **must have** a new version to make any edits. It is still recommended to make changes for private integrations with fewer than 5 users in a new version as well and [consider Zapier's best practices](/platform/manage/planning-changes) when doing so.

## Learn more

*   Recommended features for your integration [by app category](/platform/quickstart/must-have-triggers-and-actions).
*   [Step-by-step tutorial](https://community.zapier.com/featured-articles-65/zapier-platform-ui-a-complete-guide-on-how-to-integrate-with-github-26298#post108889) that uses the GitHub API.
*   About [different types of support](/platform/quickstart/get-help) available to help you build your integration.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Platform UI vs Platform CLI

There are two different developer tools to build either private or public integrations with on the Zapier Developer Platform: Platform UI or Platform CLI.

## Platform UI

Platform UI is the visual way for users with API experience to build integrations in a web app editor. It allows for some advanced calls and response parsing when using Code mode; but is predominantly designed for builders more comfortable with a visual form editor than working in code.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a8c009d1109749b44052922f2a6ec9bc.png" alt="Zapier Platform UI" />
</Frame>

To get started, check out the following resources:

*   [Quick start guide to using Platform UI](/platform/quickstart/ui-tutorial)
*   [Tutorial using Github's API to build with the Platform UI](https://community.zapier.com/featured-articles-65/zapier-platform-ui-a-complete-guide-on-how-to-integrate-with-github-26298#post108889)

## Platform CLI

Platform CLI is a terminal-based app that allows builders to scaffold new integrations locally, using standard JavaScript in your local development environment and code editor. It allows for custom coding for API calls, middleware, file support and other advanced functionality. It's the preferred tool for engineers comfortable with a standard development workflow and collaborating in a local environment using GIT version control before pushing integration versions to the Zapier server. Platform CLI can be more difficult to use for non-engineers, but will likely be more efficient for an engineering team.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/27d28a5fdd0c878d7558b4abd4f106ec.png" alt="Zapier Platform UI" />
</Frame>

To get started, check out the following resources:

*   [Quick start guide to using Platform CLI](/platform/quickstart/cli-tutorial)
*   [Tutorial using Github's API to build with Platform CLI](https://developer.zapier.com/cli-guide/introduction)
*   [CLI documentation](/platform/build-cli/overview)

## Comparison between developer tools

You can accomplish the same goals and build equally powerful Zapier integrations with both Platform UI and Platform CLI. The best tool for your integration depends on your work style and integration needs.

Below you can review what is capable between both developers tools

| Authentication         | Platform UI           | Platform CLI          |
| ---------------------- | --------------------- | --------------------- |
| Basic Authentication   | <Icon icon="check" /> | <Icon icon="check" /> |
| Session authentication | <Icon icon="check" /> | <Icon icon="check" /> |
| API Key                | <Icon icon="check" /> | <Icon icon="check" /> |
| Custom                 | <Icon icon="check" /> | <Icon icon="check" /> |
| OAuth v1               |                       | <Icon icon="check" /> |
| OAuth v2               | <Icon icon="check" /> | <Icon icon="check" /> |
| Digest                 | <Icon icon="check" /> | <Icon icon="check" /> |

| Triggers                                   | Platform UI           | Platform CLI          |
| ------------------------------------------ | --------------------- | --------------------- |
| REST Hooks                                 | <Icon icon="check" /> | <Icon icon="check" /> |
| Polling triggers                           | <Icon icon="check" /> | <Icon icon="check" /> |
| Support for static webhooks                |                       |                       |
| Customize request handling with JavaScript | <Icon icon="check" /> | <Icon icon="check" /> |

| Search Actions                             | Platform UI           | Platform CLI          |
| ------------------------------------------ | --------------------- | --------------------- |
| Search or create functionality             | <Icon icon="check" /> | <Icon icon="check" /> |
| Customize request handling with JavaScript | <Icon icon="check" /> | <Icon icon="check" /> |

| Create Actions                             | Platform UI           | Platform CLI          |
| ------------------------------------------ | --------------------- | --------------------- |
| Customize request handling with JavaScript | <Icon icon="check" /> | <Icon icon="check" /> |

| Advanced                                       | Platform UI | Platform CLI          |
| ---------------------------------------------- | ----------- | --------------------- |
| Custom middleware                              |             | <Icon icon="check" /> |
| Resources                                      |             | <Icon icon="check" /> |
| File support                                   |             | <Icon icon="check" /> |
| Hydration                                      |             | <Icon icon="check" /> |
| Import and use NPM modules                     |             | <Icon icon="check" /> |
| Organize code with common functions            |             | <Icon icon="check" /> |
| Handling long running tasks via a callback URL |             | <Icon icon="check" /> |

| Testing and Workflow               | Platform UI           | Platform CLI          |
| ---------------------------------- | --------------------- | --------------------- |
| GUI with form-based editor         | <Icon icon="check" /> |                       |
| WYSIWYG form preview               | <Icon icon="check" /> |                       |
| Write custom automated test suites |                       | <Icon icon="check" /> |
| Add team members to project        | <Icon icon="check" /> | <Icon icon="check" /> |
| Manage testers                     | <Icon icon="check" /> | <Icon icon="check" /> |
| Monitor usage                      | <Icon icon="check" /> | <Icon icon="check" /> |
| View logs                          | <Icon icon="check" /> | <Icon icon="check" /> |
| Manage versions                    | <Icon icon="check" /> | <Icon icon="check" /> |
| Use custom source code manager     |                       | <Icon icon="check" /> |
| Export integration to CLI          | <Icon icon="check" /> | -                     |

If you are still unsure after reviewing our comparsion tables, Zapier advises to build with the Platform UI.

## Switching between developer tools

### Platform UI to Platform CLI

Yes, it is possible to switch your integration from Platform UI to Platform CLI.

You can [export](/platform/manage/export-cli) an existing Platform UI integration to Platform CLI. Once exported, you can customize your integration in your local development environment. You will still have access to view your integration in the Platform UI.

Before making this change, Zapier recommends learning more about possible user impacts when [making changes to your integration](/platform/manage/planning-changes).

### Platform CLI to Platform UI

It's not possible to directly export an integration from Platform CLI to Platform UI.

You would need to create a new integration **version** built in the Platform UI (do not create an entirely new app). Existing users would most likely need to manually update their Zaps to use the new version. Learn more about [this process and best practices to minimize user impact](/platform/manage/export-ui) and contact [Developer Support](https://developer.zapier.com/contact) with any questions.

***

*Need help?* [Tell us about your problem](https://developer.zapier.com/contact) *and we'll connect you with the right resource or contact support.*


# Zapier integration structure

[Zapier's Developer Platform](https://developer.zapier.com/) includes everything needed to build and manage a new Zapier integration. When you access your integration project by name, you'll see the left sidebar outlines the core project structure.

### Dashboard

*Dashboard* tab is used to see your Partnership level, active user total count, overall embed status and [integration health score](/platform/publish/partner-program#integration-health-score). You **must** be an [admin or collaborator](/platform/manage/add-team) to view an integration's dashboard.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/3b26f7098360a25ec055ab9be6713e9d.png" />
</Frame>

### Insights

*Insights* tab is used to monitor and analyze your integration's growth, usage, and detailed embed metrics. You **must** be an [admin or collaborator](/platform/manage/add-team) to view an integration's insights.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/81930868f966d933d1edb90cc61623bc.png" />
</Frame>

### Build

*Build* tab defines your integration:

*   *Version Overview* shows the outline of the version selected in the [dropdown](https://cdn.zappy.app/ca49500dc40cd1986693223661ab22b2.png)
*   *Authentication* sets how users authenticate with your API; using basic, API key, digest, session, or OAuth v2 authentication.
*   *Triggers* control how Zapier gets data from your API into a Zap, with `GET` HTTP calls or REST Hooks.
*   *Actions* control how Zapier sends data to your API, including:
    *   *Create Actions* to send new data from a Zap to your API, with `POST` or `PUT` HTTP calls to create or update items.
    *   *Search Actions* to perform lookups through your API using `GET` calls.
*   *Advanced* manages environment variables to store secret values your integration needs to communicate with your API, such as API keys or client IDs and secrets, or manage switching between staging and production environments in a version.

When planning your integration build, think through your integration and what features from your app your users might find the most valuable. Review [integration design examples](/platform/quickstart/must-have-triggers-and-actions) by app category for inspiration.

Walk through building an integration with the [UI tutorial](/platform/quickstart/ui-tutorial) or the [CLI tutorial](/platform/quickstart/cli-tutorial).

### Manage

*Manage* tab is where you access tools to maintain your integration:

*   *Versions* gives an [overview of each version's status](/platform/manage/versions), user activity and for published apps; the changelog included in a promotion
*   *Sharing* is where you [give users access](/platform/manage/sharing) to private versions via an invite link to all versions or an email invite to a specific version
*   *Manage Team* is used to [add team members](/platform/manage/add-team) to manage your integration
*   *Monitoring* gives access to [logs for requests](/platform/build/test-monitoring#steps) made to your API by your Zapier integration
*   *History* shows the 50 most recent audit logs for changes to your integration
*   *Bugs & Feature Requests* are reported by users for published apps and should be used to communicate on these issues with Zapier Developer Support

### Embed

*Embed* tab shows for [public integrations](/platform/quickstart/private-vs-public-integrations) only and describes a [variety of ways](https://platform.zapier.com/embed/full-zapier-experience) to surface your Zapier integration directly within your own product. It allows your users to search for apps that connect with yours, see popular workflows used with your app, and (most importantly) enables them to sign up for Zapier, build Zaps and edit them, too — without leaving your product.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# AI Actions

Zapier's [AI Actions](https://actions.zapier.com/) is an AI alpha product designed to work with natural language-based products. It leverages the Zapier platform, with over [6000 apps](https://zapier.com/apps). You can include the capabilities of Zapier's platform in your own product.

With the Natural Language Actions API, you can:

*   Access Zapier's platform of 6000+ apps within your own product.
*   Integrate with chatbots or [large language models](https://en.wikipedia.org/wiki/Large_language_model).
*   Power any integration project.

Learn more in our [AI Actions documentation](https://actions.zapier.com/).

## Get access to the AI Actions API

AI Actions API is available for any Zapier partner or developer to build on.

It's free to use by visiting our [Getting Started with AI Actions page](https://actions.zapier.com/).

> **Note**: By signing up for API access, you agree to [Zapier's Platform Agreement](https://zapier.com/platform/tos) and [Privacy Policy](https://zapier.com/privacy).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Zapier integration structure for an AI app

AI app integrations built on Zapier allow users to automate tasks using AI capabilities. Here are some common pain points and recommendations when building AI apps on Zapier.

## Pain Points

### 1. Long-Running Actions

**Pain Point:** Actions that take a long time to process can cause issues, especially with Zapier's execution time limits. Test steps in the Zapier editor are limited to 50 seconds, and live Zap executions default to 30 seconds.

**Recommendations:**

*   Use `z.generateCallbackUrl()` and `performResume`
    *   `z.generateCallbackUrl()`: This method generates a callback URL that your service can call once the long-running task is complete.
    *   `performResume`: This function allows the action to pause until the callback URL is called, resuming once the task is complete.
    *   **Why:** These tools help handle tasks that exceed Zapier's execution time limits by offloading the wait to an external service and resuming only when the task is done.
    *   [Documentation](/platform/reference/cli-docs#zgeneratecallbackurl)

### 2. Handling Samples for Long-Running Tasks

**Pain Point:** Generating accurate samples for long-running tasks can be tricky, especially during the initial setup.

**Recommendation:**

*   Use `bundle.meta.isLoadingSample`:
    *   When `bundle.meta.isLoadingSample` is true, return a simplified or cached version of the data that represents a typical response.
    *   **Why:** This approach ensures that users get a quick response during setup, avoiding delays caused by the actual long-running task.
    *   [Documentation](/platform/reference/cli-docs#bundlemeta)

### 3. Hiding Complex Fields in Actions

**Pain Point:** Complex configuration fields can overwhelm users, making the setup process cumbersome.

**Recommendation:**

*   Use Custom Input Fields:
    *   You can create an input field that depends on another. For example, you could create an “Advanced Features” input field at the bottom of all of your other ones that has the property `altersDynamicFields: true` so when it's updated, all of the other fields refresh. Then any additional input fields could be dependent on whether that “Advanced Features” input field is true, then display the more advanced ones that are not necessarily required for all users. (ie. `if (bundle.inputData.advanced === true)`)
    *   These custom input fields can hide complex fields behind simpler user interfaces.
    *   **Why:** Simplifying the user interface makes it more user-friendly, leading to a better experience and fewer setup errors.
    *   [Documentation](/platform/reference/cli-docs#customdynamic-fields)

### 4. Use-Case Specific Actions

**Pain Point:** Users may struggle with setting up generalized actions that require specific configurations.

**Recommendations:**

*   Create Use-Case Specific Actions:
    *   For example, a “ChatGPT Summarize Text” action can hide the configuration and prompt details behind the scenes. This can be accomplished by hardcoding a prompt into the action configuration itself and then just have the user input the text that needs to be summarized.
    *   Why: Pre-configured actions tailored to specific use cases streamline the user experience, making it easier for users to set up and use your app effectively.
*   Create Use-Case Specific Dropdown:
    *   For example, a “Send Prompt” action could have a dropdown of available “use-cases” that when selected, pre-fill input fields for the user. This can be accomplished by having a mapping of sorts for your use-cases and some complex logic using `altersDynamicFields: true` to then have all of your input fields as custom ones which are loaded in via functions based on the the use-case selected and the default of them pre-filled.
    *   **Why:** Pre-configured AI prompts tailored to specific use cases streamline the user experience, making it easier for users to set up and use your app effectively and for them to understand how the AI is working where they could then tweak the prompt/other input fields themselves if need be.
    *   [Documentation](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#fieldschema)

### 5. Value of Zap Templates

**Pain Point:** Users often need help figuring out how to set up Zaps for common use cases.

**Recommendation:**

*   Provide [Zap Templates](/platform/publish/zap-templates):
    *   Create templates for common use cases with pre-mapped variables and good starter prompts.
    *   **Why:** Templates serve as a starting point, helping users quickly set up their Zaps without having to configure everything from scratch.

By addressing these pain points with the recommended strategies, you can significantly improve the user experience and functionality of AI apps on the Zapier platform. For detailed guidance and support, always refer to the [Zapier Developer Documentation](https://platform.zapier.com/docs) or contact the [Zapier support team](https://developer.zapier.com/contact).

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Changelog





# CLI Reference





# Zapier integration structure for a CRM app

CRM (customer relationship management) apps are detailed databases that link contacts with companies, companies with deals, and more.

To add a CRM app integration in the Platform UI:

## Prerequisites

*   A [Zapier account](https://zapier.com/sign-up).
*   If you haven't used Zapier before, you'll want to learn the basics in our [Zapier Getting Started Guide](https://zapier.com/learn/zapier-quick-start-guide/).
*   Familiarity with your app's API documentation and available endpoints.
*   Review the [Platform UI Tutorial](/platform/quickstart/ui-tutorial).

## 1. Add triggers for common record types in the CRM

*   Check out the [common record types](/platform/quickstart/must-have-triggers-and-actions#crm-customer-relationship-management) used as triggers by other CRM apps.

*   Build separate triggers for new and updated records. *Updated* triggers for CRMs are most useful when users are able to specify which field(s) to watch for updates on, instead of triggering on any and all updates.

*   Include filter options if your app lets users add custom filters or views. Displaying an optional [dynamic dropdown](/platform/build/field-definitions#dynamic-dropdown) of the user's filters and ordering them by the most recently added is best practice.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/6bb177088b4a484c7b72842a05177d5b.png" />
</Frame>

## 2. Choose trigger type under API Configuration

### REST Hook trigger

*   If your app supports webhook subscriptions that can be manipulated through a REST API, use [REST hooks](/platform/build/trigger#rest-hook-trigger) for your trigger to send new records to Zapier as soon as they are added to the CRM.

*   Webhook triggers are marked as *Instant* [in the Zap Editor](https://cdn.zappy.app/6b696dfaf34664b181b6df651067cfd3.png).

*   When your user has many app records created in a short space of time, webhooks make your integration more robust, preventing the possibility that a high number of new records will exceed the page size of polling results.

*   With REST hooks, Zapier won't need to repeatedly poll your API to check for new responses.

### Polling trigger

*   When using a Polling trigger, and for the *Perform List* URL for REST Hook triggers, the Polling URL should return the most recent record created or updated (if the trigger is an ‘Updated Record' trigger)

*   If there are no recent new records, return an empty array. Zapier will then encourage the user to create a sample and re-test their trigger in the Zap Editor to finish mapping their Zap.

## 3. Additional considerations for Updated Record triggers

*   Users expect updated triggers to run whenever a record has new data added, or when relational data is added or removed.

*   For REST hook triggers, ensure that when relational data is reapplied to a record, your app sends Zapier that updated record.

*   For polling triggers, to account for [deduplication](/platform/build/deduplication), you will need to customize your API call's code to ensure each record has a unique primary key (usually an `id` field). This could mean [copying a unqiue timestamp](/platform/build/deduplication#custom-primary-keys) to the `id` field or [setting `primary` to true](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#how-does-deduplication-work) for some output fields.

### Include linked information in trigger response

*   CRM records are one part of a linked ecosystem of data. For example, if your trigger is “New Contact” and newly added contacts are linked to organizations, users expect to see full organization data, too.

*   Include both IDs for linked data in the response output so they can be mapped to your app's action steps, as well as human-readable data such as individual fields for their name and contact info.

*   If your record API endpoint does not automatically return additional linked data beyond the ID or name of the record, add custom code to your trigger API call to fetch relevant data linked to the record as well.

### Handle custom fields

*   Use \[pagination]\((/platform/build/trigger#how-to-use-pagination) to retrieve custom fields a user might expect.

### Handle tags

*   Return tags on records as either an array of strings or a comma-delimited list in a single string, for users to be be able to easily map them into other app actions.

## 4. Include common search actions

*   Users expect to have searches for common records available for CRM integrations and for those searches to have parity with the fields returned for those same record triggers. For example, a user might have a *New Contact* trigger, and then want to find out more information about the organization the contact is associated with by using a *Find Organization* search. If the *New Contact* trigger only provides the associated organization ID, it doesn't make sense for the *Find Organization* search to only allow searches for the organization name.

*   Configure the search to find an existing record, returning it in the same format the trigger returns records to give a consistent experience and allow the data to be used in the same manner in later actions.

*   Offer multiple search key and value options where applicable.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/8b3b564da528667a3b0fc57fb105edfa.gif" />
</Frame>

*   Prevent deduplication problems for unique fields (such as email) though, by offering only a single search query for that field instead of multiple options. This prevents users from searching on a different field, trying to create a new record, and seeing an error that a record with that email already exists.

*   If a record cannot be found, do not return an error. Instead return a `200` success response with an empty array. That way, users can pair the search with a *Create* action to let users search for records and create them if they don't exist. Users will see a halted exception instead of an error.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/5e0bd80c41a78719f140e2382454cf99.gif" />
</Frame>

\_ Read more about pairing [searches and creates](/platform/build/search-or-create).

## 5. Include common create actions

*   Check out the [common record types](/platform/quickstart/must-have-triggers-and-actions#crm-customer-relationship-management) other CRM apps have create actions for.

*   Use the standard *Create* or *Add* naming pattern to show users that this action creates a new record in your app

*   Add corresponding *Update* actions with available *Search* actions to make it easier for users to update the correct record.

*   Account for linked records in action input fields, so when your app's UI allows users to create a contact and link it to existing companies or stages, the same action is available through Zapier, via a [dynamic dropdown](/platform/build/field-definitions#dynamic-dropdown) to fetch linked record options and let users select them.

*   Optionally link a [search connector](https://cdn.zappy.app/103626b1313602fa33c33711ed44ff56.png) to linked record input fields if users need to have the Zap find the correct related record, and map the found ID to the input field. To add that user prompt you'll need to work in the [Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#search-powered-fields).

*   Do not have a single action create multiple records. When a user creates a new contact through Zapier, this should only create new contacts, and should not also create other linked records at the same time. Rather include a separate action to create the other linked record, along with a contact update action to link the contact after the other record is created. This reduces the chance for errors and record redundancy.

*   Account for [custom fields in action input fields](/platform/build/dynamic-field), displaying the human-readable label instead of the internal ID.

*   The first version of your integration doesn't need to have every possible action available, but add compatible functionality users expect or request in future updates.

## 6. Support in-app workflows

*   If new records created in your app get automatically added to in-app workflows, sequences, follow-ups, or campaigns, make sure records created via the API by Zaps will also get automatically added to workflows.

*   Optionally add a boolean field to your action input fields to let users select if they want this contact added to a workflow, or include a [dynamic dropdown](/platform/build/field-definitions#dynamic-dropdown) to select the workflow they want.

## 7. Test your triggers and actions

Make Zaps with the following criteria to ensure your app integration works as expected:

*   Add a record in your app with many custom fields, and create a Zap to watch for new records to make sure each field is included when it triggers and is mappable in subsequent steps
*   Create a multi-step Zap with at least one trigger, search, and action step where every step uses your app integration, to check the ease of linking one step to another.
*   If you've included the option to link records in your integration's actions, verify these are linked correctly when creating records via a Zap.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Custom Actions and API Requests Actions

[Custom Actions](https://help.zapier.com/hc/en-us/articles/16276574838925-App-Extensions-in-Zapier) and [API Requests](https://help.zapier.com/hc/en-us/articles/12899607716493-Set-up-an-API-request-action#prerequisites-0-0) are features that have been developed internally at Zapier, designed to help our mutual customers achieve the most value out of your app integration.

## What are Custom Actions and API Requests?

Custom Actions and API Requests enable Zapier users to build raw API requests with public APIs while leveraging your app integration's existing authentication protocol.

Since the launch, we've seen that the feature has been embraced and users are increasingly building and testing Custom Actions and API Requests. As customers build these requests, they may experience certain challenges with them – issues related to client permissions, malformed API URLs, or bad requests, for instance. These challenges, however, are neither unique nor unexpected, as they occur consistently in other similar settings like Zapier's built-in [Webhooks functionality](https://help.zapier.com/hc/en-us/sections/16074864820109-Webhooks-Code).

The Zapier support team stands ready to help users understand the feature and ensure it works as anticipated. However, it is essential for the user to correct and resolve the API error themselves, which is analogous to Zapier's support policy for Webhooks. This empowers users to receive the best experience from the platform while strengthening their skills.

## Can I enable Custom Actions and API Requests for my integration if it's not currently enabled?

As of July 1, 2024, we are not supporting Custom Actions or API Requests for new integrations. However, we are committed to enhancing the experience for all users. If you are interested in enabling Custom Actions or API Requests for your integration, we encourage you to contact our Platform Support Team via our [contact form](https://developer.zapier.com/contact). Your feedback is invaluable and helps us understand the significance of this feature for our partners.

## Can I opt-out of Custom Actions and API Requests if it's already enabled?

We currently aren't supporting an opt-out initiative. However, we are committed to bettering experiences for every user and encourage you to voice your concerns or feedback regarding this feature. Should you continue to experience any challenges or have specific pain points you're encountering, we would be glad to work on those to improve your experience.

We understand that you may have apprehensions regarding the potential support load, especially with respect to users building their own API requests. Be assured, we are dedicated to helping alleviate any impacts that this may cause. The primary goal is to empower users with more control over their API requests, presenting them with a richer experience and a superior level of customization. We believe our users derive significant value from these direct API requests.

Do not hesitate to reach out with more questions and feedback via our [contact form](https://developer.zapier.com/contact). We are always open to dialogue aimed at improving our services and our working relationship with our partners.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Zapier integration structure for a forms app

Form and survey app integrations built on Zapier allow users to connect mobile data collection forms to send the responses into other apps as new contacts, document templates, messages, and more.

To add a form or survey app integration in the Platform UI:

## Prerequisites

*   A [Zapier account](https://zapier.com/sign-up).
*   If you haven't used Zapier before, you'll want to learn the basics in our [Zapier Getting Started Guide](https://zapier.com/learn/zapier-quick-start-guide/).
*   Familiarity with your app's API documentation and available endpoints.
*   Review the [Platform UI Tutorial](/platform/quickstart/ui-tutorial).

## 1. Add a new entry/response trigger

*   Start a new integration at [https://developer.zapier.com/](https://developer.zapier.com/) for your app
*   Create a *New Form Entry/Submission* or *New Survey Response* trigger
*   Use the standard *New* naming pattern to show users that this trigger retrieves new entries, not existing ones.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/d5a605fafa8aaa679f86bca3655ce0d9.png" />
</Frame>

## 2. Configure selection of a specific form

*   Add a [dynamic dropdown](/platform/build/field-definitions#how-to-add-dynamic-and-custom-fields) to retrieve form names exactly as they are listed in your app.
*   If the endpoint the dropdown uses allows for sorting, you can `sort_by` the create date to order the dropdown list by the most recently added form to make it easier for users to select the form they need.
*   Make the selection of a form required so the trigger only watches for results from this one form
*   If you keep the form field optional, it can be used for responses to any form; and you'll need to add help text to let users know what to expect if they do not select a form.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/79158aee580d74c8241e1375e73f91db.gif" />
</Frame>

## 3. Format the responses the trigger receives

### Question and answer fields

*   Form fields or survey questions must include the same name within Zapier as that form field shown in your app's user interface.

*   In a form that asks for contact information, the question/field “What's your email?” might internally use an ID like `1839dod38k01` or a generic label such as `Question 1` in your app's API. The user should also see the field's friendly name within Zapier so they'll recognize which field to use when mapping data to action steps. The raw field ID, the `key`, can still be shown beside the friendly name.

*   For example, a common use case is to log responses in spreadsheets, with each answer field mapped to different columns. Showing only the key `q_1_answer` for a question is not intuitive for users. Include the label with the full question.

*   Include the question number as a prefix to the label if possible, to help users find specific questions and fields.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/ae9c0c297710e3bce8385f5d5a7b468f.png" />
</Frame>

### Multiple choice fields

*   Include the question title, answer and value as separate fields where possible. Use the same label to group these together in the trigger output.

*   When a multiple choice question is based on a range of values (e.g., from “not really” to “very”), it is useful to return the choice values (such as `1` to `5`) with the actual answer. This can be especially helpful for users wishing to track responses in a spreadsheet.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/033953ce0537c18a6efbe47f783ddf5a.png" />
</Frame>

### Multi-select fields

*   When users can select multiple responses (like a list of checkboxes) in a form or survey, it can be challenging to use this data within a trigger.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e922d705c9f9d61c46f32b31ba03e53f.png" />
</Frame>

*   When possible, return these responses individually by splitting each individual response to the question into its own field, with a value if it is selected by the responder and a blank value if not selected. This makes mapping Zap steps simpler because users can map all responses into one field separated by the correct delimiter, or can take action on specific responses when they are present with the user of [Filters](https://help.zapier.com/hc/en-us/articles/8496276332557-Add-conditions-to-Zaps-with-filters) or [Paths](https://zapier.com/features/paths).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/a71b26a4a333fa9cb6b4a8c77607c426.png" />
</Frame>

*   Depending on your app's API response fields, you might instead opt to return the question's selected values in a single, comma separated field. Users can then split the values themselves with Zapier's [Formatter](https://help.zapier.com/hc/en-us/articles/8496030096013-How-to-use-Formatter-Functions#using-split-text-0-3) tool, or could map the all the values together to a later step's field.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/02fd5f03f00a7e3c6953c95366013473.png" />
</Frame>

### Date fields

*   Return the timestamp when the response was completed in [ISO 8601 format](/platform/publish/branding-guidelines#output-data). Also return any dates users can select in the form or survey in ISO 8601 format.

*   When possible, also include a human-friendly date for the response completion and any other selected dates in the response.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/762d2661a3da2d4a689052e316091f42.png" />
</Frame>

### File attachments

*   When users can attach files to form responses, include those in the response data as a publicly accessible URL for the file.

*   You could configure Zapier to request the full file via [file dehydration](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#file-dehydration) but you'll need to then work in the [Platform CLI](/platform/quickstart/cli-tutorial).

## 4. Choose trigger type under API Configuration

### REST Hook trigger

*   If your app supports webhook subscriptions that can be manipulated through a REST API, use [REST hooks](/platform/build/trigger#rest-hook-trigger) for your trigger to send new form entries to Zapier as soon as the form is filled out.

*   Webhook triggers are marked as *Instant* [in the Zap Editor](https://cdn.zappy.app/6b696dfaf34664b181b6df651067cfd3.png).

*   When your user has many form entries in a short space of time, webhooks make your integration more robust, preventing the possibility that a high number of new entries will exceed the page size of polling results.

*   With REST hooks, Zapier won't need to repeatedly poll your API to check for new responses.

### Polling trigger

*   When using a Polling trigger, and for the *Perform List* URL for REST Hook triggers, the Polling URL should return the most recent form entry for the chosen form.

*   If there are no entries for the selected form, return an empty array. Zapier will then encourage the user to create a sample then re-test their trigger in the Zap Editor to finish mapping their Zap.

## 5. Handle Sample data

*   Zapier requests sample results for every trigger and action when users select *Skip test* if the test API call returns no data.

*   Since a form app has dynamic fields unique to each user, it's impossible to define sample data that work for every form.

*   Instead, include only the common form fields that would be present for every result, such as form ID and timestamp, for the trigger's static sample data.

## 6. Test your triggers

Make Zaps with the trigger for the following criteria to ensure your app integration works as expected:

*   A triggering form that already has completed entries, where you should check that your polling URL is returning the latest form entry first (reverse chronological).
*   A brand new triggering form with no entries, to make sure your form sends an appropriate error when no results exists.
*   A triggering form that is composed of required and optional questions, to make sure all the questions are mappable into later action steps, regardless of whether the latest response only had a few questions answered.
*   A triggering form that has questions with multi select answers, where you should choose one or more options when sending a response in your app, to check that all the values come through.
*   Turn on the Zaps and submit new entries to each form to check that the Zap triggers and includes the expected response.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Scripting in converted Legacy Web Builder Integrations

This guide provides instructions on editing and maintaining existing scripting methods for legacy web builder integrations that have been converted to either the Platform UI or Platform CLI.

## Introduction

> **Note**: This guide isn't for new integrations built in Platform CLI or Platform UI. For new integrations, use the [Platform UI](/platform/quickstart/ui-tutorial) or the [Platform CLI](/platform/reference/cli-docs) to build an integration in code.

If you are creating new functionality, check maintaining your converted integration for the best way forward.

Much like [Code Mode](/platform/build/code-mode), Zapier's Web Builder Scripting was the previous way to allow you to directly manipulate the requests and responses that are exchanged between your app's API and Zapier. It continues to be supported using both the Platform UI and CLI.

## Access and edit scripting

For integrations converted to Platform UI, you can access and edit these scripting methods by:

1.  Log into the [Platform UI](https://zapier.com/app/developer).
2.  Select your **integration**.
3.  In the *Build* section in the left sidebar, click **Advanced**.
4.  Click the **Legacy Web Builder** tab.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/e776d2b6349552e6347ca2757f4d06c1.png" />
</Frame>

For integrations converted to Platform CLI, you can access and edit these scripting methods in the **Scripting.js** file. Its default location in the root directory of your CLI integration.

Every converted Web Builder integration will have a root module `Zap` defined in this Scripting.js file. By default, it is a blank JavaScript object. You add to it by specifying one or more of the [available methods](/platform/reference/legacy-scripting#available-methods). Each method accepts a single variable called `bundle`, which is a JSON serializable object. The content of the bundle varies depending on the method you are implementing. The output of your method must also be a serializable object.

Below is an example of implementing a method to be a pass-through:

```javascript
var Zap = {
my_trigger_pre_poll: function(bundle) {
// your code to modify bundle.request before sent
return bundle.request;
}
}
```

**Note**: All code written in the Scripting API must adhere to [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode), which is a subset of Javascript. Any issues with your code that violate this will be shown in the Scripting API editor with red X marks in the sidebar. The code will run in the current Node.js used for the platform - you can track this [here.](https://github.com/zapier/zapier-platform/blob/main/CHANGELOG.md)

## Available methods

There are various methods for manipulating requests Zapier makes to your API. Below is the complete list of methods you can use in scripting. You may provide any, all, or none of these methods.

### Trigger methods

Many of the trigger methods follow a naming pattern of key + method name, where key is the key given to the trigger when you created it. Below, Zapier uses the convention of `KEY` as the placeholder for the trigger's actual key. For example, if you define a trigger with the key “my\_trigger” and want to implement the pre\_poll method, you would write a method called `my_trigger_pre_poll`.

### Polling

#### `KEY_pre_poll`

Runs before the request to the polling URL can modify the request before it is sent.

```javascript
var Zap = {
KEY_pre_poll: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap
     bundle.meta: <object> # extra runtime information you can use

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

See [bundle.request](/platform/reference/legacy-scripting#prepared-request-via-bundlerequest).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

#### `KEY_post_poll`

It runs after Zapier receives a response from the polling URL. Can parse the response to format the data that enters Zapier or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will automatically throw for status codes 4xx and 5xx after the method runs.

```javascript
var Zap = {
KEY_post_poll: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from KEY_pre_poll bundle>

     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap
     bundle.meta: <object> # extra runtime information you can use

The response should be JSON serializable:
[
<object>, # with unique 'id' key
<object> # with unique 'id' key
]
\*/
return [];

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

#### `KEY_poll`

It runs in place of pre\_poll and post\_poll. When you get a bundle, you are expected to make the request and return a list of results or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will **not** throw for status codes like 4xx and 5xx automatically!

```javascript
var Zap = {
KEY_poll: function(bundle) {
/\*
Arguments:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap
     bundle.meta: <object> # extra runtime information you can use

     If you include a callback in the arguments, you can also perform async:
       callback(err, response)

You must make the request yourself. The response should be JSON serializable:
[
<object>, # with unique 'id' key
<object> # with unique 'id' key
]
\*/
return []; // or callback(null, [])

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### Static and REST hooks

#### `KEY_catch_hook`

It runs when Zapier receive a static or subscription hook from your API and can parse the response to format the data that enters Zapier.

```javascript
var Zap = {
KEY_catch_hook: function(bundle) {
/\*
Argument:
bundle.request.method: <str> # 'GET', 'POST', 'PATCH', 'PUT', 'DELETE'
bundle.request.headers: <object>
bundle.request.querystring: <str> # parse with \$.param(bundle.request.querystring)
bundle.request.content: <str>

     bundle.cleaned_request: <object> or <array> # our best guess at parsing
                                                 # and combining the request
                                                 # (including handling JSON & XML).
     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap

The response should be either a JSON serializable array...
[
<object>,
<object>
]

...or a single object:
<object>
\*/
return []; // or {}

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### REST hooks and notification REST hooks

#### `pre_subscribe`

It runs before Zapier subscribes.

```javascript
var Zap = {
pre_subscribe: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "POST"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>
bundle.request.data: <string>

     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the data from trigger fields
     bundle.target_url: <string> # our unique Zapier url for this subscription
     bundle.event: <string> # the event being subscribed to
     bundle.zap: <object> # info about the zap

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more in [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's legacy web builder, and is mostly incompatible with Platform CLI and Platform UI).

#### `post_subscribe`

It runs after Zapier subscribes. It is exclusively for storing results that are needed later for pre\_unsubscribe.

```javascript
var Zap = {
post_subscribe: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from pre_subscribe bundle>

     bundle.target_url: <string> # our unique Zapier url for this subscription
     bundle.event: <string> # the event (if any) that was subscribed to
     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the data from trigger fields
     bundle.zap: <object> # info about the zap

The response should be JSON serializable, you'll have access to it as
subscribe_data in the unsubscribe call. Normally you'd store some state
about the hook resource you created, for example, some apps need
an ID to locate and unsubscribe from a hook.

\*/
return ""; // or {}, or []

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

#### `pre_unsubscribe`

It runs before Zapier unsubscribes.

```javascript
var Zap = {
pre_unsubscribe: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "POST"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>
bundle.request.data: <string>

     bundle.target_url: <string> # our unique Zapier url for this subscription
     bundle.subscribe_data <json> # any data you returned from post_subscribe
     bundle.event: <string> # the event (if any) being unsubscribed from
     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the data from trigger fields
     bundle.zap: <object> # info about the zap

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more in [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### Notification REST hooks

#### `KEY_pre_hook`

It runs before the consuming call.

```javascript
var Zap = {
KEY_pre_hook: function(bundle) {
/\*
Argument:
bundle.request.method: <string>
bundle.request.headers: <object>
bundle.request.querystring: <string> # parse using \$.param(bundle.request.querystring)
bundle.request.content: <string>

     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more in [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

#### `KEY_post_hook`

It runs after Zapier receive a response from the consuming call and can parse the response to format the data that enters Zapier or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will automatically throw for status codes 4xx and 5xx after the method runs.

```javascript
var Zap = {
KEY_post_hook: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from KEY_pre_hook bundle>

     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[
<object>,
<object>
]
\*/
return [];

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### Available to all triggers

#### `KEY_pre_custom_trigger_fields`

It runs before the request to the custom field URL (if provided).

> Note: Although this method does not end with `result_fields` like there are for [actions](#key_pre_custom_action_fields) and [searches](#key_pre_custom_search_fields) it does in fact define custom fields and labels for the **result** (sample) of the trigger and not for its **Edit Template** step in the Zap editor.

```javascript
var Zap = {
KEY_pre_custom_trigger_fields: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more in [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

#### `KEY_post_custom_trigger_fields`

Runs after the response for custom fields is received. Can parse the response to format the data that enters Zapier or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx after the method runs automatically.

> Note: Although this method does not end with `result_fields` like there are for [actions](#key_pre_custom_action_fields) and [searches](#key_pre_custom_search_fields) it does in fact define custom fields and labels for the **result** (sample) of the trigger and not for its **Edit Template** step in the Zap Editor.

```javascript
var Zap = {
KEY_post_custom_trigger_fields: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from KEY_pre_hook bundle>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.trigger_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[

# `type` can be unicode, int, bool

# `key` should be unique and match a key found in the JSON representation Zapier receive from your API

# `label` is a human-readable name Zapier can give this field in the UI

{'type': <str>, 'key': <str>, 'label': <str>}
]
\*/
return [];

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

## Action Methods

Action methods follow a naming pattern of key + method name, where key is the key given to the action when you created it. Below, Zapier use the convention of `KEY` as the placeholder for the action's actual key. For example, if you define an action with the key “my\_action” and you want to implement the pre\_write method, you would write a method called `my_action_pre_write`.

### `KEY_pre_write`

Runs before the request to the action URL, can modify the request before it is sent.

```javascript
var Zap = {
KEY_pre_write: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "POST"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>
bundle.request.data: <string>
bundle.request.files: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.action_fields: <object> # pruned and replaced users' fields
     bundle.action_fields_full: <object> # all replaced users' fields
     bundle.action_fields_raw: <object> # before Zapier replace users' variables
     bundle.zap: <object> # info about the zap

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_post_write`

Runs after Zapier receive a response from the action endpoint, can modify the response or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx after the method runs automatically. *Note:* If the action occurs as part of a search-or-create Zap, the output of this method is *not* exactly what the user sees. In that case, the action will be followed up with a request to fetch the written record, and Zapier will present the user with the output from that follow-up request. If you need to modify the returned data in that scenario, use `_post_read_resource`.

```javascript
var Zap = {
KEY_post_write: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from KEY_pre_write bundle>

     bundle.auth_fields: <object>
     bundle.action_fields: <object> # pruned and replaced users' fields
     bundle.action_fields_full: <object> # all replaced users' fields
     bundle.action_fields_raw: <object> # before Zapier replace users' variables
     bundle.zap: <object> # info about the zap

The response will be used to give the user more fields to use
in the next step of the Zap. Please return a JSON serializable object.

return <object>;
\*/

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_write`

Runs in place of pre\_write and post\_write. You get a bundle and are expected to make the request and return the appropriate response or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will **not** throw for status codes like 4xx and 5xx automatically! \_Note:\_ If the action occurs as part of a search-or-create Zap, the output of this method is *not* exactly what the user sees. In that case, the action will be followed up with a request to fetch the written record, and Zapier will present the user with the output from that follow-up request. If you need to modify the returned data in that scenario, use `_post_read_resource`.

```javascript
var Zap = {
KEY_write: function(bundle, [callback]) {
/\*
Arguments:

     bundle.request.method: <string> # "POST"
     bundle.request.url: <string>
     bundle.request.auth: <array>
     bundle.request.headers: <object>
     bundle.request.params: <object>
     bundle.request.data: <string>
     bundle.request.files: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.action_fields: <object> # pruned and replaced users' fields
     bundle.action_fields_full: <object> # all replaced users' fields
     bundle.action_fields_raw: <object> # before Zapier replace users' variables
     bundle.zap: <object> # info about the zap

If you include a callback in the arguments, you can also perform async:
callback(err, response)

The response will be used to give the user more fields to use
in the next step of the Zap. Please return a JSON serializable object.

return <object>;
\*/
// your code to modify bundle.request before sent
var response = z.request(bundle.request);
return z.JSON.parse(response.content);

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_pre_custom_action_fields`

Runs before the request to the custom field URL (if provided).

```javascript
var Zap = {
KEY_pre_custom_action_fields: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.action_fields: <object> # the raw action fields (if applicable)
     bundle.zap: <object> # info about the zap (details below)

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_post_custom_action_fields`

Runs after the response for custom fields is received. Can parse the response to format the data that enters Zapier or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx after the method runs automatically.

```javascript
var Zap = {
KEY_post_custom_action_fields: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from KEY_pre_custom_action_fields bundle>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.action_fields: <object> # the fields provided by the user during setup
     bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[

# `type` can be unicode, int, bool

# `key` should be unique, and will be the "key" in "key: value" in the POST

# `help_text` and `label` are also available

{'type': <str>, 'key': <str>}
]
\*/
return []; // return fields in the order you want them displayed in the UI. They'll be appended after the regular action fields

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_custom_action_fields`

Allows you to completely take over the handling of retrieving and processing field metadata for custom action fields. Pre- and Post- methods will not be called if you've implemented this method. This method will be passed a bundle and must return an array of custom field metadata. If a request to an endpoint fails, you will need to throw an exception - the platform will not do it automatically.

```javascript
var Zap = {KEY_custom_action_fields: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # GET
bundle.request.url: <string> # ""
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

       bundle.url_raw: <string> # ""
       bundle.auth_fields: <object>
       bundle.action_fields: <object> # the raw action fields (if applicable)
       bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[

           # "type": "unicode",
           # "key": "json_key", // the field "name", will be used to construct a label if none is provided
           # "required": false, // whether this field must be filled out. defaults to true
           # "label": "Pretty Label", // optional
           # "help_text": "Helps to explain things to users.", // optional
           # "choices": { // optional
               "raw": "label"
           } // can also be a flat array if raw is the label
           {'type': <str>, 'key': <str>}

        ]
      */
      return []; // return fields in the order you want them displayed in the UI. They'll be appended after the regular action fields

}

}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_custom_action_result_fields`

It allows you to completely take over the handling of retrieving and processing field metadata for custom fields when users are working with *results* of your action in the Zap editor. Pre- and Post- methods will not be called if you've implemented this method. This method will be passed a bundle and must return an array of custom field metadata. If a request to an endpoint fails, you will need to throw an exception - the platform will not do it automatically.

```javascript
var Zap = {KEY_custom_action_result_fields: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

       bundle.request: <original object from KEY_pre_custom_action_fields bundle>

       bundle.url_raw: <string>
       bundle.auth_fields: <object>
       bundle.action_fields: <object> # the raw action fields (if applicable)
       bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[

# `type` can be unicode, int, bool

# `key` should be unique, and will be the "key" in "key: value" in the response content

# `label` is also available

{'type': <str>, 'key': <str>, 'label': <str>}
]
\*/
return []; // return fields in the order you want them displayed in the UI. They'll be appended after the regular action fields
}

}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_pre_custom_action_result_fields`

Runs before the request to the custom response fields URL (if provided).

```javascript
var Zap = {
KEY_pre_custom_action_result_fields: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

       bundle.url_raw: <string>
       bundle.auth_fields: <object>
       bundle.action_fields: <object> # the raw action fields (if applicable)
       bundle.zap: <object> # info about the zap (details below)

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_post_custom_action_result_fields`

Runs after the response for custom response fields is received. Can parse the response to format the data that enters Zapier or throw an exception [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx after the method runs automatically.

```javascript
var Zap = {
KEY_post_custom_action_result_fields: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

       bundle.request: <original object from KEY_pre_custom_action_fields bundle>

       bundle.url_raw: <string>
       bundle.auth_fields: <object>
       bundle.action_fields: <object> # the raw action fields (if applicable)
       bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[

# `type` can be unicode, int, bool

# `key` should be unique, and will be the "key" in "key: value" in the response content

# `label` is also available

{'type': <str>, 'key': <str>, 'label': <str>}
]
\*/
return []; // return fields in the order you want them displayed in the UI. They'll be appended after the regular action fields
}

}
```

## Search Methods

Search methods follow a naming pattern of key + method name, where key is the key given to the search when you created it. Below, Zapier use the convention of `KEY` as the placeholder for the search's actual key. For example, if you define an search with the key “my\_search” and you want to implement the pre\_search method, you would write a method called `my_search_pre_search`.

### `KEY_pre_search`

Runs before the request to the search URL, can modify the request before it is sent.

```javascript
var Zap = {
KEY_pre_search: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.search_fields: <object> # pruned and replaced users' fields
     bundle.zap: <object> # info about the zap

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_post_search`

Runs after Zapier receive a response from the search endpoint, can modify the response or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx after the method runs automatically. *Note:* The output of the method is *not* exactly what the user sees. Zapier follow searches up with requests for the individual resources and present the user with the output from those follow-up requests. If you wish to modify the number (or ordering) of the search results, use `_post_search`. If you wish to modify the data the user sees, use `_post_read_resource`. One other thing to be aware of is that searches must return an *array* of objects, so if your search endpoint returns a single object, you can use this method to wrap your object in an array.

> Note we'll only use the first object in the array for now, so if you can add optional fields to help narrow the search down, it's a great idea.

```javascript
var Zap = {
KEY_post_search: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from KEY_pre_search bundle>

     bundle.auth_fields: <object>
     bundle.search_fields: <object> # pruned and replaced users' fields
     bundle.zap: <object> # info about the zap

The response is an array, which may contain zero or more matches.

If multiple matches are found, sort the array with the "best match" first.

Use the available exceptions to vary errors.
_/
return [ ]; // no matches
/_ --- or --- _/
return [ { ... } ]; // return a single match
/_ --- or --- \*/
return [ { ... }, { ... }, ... ]; // several matches, with "best match" first

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_search`

Runs in place of pre\_search and post\_search. You get a bundle and are expected to make the request and return the appropriate response or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will **not** throw for status codes like 4xx and 5xx automatically! Note: The output of the method is not exactly what the user sees. Zapier follow searches up with requests for the individual resources and present the user with the output from those follow-up requests. If you wish to modify the number (or ordering) of the search results, you can do that inside `_search`. If you wish to modify the data the user sees, use `_post_read_resource`.

> Note we'll only use the first object in the array for now, so if you can add optional fields to help narrow the search down, it's a great idea.

```javascript
var Zap = {
KEY_search: function(bundle, [callback]) {
/\*
Arguments:

     bundle.request.method: <string> # "GET"
     bundle.request.url: <string>
     bundle.request.auth: <array>
     bundle.request.headers: <object>
     bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.search_fields: <object> # pruned and replaced users' fields

     bundle.zap: <object> # info about the zap

If you include a callback in the arguments, you can also perform async:
callback(err, response)

The response will be used to give the user more fields to use
in the next step of the Zap. Please return a JSON serializable object.
\*/

return {...}; // or callback(null, {...})

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_pre_custom_search_fields`

Runs before the request to the custom field URL (if provided).

```javascript
var Zap = {
KEY_pre_custom_search_fields: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.search_fields: <object> # the raw search fields (if applicable)

     bundle.zap: <object> # info about the zap (details below)

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_post_custom_search_fields`

Runs after the response for custom fields is received. Can parse the response to format the data that enters Zapier or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx after the method runs automatically.

```javascript
var Zap = {
KEY_post_custom_search_fields: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from KEY_pre_custom_search_fields bundle>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[

# `type` can be unicode, int, bool

# `key` should be unique, and will be the "key" in "key: value" in the POST

# `help_text` and `label` are also available

{'type': <str>, 'key': <str>}
]
\*/
return []; // return fields in the order you want them displayed in the UI. They'll be appended after the regular search fields

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_custom_search_fields`

Allows you to completely take over the handling of retrieving and processing field metadata for custom search action fields. Pre- and Post- methods will not be called if you've implemented this method. This method will be passed a bundle and must return an array of custom field metadata. If a request to an endpoint fails, you will need to throw an exception - the platform will not do it automatically.

```javascript
var Zap = {KEY_custom_search_fields: function(bundle)
{
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

     bundle.auth_fields: <object>
     bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[

# `type` can be unicode, int, bool

# `key` should be unique, and will be the "key" in "key: value" in the POST

# `help_text` and `label` are also available

{'type': <str>, 'key': <str>}
]
\*/

return []; // return fields in the order you want them displayed in the UI. They'll be appended after the regular search fields
}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_custom_search_result_fields`

Allows you to completely take over the handling of retrieving and processing field metadata for custom fields when users are working with *results* of your search action in the Zap editor. Pre- and Post- methods will not be called if you've implemented this method. This method will be passed a bundle and must return an array of custom field metadata. If a request to an endpoint fails, you will need to throw an exception - the platform will not do it automatically.

```javascript
var Zap = {
KEY_custom_search_result_fields: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

           bundle.request: <original object from KEY_pre_custom_search_fields bundle>

           bundle.url_raw: <string>
           bundle.auth_fields: <object>
           bundle.zap: <object> # info about the zap
      The response should be JSON serializable:
          [
              # `type` can be unicode, int, bool
              # `key` should be unique, and will be the "key" in "key: value" in the response content
              # `label` should be provided
              {'type': <str>, 'key': <str>, 'label': <str>}
          ]
       */
       return []; // return fields in the order you want them displayed in the UI. They'll be appended after the regular search fields

}

}
```

> **Note**: This code is only valid for Zapier's legacy web builder, and is mostly incompatible with Platform CLI and Platform UI).

### `KEY_pre_custom_search_result_fields`

Runs before the request to the custom search result field URL (if provided)

```javascript
var Zap = {
KEY_pre_custom_search_result_fields: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

       bundle.url_raw: <string>
       bundle.auth_fields: <object>
       bundle.search_fields: <object> # the raw search fields (if applicable)
       bundle.zap: <object> # info about the zap (details below)

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;
}

}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_post_custom_result_search_fields`

Runs after the response for custom search result fields is received. Can parse the response to format the data that enters Zapier or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx after the method runs automatically.

```javascript
var Zap = {
KEY_post_custom_search_result_fields: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

       bundle.request: <original object from KEY_pre_custom_search_fields bundle>

       bundle.url_raw: <string>
       bundle.auth_fields: <object>
       bundle.zap: <object> # info about the zap

The response should be JSON serializable:
[

# `type` can be unicode, int, bool

# `key` should be unique, and will be the "key" in "key: value" in the response content

# `label` should be provided

{'type': <str>, 'key': <str>, 'label': <str>}
]
\*/
return []; // return fields in the order you want them displayed in the UI. They'll be appended after the regular search fields
}

}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_pre_read_resource`

Runs before Zapier do the request to read an individual resource. Use to modify the request before it is sent.

```javascript
var Zap = {
KEY_pre_read_resource: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "GET"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.read_fields: <object> # the response data from the search (or the write in case of search-or-create)
     bundle.read_context: <object> # the original params passed into the search (or the write in case of search-or-write)
     bundle.zap: <object> # info about the zap (details below)

The response should be bundle.request or a similar object
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
auth: bundle.request.auth,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_post_read_resource`

Runs after Zapier do the request to read an individual resource. Use to modify the data returned or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx after the method runs automatically.

```javascript
var Zap = {
KEY_post_read_resource: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.request: <original object from KEY_pre_read_resource bundle>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.read_fields: <object> # the response data from the search (or the write in case of search-or-create)
     bundle.read_context: <object> # the original params passed into the search (or the write in case of search-or-write)

     bundle.zap: <object> # info about the zap

The response will be used to give the user more fields to use
in the next step of the Zap. Please return a JSON serializable object.

return <object>;
\*/
return {...};

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `KEY_read_resource`

Runs in place of pre\_read\_resource and post\_read\_resource. You get a bundle and are expected to make the request and return the appropriate response or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will **not** throw for status codes like 4xx and 5xx automatically!

```javascript
var Zap = {
KEY_read_resource: function(bundle, [callback]) {
/\*
Arguments:

     bundle.request.method: <string> # "GET"
     bundle.request.url: <string>
     bundle.request.auth: <array>
     bundle.request.headers: <object>
     bundle.request.params: <object>

     bundle.url_raw: <string>
     bundle.auth_fields: <object>
     bundle.read_fields: <object> # the response data from the search (or the write in case of search-or-create)
     bundle.read_context: <object> # the original params passed into the search (or the write in case of search-or-write)

     bundle.zap: <object> # info about the zap

If you include a callback in the arguments, you can also perform async:
callback(err, response)

     The response should be an object representing the resource. Can also use the available exceptions to vary errors.

\*/
return {...}; // or callback(null, {...})

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

## Authentication Methods

### `pre_oauthv2_token`

Modify the request we'd send to the access token endpoint.

> Be aware that for legacy reasons the request does not follow [RFC6749](https://tools.ietf.org/html/rfc6749#section-4.1.3) and passes the parameters via the query string. If you define `pre_oauthv2_token` then it is up to you correct this if needed. Without the method, Zapier will retry the request conform standards if the API returns an error on the first attempt.

```javascript
var Zap = {
pre_oauthv2_token: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "POST"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object> # {"Accept": "application/json", "Content-Length": 0}
bundle.request.params: <object> client_id, client_secret, redirect_uri, grant_type:authorization_code, code

     bundle.load: <object> # identical to bundle.request.params
     bundle.oauth_data: <object> # obj that contains your client_id, client_secret, etc...
     bundle.zap: <object> # info about the zap

The response should be an object of:
url: <string>
method: <string> # 'GET', 'POST', 'PATCH', 'PUT', 'DELETE'
headers: <object>
params: <object> # this will be mapped into the query string
data: <string> or null # request body: optional if POST, not needed if GET
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `post_oauthv2_token`

Modify the response from the access token endpoint or throw an [exception](/platform/reference/legacy-scripting/#available-exceptions). Zapier will throw for status codes 4xx and 5xx **before** the method runs automatically.

```javascript
var Zap = {
post_oauthv2_token: function(bundle) {
/\*
Argument:
bundle.response.status_code: <integer>
bundle.response.headers: <object>
bundle.response.content: <str>

     bundle.oauth_data: <object> # client id/secret and oauth related URLs
     bundle.auth_fields: <object>

\*/
// If you have defined extra fields besides access_token
// and refresh_token in the Extra Requested Fields setup,
// you may return them here as well.
return z.JSON.parse(bundle.response.content);

}
}
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `pre_oauthv2_refresh`

Modify the request we'd send to the refresh token endpoint. Only use if you have set the auth type for your App to be OAuth V2 with Refresh.

```javascript
var Zap = {
pre_oauthv2_refresh: function(bundle) {
/\*
Argument:
bundle.request.method: <string> # "POST"
bundle.request.url: <string>
bundle.request.auth: <array>
bundle.request.headers: <object>
bundle.request.data: <object> # client_id, client_secret, refresh_token, grant_type

     bundle.load: <object> # same as bundle.request.data
     bundle.auth_fields: <object>
     bundle.oauth_data: <object> # obj that contains your client_id, client_secret, etc...
     bundle.zap: <object> # info about the zap

The response should be an object of:
url: <string>
method: <string> # 'GET', 'POST', 'PATCH', 'PUT', 'DELETE'
headers: <object>
params: <object> # this will be mapped into the query string
data: <string> or null # request body: optional if POST, not needed if GET
\*/
return {
url: bundle.request.url,
method: bundle.request.method,
headers: bundle.request.headers,
params: bundle.request.params,
data: bundle.request.data
}; // or return bundle.request;

}
}
```

Learn more about [bundle.request](/platform/reference/legacy-scripting#bundle-details).

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `get_session_info`

Zapier exposes a `get_session_info()` function for APIs that require any form of session-based authorization. Feel free to use the following skeleton function to inspire your session authorization:

> Zapier will only invoke this function on an as-needed basis. It will be called when your API returns a 401 or when you raise an `InvalidSessionException` in your `KEY_post_poll` or `KEY_post_write` functions. Zapier will retry the request if the function returns new session info successfully.

```javascript
Zap = {
get*session_info: function(bundle) {
/*
Argument:
bundle.auth*fields: <object>
bundle.zap: <object> # info about the zap
*/

// Make z.request calls as needed.

// Returned object will be mixed into bundle.auth_fields in future calls.
return {'api_key': api_key};

}
};
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

### `get_connection_label`

Zapier exposes a `get_connection_label()` function for APIs that need customization on their Connection Label:

> Zapier will only invoke this function after the authentication is tested (when a new account is connected, and when the “test” button is pressed).

```javascript
Zap = {
get*connection_label: function(bundle) {
/*
Argument:
bundle.test*result: <object> # results from the test trigger, if it returned an object
bundle.auth_fields: <object>
bundle.zap: <object> # info about the zap
*/

// Make z.request calls as needed.

// Returned string will be used as a Connection Label.
// Please note it should be short and easily identifiable
return 'partners@zapier.com';

}
};
```

> **Note**: This code is only valid for Zapier's Legacy Web Builder. It's mostly incompatible with Zapier's Platform CLI and Platform UI.

## Built-in Functions and Tools

### Available Libraries

Our scripting engine uses JavaScript. As you'd expect, it provides the standard JavaScript interfaces (JSON, Math, Date and more) as well as `$` for jQuery (1.8.3), `_` for Underscore (1.4.4), and `moment` for Moment.js (2.0.0 with timezone), `crypto`, and `async` (0.2.9). Plus, it has some handy Zapier specific tools on the `z` object!

> **Note:** Do not use Underscore's collection methods (`_.each()`, `_.map()`…) on objects. This [will break](https://github.com/jashkenas/underscore/issues/2407#issuecomment-169541016) if the object has a `length` property. Use `Object.keys(obj, fn)` instead.

### Unavailable Objects

Some “common” objects you might expect to find Zapier don't provide access to include `root`, `child_process`, `Function`, `module`, `process`, `global`, and `setInterval` (they'll be `undefined` at runtime).

### Making Outbound Requests (z.request)

The `z.request` function allows you to make external calls. It performs in a synchronous manner for ease of use, but also provides standard asynchronous features as well if you pass an optional callback.

#### Usage

*   Asynchronous: `z.request(request, [callback])`
*   Synchronous: `var response = z.request()`

#### Options

*   `request <Object>`: Takes all [options for the NPM request package](https://www.npmjs.com/package/request#requestoptions-callback) plus some that allow it to accept what most pre-scripting methods receive as `bundle.request`:
*   `request <Object>`: Means you can also pass `bundle` and we'll extract `bundle.request` to work with.
*   `auth <Array> | <Object>`: If `auth` is an `Array`, it will be transforrmed to `{'user':auth[0], 'pass':auth[1]}` to match the package's [HTTP Authentication](https://www.npmjs.com/package/request#http-authentication). If your Zapier app is configured to use Basic Auth then `bundle.request.auth` will be array with 2 elements for the username and password. If it uses Digest Auth then the third element will be `false`. In this case, when `auth[2]` is set, Zapier will set `auth.sendImmediately` to `auth[2]` and also default `jar` to `true` since most digest servers require cookies.
*   `params <Object>`: Will become `qs` unless that already exists.
*   `data <string>` Will become `body` unless that already exists. While `bundle.request.data` will always be a `String`, be aware that `body` also accepts a `Buffer`, `ReadStream` or - if `json` is set to `true` - any JSON-serializable object.
*   `callback Function ([error], response)`: Will be called on completion, with: - `error <Error>`: Any error, when applicable. - `response <Object>`: See *Response*.

#### Response

You will not receive the full response but a selection from [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) that matches what most post-scripting methods receive as `bundle.response`:

*   `status_code <number>`: The 3-digit HTTP response status code. E.G. `404`.
*   `headers <Array>`: Key-value pairs of header names (lower-cased) and values.
*   `content <string>`: If `http.IncomingMessage.body` is a `string` or `Buffer` then we'll wrap it in `String()`.

### Errors

When you call `z.request()` without a callback and an error occurs, it will be thrown. Wrap your call in `try/catch` block to handle it.

### Example

Let's call [http://httpbin.org/get?hello=world](http://httpbin.org/get?hello=world) with a header to tell it Zapier like JSON:

```javascript
var request = {
'method': 'GET',
'url': 'http://httpbin.org/get',
'params': {
'hello': 'world'
},
'headers': {
'Accept': 'application/json'
},
};

// Synchronous
var response = z.request(request);
console.log('Status: ' + response.status_code);
console.log('Headers: ' + JSON.stringify(response.headers));
console.log('Content: ' + response.content);

// Asynchronous
z.request(request, function(err, response){

if (err) {
// you can always throw it, we'll catch it, but it
// is better to throw a user-friendly ErrorException
throw err;
}

console.log('Status: ' + response.status_code);
console.log('Headers: ' + JSON.stringify(response.headers));
console.log('Content: ' + response.content);
});
```

> Please note that your scripts have 30 seconds, including waiting for and processing any requests. If you need to do lots of extra API calls, especially in a loop, you should look our hydration routine.

### Parsing JSON (z.JSON.parse)

The `z.JSON.parse` function acts a lot like the native `JSON.parse`, but adds some helpful logging and error handling.

`z.JSON.parse(string)`, where `string` is the string representation of a valid JSON object. An error will be thrown if the structure is invalid.

```javascript
var response = z.request({
method: 'GET',
url: 'https://api.someservice.com/me',
auth: [bundle.auth_fields.api_key, '']
});
var str = response.content;
var obj = z.JSON.parse(str);
// return obj.fields.whatever[0];
```

### Hashing

Zapier support both hashing and HMAC hashing.

```javascript
// z.hash(algorithm, string, encoding="hex", input_encoding="binary")
var hash = z.hash('sha256', "my awesome string");

// z.hmac(algorithm, key, string, encoding="hex")
var hmac_hash = z.hmac('sha256', 'key', 'string');

// Node.js crypto's library does not officially document using input encodings with hmac, but you can do the following:
var crypto = require('crypto');
crypto.createHmac('sha256', 'key').update('string', 'input_encoding').digest('encoding');
```

For output encoding (the `encoding` parameter) Zapier defaults to `hex` and also support `base64` as a parameter value. For input encoding (the `input_encoding` parameter) Zapier default to `binary` and also support `utf8` as a parameter value. You should use `utf8` if you expect data to be hashed that may include UTF8 characters.

The following hash algorithms are supported:

`DSA-SHA1-old`, `dsa`, `dsa-sha`, `dsa-sha1`, `dsaEncryption`, `dsaWithSHA`, `dsaWithSHA1`, `dss1`, `ecdsa-with-SHA1`, `md4`, `md4WithRSAEncryption`, `md5`, `md5WithRSAEncryption`, `mdc2`, `mdc2WithRSA`, `ripemd`, `ripemd160`, `ripemd160WithRSA`, `rmd160`, `rsa-md4`, `rsa-md5`, `rsa-mdc2`, `rsa-ripemd160`, `rsa-sha`, `rsa-sha1`, `rsa-sha1-2`, `rsa-sha224`, `rsa-sha256`, `rsa-sha384`, `rsa-sha512`, `sha`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `ssl2-md5`, `ssl3-md5`, `ssl3-sha1`, `whirlpool`

### Base 64 Encoding

If you're looking to turn some text to base64 for something like Basic Auth or otherwise, use this simple function available from Node.js:

```javascript
var b64data = btoa("this is my string to turn into base64");
```

### Hydration & Dehydration

Dehydration is what Zapier calls the creation of a pointer to some data, this is what you'll normally use to provide data that may not be needed now but could the future.

Hydration is the opposite of dehydration. It is the consumption of a pointer that returns data. Let's show an example!

```javascript
var Zap = {
get_contact: function(bundle) {
/\*
Argument:
bundle.auth_fields: <object>
bundle.zap: <object> # info about the zap

           Anything else you might need, like bundle.request.headers, will need to be passed in when you call z.dehydrate()
       */
       var contact = z.JSON.parse(z.request({
           method: 'GET',
           url: 'https://api.fancycrm.com/v1/contacts/' + bundle.contact_id + '.json',
           auth: [bundle.auth_fields.api_key, ''] // you'll need to handle auth
       }).content) || {};
       return contact;

},
deal*post_poll: function(bundle) {
var deals = z.JSON.parse(bundle.response.content);
return *.map(deals, function(deal) {
// this delays Zap.get_contact({contact_id: deal.contact_id, auth_fields: bundle_auth_fields})
deal.contact = z.dehydrate('get_contact', {contact_id: deal.contact_id});
return deal;
});
}

};
```

In the example above, `get_contact` will not be called when post\_poll is called. Instead, a unique hash is created and stored in place of `deal.contact`.

There are two scenarios when `get_contact` will then be called and “hydrated”.

1.  When a user is first setting up their Zap in the UI and accessing fields (because `deal.contact` may have keys to choose from on itself).

2.  When a user's Zap tries to send `deal.contact` out to another app.

### Files

#### Dehydrating Files

Dehydration is what Zapier call the creation of a pointer to data, this is what you'll normally use in triggers to provide binary data out of band to Zapier. The idea is you don't want to download all the attachments from all 100 records in a poll - that would take way too long and would be wasteful. So Zapier offer a handy way to create pointers that Zapier can consume “on-demand”.

```javascript
// you can pass along urls and extra request information

// important: dehydrateFile will not download the file immediately!
// it just creates a pointer that our system understands so it can
// get the file on demand if it is needed

// if you just provide the url, we'll include any standard api
// headers or querystrings you configure for oauth or similar
var url = 'https://example.com/test.txt';
var filePointer = z.dehydrateFile(url);

var filePointer = z.dehydrateFile(
url,

// you can configure the request using the same options
// as z.request, but this makes you responsible for authorization
// and Zapier will not refresh tokens or sessions before hydrating
{
method: 'post',
params: {
custom: 'param'
},
headers: {
'X-Download-Key': 'abcdef1234567890'
}
},

// you can configure the content filename and length
// if you leave it out we'll try to determine it for you
{
name: 'mytextfile.txt',
length: 123
}
);
```

> Please note that request will be done via the [requests library for Python](http://docs.python-requests.org/en/master/user/quickstart/#make-a-request) which is different from [z.request()](/platform/reference/legacy-scripting#making-outbound-requests-zrequest). You cannot set an `auth` property (e.g. for Basic Auth). Instead provide the exact `params` and `headers`.

#### Hydrating Files

The scripting portion of hydration in actions is incomplete - right now your API will need to adhere to our multipart pattern.

## Bundle Details

### Prepared request via `bundle.request`

All `pre` and methods like `KEY_write` receive a prepared request via `bundle.request`. This object has the same format as what the `pre` methods are expected. This means you can modify and return it. It is also the same format that `z.request()` accepts. This is what you'd do in most `KEY_TYPE` methods.

*   `url <string>`: Configured Polling, Action or Search Endpoint URL. Any variables will be resolved.
*   `method <string>`: Will be `GET` in most cases, but `POST` for actions (`_write`), `pre_subscribe` and `pre_unsubscribe`.
*   `auth <Array>`: Will only be set if the app's Auth Type is *Basic* or *Digest Auth*. The first 2 elements are the username and password. *Digest Auth* has a third element which is always `false`.
*   `headers <Object>`: If the Auth Type is *API Key (Headers)* or when it is *OAuth V2*, *OAuth V2 (w/refresh)* or *Session Auth* and you have selected *Header* or *Both* for *Access Token Placement*, then this object will have the required/mapped headers. In addition, Zapier always set `Content-Type: application/json; charset=utf-8` and `Accept: application/json` since `data` defaults to JSON and Zapier prefer to get that back as well.
*   `params <Object>`: Will be mapped into the querystring. If the Auth Type is *API Key (Query String)* or when it is *OAuth V2*, *OAuth V2 (w/refresh)* or *Session Auth* and you have selected *Querystring* or *Both* for *Access Token Placement*, then this object will have the required/mapped parameters. If you need to convert this to an actual querystring, use `$.param(bundle.request.params)`. Be aware that Zapier do not automatically set params for your *Trigger Fields*!
*   `data <string>`: Will form the body of the request. This will be set to a JSON string for `pre_subscribe`, `pre_unsubscribe` and actions (`_write`) that have one or more fields with *Send to Action Endpoint URL in JSON body* enabled. To parse a JSON string back to an object use `z.JSON.parse(bundle.request.data)`. To stringify an object use `JSON.stringify(your_object)`.
*   `files <Object>`: For actions (`_write`) that have file-type *Action Fields* this will be an object with the field keys as keys while the values are an `Array` of: - `[0] <string>` Filename or `null`. - `[1] <string>` URL to a zapier.com endpoint that will stream the file. - `[2] <string>` Mimetype or `null`.

For Static and REST Hooks, `KEY_catch_hook` also receives `bundle.request`, but as this is an incoming request it has a different structure. It does not have an `url` or `auth` property, `params <Object>` is `querstring <string>` and `data` is called `content`.

### Raw URL via `bundle.url_raw`

The `bundle.url_raw` is simply the unrendered version of the URL with `{% templatetag openvariable %}curlies{% templatetag closevariable %}` still intact.

### Auth Fields via `bundle.auth_fields`

The `bundle.auth_fields` is a javascript object that matches the authentication settings provided by the user when the API is connected. For example, if you have an authentication field of `api_key` and `subdomain` you can expect:

```json
{
"api_key": "fc5e038d38a57032085441e7fe7010b0",
"subdomain": "example"
}
```

### Rendered Fields via `bundle.trigger_fields` or `bundle.action_fields`

Both `bundle.trigger_fields` and `bundle.action_fields` are javascript objects that surface the data given by a user to power a part of a zap. This is after rendering `{% templatetag openvariable %}curlies{% templatetag closevariable %}`. These follow the trigger fields or action fields) you define. For example, maybe you have a field with a key `list_id` and `name`:

```json
{
"list_id": "1234",
"name": "Joe Blow"
}
```

For actions, this will prune out any fields you chose not to send in the JSON. Use `bundle.action_fields_full` if you want them included as well.

### Raw Fields via `bundle.trigger_fields_raw` or `bundle.action_fields_raw`

Both `bundle.trigger_fields_raw` or `bundle.action_fields_raw` are javascript objects that surface the data given by a user to power a part of a zap. This is before rendering `{% templatetag openvariable %}curlies{% templatetag closevariable %}`. These follow the trigger fields or action fields you define. For example, maybe you have a field with a key `list_id`:

```json
{
"list_id": "1234",
"name": "{% templatetag openvariable %}first_name{% templatetag closevariable %} {% templatetag openvariable %}last_name{% templatetag closevariable %}"
}
```

For actions, this will prune out any fields you chose not to send in the JSON.

### Trigger Details via `bundle.trigger_data`

This is deprecated and will be going away entirely soon. Instead, use standard action fields which the user maps, to access trigger data.

### Webhook Payload via `bundle.cleaned_request`

The `bundle.cleaned_request` is our best guess at the parsed payload. Zapier does its best to parse JSON, XML and form-encoded data into respective javascript objects. If Zapier cannot parse it correctly - look into `bundle.request.content` and parse it yourself.

### Zap Details via `bundle.zap`

The `bundle.zap` object contains extra information about the zap (FYI: you may not see this information in debug bundles until the `zap` is referenced at least once in your script):

```json
{
"name": "My Fancy Zap Title",
"live": false,
"link": "https://zapier.com/app/editor/12345",
"user": {
"timezone": "America/Denver",
},
}
```

You can access the information like this:

```javascript
var Zap = {
any_old_pre_poll: function(bundle) {
var zap = bundle.zap;
var message = 'The Zap title is ' + zap.name'!';
// message == "The Zap title is My Fancy Zap Title!"
return bundle.request;
}
}
```

### Extra Request Info via `bundle.meta`

The `bundle.meta` object contains some runtime information about the Zap which you can use.

```javascript
{
"frontend": true, // if true, it's being done through the Zap editor/setup
"prefill": false, // if true, this poll is running as a prefill (dynamic dropdown)
// for another poll
"filter": false, // if true, this poll will be filtered afterwards
"hydrate": true, // if true, the results from this poll will be hydrated
"limit": 5, // how many items you should limit the API call to for
// performance reasons. a value of -1 means not limited
"test_poll": false, // if true, means this API call came from the user
// testing their account in the UI
"standard_poll": true, // opposite of bundle.meta.test_poll
"first_poll": false, // if true, means this API call is our initial check for
// items in the API when the Zap is turned on
"page": 0 // which page of API results you should return, useful for
// paging backwards for dynamic dropdowns. It is 0-indexed
// (add 1 via JS if your paging scheme is 1-indexed)
// note this is only available for dynamic dropdowns,
// when bundle.meta.frontend === true
}
```

> Use bundle.meta.page to implement pagination - this is especially important for triggers that power dropdowns.

You can access the information for limited pagination features like this:

```javascript
var Zap = {
any_old_pre_poll: function(bundle) {
// adds ?page=0 to URL querystring
bundle.request.params.page = bundle.meta.page
return bundle.request;
}
}
```

## Available exceptions

> **Note**: This guide is for Zapier's legacy web builder. If you use Platform UI, Zapier expects [standard HTTP errors](/platform/build/auth#common-authentication-error-messages) to be thrown. If you use Platform CLI, learn more in our [CLI error handling docs](https://zapier.github.io/zapier-platform-cli/#error-handling).

In scripting, you have several exception classes at your disposal:

*   General errors
*   Halting execution
*   Stale authentication credentials
*   Updating session credentials

### General errors

The most rudimentary exception class is the `ErrorException`. Use it in situations where the user has something misconfigured with their Zap and will need to take action. Typically, this will be for prettifying `4xx` responses and API's that return errors as `200` with a payload that describes the error.

Example: `throw new ErrorException('Your error message.');`

If a Zap raises too many error messages it will be automatically turned off, so only use these if the scenario is truly an error that needs to be fixed.

### Halting execution

Any method can be interrupted or “halted” (not success, not error, but stopped for some specific reason) with a `HaltedException`. You might find yourself using this exception in cases where a required pre-condition is not met. For example, in an action to add notes to a contact where contacts are searched for by email address, you would want to throw a `HaltedException` if a contact was not found. Unlike the `ErrorException`, a Zap will never be turned off when this exception is raised (even if it is raised more often than not).

Example: `throw new HaltedException('Your reason.');`

Any pre\_XXX call can be interrupted **silently** with `StopRequestException`. This will prevent the request from being made and will never cause a user's Zap to be turned off.

Example: `throw new StopRequestException('Your reason.');`

### Stale authentication credentials

For apps that require manual refresh of authorization on a regular basis, Zapier provide a mechanism to notify users of expired credentials. With the `ExpiredAuthException`, the current call is interrupted, the Zap is turned off (to prevent more calls with expired credentials), and a predefined email is sent out informing the user to refresh the credentials.

Example: `throw new ExpiredAuthException('Your message.');`

For apps that use OAuth, but do not return a typical 401 when tokens expire, you can use the `RefreshTokenException` in a post\_XXX. This will signal to Zapier to attempt to refresh the access token and then repeat the failed call.

Example: `throw new RefreshTokenException('Your message.');`

### Updating Session Credentials

For session-based APIs only, stale authorization credentials can be refreshed by throwing the `InvalidSessionException`. This will tell Zapier to invoke your provided `get_session_info()` function. Zapier will store these results for you and make them available to every `poll` and `write`. Zapier will throw the exception for you if the API responds with a 401 status.

## Code examples

### Trigger pre-poll examples

> **Note** This code is only valid for the v2 platform. It's incompatible with Platform CLI and Platform UI.

```javascript
"use strict";

var Zap = {
// STRAIGHT PASS THROUGH
// same as not providing the method at all
// for illustration purposes only
simple_pre_poll: function(bundle) {
return bundle.request;
},

// CHANGE REQUEST METHOD
// for endpoints that don't like the default GET
simple_pre_poll: function(bundle) {
bundle.request.method = 'POST';
return bundle.request;
},

// PLACE A HEADER BASED ON USER INPUT
// a trigger field that should be added as a header but
// if there isn't one, default to "None"
some_header_pre_poll: function(bundle) {
var request = bundle.request;
request.headers["X-Project-ID"] = bundle.trigger_fields.project_id || "None";
return request;
},

// SUBSTITUTE HUMAN FRIENDLY CHOICES
// if you add a trigger field with human friendly choices:
// Yesterday,Today,Tomorrow
// but the querystring should be:
// &when=-1, &when=0, or &when=1
event_pre_poll: function(bundle) {
var request = bundle.request;
switch (bundle.trigger_fields.when) {
case "Yesterday":
request.params.when = -1;
break;
case "Today":
request.params.when = 0;
break;
default:
request.params.when = 1;
}
return request;
},

// BUILD THE URL YOURSELF
// this does exactly what Zapier do when transforming the URL
// from bundle.url*raw to bundle.request.url
// utilizes underscores template system (preloaded with proper syntax)
room_pre_poll: function(bundle) {
var request = bundle.request;
// bundle.url_raw is 'http://{{account_name}}.campfirenow.com/room/{{room_id}}/speak.json'
// bundle.auth_fields is {account_name: 'myaccount', api_key: '1234567890'}
request.url = *.template(bundle.url*raw)(bundle.auth_fields);
// request.url is 'http://myaccount.campfirenow.com/room/{{room_id}}/speak.json'
// bundle.auth_fields is {room_id: 12345, message: 'Hello world!'}
request.url = *.template(request.url)(bundle.trigger_fields);
// request.url is 'http://myaccount.campfirenow.com/room/12345/speak.json'
return request;
}
}
```

### Trigger post-poll examples

> **Note** This code is only valid for the v2 platform. It's incompatible with Platform CLI and Platform UI.

```javascript
"use strict";

var Zap = {
// STRAIGHT PASS THROUGH OF JSON
// same as not providing the method at all
// for illustration purposes only
straight_post_poll: function(bundle) {
// bundle.response.content is '[{"id":1234,"title":"Hello!"}, ... ]'
return z.JSON.parse(bundle.response.content);
},

// WRAP IN ARRAY
// for APIs that return a single object
straight_post_poll: function(bundle) {
// bundle.response.content is '{"id":1234,"title":"Hello!"}'
return [z.JSON.parse(bundle.response.content)];
},

// CENTS INTO DOLLARS
// take a key in cents and offer it up as a dollar formatted string
sale*post_poll: function(bundle) {
// bundle.response.content is '[{"id":1234,"cents":925}, ... ]'
var results = z.JSON.parse(bundle.response.content);
*.each(results, function(result) {
result.dollars = "$" + (Math.floor(result.cents / 100)) + "." + (result.cents % 100);
})
// results is '[{"id":1234,"cents":925,"dollars":"$9.25"}, ... ]'
return results;
},

// XML INTO JSON
// given an string of xml data, turn that into JSON:
// <messages type="array">
// <message>
// <id type="integer">2</id>
// <title>Anyone home?</title>
// <body>I can't seem to see anything!</body>
// </message>
// <message>
// <id type="integer">1</id>
// <title>Hello there world!</title>
// <body>I am just tickled to see you!</body>
// </message>
// </messages>
my*xml_doc_post_poll: function(bundle) {
// bundle.response.content is xml string from API, $ is preloaded jQuery
var xml = $(\$.parseXML(bundle.response.content)).find('message');
// build javascript primitives: array of objects
var results = *.map(xml, function(element){
return {
id: $(element).find('id').text(),
title: $(element).find('title').text(),
body: \$(element).find('body').text()
};
});
// results is '[{"id":"2","title":"Anyone home?","body":"I can't seem to see anything!"}, ... ]'
return results;
}
}
```

### Catching webhooks example

> **Note** This code is only valid for the v2 platform. It's incompatible with Platform CLI and Platform UI.

```javascript
"use strict";

var Zap = {
// STRAIGHT PASS THROUGH OF CLEANED_REQUEST
// same as not providing the method at all
// for illustration purposes only
straight_catch_hook: function(bundle) {
// bundle.cleaned_request is usually an object or array
return bundle.cleaned_request;
},

// PLACE QUERY STRING AS MAIN OBJECT
// in this example our hook is just a GET with a query string
// there is no request content or body, maybe a url like this:
// GET https://zapier.com//hooks/catch/123/n/456789/?name=bryan&age=27
simple_get_catch_hook: function(bundle) {
// bundle.cleaned_request includes the query string already parsed
// but you could parse it yourself from bundle.request.querystring:
var example = \$.param(bundle.request.querystring);

// but let's just return the cleaned_request version:
return bundle.cleaned_request.querystring; // {"name":"bryan","age":27}

},

// MOVE LIST ON SUBKEY TO MAIN OBJECT
// if a json POST contains a list on a root object's key "data"
// Zapier can move the list to the parent to trigger for each item
// the content/body might look like:
// {"data":[{"name":"bryan","age":27},{"name":"mike","age":23}]}
myjson_catch_hook: function(bundle) {
// bundle.cleaned_request includes the json already parsed
// but you could parse it yourself from bundle.request.content:
var example = z.JSON.parse(bundle.request.content).data;

// but let's just return the cleaned_request version:
return bundle.cleaned_request.data; // the array

}

// FILTER OUT CERTAIN STATIC WEBHOOKS
// if your hooks are noisy, then you may want to filter based on
// some static value, or even user provided trigger fields
filter_out_catch_hook: function(bundle) {
// manually create json from the posted string
var json = z.JSON.parse(bundle.request.content);

// filter out hooks that aren't the event type Zapier cares about
if (json.event_type != 'new_comment') {
return []; // return [] or {} to take no action
}

// filter out hooks that aren't the status that the user expected
if (bundle.trigger_fields.status && json.status != bundle.trigger_fields.status) {
return []; // return [] or {} to take no action
}

// else
return json;

},
}
```

### Action pre-write examples

> **Note** This code is only valid for the v2 platform. It's incompatible with Platform CLI and Platform UI.

```javascript
"use strict";

var Zap = {
// STRAIGHT PASS THROUGH
// same as not providing the method at all
// for illustration purposes only
pass_through_pre_write: function(bundle) {
return bundle.request;
},

// WRAP FIELDS IN TOP-LEVEL ARRAY UNDER DATA KEY
wrap_in_array_pre_write: function(bundle) {
bundle.request.data = JSON.stringify({data: [bundle.action_fields]})
return bundle.request;
},

// DO NOT UNFLATTEN **
// by default Zapier will turn fields like {custom**c: 12}
// into {custom: {c: 12}}, but this can avoid this by
// using the "full" action fields, like this
dont_unwrap_pre_write: function(bundle) {
bundle.request.data = JSON.stringify(bundle.action_fields_full);
return bundle.request;
},

// LIMIT STRING LENGTH
// a field called message cannot be longer than 256 characters
message_pre_write: function(bundle) {
var outbound = z.JSON.parse(bundle.request.data);
outbound.message = outbound.message.substring(0, 256);
bundle.request.data = JSON.stringify(outbound);
return bundle.request;
},

// GENERATE XML FOR POSTING
// you'll have access to your action fields
// we'd normally POST as JSON, but this shows how to do XML
xml_doc_pre_write: function(bundle) {
// root el is ignored in .html()
var xml = $("<XMLDocument><message/></XMLDocument>");
// bundle.action_fields is {title: "Anyone home?", body: "I can't seem to see anything!"}
Object.keys(bundle.action_fields).forEach(function(key){
$(xml.find("message")).append(\$("<" + key + " />").text(bundle.action_fields[key]));
})
// '<message><title>Anyone home?</title><body>I can't seem to see anything!</body></message>'
bundle.request.data = xml.html();
// set the headers
bundle.request.headers['Content-Type'] = "application/xml; charset=utf-8";
bundle.request.headers['Accept'] = "application/xml";
return bundle.request;
},

// FORM ENCODE INSTEAD OF JSON
// sometimes you don't want to JSON encode, so this example shows how
// to form encode POST data instead (just like a form submission)
my_form_pre_write: function(bundle) {
// build a querystring from the object with all fields
bundle.request.data = \$.param(bundle.action_fields_full);
// correct the content type header
bundle.request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
return bundle.request;
}
}
```

### Search post-write examples

> **Note** This code is only valid for the v2 platform. It's incompatible with Platform CLI and Platform UI.

Sometimes, a search endpoint will return a successful response despite the search being unsuccessful. To account for this, you need to manipulate the response in a [`_post_search`](#key_post_search) method:

```javascript
// let's say the response content looks like this:
// {
// "search_results": {},
// "time": "2018-04-01T01:02:03.456-00:00"
// }
// by default, our system will interpret this as a successful search
// and return the "search_results" and "time" keys -- what Zapier really
// wants is the content nested under that search_results key (which in
// this example is an empty object, because there was no search match)

var Zap = {
my_search_key_post_search: function(bundle) {
var response = z.JSON.parse(bundle.response.content);
return response.search_results;
}
}
```

### REST hook subscription xamples

> Heads up! This code is only valid for the v2 platform, and is incompatible with today's [Platform CLI and Platform UI](/platform/quickstart/ui-vs-cli).

```javascript
var Zap = {
pre_subscribe: function(bundle) {
bundle.request.method = 'POST';
bundle.request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
bundle.request.data = \$.param({
url: bundle.target_url,
list_id: bundle.trigger_fields.list_id, // from trigger field
append_data: 1
});
return bundle.request;
},
post_subscribe: function(bundle) {
// must return a json serializable object for use in pre_unsubscribe
var data = z.JSON.parse(bundle.response.content);
// Zapier need this in order to build the {% templatetag openvariable %}webhook_id{% templatetag closevariable %}
// in the rest hook unsubscribe url
return {webhook_id: data.id};
},
pre_unsubscribe: function(bundle) {
bundle.request.method = 'DELETE';
// bundle.subscribe_data is from return data in post_subscribe method
bundle.request.url = 'https://example.com/x.php?id=' + bundle.subscribe_data.webhook_id;
bundle.request.data = null;
return bundle.request;
},
};
```

**Adding trigger fields to subscription payload**

```javascript
var Zap = {

pre_subscribe: function (bundle) {
if (Object.keys(bundle.trigger_fields).length) {
var data = z.JSON.parse(bundle.request.data);
var dataWithFields = Object.assign({}, data, bundle.trigger_fields);
bundle.request.data = JSON.stringify(dataWithFields);
}
return bundle.request;
}

};
```

### Session authentication examples

> **Note** This code is only valid for the v2 platform. It's incompatible with Platform CLI and Platform UI.

```javascript
var Zap = {
get_session_info: function(bundle) {
var api_key,
api_key_request_payload,
api_key_response;

// Assemble the meta data for our key swap request
api_key_request_payload = {
method: 'POST',
url: 'https://api.domain-name.com/api/login',
params: bundle.auth_fields,
headers: {
'Content-Type': 'application/json', // Could be anything.
Accept: 'application/json'
}
};

// Fire off the key exchange request.
api_key_response = z.request(api_key_request_payload);

// Extract the `api_key` from returned JSON.
api_key = z.JSON.parse(api_key_response.content).api_key;

// This structure is an example. You may need to add
// a different key name, or multiple keys, depending
// on your API's requirements.
// This will be mixed into bundle.auth_fields in future calls.
return {'api_key': api_key};

},

new_contact_post_poll: function(bundle) {

// Zapier will catch bundle.response.status_code === 401
// If your API doesn't conform to this standard, you can handle it yourself:
if (z.JSON.parse(bundle.response.content).code === 401) {
throw new InvalidSessionException(); // Call get_session_info() and try the request again
}

return z.JSON.parse(bundle.response.content);

}
};
```

## Testing and Debugging

As your legacy scripting methods are now running within the platform, you'll want to use the tools available on each.

If you have `console.log` references, these will be translated to `z.console.log`, so the console logs can be made available in our different logging tools.

For Platform UI: /build/test-auth#monitoring

For Platform CLI: /reference/cli-docs#logging

***

[Previous: Custom Actions and API Requests Actions](/platform/reference/custom-actions-api-requests) [Next: Changelog](/platform/reference/changelog)

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Zapier integration structure for a project management app

While you can't automate project work, you can automatically add tasks, create new projects, and keep track of progress via an app integration on Zapier.

To add a project management app integration in the Platform UI:

## Prerequisites

*   A [Zapier account](https://zapier.com/sign-up).
*   If you haven't used Zapier before, you'll want to learn the basics in our [Zapier Getting Started Guide](https://zapier.com/learn/zapier-quick-start-guide/).
*   Familiarity with your app's API documentation and available endpoints.
*   Review the [Platform UI Tutorial](/platform/quickstart/ui-tutorial).

## 1. Add triggers for common record types in each project

*   Check out the [common record types](/platform/quickstart/must-have-triggers-and-actions#project-management) used as triggers by other project management apps.

*   For apps with many record types, consider including a *New Activity* trigger, with options to let users choose which type of activity should trigger the Zap.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/b34f31e26be51d5581f2a669c62c8d14.png" />
</Frame>

![new activity trigger](https://cdn.zappy.app/)

*   Build separate triggers for new and updated records. *Updated* triggers for project management apps are most useful as these updates affect work to be done. Users want to know if the task deadline changed, if details were added or the scope was redefined, or if someone completed something.

*   It is strongly recommended to ensure that any trigger behavior encompasses all updates that are visible to a logged-in user in your app's user interface, independent of admin/creator permissions for a record type. Users expect to be able to automate off any record that is shared with them, regardless if they're an admin/creator or not. If this is not possible with your API, make this clear in the Trigger description. For example, for a *New Note* trigger, add help text such as “Triggers when the **connected** user account adds a note to a project.”

*   Allow users to filter which records they want to trigger on. For example, a *New Task* trigger could trigger when tasks are added to any project *or* to one specific project. Include [dynamic dropdowns](/platform/build/field-definitions#dynamic-dropdown) ordered by the most recently added to fetch accounts, projects, lists, and other items users would want to filter.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/fe08bdcbf666e8f2961f0c2b3932b460.png" />
</Frame>

*   Project management records are linked to other records in your app. If the data is visible for that record in your app, users expect to see it in their Zap as well. For example, a “New Task” trigger should include details about the task's project too.

*   Include both IDs for linked data in the response output so they can be mapped to your app's action steps, as well as human-readable data such as individual fields for project name and description.

*   If your record API endpoint does not automatically return additional linked data beyond the ID or name of the record, add custom code to your trigger API call to fetch relevant data linked to the record as well.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/3f2f5450c1c7981b41a4d2234047bfaa.png" />
</Frame>

## 2. Include common search actions

*   Users expect to have searches for top-level records available for project management integrations so they can add child-level details. For example, a Project can have many Tasks, and some projects may have the same or similar tasks. Users are more likely to want a *Find Project* search so they can add tasks to projects, rather than a Find Task search which may not return the precise item they need.

*   Configure the search to find an existing record, returning it in the same format the trigger returns records to give a consistent experience and allow the data to be used in the same manner in later actions.

*   Offer multiple search key and value options where applicable. For example, a *Find Project* search should let users search by project ID and name. The former is useful for searching based on data in previous steps, where the latter is useful to find projects with human input. Return exact matches first and provide help text to communicate to users how the search matches records to search terms.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/zapier-82f0e938/images/542be79e48ccbd6d12e43646690dcebb.png" />
</Frame>

## 3. Include common create actions

*   Check out the [common record types](/platform/quickstart/must-have-triggers-and-actions#project-management) other project management apps have create actions for.

*   Add corresponding *Update* actions with available *Search* actions to make it easier for users to update the correct project record.

*   Account for linked records in action input fields, so when your app's UI allows users to create a task and link it to existing projects, the same action is available via a Zap, via a [dynamic dropdown](/platform/build/field-definitions#dynamic-dropdown) to fetch linked record options and let users select them.

*   Do not have a single action create multiple records. When a user creates a new task through Zapier, this should only create new tasks, and should not also create other linked records at the same time. Rather include a separate action to create the other linked record, along with a task update action to link the task after the other record is created. This reduces the chance for errors and record redundancy.

*   Optionally link a [search connector](https://cdn.zappy.app/99829074f857d0938c0ba7b3c5e97e84.png) to linked record input fields if users need to have the Zap find the correct related record, and map the found ID to the input field. To add that user prompt you'll need to work in the [Platform CLI](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#search-powered-fields).

## 4. Test your triggers and actions

Make Zaps with the following criteria to ensure your app integration works as expected:

*   Connect your integration with [other project management apps](https://zapier.com/apps/categories/project-management), to see if the data your integration returns blends well with our common apps. Companies frequently use integrations to copy tasks and projects across a blend of project management apps to keep track of work between teams.
*   If your integration supports events or tasks with due dates, connect a Zap to popular [calendar or scheduling apps](https://zapier.com/apps/categories/calendar) to verify those dates can be added to other apps.
*   If your integration has many nested items (such as projects containing sub-projects with tasks and sub-tasks), try setting up a few Zaps to see if the behavior across nested objects is the same as parent objects. Data returned should always be the same format; if your API doesn't enable access to nested objects, note that in the help text for that trigger/search/action.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*


# Schema Reference





# Transfer

[Transfer](https://help.zapier.com/hc/en-us/articles/8496274335885) is a Zapier functionality that enables users to perform bulk operations using their historical data.

> **Note**: The scheduled Transfer functionality is being deprecated as of 15th January 2024.

One time transfers will be available to users [within their Zaps with supported triggers](https://help.zapier.com/hc/en-us/articles/8496274335885).

We are no longer accepting new Transfer sources built by partners for review.

## What is Transfer?

Today Zaps only process new data and events created after the user turns on their Zap. Transfer lets users access data that existed in their trigger app before the Zap was on. It allows users to leverage Zapier for ETL (“Extract, Transform, Load”) tasks.

Some example usage scenarios:

*   A user has new email campaign software and they want to load all the leads from their CRM into it.
*   A user ran a survey, but didn't build a Zap ahead of time to move those responses to their email marketing tool. They can use Transfer to reach back and pull those responses into their software of choice.
*   A user wants to choose specific leads that previously ran through a Zap, and send them to a different tool.
*   A user has a one-off data migration where the source has a paginated API endpoint and the destination has a Zapier integration.

***

*Need help? [Tell us about your problem](https://developer.zapier.com/contact) and we'll connect you with the right resource or contact support.*