Complete Reference#

Below you'll find in-depth textual descriptions of terminology around our Developer Platform. You probably do not want to start by reading this all the way through. Rather, use it as a reference when you run into trouble when creating your App.

↑ Was this documentation useful? Yes No

Apps#

A Developer App (App for short) is an implementation of your app's API. It's the thing you build to make Zapier support your app.

An App is composed of four main parts:

  • Authentication (usually) which lets us know what credentials we need to ask users for and where to include them in the requests we make to your API.
  • Triggers, which read data from your API.
  • Actions, which send data to your API.
  • Searches, which find specific records in your API.
  • Code written in scripting that lets you handle any mismatches between what Zapier needs and what your API generates.
↑ Was this documentation useful? Yes No

Actions#

Overview#

Actions answer the question: What should my users be able to create via Zapier? They are things like:

  • Create ToDo Task (EG: Basecamp)
  • Create Chat Message (EG: Campfire or Hipchat)
  • Create Issue (EG: GitHub or Pivotal Tracker)

You can think of Actions as POSTs, writes, or the creation of a resource. It involves Zapier sending data to your app.

What a user sees:

What a developer sees:

See also: Actions in the CLI

You can define your actions via your app's dashboard. When you create a new action, you'll be prompted with several options. Below are complete definitions of what each option is for.

Action Options#

Name#

This is a human readable label a user would see when browsing the directory and adding your app. Make it short but descriptive.

Example:
Create Issue, Send Alert or Unsubscribe User

Noun#

This is the object that the action is most closely associated with. It will be a noun you used in the Name field. We rely on an accurate noun to generate human-friendly sentences for users.

Example:
"Create Issue" would have "Issue" as the noun. "Unsubscribe User" would use "User".

Key#

This is a field only really used internally for both dynamic dropdowns and scripting references. Needs to be at least two characters long, start with an alpha, and only contain a-z, A-Z, 0-9 or _.

Example:
create_issue, ticket or newNote

Help Text#

This some human readable explanatory text, usually something that clarifies what the action does.

Example:
Creates a new issue in a selected repository.

The user will see Name and Help Text like below:

label and help

Action Endpoint URL#

Define the URL where we will, by default, POST the payload to. You can also make use of variable syntax where auth fields and action fields will be injected.

Example:
http://api.example.com/v2/clients.json or http://{{account}}.example.com/api/v1/projects.json

A Warning about encoding URL params#

We will not automatically encode any URL variables, so you're responsible for encoding any if they require that. For example, emails might include a + sign, so if you have https://example.com/api/v2/clients.json?email={{email}} you'll want to encode that in your ACTION_KEY_pre_write (or remove it from there and add it to the bundle.request.params), otherwise you'll get a "space" where the + sign is.

A better approach is to not even include it in the URL (it'll be added and encoded automatically in that case)

Custom Action Fields URL#

This allows you to dynamically define action fields that are user set (IE: custom fields). They get passed into the POST'ed JSON just like normal action fields (or into scripting).

Example:
http://api.example.com/v2/fields.json or http://{{account}}.example.com/api/v1/fields.json

Read more about custom field formatting here.

Important#

Usually you'll want to leave this checked, but if you don't we'll hide that action behind an "uncommon" link when a user selects their action. Mainly this is helpful for removing actions that are there for breadth but are rarely used.

Hide#

Usually you'll want to leave this unchecked. If you check it, we'll completely hide the action from the enduser. This can be useful if an action is incomplete, but you need to deploy your app in it's current state. This option is also a way to hide actions that become deprecated in your API.

↑ Was this documentation useful? Yes No

Action Fields#

Overview#

Action Fields answer the question: what details can a user provide when setting up an Action?

These details might include:

  • Title (EG: Note Title in Evernote)
  • Description (EG: Issue Description in Github)
  • Parent Object (span relationships via dynamic dropdowns)
  • Repo for an Issue (EG: Github)
  • Notebook for a Note (EG: Evernote)
  • Message Body (EG: Chat Body in Campfire or Hipchat)

What a user sees:

What a developer sees:

Each action should have at least one action field, because, hey, it really makes no sense to POST nothing to an endpoint!

You can also dynamically load custom action fields by inspecting a custom field endpoint of your own. Learn more.

Action Field Options#

Key#

A key for you and your API's consumption. This is available for variable syntax in the Action URL field as well as being added to the POSTed JSON (optional). Needs to be at least two characters long, start with an alpha, and only contain a-z, A-Z, 0-9 or _.

Example:
room

We'll take double underscores and convert them to nested dictionaries before we POST JSON.

Example:
project__title converts to {"project": {"title": "some value"} }

Label#

A human readable Label shown in the UI as a user works to complete an Action.

Example:
Room or Title

label help default

Help Text#

Human readable description of an action field, useful for describing some detail you couldn't list in the Label.

Example:
Choose which room to send the message to. or Add a title to the note.

Default#

A default value for a field. The behavior varies between required and optional fields. For required fields, the default will be set once when the user first creates the Action, but it is not guaranteed after that (we raise an error on missing/null values instead). For optional fields, it is set on initial creation and used in place of missing or null values every time the Zap runs.

Type#

The type we will try to coerce to on the backend. Fails silently to ensure that tasks aren't dropped on coercion errors.

You can get a full list of supported types and the coercion implied here: Field Types.

Required#

If checked a user will not be able to continue without entering some value.

Dynamic Dropdown#

Use an existing Trigger to load in values for selection, using the machine readable value your API consumes (like id or hash) while showing a human readable version to the user (like name or itemName).

Refer to our dynamic dropdown docs for a more in depth explanation.

Example:
TRIGGERKEY.id.name or TRIGGERKEY.hash.itemName

dynamic dropdown

Static Dropdowns#

A comma separated string that will be turned into a select field for limiting the choices a user can provide to an action field.

Example:
choice_a,choice_b,choice_c or Yesterday, Today, Tomorrow

simple static dropdown

If you would like to provide a label for the raw value of each choice, you can also use the raw|label,raw|label syntax instead.

Example:
1|Option 1,2|Option 2

simple static dropdown as key-value pairs

Search Connector#

Use an existing Search to find a value based on user input. Set the key of this field to the same key as the field in the search you want to use in executing the search. The second part of the definition is the attribute of the returned object that is sent to your service in place of this field.

Example:
SEARCHKEY.id

The above definition sets the field as an input to SEARCHKEY (as long as the field key matches a field key within SEARCHKEY), and the id field from the first result returned from SEARCHKEY is used as the value for this field.

The user does not see anything different for this type of field, so make sure to explain in the help text what kind of input you expect here.

Note: Any field with a search connector specified must also have a dynamic dropdown specified. This is because search connectors are meant to save the user from having to find and copy an ID value into a field - which the dynamic dropdown will handle for when the user wants to use the same ID every time.

Parent Key#

Ok, parent key is a little tricky, but it can be really helpful if you want to support line items (an array of sub-objects). When an action has one or more fields that have a parent key, we treat all those fields as the schema for building sub-objects, which we combine into a list and nest under the parent key.

For example, say you define two fields amount and quantity and give them both the parent key line_items. Now imagine a user has a trigger that provides this data:

{
    "sale": {
        "items_sold": [
          {
             "cost": "$3.00",
             "# sold": 12
          },
          {
             "cost": "$2.55",
             "# sold": 1
          }
        ]
    }
}

NOTE: The kind of data you receive ultimately depends on what the app's trigger providing the data returns. This means that if the app's trigger a customer's using doesn't provide line items, we'll transform arrays into CSV strings. There's really nothing you can do about this, but when testing, make sure the app you're using to test triggers supports line items.

The user could map cost into your amount field and # sold into the action's quantity field, like this:

parent key UI example

Zapier would produce this JSON for POSTing:

{
    "line_items": [
      {
         "amount": "$3.00",
         "quantity": 12
      },
      {
         "amount": "$2.55",
         "quantity": 1
      }
    ]
}

Zapier will automatically expand fields with the same parent_key into a nested list of objects according to how the source data comes in (we'll make as many objects inside the list as the original source). All you need to do is provide the same parent_key and expect an array of objects under that parent_key.

Send to Action Endpoint URL in JSON body#

If checked, we'll include the user-provided value in the POSTed JSON on the provided key (or nested key if double underscores __ are present).

If you utilize the pre_poll or ACTIONKEY_pre_poll methods via scripting you can get complete control over the JSON output beyond simple exclusion/inclusion.

List#

Indicates if this field can hold multiple values. For example, this could be useful if you want to allow users to add tags to a new contact, and you want them to be able to add more than one. List fields gain the +/- icons to the side.

list field example

↑ Was this documentation useful? Yes No

Action Fields (Custom)#

A natural extension of normal hard coded action fields are dynamic action fields, or custom fields. Custom fields are very commonly used in services that allow users to create their own data fields. Examples include contacts in CRMs, form management apps, or ticket fields in helpdesk software. These apps will assign a generated unique key to the field, while storing information about the field, such as its human readable label and data type, separately. Working with this data requires an extra step to retrieve that field metadata in order to allow users to make sense of the information.

All you need to do to enable custom fields is:

  • Provide a Custom Action Fields URL for your action.
  • Ensure the URL route returns data in the below format, or manipulate it to fit with scripting.
  • You can choose from several internal types, documented here: Field Types.
[
  {
    "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
    "prefill": "contact.id.name", // optional, defines a dynamic dropdown
    "searchfill": "contact.id" // optional, defines a search connector
  }
  ...
]

If your action returns custom fields you'll also want to configure a source for labels so other actions in multi-step Zaps can display those fields properly and allow users to correctly use the data in the Zap editor.

  • Provide a Custom Action Result Fields URL for the action. (This will likely be the same endpoint you used for Custom Action Fields)
  • Ensure that the URL route returns data in the below format, or manipulate it to fit with scripting. Extra data will be ignored, but we require at least the following to properly format your action results.
[
    {
        "type": "unicode",
        "key": "json_key", // the field "name", will be used to construct a label if none is provided
        "label": "Pretty Label",
        "important": true // optional    
    },
    ...
]

Right now parent_key and type=dict is not supported in custom fields.

↑ Was this documentation useful? Yes No

Action Sample Results#

(This works similar to trigger sample results)

To help give users a smooth experience when creating multi-step Zaps, we ask that you paste a JSON object that contains the fields (along with sample values), so that your user can map those fields into the next step.

We will use this sample JSON for two things:

  1. To detect a list of hard-coded key names which the user can pick from during Zap setup
  2. To use as a hard-coded fallback for sample data so that we can provide fields to insert during Zap setup (if your API returns 0 results)

These results will NOT be used for a user's Zap testing step. That step requires data to be received when performing the action.

Here is the sample JSON for something like a newly created email message, and how it shows up in our user-facing Editor:

{
  "labelIds": "DRAFT",
  "id": "150f2791b6723cb5",
  "threadId": "150f2791b6723cb5"
}

We will parse this sample and provide dropdowns like this to the user:

samples

By default, we can handle flat dictionaries and dictionaries within dictionaries (via our __ delimiter in keys).

When the user is inserting fields in the Zap editor, and your API returns no results ([]) then we will use your hard-coded fallback JSON if it exists.

Your hard-coded JSON provided above will not be run through the Scripting API (either for key enumeration or sample data fallback) so if you use the Scripting API to add or modify fields on top of your normal API response, you'll want to make sure you perform the same manipulations manually before pasting in the JSON above.

↑ Was this documentation useful? Yes No

Activation#

Developer applications on Zapier can exist in several states. When you first create your application, it will be set to private.

When you are ready to publish your app to the world, you have two options: Invite-Only or Public.

You can move applications from one state to another using available options on your developer application dashboard page.

Note: If you want make changes to a Public or Invite-Only application, the procedure is to clone the application, make the changes to the clone, then deploy and replace replace the live application.

Private#

The default status. Visible only to you.

Invite-Only#

Only visible to you and the users you invite. This is perfect if you have a app on Zapier that you'd like to keep out of wider circulation. To invite users you can send emails in bulk through Zapier, or share a direct link.

Pending#

The waiting period before being publicly enabled.

Public#

Most vendors who add themselves want to submit for public activation, as this allows you to be placed in both the Zapbook and show up in our standard list for all users to see. Once you submit, expect a response within seven days.

To find out more about the process to become publicly active, read the activation section of the App Lifecycle.

↑ Was this documentation useful? Yes No

Authentication#

Your API probably has some kind of authentication needs in order for us to talk to it on behalf of a user. Zapier supports the following authentication schemes: Basic Auth, Digest Auth, API Keys or OAuth V2.

Basic Auth#

What a user sees:

What a developer sees in the Web Builder:

See also: Basic auth via CLI

Classic Basic Auth, where users provide Zapier their username and password to authenticate with your API. This is also the scheme you want to use if your API relies on Basic Auth, but with atypical values for the username and password. For instance, an API that uses an API Key as the username and a dummy value as the password would still want to select Basic Auth for their App.

Digest Auth#

Users will provide Zapier their username and password, and we will handle all the nonce and quality of protection details automatically.

See also: Digest Auth via CLI

API Keys#

What a user sees:

Inside the developer platform for devs:

See also: API Key auth via CLI

Typically, you'll provide your users with an API Key inside your app somewhere. Many times these are provided on the user's settings or accounts page. These keys can be given to Zapier by the user so that we may make authenticated requests to access that user's information on their behalf.

Define any data which you Zapier must always include with each request as auth fields. Eg. api_key, account_name, etc.

Session-based Auth#

What the user sees:

Inside the developer platform for devs:

See also: Session auth via CLI

Be sure to tweak which fields are necessary for Zapier successfully attain a valid session with your API. Zapier will then store the credentials for this session until they expire, refreshing them only after encountering a 401, or when specified by the get_session_info method provided in your your custom scripting.

OAuth#

↑ Was this documentation useful? Yes No

Authentication Fields#

Overview#

Authentication fields are simply what you need from the user to get started authenticating their API calls. For example, when a user tries to authenticate your service:

auth fields for user

You can see what it will be powered by a developer set up like:

auth fields in developer

Additionally, these user-provided variables will be passed in with the bundle for usage in scripting. For example, the above screenshots would translate to:

{
  "account_name": "myaccount",
  "api_key": "123456789"
}

You can use Auth Mappings to put your Auth Fields to use in Basic Auth, Digest Auth, HTTP headers and query strings.

Let's look at each of the options you define for a single Auth Field.

Authentication Fields Options#

Key#

This is somewhat arbitrary but you should pick a good, short key name because you'll need to include it elsewhere in the developer interface as {{key}} using the variable syntax. Needs to be at least two characters long, start with an alpha, and only contain a-z, A-Z, 0-9 or _.

Example:
(you may not need all of these)

  • api_key an API Key which you give to your users somewhere inside your service. Allows Zapier to authenticate on their behalf
  • account_name an account name which you use to build the API Request URL (perhaps as a subdomain: https://{{account_name}}.example.com/api/v1/)

Label#

This will show up just above the text input field as a human-readable name. You don't need to be super descriptive here; you can provide more details and information about this field in the Help Text field.

Example:
API Key, Account Name

label help default

Required#

Pretty easy concept; most all auth fields should be required. You really shouldn't ask for non-essential information as an auth field.

Help Text#

Smaller text which appears under the Label of the field. You can type longer directions here. A common pattern for Auth Field Help Text is to tell the user where in your interface they can find their API Key.

Example:
The API Key for your account, you can find this by going to your Basecamp Profile page, clicking settings, then API Keys.

Default#

If you specify a default value, the actual field will be pre-filled with this value when the user goes to add a new authentication for your service. If the user clears out a required auth field which had a default set, they will receive an error. Optional fields that are missing a value will use the default every time.

Most auth fields will not have a default.

Example:
choice_b (if you use the choices feature detailed below)

Static Dropdowns#

You can create a dropdown-style select box by giving us a comma-separated list of choices. This works well in conjunction with the default value option above.

Example:
choice_a,choice_b,choice_c or Yesterday, Today, Tomorrow

simple static dropdown

If you would like to provide a label for the raw value of each choice, you can also use the raw|label,raw|label syntax instead.

Example:
1|Option 1,2|Option 2

simple static dropdown as key-value pairs

Subdomain Format#

If you need the user to supply an account name (or similar) which gets translated into a subdomain (or similar) URL format, we recommend you take advantage of this feature. It helps guide the user with what to type in.

Example:
https://,{{input}},.yourdomain.com/ will create:

subdomain format

Note: don't forget the commas!

↑ Was this documentation useful? Yes No

Authentication Mappings#

Auth mappings tell us how to interpret what your user provides as usable Basic Auth, Digest Auth, HTTP headers and query strings. Let's use an example where there are two auth fields available to us: account_name and api_key.

Basic Auth#

Your app might use api_key as the username and password is ignored:

{
  "username": "{{api_key}}",
  "password": "x"
}

Or maybe, where account_name is the username and api_key is the password:

{
  "username": "{{account_name}}",
  "password": "{{api_key}}"
}

Which will automatically create and use a header like:

Authorization: Basic WkFQSUVSIExPVkVTIFlPVTpYT1hP

Digest Auth#

Your app might have account_name as the username and api_key as the password:

{
  "username": "{{account_name}}",
  "password": "{{api_key}}"
}

We handle the computation of the response, so you don't have to think about realms, nonces, or qop's.

API Key Header Auth#

Say your app has two headers called X-Account-Name and X-API-Key:

{
  "X-Account-Name": "{{account_name}}",
  "X-API-Key": "{{api_key}}"
}

Which will automatically create and use headers like:

X-Account-Name: myfancyaccount
X-API-Key: 0123456789

API Key Query strings#

If your app uses an API key passed as a parameter in the query string, let's call it user_key:

{
  "user_key": "{{api_key}}"
}

Which will append ?user_key=0123456789 to the end of a URL.

↑ Was this documentation useful? Yes No

Clone#

Once your app is live (Public or Invite-Only), you'll probably want to make changes to it and add features or fix bugs. While you cannot make changes to a live app, you can clone an app and then make changes to the clone.

To clone your app, click on the Deployment tab in your app editor, and click the button Make a Copy of Your App by Cloning:

deployment tab

A cloned app will begin in Private mode, which allows you to make your edits. If you wish to use the cloned app to replace your previous app (either Public or Invite-Only), you'll want to deploy a replacement.

↑ Was this documentation useful? Yes No

Data Sources#

There are four ways Zapier can obtain data from your API.

  • Polling - repeatedly hit a REST endpoint looking for new data.
  • Static Webhooks - gives the user a URL to enter into your app. Hooks carry the full object.
  • REST Hooks work similar to static webhooks, but our system handles the subscriptions through your REST API.
  • Notification REST Hooks are "lightweight" REST Hooks as they only contain a callback URL to retrieve the object.

Learn more about each type by following the links to the specific sections.

↑ Was this documentation useful? Yes No

Datetimes#

For triggers, please supply any date-related fields in an RFC standard of your choosing. We do our best to parse a variety of datetime formats.

For actions, datetimes are normalized to an ISO-8601 formatted string. For details, see the datetime field type. An example: a user has a Zap between Wufoo and Mailchimp. The Wufoo "new entry" trigger supplies a UNIX epoch timestamp field. If the user maps the datetime field from Wufoo into Mailchimp, it will be supplied to Mailchimp as a string, formatted like 2015-03-05T01:00:00.

If you need a different date format (like epoch as int) - you'll need to use our scripting platform to do a conversion.

↑ Was this documentation useful? Yes No

Deduplication#

We do the deduplication automatically for you, but you must be mindful of how it works. The important bits are outlined below!

Deduplication tl;dr:

  • Provide a unique id key.
  • Sort reverse-chronologically by time created.

An unfortunate artifact of polling for new data is that we must deduplicate the results we get back from your API. After all, we don't want to trigger an action multiple times when a newly created item in your API exists in multiple distinct polls.

For example, say your endpoint returns a list of to-dos:

[
  {
    "id": 7,
    "created": "Mon, 25 Jun 2012 16:41:54 -0400",
    "list_id": 1,
    "description": "integrate our api with zapier",
    "complete": false
  },
  {
    "id": 6,
    "created": "Mon, 25 Jun 2012 16:41:45 -0400",
    "list_id": 1,
    "description": "get published in zapier library",
    "complete": false
  }
]

Every time a new Zap is created or turned on, we make a call to your API to populate our deduplication mechanism. We will cache and store each id field in our database.

After this, we will poll at an interval (based on customer's plan) looking for changes against this cached list of ids.

Now let's say we created a new to-do:

[
  {
    "id": 8,
    "created": "Mon, 25 Jun 2012 16:42:09 -0400",
    "list_id": 1,
    "description": "re-do our api to support webhooks",
    "complete": false
  },
  }
    "id": 7,
    "created": "Mon, 25 Jun 2012 16:41:54 -0400",
    "list_id": 1,
    "description": "integrate our api with zapier",
    "complete": false
  },
  {
    "id": 6,
    "created": "Mon, 25 Jun 2012 16:41:45 -0400",
    "list_id": 1,
    "description": "get published in zapier library",
    "complete": false
  }
]

Only the first to-do with id equal to 8 will be seen as a new item. That particular JSON object will then be passed through to the action in the user's Zap. The others will be ignored since we already have seen their ids.

Your API must be able to return results in reverse-chronological order to make sure new/updated items can be found on the first page of results, as we don't fetch additional pages. Wufoo is a great example of an API that supports sorting on any field and direction.

One other requirement is the id field should always be supplied and unique among all items in the result.

Custom or multiple ID fields#

What if the items your API returns do not have an id field? Or how would you go about adding a Updated to-do trigger to our to-do app? In both cases you'll use scripting to modify the API response.

Let's assume your to-do API has an endpoint to return to-dos sorted by updatedAt in descending direction. This is how you would use the scripting post poll trigger method:

var Zap = {
  TRIGGER_KEY_post_poll: function(bundle) {
    var items = z.JSON.parse(bundle.response.content);
    items.forEach(function(item) {
      item.originalId = item.id;
      item.id = item.id + '-' + item.updatedAt;
    });
    return items;
  }
}

Notice how we preserve the original value before setting id to a new combined value that is unique for every update of a to-do.

Re-order items#

What if your API cannot order its results in reverse-chronological order? How would you fetch the last page if you don't know how many pages there might be? Again scripting can help you deal with this.

Depending on your API it is most likely you will need to use the scripting poll trigger method. This will let you handle the request(s) to your API.

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 it.
↑ Was this documentation useful? Yes No

Deploy#

If your app is still private, read the deploy lifecycle doc on how to make it public! This doc is for deploying updated code to an already public or Invite-Only app.

Once your app is live (Public or Invite-Only), you'll probably want to make changes to it and add features or fix bugs. While you cannot make changes to a live app, you can clone an app and then make changes to the clone.

The first step is to click on the Deployment tab, and click the button Make a Copy of Your App by Cloning:

deployment tab

In the new/cloned app, make your changes. Be sure to leave this app in the Private status!

When you've finished testing your changes, and are ready to update the existing/public app, go into the new/cloned app, click on the Deployment tab, and click the button to Deploy and Replace an Existing App.

Once you have deployed your new/cloned app to replace an existing app, all zaps will be updated to use this new app. Read on for important notes on handling breaking app changes.

A screen will ask you which app you want to replace. Select the existing/live app:

select app

The system will run an audit and show you the results:

deploy page

In the screenshot you can see an error for New Item, with the error of Polling triggers should always have sample data. This simply means you need to provide a sample result for a polling trigger.

Usually you can just make some changes and ensure there is a symmetry between old and new versions of the app. If there is a breaking changes that resolves a bug, please contact us and we'll perform a manual deployment.

There are a number of situations where making changes in your app may cause the existing Live Zaps to stop working. If any of those situations are detected, the system will not allow you to complete the deploy/replace process. Please visit this page of Deploy Errors and try a workaround.

If your app is Invite-Only, your cloned app's OAuth redirect URI will become active upon migration (it will contain a new ID #), and the old OAuth redirect URI will no longer function. Please plan accordingly. Contact us for a solution to this problem if it is difficult to manage - we can set a permanent redirect URI for your app.

How to Handle Breaking Changes#

The best approach is to copy the Triggers/Actions/Searches you want to update. Mark the originals hidden and append "(Legacy)" to their labels. On the copies, make the needed updates (it also helps to update their keys to something instructive, like append "_v2" to the original key). Once you deploy, new Zaps will only have the copies available and old Zaps will continue to work unaffected.

Deploy in process#

If all goes well and you have no warnings when trying to deploy, a deploy process will start asynchronously and you can come back to the Deployment tab to check on its progress.

↑ Was this documentation useful? Yes No

Monitoring#

You can access Monitoring by clicking the "Monitoring" tab on your App's details.

This section is meant to allow you to quickly understand if your app is OK or not, by checking and comparing types of events (ideally you want to see a lot of 2xx - green, and no errors - red).

NOTE: The monitoring information is less useful for services/endpoints that consistently return an HTTP 200 status, and contain a payload with an error message.

If you have access to it, you can click on a data point in the chart and get details about up to 500 events for the chosen period.

If you have too many events, you can use the filters on the right side to filter which events will be displayed on the list once you click a data point.

This is useful for mainly for debugging purposes.

↑ Was this documentation useful? Yes No

Dynamic Dropdowns#

Sometimes, API endpoints require clients to specify a parent object in order to create or access the child resources. Imagine having to specify a company id in order to get a list of employees for that company. 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.

Tip: We used to call the dynamic dropdowns "prefills"!

Here is what a user sees as a dynamic dropdown:

prefill

In order for a dynamic dropdown to work, we must have a working trigger set up for the parent object. Zapier will query for that dynamic dropdown, parse out an identifier and a human readable handle for each record, and then offer the user a dropdown. Once they make a selection, we store the identifier for use later when reading or writing child objects.

The dynamic dropdown syntax is split into three parts:

  • Which trigger is this dynamic dropdown referring to?
  • Which piece of data is the unique identifier?
  • Which piece of data is a human readable representation?

We combine those into one field: TRIGGERKEY.identifier_key.human_readable_key.

For example, let's say you have a trigger called Project which offers the following data (remember, triggers are expected to return a list of JSON objects with an ID field):

[
  {
    "id": 4287243,
    "owner_id": 4632,
    "date_created": "Mon, 25 Jun 2012 16:41:54 -0400",
    "title": "My secret project!",
    "description": "It's a Facebook for dogs!"
  }
  ...
]

You want to let users read just the messages from that project as a trigger. As you are making the message trigger you'd need to create a project trigger field with the following dynamic dropdown value:

Example:
new_project.id.title

Note: new_project is a fictional key for the Project trigger. You'll set your specific trigger key when you create your trigger. id is the identifier from the above project data. The human readable representation key title is located there as well.

Notice that a dynamic dropdown is really nothing more than a trigger field/action field that is populated by another trigger.

↑ Was this documentation useful? Yes No

Combining Multiple Dynamic Dropdowns#

There are times where a Dynamic Dropdown may need to use a value (or ID) that is selected from a previous or dependent parent Dynamic Dropdown. An example is a task-planning service that has Projects, and within each of those projects are Tasks. To select a Task, a Project must first be selected, like this:

double-dropdown

There are three Triggers involved here:

  1. A hidden Trigger that supplies the list of Projects.
  2. A hidden Trigger that supplies the list of Tasks for a specific Project.
  3. A visible Trigger that contains a pair of Trigger Fields, which pull their values from the hidden triggers.

The first trigger#

Here's the definition for the first Trigger, named List Projects:

projects-key
This trigger doesn't need any Trigger Fields, so leave that empty. projects-url

The second trigger#

Here's the definition for the second Trigger, named Tasks in a Project:

projects-key
This trigger doesn't need any Trigger Fields either, so leave that empty.
In the Polling URL we'll use a key called project (which will be filled in by the previous Dynamic Dropdown), but it's a "leap of faith", because it relies on the third trigger to define the key called project.
tasks-in-project-url

The third trigger#

Here's the definition for the third Trigger, that uses the previous two as Dynamic Dropdowns:

The important part is that the Trigger Fields specifies the keys used in the previous Triggers to use for the Dynamic Dropdowns: When users create a Zap for this trigger, they'll be shown the two Dynamic Dropdowns, and the value from the first one will be stored within trigger_fields.project, and the second in trigger_fields.task.

This results in the bundle.trigger_fields to contain these two values:

Which can either be specified within the Polling: URL Route of the third trigger:

Or can be used within the Scripting

↑ Was this documentation useful? Yes No

Field Types#

The following is a list of the field types we use internally. Normally, you'd choose one of these fields when creating your action fields or trigger fields via the type dropdown:

field types

However, if you're using Custom Fields you might want to know the available field types. The key column corresponds to the type key found in the code example here: Action Fields (Custom).

Unicode#

Unicode fields are essentially one-line text fields that can support unicode characters. There is no coercion done for this type.

Unicode fields are represented by a type: unicode key.

Textarea#

Think of this as a multi-line unicode field. It's really only used to give the user a textarea in the UI instead of a one-line input field. There is no coercion done for this type.

Textarea fields are represented by a type: text. key

Integer#

Suitable for whole integer numbers, we'll coerce any text down into an integer by stripped non-numeric values from the string. A negative sign (-) in front is also allowed.

Integer fields are represented by a type: int key.

Float#

Like integers, this will coerce any text down to a floating point number with the addition of allowed characters like . and ,.

Float fields are represented by a type: decimal key.

Boolean#

We apply some natural language parsing to try and coerce any text into True or False. This UI field is also replaced with a dropdown allowing the user to specifically pick "Yes" or "No" explicitly.

Boolean fields are represented by a type: bool key.

DateTime#

Our most complex coercion. We'll attempt to convert any given value into an ISO-8601 formatted string. The parser is quite robust, supporting epoch timestamps, ISO-8601 and even natural language parsing! You can use moment.js via Scripting to parse and replace if your servers expect a different format sent to it.

DateTime fields are represented by a type: datetime key.

Dictionary/Object#

This is a fairly complex shortcut to a widget that allows a user to provide a series of key/value pairs, perfect for providing completely unstructured and custom metadata (do not use this if your API provide definitions for that metadata, look into custom fields instead!).

Dictionary/Object fields are represented by a type: dict key.

There are also a few other special things, related to types, you can do.

Static Dropdown#

You can provide a choices array which will be mapped automatically into a dropdown in the Zap Editor. In the developer UI you should simply provide us with a hard-coded list of comma-separated values for the "choices" option when defining a field. If you're using scripting, you'll want to provide this array to us manually. Here is a code sample and what it looks like:

[
  {
    "type": "unicode",
    "key": "color",
    "label": "Label",
    "help_text": "Pick a color label to apply to the card.",
    "choices": ["none", "green", "yellow", "orange", "red", "purple", "blue"]
  }
]

field choices

You can also provide an object with key and value for choices if you like.

Lists#

Say you wanted to allow the user to specify multiple values for a single field. Maybe, a list of tags to apply or a list of email addresses to send to. You can use our list feature to enable "+" and "-" buttons in the Zap Editor which users can use to specify more than one value. list fields can be any type except dict or file.

You can make a field a list by checking the appropriate box in the UI. For custom fields, you can use Scripting to alter the field definition to include list: true like this:

[
  {
    "type": "unicode",
    "key": "to",
    "label": "To",
    "help_text": "Who will this email be sent to?",
    "list": true
  }
]

field list

↑ Was this documentation useful? Yes No

Files#

Zapier supports some limited file operations via both triggers and actions, though they behave a bit differently depending on what you want to do. At a high level, this is what you can expect:

Triggers via URLs/Dehydration#

This is how you'd surface a file hosted by your app, so other apps can consume it.

If your files aren't private (IE: they have a public route), just provide the URL in the normal payload (right alongside id, name, etc.). Our system is smart enough to download public URLs when they get used like a file!

However, if the files are behind authentication, you can define a route for us to retrieve the file at some time in the future and we'll attempt the normal style of authentication:

var Zap = {
  your_trigger_post_poll: function(bundle) {
    var records = z.JSON.parse(bundle.response.content);
    return _.map(records, function(record) {
      // if you just do url, we'll include any standard authentication headers
      record.file = z.dehydrateFile('https://yoursite.com/files/download/' + record.id);
      return record;
    });
  }
}

You can also define a more specific request if there is more data you need to provide to activate the download, like a special key or checksum:

var Zap = {
  your_trigger_post_poll: function(bundle) {
    var records = z.JSON.parse(bundle.response.content);
    return _.map(records, function(record) {
      // if you provide the full request, we will NOT include
      // any standard authentication headers
      var url = 'https://yoursite.com/files/download/' + record.id;
      record.file = z.dehydrateFile(url, {
        method: 'post',
        headers: {
          'X-Download-Key': record.key
        }
      }, {
        name: record.fileName, // if blank we will guess/inspect!
        length: record.size // if blank we will guess/inspect!
      });
      return record;
    });
  }
}

And we'll handle the rest!

Actions via Multipart#

This is how you'd accept binary data to be uploaded to your app, the data is coming from some other app.

Downloading files is a little simpler than uploading them, so files in actions is a bit more involved. By default we attempt multipart uploading and include the original JSON right alongside, but you can (of course) tweak this with the developer platform.

Let's assume your API can accept JSON as well as JSON + multipart for attachments, if you set up this action:

You can expect this request to be sent by default (no scripting required at all):

POST https://yoursite.com/files/upload

Content-Type: multipart/form-data; boundary=f94636b7375c4a37862029d4dc8bafe7


--f94636b7375c4a37862029d4dc8bafe7
Content-Disposition: form-data; name="data"
Content-Type: application/json; charset=utf-8

{"path": "/user/provided/path", "delete_date": "2014-10-10T13:59:36"}
--f94636b7375c4a37862029d4dc8bafe7
Content-Disposition: form-data; name="file_to_upload"; filename="user-provided-file.pdf"
Content-Type: application/pdf

<BINARY DATA HERE>
--f94636b7375c4a37862029d4dc8bafe7--

The name="data" key for the JSON subtype in multipart/form-data is the default, though the rest of the multipart files will respect your action field keys.

Limited Customization Via Scripting#

If you have some other method of uploading files, you'll need to break out the scripting platform. Probably the most common way to tweak the upload is with the classic pre_write method.

A popular alternative pattern is likely just a pure multipart/form-data, no JSON at all. We support that just fine, just remove the Content-Type and return an object as request.data:

var Zap = {
  your_action_pre_write: function(bundle) {
    // bundle.request.files is an object of strings: arrays
    // bundle.request.files.file_to_upload is an array:
    // * first item is the filename, if any
    // * second item is a zapier.com endpoint that will stream the file
    // * third item is the mimetype, if any

    bundle.request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
    // leave request data as object, not string!
    bundle.request.data = z.JSON.parse(bundle.request.data);
    // we will mix request.data and request.files together
    return bundle.request; // let zapier complete it
  }
}

That pure multipart/form-data raw request will now look like this:

POST https://yoursite.com/files/upload

Content-Type: multipart/form-data; boundary=0c61d2f9bd1a4675a2db6afe21f230f1


--0c61d2f9bd1a4675a2db6afe21f230f1
Content-Disposition: form-data; name="path"

/user/provided/path
--0c61d2f9bd1a4675a2db6afe21f230f1
Content-Disposition: form-data; name="delete_date"

2014-10-10T13:59:36
--0c61d2f9bd1a4675a2db6afe21f230f1
Content-Disposition: form-data; name="file_to_upload"; filename="user-provided-file.pdf"
Content-Type: application/pdf

<BINARY DATA HERE>
--0c61d2f9bd1a4675a2db6afe21f230f1--

All name keys respect the keys provided.

Advanced Streaming via Scripting#

At this time we do no support advanced streaming of files via the scripting platform (for example, uploading a file to receive an attachment ID that gets mixed into the normal JSON for POSTing). We may support that in the future, please send us an email if you have questions!

↑ Was this documentation useful? Yes No

Migration#

The old name for what we now call a Deploy. Check out the deploy section for details.

↑ Was this documentation useful? Yes No

Notification REST Hooks#

Below is the same pattern as our REST Hooks, but with a twist (and an extra step!). Instead of delivering the payload with each hook, it just delivers a resource URL where the payload resides. This helps us move big requests into a queue with everything else which lets us batch bigger requests. The URL can be one-time use or time sensitive as well.

Your Checklist#

  • enumerate the events you'd like to make available as triggers
  • create a subscribe REST endpoint for hooks. For example: POST /api/hooks
  • when each event happens, loop over and notify each active subscription (or batch events of same type)
  • respect 410 responses on payload delivery and remove subscriptions
  • create a unsubscribe REST endpoint for hooks. For example: DELETE /api/hooks/:id

You need to set the subscrube and unsubscribe REST endpoints under Manage Trigger Settings:

Manage Trigger Settings

Read on for a step by step through the endpoints involved...

Step 1: Subscribe (a call from Zapier to your app)#

POST <subscribe_endpoint> \
    -H Authenticated: Somehow \
    -H Content-Type: application/json \
    -d '{"target_url": "https://hooks.zapier.com/<unique_target_url>",
         "event": "user_created"}'

This endpoint reuses whatever auth standard you have across the rest of your API (IE: Basic Auth, API Key, OAuth2, etc...). We'd send along a unique, auto-generated subscription URL and the event we'd like to subscribe to. These three items would be persisted on your backend (the authenticated user, target_url, and event). A subscription is created when a user turns their Zap on.

If you prefer, you can modify this request to provide additional information (like further filtering or other metadata) via our pre_subscribe scripting method.

On a successful subscribe, return a 201 status code. You should store data about the hook you just created via the post_subscribe scripting method (return an object like {"id": 1234}). You'll need this data later to unsubscribe, unless you use the subscription URL as the unique identifier, which is uncommon).

Generally, subscription URLs should be unique. Return a 409 status code if this criteria isn't met (IE: there is a uniqueness conflict).

Step 2: Sending Hooks (a call from your app to Zapier)#

POST https://hooks.zapier.com/<unique_target_url> \
    -H Content-Type: application/json \
    -d {"resource_url": "<resources_url>"}

This hook must include a resource_url or we will not perform a followup GET and thus Zaps will not trigger. If you are currently sending an id, but not a resource_url, you can modify the trigger's catch_hook in scripting to add a resource_url key that uses this id.

On a successful hook, we'll return a 200 status code, content is irrelevant.

If Zapier responds with a 410 status code you should immediately remove the subscription to the failing hook (unsubscribe). Additionally, excessive failures (multiple 4xx or 5xx failures) can be handled at your discretion. It is the clients responsibility to resubscribe if needed after failures.

The maximum hook size is 100MB. Any payloads that exceed this limit will see a 413 status code.

Step 3: Consuming (a call from Zapier to your app)#

GET <resources_url> \
    -H Authenticated: Somehow \
    -H Content-Type: application/json

This is an old fashioned REST hit to a standard endpoint, it requires the same authentication as the rest of the API. It can return a list or a single object.

If you don't want to perform this extra step, look at REST Hooks!

Step 4: Unsubscribe (a call from Zapier to your app)#

DELETE <unsubscribe_endpoint> \
    -H Authenticated: Somehow \
    -H Content-Type: application/json \
    -d <content built/omitted in pre_unsubscribe scripting method>

If you've properly stored identification data about the hook (like its unique ID) from the post_subscribe scripting method, you should use our pre_unsubscribe scripting method to formulate the DELETE call. In that call you have access to your previously stored data under thebundle.subscribe_data variable. Unsubscribing occurs when a user turns their Zap off.

Some example code is provided to make this as easy as possible. A heads up: you will need to use that code if you intend to do a DELETE call. You'll be able to access bundle.subscribe_data to build the URL in pre_unsubscribe. The default behavior is slightly different and listed below.

However, the current default does not perform a DELETE. For example, if you omit the pre_unsubscribe function entirely, we will attempt a default unsubscribe call:

POST <unsubscribe_endpoint> \
    -H Content-Type: application/json \
    -d '{"target_url": "https://hooks.zapier.com/<unique_target_url>"}'

On a successful unsubscribe, return a 200 status code, content is irrelevant.

It is worth remembering that the subscription URL should effectively be unique (this could be enforced by your app as well) which also allows us to clean up unrecognized hooks (we also recommend not requiring authentication for such an endpoint).

↑ Was this documentation useful? Yes No

OAuth V1#

The flow we respect matches Twitter's and Trello's implementation of the 3-legged oauth flow.

Note that OAuth v1 is not officially supported on Zapier; we highly recommend using OAuth 2 if possible!

That said, because OAuth v1 has been a popular authentication mechanism, we have a sample implementation if you want to try it, respecting OAuth v1 "revision a".

Initial Setup:#

First, you'll need to select OAuth 1 as your auth type, and then provide the required parameters:

OAuth parameters

Capture extra data from token response

If your app returns extra data through the token response (user id for example) you can grab those extra parameters using the Extra Requested Fields option.

How we authenticate with your OAuth service#

We attempt a 3 legged OAuth 1 process that has 3 high level steps:

  1. Obtain temporary oauth_token and oauth_secret from your app
  2. Get user to authorize Zapier access to your app
  3. Exchange the temporary oauth_token for an authorized oauth_token

1. Requesting Temporary Credentials From Your App:#

When a user goes to add a new account for your app, we make a request behind-the-scenes to ask your app for a request token and secret. Your app should respond back with a valid oauth_token and oauth_token_secret. Additionally, our request contains an oauth_callback URL that your app will use in a later step.

The signed POST request to your application's request_token_url will look like this:

POST https://example.com/api/1.0/oauth/request_token
Authorization: OAuth 
oauth_nonce="ZAPIER-GENERATED-NONCE", 
oauth_callback="ZAPIER-OAUTH-CALLBACK", 
oauth_signature_method="HMAC-SHA1", 
oauth_timestamp="ZAPIER-GENERATED-TIMESTAMP", 
oauth_consumer_key="<Your consumer key>", 
oauth_signature="ZAPIER-GENERATED-SIGNATURE", 
oauth_version="1.0"

Your app should validate the request and return with the temporary response token, like so:

oauth_token_secret=TEMP-OAUTH-TOKEN-SECRET&oauth_token=TEMP-OAUTH-TOKEN&oauth_callback_confirmed=true

2. Redirecting the User to Your App:#

With token in hand, we redirect the user to the authorization_url you provided, so that they can grant access to Zapier.

User grants access

302 https://example.com/api/v1/oauth/authenticate?oauth_token=<oauth_token>

3. Redirecting the User Back to Zapier:#

After the user clicks allow, your app redirects the user to oauth_callback (from Step 1), along with an oauth_verifier parameter in the query string.

302 <oauth_callback>?oauth_verifier=<oauth_verifier>

4. Exchanging Temporary Credentials for Token Credentials:#

We use that oauth_verifier to make a POST to the access_token_url you provided.

POST https://example.com/api/1.0/oauth/access_token \
    -H Accept: application/json \
    -d 'consumer_key=<consumer_key>&
        consumer_secret=<consumer_secret>&
        oauth_verifier=<oauth_verifier>&
        oauth_token=<oauth_token>&
        oauth_token=<oauth_token_secret>

Your service should then provide an access_token and access_token_secret, along with any extra parameters that you may have included in the optional Capture extra data from token response step.

While we ask for a JSON response, the response can be JSON, XML or query string encoded. For example, any of the below are valid responses:

With Content-Type: application/json:

{
  "oauth_token": "1234567890abcdef",
  "oauth_token_secret": "1234567890abcdef",
}

With Content-Type: application/xml:

<AnyRootElem>
  <oauth_token>1234567890abcdef</oauth_token>
  <oauth_token_secret>1234567890abcdef</oauth_token_secret>
</AnyRootElem>

With Content-Type: text/plain:

oauth_token=1234567890abcdef&oauth_token_secret=1234567890abcdef

5. Using the Token Credentials#

We sign all requests using your secret and the new oauth_token and oauth_token_secret. We're talking standard HMAC-SHA1, RSA is not supported. However, you can use scripting to modify request parameters (including headers) as you see fit.

For example, if a POST request to your API expects Content-Type:application/x-www-url-form-encoded instead of the standard application/x-www-form-urlencoded then you can use a custom script to modify the request that we make. In an app with a trigger create_ticket the Scripting API would look like so:

Edit app script

var Zap = {
    create_ticket_pre_write: function(bundle) {
        bundle.request.headers['Content-Type'] = 'application/x-www-url-form-encoded';
        return bundle.request;
    }
}

Example:#

We highly recommend looking at each of Bitbucket's, Twitter's and Trello's authentication documentation for some great examples of how OAuth 1a can be implemented. Our system is designed to match their industry standard implementation pattern. Additionally, oauth1a spec and oauth bible can be extremely useful in understanding the 3 legged oauth flow.

↑ Was this documentation useful? Yes No

OAuth V2#

While the official OAuth V2 spec offers lots of options, the flow we respect matches GitHub's and Facebook's implementation of the authorization_code flow.

A complete OAuth V2 example is available on our Formstack example page. Check that out for more detail in a real world example!

Initial Setup#

First, you'll need to select OAuth V2 or OAuth V2 (w/refresh) as your auth type, and then provide the following parameters:

OAuth parameters

We'll automatically create the required access_token and refresh_token (optional) auth fields for you. They look something like this in the developer interface - the access_token field is required at minimum:

Hint: Does your app need to change the URL based on the user's subdomain or domain? Simply add another authentication field (right alongside access_token from the above screenshot) that has the key like subdomain and then use it like https://{{subdomain}}.yourwebsite.com/api/token in all your URLs.

1. Redirecting the User to Your App#

We'll redirect the user to an authorize_url (provided by you), along with a query string which contains a redirect_uri (provided by us), scope (optional, provided by you) and client_id (provided by you).

302 <authorize_url>?client_id=<client_id>&scope=<scope>&redirect_uri=https://zapier.com/dashboard/auth/oauth/return/AppIDAPI/

2. Redirecting the User Back to Zapier#

After the user clicks allow, you should redirect the user to the redirect_uri along with a code parameter in the query string.

Note: Don't hardcode the redirect_uri on your side, as it will change as you deploy new versions of your app. If your app goes public, we'll assign you a permanent ID, but some users may be using older versions even after that point.

302 <redirect_uri>?code=<code>

We use that code to make a POST to the access_token_url (provided by you) along with the code, matching redirect_uri (provided by us), client_id (provided by you), and client_secret (provided by you) as a form encoded request.

POST <access_token_url>?client_id=<client_id>&client_secret=<client_secret>&code=<code>&grant_type=authorization_code&redirect_uri=https://zapier.com/dashboard/auth/oauth/return/AppIDAPI/ \
    -H Accept: application/json

Zapier will try two ways to get the token: POST with data in querystring, if that returns a 4xx, we will retry a POST with the data in the form-encoded body. You can always override this behavior with the Scripting though!

While we ask for a JSON response, the response can be JSON, XML or query string encoded as where the value you return is access_token. For example, any of the below are valid responses:

With Content-Type: application/json:

{
  "access_token": "1234567890abcdef",
  "refresh_token": "1234567890abcdef" // optional
}

With Content-Type: application/xml:

<AnyRootElem>
  <access_token>1234567890abcdef</access_token>
  <refresh_token>1234567890abcdef</refresh_token> <!-- optional -->
</AnyRootElem>

With Content-Type: text/plain:

access_token=1234567890abcdef&refresh_token=1234567890abcdef

We automatically pull and store access_token and refresh_token from the JSON, XML, or query string data, but if you want to pull and store more fields, you can set them up in your auth setup under Extra Requested Fields:

3. Authorizing Requests to Your App#

By default, we append the access_token to the querystring for all requests, as well as in an Authorization header, however, you can use scripting to modify request parameters as you see fit (IE: place the access_token in a special header). This is called the bearer token type.

An example of the default with access_token_placement set to header:

GET https://api.example.com/v2/tickets.json \
    -H Authorization: Bearer 1234567890abcdef

And now with access_token_placement set to querystring:

GET https://api.example.com/v2/tickets.json?access_token=1234567890abcdef

4. Refreshing Tokens (optional)#

Optionally, if your app supports refresh tokens, we natively support that.

You'll need to select OAuth V2 (w/refresh) and provide us the refresh_token_url in addition to the previous requirements. When we encounter a 401 status code, we'll attempt to refresh like so:

POST <refresh_token_url> \
    -H Accept: application/json \
    -d 'client_id=<client_id>&
        client_secret=<client_secret>&
        grant_type=refresh_token&
        refresh_token=<refresh_token>'

Zapier will try two ways to get the token: POST with data in the form-encoded body, if that returns a 4xx, we will retry a POST with the data in querystring. Note that this is the exact reverse of how we try to get the initial access token. You can always override this behavior with the Scripting though!

We will only attempt to refresh an access token when we encounter a 401 status code from your API.

If your API does not signal expired tokens with a 401, use scripting to manually set the status code header to 401 upon conditions of your choosing. Or, you can raise a RefreshTokenException in your post_XXXX function call to kick off a new refresh.

The resulting access_token will be updated in our system and the previous, failed call will be attempted once more.

We prefer and advocate one of two patterns around expiring refresh tokens:

  1. Never expire refresh tokens except on password changes or user revocation.
  2. In the last 10% window of a refresh token's life (IE: last 9 days of a 90 day lifetime), give a brand new refresh_token along with the normal access_token refresh.

We fully support each pattern. While option 1 is definitely more common, option 2 works very nicely and has the effect of eventually cleaning up unused oauth tokens.

If you cannot adhere to one of the above patterns and you app requires manual refresh of tokens on a regular basis, you should check for this condition and notify users via scripting. With throw new ExpiredAuthException('Your reason.');, the current call is interrupted, the zap is turned off (to prevent more calls with expired auth), and a predefined email is sent out informing the user to refresh the credentials.

Some Examples#

We highly recommend looking at each of GitHub's, Facebook's and Podio's (with refresh tokens) authentication documentation for some great examples of how OAuth V2 can be implemented. Our system is designed to match their industry standard implementation pattern.

A complete OAuth V2 example is available on our Formstack example page. Check that out for more detail in a real world example!

↑ Was this documentation useful? Yes No

Polling#

Polling is the process of repeatedly hitting the same endpoint looking for new data. We don't like doing this (its wasteful), vendors don't like us doing it (again, its wasteful) and users dislike it (they have to wait a maximum interval to trigger on new data). However, it is the one method that is ubiquitous, so we support it.

It is also closely tied into how we handle deduplication.

That said, we support REST Hooks, so if you like to make your users and servers happy, implement hooks!

If you don't use classic GET with your API to retrieve lists of objects - you can use our scripting API to customize the polling request with a different method and body.

↑ Was this documentation useful? Yes No

REST Hooks#

Below is our simple standard to stop the Polling Madness™. This is a suggested pattern for you to implement. It was chosen because there are many examples of REST APIs providing resources around GETing, POSTing and DELETEing webhooks just like any other REST resource.

The big benefit to REST Hooks is consumers wouldn't need to poll for changes, but could instead wait for hooks to deliver the payload. Additionally, producers can provide real-time updates with fewer devoted resources (compared to polling).

The gist is: we POST a subscription to e.g. /api/hooks requesting to receive hooks at some target URL. Every time the event happens, ping us at the target URL with the payload.

After we're done, we can cleanup with a DELETE (.e.g. /api/hooks/:id).

Though we highly recommend this native pattern, you can override all the methods below with scripting. This means you can bend our pattern to match an existing pattern of your own.

Your Checklist#

  • enumerate the events you'd like to make available as triggers
  • create a subscribe REST endpoint for hooks. For example: POST /api/hooks
  • when each event happens, loop over and notify each active subscription (or batch events of same type)
  • respect 410 responses on payload delivery and remove subscriptions
  • create a unsubscribe REST endpoint for hooks. For example: DELETE /api/hooks/:id
  • add a polling URL for your REST Hook trigger for the best user experience

You need to set the subscribe and unsubscribe REST endpoints under Manage Trigger Settings:

Manage Trigger Settings

Read on for a step by step through the endpoints involved...

Step 1: Subscribe (a call from Zapier to your app)#

POST <subscribe_endpoint> \
    -H Authenticated: Somehow \
    -H Content-Type: application/json \
    -d '{"target_url": "https://hooks.zapier.com/<unique_target_url>",
         "event": "user_created"}'

You may notice a duplicate property subscription_url. That's legacy terminology. You can safely ignore it and use target_url only.

This endpoint reuses whatever auth standard you have across the rest of your API (IE: Basic Auth, API Key, OAuth2, etc...). We'd send along a unique, auto-generated subscription URL and the event we'd like to subscribe to. These three items would be persisted on your backend (the authenticated user, target_url, and event).

The value for the subscribe_endpoint field can be specified within the "Manage Trigger Settings" option in your app's dashboard.

The value for the event field can be specified within the Trigger's setup:

If you prefer, you can modify this request to provide additional information (like further filtering or other metadata) via our pre_subscribe scripting method.

On a successful subscribe, return a 201 status code. You should store data about the hook you just created via the post_subscribe scripting method (return an object like {"id": 1234}). You'll need this data later to unsubscribe, unless you use the subscription URL as the unique identifier, which is uncommon).

Generally, subscription URLs should be unique. Return a 409 status code if this criteria isn't met (IE: there is a uniqueness conflict).

Your service should permit a user to connect multiple webhook URLs to their account

Step 2: Sending Hooks (a call from your app to Zapier)#

POST https://hooks.zapier.com/<unique_target_url> \
    -H Content-Type: application/json \
    -d <json payload>

This hook could provide any amount of data or payload in either JSON or XML (or form-encoded). See static webhooks on how we'll do our best to parse the response.

Usually Zapier expects an array of objects. If your API only sends a single object, wrap it in a single element array:

[ {"firstName": "Tom", "lastName": "Smith"} ]

On a successful hook, we'll return a 200 status code, content is irrelevant.

If Zapier responds with a 410 status code you should immediately remove the subscription to the failing hook (unsubscribe). Additionally, excessive failures (multiple 4xx or 5xx failures) can be handled at your discretion. It is the client's responsibility to resubscribe if needed after failures.

Step 3: Unsubscribe (a call from Zapier to your app)#

DELETE <unsubscribe_endpoint> \
    -H Authenticated: Somehow \
    -H Content-Type: application/json \
    -d <content built/omitted in pre_unsubscribe scripting method>

If you've properly stored identification data about the hook (like its unique ID) from the post_subscribe scripting method, you should use ourpre_unsubscribe scripting method to formulate the DELETE call. In that call you have access to your previously stored data under thebundle.subscribe_data variable.

Some example code is provided to make this as easy as possible. A heads up: you will need to use that code if you intend to do a DELETE call. You'll be able to access bundle.subscribe_data to build the URL in pre_unsubscribe. The default behavior is slightly different and listed below.

The value for the unsubscribe_endpoint field can be specified within the "Manage Trigger Settings" option in your app's dashboard.

However, the current default does not perform a DELETE. For example, if you omit the pre_unsubscribe function entirely, we will attempt a default unsubscribe call:

POST <unsubscribe_endpoint> \
    -H Content-Type: application/json \
    -d '{"target_url": "https://hooks.zapier.com/<unique_target_url>"}'

On a successful unsubscribe, return a 200 status code, content is irrelevant.

It is worth remembering that the subscription URL should effectively be unique (this could be enforced by your app as well) which also allows us to clean up unrecognized hooks (we also recommend not requiring authentication for such an endpoint).

Optional: Reverse Unsubscribe (a call from your app to Zapier)#

DELETE https://hooks.zapier.com/<unique_target_url> \
    -H Content-Type: application/json

If you'd like to allow users to manage their subscriptions from inside your app (or maybe you are cleaning up after a user deletes their account or revokes credentials) you can issue a DELETE to the unique target URL which was generated when the subscription was created -- this will pause the Zap on Zapier's end.

Required to Go Public: Set a polling URL#

In the trigger setup, there is a field to define a polling URL. This URL will be used when users are setting up a Zap and must test it. Without this defined, the user must always go to your app, create a new data point, and have it send the hook to Zapier. This is not ideal, and in some cases may be particularly undesirable (if for example other Zaps are already set up on that hook, it would trigger them).

Setting up a polling URL fixes this. The polling URL is only used during this testing phase, not during the regular operation of the Zap. If you need to script special behavior, you can do so using the standard pre_poll and post_poll methods on the trigger key.

Note: Please ensure that the data format that returns from the Polling URL is the same as what returns from the REST Hook, so users can correctly map fields.

↑ Was this documentation useful? Yes No

Request Log#

We log the requests your app makes to aid in debugging. We collect every request, successful or not, during the development stage. As soon as your app is approved, we only log requests that have status codes of >= 300.

The logged request list on your app dashboard should look like:

logged requests history

Clicking details will give you lots more information, like headers, query strings, POST and response data.

Note: we also offer rudimentary JavaScript exceptions messages via scripting. We plan on offering more details along with tracebacks soon.

↑ Was this documentation useful? Yes No

Scripting#

See our full Scripting document for more details.

↑ Was this documentation useful? Yes No

Searches#

Overview#

Searches answer the question: What records can I lookup by a particular query? They are things like:

  • Find a Contact
  • Find a Product
  • Find an Issue

Searches can be useful on their own, or they can be combined with Actions to perform "Get or Create" style logic.

What a user sees:

What a developer sees:

See also: Searches in the CLI

You can define your searches via your app's dashboard. When you create a new search, you'll be prompted with several options. Below we list all complete definitions of what each option is for.

Search Options#

Name#

This is a human readable label a user would see when browsing the directory and adding your app. Make it short but descriptive.

Example:
Find a Contact or Find Ticket

Noun#

This is the object that the search is most closely associated with. It will be a noun you used in the Name field. We rely on an accurate noun to generate human-friendly sentences for users.

Example:
"Find Contact" would have "Contact" as the noun. "Find Completed Sale" would use "Sale" or "Completed Sale".

Key#

This is a field only really used internally for both dynamic dropdown and Scripting references. Needs to be at least two characters long, start with an alpha, and only contain a-z, A-Z, 0-9 or _.

Example:
find_contact, or findContact

Help Text#

A longer description of what this Search actually looks for. Point out any un-standard behavior as far as how searching happens.

Example:
Finds a contact by email address.

Important#

Usually you'll want to leave this checked, but if you don't we'll hide that search behind an "uncommon" link when a user selects their action. Mainly this is helpful for removing searches that are there for breadth but are rarely used.

Hide#

Usually you'll want to leave this unchecked. If you check it, we'll completely hide the search from the enduser. This can be useful if a search is incomplete, but you need to deploy your app in it's current state. This option is also a way to hide searches that become deprecated in your API.

Search Endpoint#

Define the URL route where we will, by default, GET for a list of results. Note that the results must be an array, otherwise your search may fail. If your API returns a single object, you can use scripting to wrap the object in an array in a _post_search method. You can also make use of variable syntax where auth fields and search fields will be injected.

Example:
http://api.example.com/v2/clients.json or http://{{account}}.example.com/api/v1/projects.json

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.

A Warning about encoding URL params#

We will not automatically encode any URL variables, so you're responsible for encoding any if they require that. For example, emails might include a + sign, so if you have https://example.com/api/v2/leads?email={{email}} you'll want to encode that in your SEARCH_KEY_pre_search (or remove it from there and add it to the bundle.request.params), otherwise you'll get a "space" where the + sign is.

A better approach is to not even include it in the URL and to instead only use bundle.request.params in this case since bundle.request.params will be encoded automatically.

Custom Search Fields URL#

This allows you to dynamically define search fields that are user set (IE: custom fields).

Example:
http://api.example.com/v2/fields.json or http://{{account}}.example.com/api/v1/fields.json

Read more about custom field formating here.

Resource URL#

The URL we can use to fetch a single record. This will be used to perform a follow-up GET on results we get back from the Search Endpoint (and in the case of a Search-or-Create Zap, the create step as well).

You can make use of variable syntax where auth fields and search result fields (not the actual search fields) will be injected.

You will want to provide this in case your Search (or Create) Endpoint don't return all useful information about a record that you might get by fetching it directly.

Example:
https://api.example.com/v2/leads/{{id}}.json or https://{{account}}.example.com/api/v1/leads/{{id}}.json

Assuming your search would return something like:

[
  {
    "id": 124
  }
]

And you had an account Authentication variable, for example.

↑ Was this documentation useful? Yes No

Search Fields#

Overview#

Search Fields answer the question: what details can a user provide when setting up a Search?

These details might include:

  • Name to query for a Contact (EG: Salesforce)
  • Repo to restrict search to for an Issue (EG: Github)
  • Notebook for a Note (EG: Evernote)

What a user sees:

What a developer sees:

Each Search should have at least one Search Field, because otherwise we won't know what to include in the query.

You can also dynamically load custom Search Fields by inspecting a custom field endpoint of your own. Learn more.

Search Field Options#

Key#

A key for you and your API's consumption. This is available for variable syntax in the Search URL field as well as in Scripting. Needs to be at least two characters long, start with an alpha, and only contain a-z, A-Z, 0-9 or _.

Example:
room

Label#

A human readable Label shown in the UI as a user works to complete a Search.

Example:
Email or Name

label help default

Help Text#

Human readable description of a Search field, useful for describing some detail you couldn't list in the Label.

Example:
Specify the first name to search by. or Restrict the search to contacts in this category.

Default#

A default value for a field. The behavior varies between required and optional fields. For required fields, the default will be set once when the user first creates the Search, but it is not guaranteed after that (we raise an error on missing/null values instead). For optional fields, it is set on initial creation and used in place of missing or null values every time the Zap runs.

Type#

The type we will try to coerce to on the backend. Fails silently to ensure that tasks aren't dropped on coercion errors.

You can get a full list of supported types and the coercion implied here: Field Types.

Required#

If checked a user will not be able to continue without entering some value.

Dynamic Dropdown#

Use an existing Trigger to load in values for selection, using the machine readable value your API consumes (like id or hash) while showing a human readable version to the user (like name or itemName).

Refer to our dynamic dropdown docs for a more in depth explanation.

Example:
TRIGGERKEY.id.name or TRIGGERKEY.hash.itemName

dynamic dropdown

Static Dropdowns#

A comma separated string that will be turned into a select field for limiting the choices a user can provide to a Search field.

Example:
choice_a,choice_b,choice_c or Yesterday, Today, Tomorrow

simple static dropdown

If you would like to provide a label for the raw value of each choice, you can also use the raw|label,raw|label syntax instead.

Example:
1|Option 1,2|Option 2

simple static dropdown as key-value pairs

List#

Indicates if this field can hold multiple values. For example, this could be useful if you want to allow users to search for a contact by name, but limit the search to contacts with one or more tags applied. List fields gain the +/- icons to the side.

list field example

↑ Was this documentation useful? Yes No

Search or Create#

Search or Create allows users to combine your Searches and your Actions together into one flow. Each Search may be associated with one Action, which represents the creation of the same type of object that search is used to look up. (Practical example here)

Example use cases might include:

  • Find or create a new customer (EG: Salesforce)
  • Notebook for a Note (EG: Evernote)

What a user sees:

What a developer sees:

The user's UI is built using a combination of the label above and the related label and noun from the single Search and Action.

At this point, when the user selects the checkbox to do a Find or Create, they will be given the option to fill in both the fields for the search, as well as the fields for the create.

When the Zap runs, if an element is found during the Search, it will be used. If not, a new item will be created.

Errors like a 404 will not be interpreted as a "miss" on search and will not trigger a follow up create - explicitly return [] when no records are found. If your API can't do that, use scripting to return an empty list [] (note a _post_search won't work, you'll have to replace _search completely)

This type of connection should be used in cases where the search is likely to yield one correct result, and unlikely to yield incorrect results. Good use cases include searching by keys like email or phone number, or other uniquely identifying information that the user might have.

After every successful Search or Create - we'll attempt to grab a fresh record via the Resource URL.

↑ Was this documentation useful? Yes No

Search Fields (Custom)#

A natural extension of normal hard coded search fields are dynamic search fields, or custom fields. Custom fields are very commonly used in services that allow users to create their own data fields. Examples include contacts in CRMs, form management apps, or ticket fields in helpdesk software. These apps will assign a generated unique key to the field, while storing information about the field, such as its human readable label and data type, separately. Working with this data requires an extra step to retrieve that field metadata in order to allow users to make sense of the information.

All you need to do to enable custom fields is:

  • Provide a Custom Search Fields URL for your search action.
  • Ensure the URL route returns data in the below format, or manipulate it to fit with Scripting.
  • You can choose from several internal types, documented here: Field Types.
[
    {
        "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
     },
     ...
 ]

If your search action returns custom fields you'll also want to configure a source for labels so other actions in multi-step Zaps can display those fields properly and allow users to correctly use the data in the Zap editor.

  • Provide a Custom Search Result Fields URL for the search action. (This will likely be the same endpoint you used for Custom Search Fields)
  • Ensure that the URL route returns data in the below format, or manipulate it to fit with Scripting. Extra data will be ignored, but we require at least the following to properly format your search action results.
[
    {
        "type": "unicode",
        "key": "json_key", // the field "name", will be used to construct a label if none is provided
        "label": "Pretty Label",
        "important": true // optional
    },
    ...
]

Right now parent_key and type=dict is not supported in custom fields.

↑ Was this documentation useful? Yes No

Search Sample Results#

(This works similar to trigger sample results)

To help give users a smooth experience when creating multi-step Zaps, we ask that you paste a JSON object that contains the fields (along with sample values), so that your user can map those fields into the next step.

We will use this sample JSON for two things:

  1. To detect a list of hard-coded key names which the user can pick from during Zap setup
  2. To use as a hard-coded fallback for sample data so that we can provide fields to insert during Zap setup (if your API returns 0 results)

These result is primarily used when the user picks "Skip Test" when testing their Zap. Zapier will try to use a live API result, first.

Here is the sample JSON for something like an search for a Trello Card, and how it shows up in our user-facing Editor:

{
  "name": "Some Card",
  "url": "https://trello.com/c/YZSaTvjM/59-some-card",
  "id": "5642335fa1041c95e73f290b",
  "idList": "55159df870b769081c4a8e82",
  "pos": 65535
}

We will parse this sample and provide dropdowns like this to the user:

samples

By default, we can handle flat dictionaries and dictionaries within dictionaries (via our __ delimiter in keys).

When the user is inserting fields in the Zap editor, and your API returns no results ([]) then we will use your hard-coded fallback JSON if it exists.

Your hard-coded JSON provided above will not be run through the Scripting API (either for key enumeration or sample data fallback) so if you use the Scripting API to add or modify fields on top of your normal API response, you'll want to make sure you perform the same manipulations manually before pasting in the JSON above.

↑ Was this documentation useful? Yes No

Static Webhooks#

Static webhooks are a very simple version of webhooks wherein the user of your service will have to take action to enable a webhook. Usually, this is done copying a url that Zapier provides during the Zap setup process (like https://zapier.com/hooks/catch/123/n/456789/). We would prefer you use REST hooks but that usually requires extra code and a full REST API.

While Static webhooks are very useful for rapid prototyping, we don't generally accept apps for inclusion in the Zapbook that are solely Static webhooks. Users can use our built in Webhook app to do the same things that a purely Static webhook app can do. You should look at REST Hooks as the next step after you've proved the concept privately with Static webhooks.

To get a taste of how static webhooks work, check out our built-in generic webhook service.

Setup the Webhooks#

To get started simply create a new trigger on your app, choosing static webhook as the type. You can follow the Hubspot example to see a detailed walk through of how to do this.

Once defined in your app, static webhook triggers will appear like this to users when they create a Zap:

static webhook user display

Using the Webhooks#

For as long as the Zap exists, requests made to that URL will be associated with your app and the user. That means requests made during the setup process (while the Zap is paused) will still get stored, making it easier for users to debug. However, requests will not trigger any actions until the Zap is turned on.

Also, it is important to know that by default, we do our best to inspect and pull out all relevant information in a request. If you use scripting to further refine the hook data, you'll see it passed in as bundle.cleaned_request. For example, a request might have a cleaned_request similar to:

POST https://zapier.com//hooks/catch/123/n/456789/?hello=world \
    -H Content-Type: application/x-www-form-urlencoded \
    -d 'some_json=%7B%22foo%22%3A%22hooray!%22%7D&some_xml=%3Cbar%3Eyay!%3C%2Fbar%3E'
{
  "queryset": {
    "hello": "world",
  },
  "some_json": {
    "foo": "hooray!"
  },
  "some_xml": {
    "bar": "yay!"
  }
}

Note: We only support simple query strings at the moment. &foo[hello]=world will be parsed to the JavaScript object { "foo[hello]": "world" } and not { "foo": { "hello": "world" } }.

Of course, more simplistic examples like straight JSON or XML are handled as you'd expect.

Catching Single Objects vs. Lists#

Some webhooks send single objects, while some are batched as lists of objects for efficiency reasons. Though Zapier supports both, we highly recommend using scripting if you are dealing with batched lists. We do our best via cleaned_request to interpret lists where possible, but if your list is hidden in a subkey, we won't capture it properly.

For example, a generically supported list:

POST https://zapier.com//hooks/catch/123/n/456789/?hello=world \
    -H Content-Type: application/json \
    -d '[{"name":"bryan","age":27},{"name":"mike","age":23}]'

Would result in two triggers...

{
  "name": "bryan",
  "age": 27
}

...and...

{
  "name": "mike",
  "age": 23
}

Notice that the query string hello=world is ignored? That is because we assumed the data list was more important and we didn't want to maim the data contained therein.

Check out an example where we would ignore a subkeyed list:

POST https://zapier.com//hooks/catch/123/n/456789/?hello=world \
    -H Content-Type: application/json \
    -d '{"data":[{"name":"bryan","age":27},{"name":"mike","age":23}]}'

Because the list isn't at the root, by default, we'll interpret this as a single object. That means we'll only trigger once for this object:

{
  "queryset": {
    "hello": "world",
  },
  "some_json": [
    {
      "name": "bryan",
      "age": 27
    },
    {
      "name": "mike",
      "age": 23
    }
  ]
}

This is where you'd need to break out scripting to do some refinement. Check out the examples we provide in the scripting examples.

Testing Webhooks#

Normally, a user will just log back into your service and force an event by performing the action that causes a webhook to be emitted. This can be a little annoying to the user during setup, so alternatively you can send a X-Hook-Test: true and we will never trigger an action for real, we'll just cache the payload for our UI. This means when the user enables webhooks in your app, just send some sample data across.

↑ Was this documentation useful? Yes No

Style Guide#

See our full Style Guide document.

↑ Was this documentation useful? Yes No

Test Triggers#

A test trigger is a regular trigger with a special responsibility. The test trigger is the trigger Zapier will use to verify the authentication credentials users provide when they first attempt to access your API through Zapier. If your API returns a 2XX status code, we will assume the credentials are valid. Anything else (or an empty response that isn't a 204), and we will assume the credentials are bad.

You can only mark one trigger as the test trigger. A test trigger can be made for any endpoint in your API that:

  1. Requires authentication
  2. Is guaranteed to always return some data (or a 204 for empty responses)

Some examples of good endpoints to use for the test trigger:

  • A url like /ping that is meant solely to test authentication
  • An endpoint that returns a user's profile
  • An endpoint for high-level objects in your API that users are virtually guaranteed to have (i.e. contacts if you are a CRM, tickets if you are a support center)

Setting one up#

In your app, you'll need choose a trigger that performs a "simple test" when its Polling URL is used to access an endpoint that requires valid authorization/credentials.

If your trigger is intended to be solely used for authentication testing, then you can mark it hidden.

Important make sure that the test trigger doesn't have any "Required" trigger fields (because they'll be empty when we perform the auth test)

Errors#

When your test trigger fails, a message will be displayed to the user "App returned (400) Error and said nothing" or something similar. This is constructed from the response status code and status message returned from your endpoint, along with a message from the response body, like so:

App return (response.status_code) response.status_message and said response.body

The message is interpreted from the response.body as such:

  1. We check to see if you send back JSON. If so, we look for a message field, or similar in the JSON object.
  2. We check to see if you send back an XML file (i.e., xml in the Content-Type header). If so, we do a similar lookup for an error message in the body.
  3. We then check to see if you sent back a plain text response(i.e., Content-Type: text/plain). If so, we check that the content is < than 180 characters and use it verbatim.

Testing#

To select your test trigger, use the button "Manage Trigger Settings":

Scroll down to the bottom of the page, and select the appropriate trigger:

↑ Was this documentation useful? Yes No

Triggers#

Overview#

Triggers answer the question: What events can my users listen for with Zapier? They are things like:

  • New Contact (EG: Highrise or Salesforce)
  • New Email (EG: Gmail or IMAP)
  • New Issue (EG: GitHub or Pivotal Tracker)

You can think of a Trigger as a GET or read. It involves Zapier receiving data from your app.

For example, say your app has a "New Ticket Opened" trigger. We will watch for new tickets in a user's account. The data we trigger off of might look like this:

[
  {
    "id": 123456,
    "owner_id": 654,
    "date_created": "Mon, 25 Jun 2012 16:41:54 -0400",
    "description": "Add our app to Zapier"
  }
]

Note: The data in the response must be an array, even for a single data point.

These key/values are available for users to map into the action as they see fit.

What a user sees:

What a developer sees:

See also: Triggers in the CLI

You can define your triggers via your app's dashboard. When you create a new trigger, you'll be prompted with several options. Below we list all complete definitions of what each option is for.

Trigger Options#

Name#

This is a human readable label a user would see when browsing the directory and adding your app. Make it short but descriptive.

Example:
New Ticket Created or New Email with Label

Noun#

This is the object that the trigger is most closely associated with. It will be a noun you used in the Name field. We rely on an accurate noun to generate human-friendly sentences for users.

Example:
"New Ticket Created" would have "Ticket" as the noun. "New Email with Label" would use "Email" or "Labeled Email".

Key#

This is a field only really used internally for both dynamic dropdown and scripting references. Needs to be at least two characters long, start with an alpha, and only contain a-z, A-Z, 0-9 or _.

Example:
new_ticket, or newEmailLabel

Help Text#

A longer description of what this trigger actually watches for.

Example:
Triggers when a new email occurs with a label of your choice.

Demo:
the user will see Name and Help Text like below:

label and help

Important#

Occasionally, you'll have unimportant triggers which are used mostly to drive things like dynamic dropdown, but could be useful to a small subset of users. If you mark a trigger as unimportant, we will hide the trigger behind a link. The user can still pick these if they really want to but it is hidden by default.

Note: if you have no important triggers, we will not hide any of them by default.

Hide#

If you create a trigger solely to be used in a dynamic dropdown and it will never be helpful to users, you can mark it as hidden. We will never show the trigger in the UI and users will not be able to pick it.

This usually comes up with test triggers, where the test trigger is for an endpoint that always returns the same data (a user profile, for instance).

Paging#

When defining a trigger to power a dynamic dropdown you can use the Scripting API to implement paging by relying on bundle.meta.page.

This flag must be set in order to enable paging in the dynamic dropdowns powered by this trigger. If set, the user will see the option in the dropdown to load more choices: “Don’t see your choices? Try loading more than {x}.”

paging

Otherwise, the user will see standard language: “Check {your app} and reload the data”

Note: Paging is not used in normal trigger polling calls.

Polling: URL#

Where in your API can we poll for this trigger? Your URL route can include variables (from authentication fields and trigger fields), as well as query parameters. Query parameters are helpful for telling your API to sort things in reverse-chronological order, a requirement we have.

Important: this endpoint should return a list of JSON objects. If the list is nested in an object with meta information we will do our best to find it. If we can't or your API only returns a single object, then you will need to use a _post_poll scripting method to take the response and transform it to a list.

Example:
https://example.com/api/v1/users/{{id}}/contacts.json?sort=desc&on=date_created

A Warning about encoding URL params#

We will not automatically encode any URL variables, so you're responsible for encoding any if they require that. For example, emails might include a + sign, so if you have https://example.com/api/v1/users?email={{email}} you'll want to encode that in your TRIGGER_KEY_pre_poll (or remove it from there and add it to the bundle.request.params), otherwise you'll get a "space" where the + sign is.

A better approach is to not even include it in the URL (it'll be added and encoded automatically in that case).

Custom Trigger Fields URL#

This allows you to dynamically define user-friendly labels for data returned by triggers. These labels are shown to users in the Zap editor when setting up Actions.

Example:
http://api.example.com/v2/fields.json or http://{{account}}.example.com/api/v1/fields.json

If your polling trigger returns:

[
  {
    "id": 1,
    "q_1": "Yes"
  }
]

You can return in the custom fields URL something like:

[
  {
    "label": "Are you happy with your service?",
    "key": "q_1",
    "important": true,
    "type": "unicode"
  }
]

And the UI will be more easy to understand.

Read more about custom field formatting here.

Webhook: Event Name#

A part of our REST Hooks, this lets you namespace the name of event that this trigger identifies with.

Webhook: Static Webhook#

A part of our static webhooks, this simply toggles the ability to receive webhooks and disables polling.

Webhook: Static Webhook Directions#

A part of our static webhooks, when static webhooks are in use this lets you define specialized directions for setup. It also supports Markdown so you can provide links or other specialized HTML formatting.

↑ Was this documentation useful? Yes No

Trigger Fields #

Overview#

Trigger Fields answer the question: How can a user filter Triggers? Almost exclusively these are dynamic dropdowns, but some real world examples are:

  • Search Term (EG: Twitter Tweet Search)
  • Label (EG: Gmail Inbox)
  • Parent Object (span relationships via dynamic dropdowns)
  • Repo for an Issue (EG: Github)
  • Notebook for a Note (EG: Evernote)

Imagine an endpoint like https://example.com/api/v1/prospects.json - that would require no trigger fields at all. However an endpoint like https://example.com/api/v1/list/1234/prospects.json - that would require at least a trigger field to select the list ID (and it would be a dynamic dropdown at that!).

What a user sees:

What a developer sees:

When you attempt to add a trigger field, you'll be prompted to provide some options which are outlined below.

Use the Scripting API to modify the request to your Polling URL or Subscribe URL to include these fields or use them in the post poll or catch hook methods to filter the response from your API.

Trigger Field Options#

Key#

A key for your internal use. It is available for variable syntax in the Trigger URL field (as well as in scripting). Needs to be at least two characters long, start with an alpha, and only contain a-z, A-Z, 0-9 or _.

Example:
project or search_term

Label#

A human readable Label shown in the UI as a user builds a Zap.

Example:
Room or Title

label help default

Help Text#

Human readable description of a trigger field, useful for describing some detail you couldn't list in the Label.

Example:
Choose which project to watch for new messages. or Define a search term for finding Tweets.

Default#

A default value for a field. The behavior varies between required and optional fields. For required fields, the default will be set once when the user first creates the Trigger, but it is not guaranteed after that (we raise an error on missing/null values instead). For optional fields, it is set on initial creation and used in place of missing or null values every time the Zap runs.

Type#

The type we will try to coerce to. Falls back to unicode (text) if coercion fails.

Required#

If checked a user will not be able to continue without entering some value.

Dynamic Dropdown#

Use an existing Trigger to load in values for selection, using the machine readable value your API consumes (like id or hash) while showing a human readable version to the user (like name or itemName).

In our experience, most trigger fields are dynamic dropdowns.

Refer to our dynamic dropdown docs for a more in depth explanation.

Example:
TRIGGERKEY.id.name or TRIGGERKEY.hash.itemName

dynamic dropdown

Static Dropdown#

A comma separated string that will be turned into a select field for limiting the choices a user can provide to a trigger field.

Example:
choice_a,choice_b,choice_c or Yesterday, Today, Tomorrow

simple static dropdown

If you would like to provide a label for the raw value of each choice, you can also use the raw|label,raw|label syntax instead.

Example:
1|Option 1,2|Option 2

simple static dropdown as key-value pairs

List#

Indicates if this field can hold multiple values. For example, this could be useful if you want to allow users to only trigger on new contacts with one or more tags applied. List fields gain the +/- icons to the side.

list field example

↑ Was this documentation useful? Yes No

Trigger Fields (Custom)#

Sometimes, the data returned by a trigger is hard to use in a Zap because of how the keys are named. When a user goes to map fields in an action, the trigger data's keys are used to identify which field is which. If those keys are something unintelligible like UUIDs rather than human-readable data points like "name" or "email", the user may not be able to identify which fields to map in their action.

To remedy this, we have Custom Trigger Fields, an additional HTTP GET to a URL you provide in your trigger definition that tells us additional metadata about the data the trigger will return, such as human-readable labels to display when mapping fields. All you need to do to enable custom trigger fields is:

[
  {
    "type": "unicode", // unicode, int, or bool
    "key": "json_key", // the key in the trigger data
    "label": "Pretty Label", // the human-readable label to display
  },
  ...
]
↑ Was this documentation useful? Yes No

Trigger Sample Results#

To help give users a clean experience when creating Zaps, we ask that you paste a sample JSON dictionary of a single item from your API into Zapier.

We will use this sample JSON for two things:

  1. To detect a list of hard-coded key names which the user can pick from during Zap setup
  2. To use as a hard-coded fallback for sample data so that we can provide fields to insert during Zap setup (if your API returns 0 results)

These results will NOT be used for a user's Zap testing step. That step requires data to be received by an event or returned from a polling URL.

Here is the sample JSON for something like a new email message, and how it shows up in our user-facing Editor:

{
  "to_name": "Mike Knoop",
  "to_address": "mike@zapier.com",
  "from_name": "Bryan Helmig",
  "from_address": "bryan@zapier.com",
  "subject": "Testing out this new Zap!",
  "message": {
    "no_html": "Let me know it it works.",
    "html": "<div>Let me know if it works.</div>"
  }
}

Note: Even though your API endpoint has to return an array, the sample JSON here must be of a single object.

We will parse this sample and provide dropdowns like this to the user:

samples

By default, we can handle flat dictionaries and dictionaries within dictionaries (via our __ delimiter in keys).

When the user is inserting fields in the Zap editor, and your API returns no results ([]) then we will use your hard-coded fallback JSON if it exists.

Your hard-coded JSON provided above will not be run through the Scripting API (either for key enumeration or sample data fallback) so if you use the Scripting API to add or modify fields on top of your normal API response, you'll want to make sure you perform the same manipulations manually before pasting in the JSON above.

Note - our system often does a sample "poll" or pulls cached values for a live example - samples are just a part of that system. If you need to omit bad fields from usage - you'll need to ensure the poll or webhook or whatever does not included the bad fields either!

↑ Was this documentation useful? Yes No

Variable Syntax#

If you've used Django or Mustache (or many other templating engines), you are probably well versed in the variable syntax we use. This also happens to be the exact same syntax our users use to map data from triggers into Action Fields (though they get a dropdown interface to do it).

Given the following context (think authentication fields or data from triggers):

{
  "id": 123456,
  "owner": "Larry",
  "title": "Hello world!",
  "description": "It's a beautiful day!"
}

And the following template (think authentication mapping or action fields):

{
  "talking_user": "{{owner}}",
  "chat_message": "{{title}} and of course {{description}}"
}

We will generate the following output:

{
  "talking_user": "Larry",
  "chat_message": "Hello world! and of course It's a beautiful day!"
}

The keys to put between the {{}} come from the keys you specify when you setup action fields, trigger fields, and authentication fields.

↑ Was this documentation useful? Yes No

Versions Changelist#

Below is a changelog of the major releases of the Zapier Developer Platform. Each release has a summary of the new features added and any breaking changes that were made.

Version 2 (2015-07-31)#

This is a backward incompatible update to the Developer Platform. It adds several new features and fixes some of the limitations of the first version.

  • Apps can now have Searches. A Search is used to find individual records by a field (say finding a contact by name).
    • Searches can be linked with Actions to create a Search or Create flow, giving the user a way to search for an item, and if it doesn't exist, create it.
    • Searches can be used to power action fields, similar to Dynamic Dropdowns. Users input data to the field which is used to search for items, and a given data element returned from the search result (such as id) is used in place of their input.
  • Fields can now have the “List” property defined through the UI. Before this was only possible to set via Scripting.
  • Action fields have a “Parent Key” option that enables line item support.
  • Fields now have a "Placeholder" option that operates solely as an HTML5 style placeholder - it is only for helping the user understand what will happen if they enter nothing in the given field.
  • Breaking Change Scripting can no longer access trigger_data from bundle in pre_write and post_write.
  • Breaking Change Scripting can no longer access trigger and action from bundle.zap.
  • Breaking Change Scripting code now runs under JS strict mode ('use strict';), so developer should verify their code still executes correctly (the built in code editor runs jshint - so check there!)
  • Breaking Change Data mapped into Action Fields is always coerced according to the field's type
  • Breaking Change Data entering actions is no longer flattened into a string. This means lists and dictionaries will pass through to actions intact rather than being converted to CSV and 'key|value' respectively. If there is existing Scripting code that does any sort of parsing on those values, it will need to be updated to handle the new structure (likely this means deleting code that was expanding the strings back into arrays and dictionaries).
  • Breaking Change We now automatically include a state parameter on the Authorization URL for apps that use OAuth2.
  • Bug Fix Trigger fields passed into custom fields, REST hook and catch hook methods are always coerced according to the field's type

Version 1 (2012-08-01)#

The initial launch of the platform.

↑ Was this documentation useful? Yes No

CLI vs Web Builder#

Everyone has a different comfort level with development tools. That's why we offer two ways to interact with the Zapier Developer Platform: the Command Line Interface and the Web Builder.

Both enable you to expose your app to Zapier users by connecting your API to our auth, trigger, action, and search structure, and the two interfaces are geared towards different technical levels and organizational needs.

Command Line Interface#

Best for: Engineers with JavaScript experience who plan to support their Zapier app long-term.

The Command Line Interface (CLI) to the Zapier Developer Platform is based on Node JS. Engineers—or those with high technical skills—describe their apps using a JavaScript definition format. Functions you write call Zapier’s helper libraries to receive inputs from a user’s Zap and pass back outputs from your app.

Advantages of the CLI:

  • Write your app entirely with JavaScript, including transpiler support for newer versions
  • Easily collaborate with teammates via GitHub or another source control tool
  • Control migrations between versions of your app
  • Reuse code through request handling middleware
  • Prevent unintended breaking changes through unit testing

Disadvantages of the CLI:

  • Slower startup: It may take more time to get to your first working functionality
  • Much harder for non-engineers to understand and contribute to your integration

How to Get Started with the CLI#

Interaction with the Zapier platform happens via the command line after installing our Node module. You can run tests locally, then zapier push the app to Zapier’s production servers, where you can test the end-user experience.

Web Builder Interface#

Best for: Non-engineers or those looking to get started quickly.

Most of the apps you see on Zapier were built using the User Interface (UI) to the Zapier Developer Platform. A good number of those apps have been built by non-developers. Even if you don't write code on a day-to-day basis, you should be able to get an app up and running if you have a solid understanding of APIs.

Advantages of the Web Builder:

  • Get started fast
  • Build app using a Web interface
  • Write little to no code
  • Optionally use Scripting to alter API calls

Disadvantages of the Web Builder:

  • Some use cases require workarounds and repeated code in the Scripting API
  • Collaboration on teams is only possible via sharing Zapier credentials
  • Version migrations can be more dangerous to execute

How to Get Started with the Web Builder#

All you need to get started with the Web Builder Interface is a web browser and an understanding of how APIs work. It also helps to have an API in mind to use (such as your own!).

Some Things to Consider#

While both interfaces provide access to the Zapier Developer Platform, they're not interchangeable. For technical and logistical reasons, CLI app cannot be converted into a Web Builder app. Similarly, we don’t recommend converting Web Builder apps into CLI apps, though you can reach out to us to see whether this is possible for your app.

↑ Was this documentation useful? Yes No
Get Help