The Perfect Integration-ready API

Ben Peter
Ben Peter / January 19, 2017

You’ve probably seen software-as-a-service (SaaS) companies boast about integrations with other services. And for good reason–users love to connect their apps together. Behind the scenes, APIs and a little code make it possible to exchange data between services. Sometimes these integrations increase signups and often they’ll decrease churn, as they help users get their favorite tools working with your app. Before you can get your marketing team to work on the deals and see positive results, though, you need an API that’s ready to support integrations.

We’ve seen over 750 APIs integrate with Zapier, and over time have learned what makes an API great for integrations. Our tips below may skew toward Zapier, but should help make your API ready for building any integration.

Why Even Build an API?

Chances are, if you’re here for tips on an integration-ready API, you’ve committed to building an API. You may already have an API and want ideas to make it better. The first step is to understand who will benefit from your API. That audience is almost always the same as that of your application itself; your app's users will be the end users of your API.

Of course, your users may not write the code that integrates with the API, so you’ll also have to consider the developers who will consume your API. But developers should be secondary to the main reason you build your API–to help your users.

As you prepare your API, think through its use cases. What are the circumstances where users would want to learn about new data available in their account? What actions do users take in your app that you should expose to integrations?

Answering these questions will be easier if you have a list of potential services to integrate, perhaps from integrations your users have requested. It will help use cases become more real, and help everyone understand exactly why the project of building or improving your API is a high priority.

Authenticating to Your Service

Now that you know you’re building an API for users, it's time to think about how you want to authenticate them via API. On your website or in your app, you likely take a username and password. With an API, handing out passwords is unsafe (what if someone finds it?) and unlikely to last (what if the user changes their password?).

The best approach is an OAuth v2 access token (optionally with a refresh token). In situations where that isn't feasible to implement, API Keys are the next-best alternative. For security purposes, these tokens/keys should be revocable by the user.

Your service should use an "Authorization" header to receive this token/key. Either the "Bearer" or "Basic Auth" (passing the token/key, and an empty password). Your service should provide an endpoint that checks whether the authentication token/key in the header is currently valid. This should perform the following steps:

  • Ensure the token/key is present and formatted correctly. If not, return HTTP 400 with a meaningful JSON formatted message, like { "error": "Auth header missing" }
  • Verify the token/key is valid. If not, return HTTP 400 with a JSON error like { "error": "Auth invalid or revoked" }
  • If everything is good, then return an HTTP 204 status, No Content

Authentication can be the hardest part of building any integration. Providing an endpoint to check authentication makes it easier for a developer to get over this hurdle. When integrating with Zapier, you can use this authentication endpoint for a test trigger, which gives your end users peace of mind when setting up Zaps.

Finally, your OAuth v2 API may use refresh tokens. If it does, your access token will expire after a period of time and need to be refreshed. Ideally, this is indicated by returning an HTTP 401 error status, which can be detected by the client. Then the system would automatically try refreshing the token and retry the original request with the new token. This is the best experience for the user, who will never need to intervene as the servers negotiate authentication.

How to Format Your Data

We’ve already alluded to it in the authentication section, but we’ll come out and say it here: use JSON. Over the last 10 years, this has become the data format developers expect. While it stands for JavaScript Object Notation, it is easily parsed in most modern languages. It is lighter than XML and easier to read. Unless those integrating with your API have a specific toolset that requires XML, you’ll do fine providing only JSON.

That said, don’t use JSON for everything. HTTP status codes should be sent in the headers, where machines can easily parse them, for instance. But do send a JSON body with a human-readable error message.

How about the format of the data within the JSON? Here are two common issues we see:

  1. A single name field: Many systems, such as CRMs, require separate first and last name fields. If your service stored the name as "Full Name", then provide that field, and also provide your best-guess for splitting the name into two pieces.
  2. Date formatting: Interpreting dates and converting time zones frequently cause issues with exchanging date and time information. You can have other “pretty” formats in the user’s time zone, but for clarity also provide ISO 8601 datetimes in UTC.

With your data properly formatted, we can now read, write, and search.

Reading Data from Your Service

With an authenticated API request, the data coming back is specific to the user who is “logged in” to the API. The most useful integrations are going to share user-specific data. Usually, integrations care about the most recent data. Your API should provide both polling and webhook mechanisms for receiving data.

  • Polling is a standard API GET request, with a way to sort data by its recency. A good default is to sort “newest first,” so integrations can always start with the latest data. You’ll also want a field to serve as a unique identifier–and include id in the name to make it clear to those consuming the data.
  • Webhooks provide notifications when new data is available by sending a message to a predetermined URL. Modern webhooks should support subscriptions that allow multiple external applications to request one or more updates on data within your service. You shouldn’t require your users to copy and paste URLs to initiate webhooks.

An integration-ready API will have both polling and webhooks, because each have their purpose with an integrated application. Polling retrieves the current state. Technically, it would be sufficient to compare polling results periodically, but in practice that’s extremely inefficient for both parties. Webhooks are the mechanism for determining when new data is available.

Zapier integrations, for example, use polling to help users create new Zaps. Our tool retrieves the latest data to use as an example. Once a Zap is enabled, we listen for notifications from webhooks, if they’re available.

In addition to webhooks for newly-available data, consider notifications for updated data. That’s one type of use case polling alone typically won’t catch.

Writing and Updating Data to Your Service

Depending on your service, writing data may be even more powerful than receiving updates to data that changes. Any time you open up the ability to change or add data, you’ll absolutely want to have authenticated a user. Also, very likely, the data that is being altered belongs to the user, so it makes even more sense to log them in.

Your API endpoints should expect data in a POST over HTTPS with a single JSON encoded object as the payload. For updating data, it’s reasonable to use PUT or PATCH verbs instead of POST. Technically, POST is for new data, PUT is to update an existing record, and PATCH is to update a subset of an existing record. These nuances can be incredibly useful, but can add some complexity for the integrator. At Zapier we’ll forgive you for simply using POST.

When you run into data validation errors, you’ll want to communicate them back to the client in both machine- and human-readable formats. Return the most appropriate HTTP 400-level status code with an error message in the JSON body, such as:

{ "error": "The name cannot be more than 100 characters" }

When new data is successfully created, return a JSON formatted response with the ID of the new record, like { "id" : 314 }. Some REST frameworks return HTTP 201 on successful writes with a Location header for the URL of the new record. That’s fine, though Zapier requires these cases to use Scripting to generate a body with the ID.

You’ll want to require that same ID when updating existing data. Make it clear how the record can be uniquely identified. If you have multiple types of IDs, name them all (user_id, order_id, etc). It’s fine to have non-numeric unique fields, such as order number, e-mail address, or phone number–just be clear what field you expect when updating data.

Finally, it’s important to allow developers to make atomic updates, rather than having to load and save the entire record to make an update. You can achieve this through HTTP PATCH mentioned earlier. Be sure to check out JSON Patch if you intend to fully implement this feature.

Searching for Data in Your Service

A search is a specialized way of reading data. Rather than looking for the latest data, you’re usually trying to filter to specific data. One of the most useful types of search is really a lookup–you want to find a specific record based on a unique identifier.

Integrations will need this lookup to perform additional actions in your API. For example, if you use a numeric user_id, a developer may need to find the user by email address to connect another service’s record to your user. The best approach is to use the endpoints you provided for Polling, and have them accept a URL-encoded param string that contains the criteria to be found: ?name=foobar. If there are multiple matches, your service should return an array of IDs/objects that match, like [ { "id" : 314 }, { "id" : 628 } ]. If possible, the matches should be sorted with the “best match” first.

A powerful feature in the multi-step Zap editor is the ability to search for data. We provide the first result to the user, though eventually we may do something with the extra matches. Optionally, we allow “search and create” functionality, essentially creating a result if none are found. End users in Zapier perform these lookups to connect services together, but they’re useful for other integrations, as well.

Follow these suggestions to make your API integration-ready. What fits with Zapier will also simplify writing code directly against your API.

If you already have an API, you can use this post to gauge your integration-readiness. In fact, a great way to test an existing API is to try building it on the Zapier Developer Platform. Once published, your users will be able to integrate your API with 750+ other services.

Load Comments...

Comments powered by Disqus