Skip to content

Authenticating with other services

One of the key use cases for Packs is integrating Coda with other apps and services, which often involves some form of authentication. When building a Pack you specify the type of authentication required by the API and Coda handles the credentials exchange, token storage, and account management.

View Sample Code

Using a Pack with authentication

Packs that use system-wide authentication (all users use the Pack makers's credentials) don't require any additional setup by the user, and can be used just like Packs without any authentication.

Packs that support per-user authentication require some additional setup. Users must sign in to their accounts and connect them to Coda, as well as choose which account to authenticate with when using the building blocks from that Pack.

Connecting an account

The listing for the Pack will display a Sign in to install button in place of the usual Install button. Upon clicking it the user will immediately be launched into a sign-in flow, which varies depending on the type of authentication used and the service the user is connecting to. After the sign-in flow is complete Coda will ask the user if that connected account should be shared with other users in the same doc.

Sign-in when installing a Pack with authentication

Users can sign in to additional accounts and change their sharing settings from the Settings tab of the Pack's side panel. Accounts can be reused across docs, and users can manage all of their connected accounts on the Account settings page.

Selecting an account

In the formula editor the account is shown as the first parameter to the formula, and in the other dialogs the account to use is displayed as a dropdown list.

Account selection in the formula editor

Account selection in the action builder

Account selection in the column format settings

Account selection in the sync table settings

Adding authentication to your Pack

There are two supported forms of authentication: per-user and system-wide.

Per-user authentication

User authentication requires that users of the Pack provide their own credentials. This is the most common form of authentication, as it allows the Pack to access the private data of each user. To add per-user authentication use the setUserAuthentication() method:

pack.setUserAuthentication({
  // A variety of different authentication types are supported.
  type: coda.AuthenticationType.HeaderBearerToken,
  // Additional settings...
});

The types of authentication supported, as well as the additional settings, are described in the sections below.

System-wide authentication

System authentication requires the Pack maker to provide a single set of credentials that all users will share. This form of authentication is often used for APIs that require an API key or token, but don't provide access to private data. To add system-wide authentication use the setSystemAuthentication() method:

pack.setSystemAuthentication({
  // A variety of different authentication types are supported.
  type: coda.AuthenticationType.HeaderBearerToken,
  // Additional settings...
});

After adding the code, build a new version of your Pack and then navigate to the Settings tab. There you'll see an Add system authentication button you can use to set the credentials.

Entering system credentials

The types of authentication supported, as well as the additional settings, are described in the sections below.

Authentication types

Coda supports a fixed set of authentication types which cover the most common patterns that APIs use. In addition you can define your own form of custom token authentication to support more complex scenarios. It's not possible to write completely custom authentication code however, as Coda alone has access to the user's credentials. If your API's authentication needs can't be met by any of these types please contact support.

The sections below will cover some of the most common types of authentication, and you can see the full set in the AuthenticationType enum.

Single token

Many APIs use tokens or keys for authentication. Per-user tokens are typically generated from a settings screen within the application, while API keys are often generated when registering an app in a developer portal. How you pass these tokens varies depending on the API, and Coda provides built-in support for the most common methods:

Use HeaderBearerToken authentication for APIs that expect the token to be passed in the Authorization header with the prefix Bearer. For example:

GET /users/me
Host: api.example.com
Authorization: Bearer <token>

Can be implemented using:

pack.setUserAuthentication({
  type: coda.AuthenticationType.HeaderBearerToken,
});

View Sample Code

Use CustomHeaderToken authentication for APIs that expect the token in a custom header, with an optional prefix. For example:

GET /users/me
Host: api.example.com
X-API-Key: <token>

Can be implemented using:

pack.setUserAuthentication({
  type: coda.AuthenticationType.CustomHeaderToken,
  headerName: "X-API-Key",
});

View Sample Code

Use QueryParamToken authentication for APIs that expect the token in a URL query parameter. For example:

GET /users/me?key=<token>
Host: api.example.com

Can be implemented using:

pack.setUserAuthentication({
  type: coda.AuthenticationType.QueryParamToken,
  paramName: "key",
});

View Sample Code

When using per-user authentication, the user will be prompted to enter their token when connecting their account.

Users entering an auth token

Set an instructions URL

It may not be obvious to users where they can find their API token. You can set the instructionsUrl field of the authentication configuration to a relevant help center article, or in some cases directly to the screen within the application that lists the API token. Coda will link to this URL in the dialog.

pack.setUserAuthentication({
  type: coda.AuthenticationType.HeaderBearerToken,
  instructionsUrl: "https://help.example.com/where-is-my-api-token",
});

Custom tokens

Some APIs require a combination of tokens to be used, or for them to be passed in the request body or URL. In these cases you can use the Custom authentication type. Consider this example, where a key is passed in the URL and an additional token is passed in the request body:

POST /api/<key>/users
Host: api.example.com
Content-Type: application/json

{
  "token": "<token>",
  "name": "Art Vandelay"
}

This can be accomplished using a Custom authentication configuration like:

pack.setSystemAuthentication({
  type: coda.AuthenticationType.Custom,
  params: [
    {name: "key", description: "The API key"},
    {name: "token", description: "The account token"},
  ],
});

Each token defined within the params array will result in an additional prompt in the authentication dialog:

Entering tokens for custom authentication

Unlike with other authentication types where the values are added to your fetch requests automatically, with Custom authentication you must manually add these tokens to your request. Wherever you need to inject one of these tokens enter a placeholder value instead, and before your request is sent Coda will replace it with the value of the corresponding token. The placeholder format is {{<paramName>-<invocationToken>}}, where <paramName> is the name of the token you defined and <invocateToken> is the unique ID generated for that execution of your Pack.

pack.addFormula({
  // ...
  execute: async function ([], context) {
    let invocationToken = context.invocationToken;
    let keyPlaceholder = "{{key-" + invocationToken + "}}";
    let tokenPlaceholder = "{{token-" + invocationToken + "}}";
    let url = "https://api.example.com/api/" + keyPlaceholder + "/users";
    let body = {
      token: tokenPlaceholder,
      name: "Art Vandelay",
    };
    let response = await context.fetcher.fetch({
      method: "POST",
      url: url,
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
    // ...
  },
});

Username and password

Some APIs authenticate using a username and password, or an equivalent account identifier and secret. The web has long supported this through the "Basic" authorization scheme, where the username and password are encoded and passed in the Authorization header.

GET /users/me
Host: api.example.com
Authorization: Basic <base64 encoded username & password>

You can support this by using WebBasic authentication in your Pack:

pack.setUserAuthentication({
  type: coda.AuthenticationType.WebBasic,
});

Users entering their username and password

View Sample Code

Customize the dialog

Sometimes Basic authentication is used for other types of identifiers and secrets, and the terms "Username" and "Password" in the dialog can be misleading. You can customize the dialog using the uxOptions field of the authentication configuration.

pack.setUserAuthentication({
  type: coda.AuthenticationType.WebBasic,
  uxConfig: {
    placeholderUsername: "Account ID",
    placeholderPassword: "Secret Token",
  },
});

Token exchange

Some APIs use short-lived tokens which must be obtained through an credential exchange or approval process. The industry-wide standard for this is OAuth 2.0, which is supported in the Pack SDK. You can read more about it in the OAuth guide. Other forms of token exchange are not currently supported.

Requiring authentication

If a Pack includes any form of user authentication then by default all of the building blocks within that Pack are assumed to require a connected account. You can set the connectionRequirement property on individual formulas or sync tables to make an account optional or not used.

pack.setUserAuthentication({
  // ...
});

pack.addFormula({
  name: "NeedsAuthFormula",
  // ...
});

pack.addFormula({
  name: "NoAuthNeededFormula",
  // ...
  connectionRequirement: coda.ConnectionRequirement.None,
});

Alternatively, you can set the defaultConnectionRequirement field of the authentication configuration to specify a different default, and then opt-in formulas or sync tables as needed.

pack.setUserAuthentication({
  // ...
  defaultConnectionRequirement: coda.ConnectionRequirement.None,
});

pack.addFormula({
  name: "NeedsAuthFormula",
  // ...
  connectionRequirement: coda.ConnectionRequirement.Required,
  // ...
});

pack.addFormula({
  name: "NoAuthNeededFormula",
  // ...
});

Setting account names

By default the accounts that users connect to will be given the same name as their Coda account. While this works fine the majority of the time, if they are connecting to a team account or multiple accounts it is not very helpful. Therefore we strongly recommend that you implement a getConnectionName function in your authentication configuration to set a more meaningful account name. This is typically done by making a Fetcher request to a user information endpoint in the API and then returning the name of the account.

pack.setUserAuthentication({
  // ...
  getConnectionName: async function(context) {
    let response = await context.fetcher.fetch({
      method: "GET",
      url: "https://api.example.com/users/me",
    });
    return response.body.username;
  },
});

This function is run after the user has entered their credentials, and the credentials are automatically applied to the Fetcher request.

View Sample Code

Use detailed account names

If your service allows users to connect to multiple endpoints, we recommend that you include the endpoint name as well. For example, a pattern like "User Name (Endpoint Name)".

Account-specific endpoints

Some services host a unique domain or subdomain for each account, and require that API requests be sent there. For example, a user may access the service at vandelay.example.com and the API requests should be sent to vandelay.example.com/api. This is supported in Packs using account-specific endpoints.

The endpoint URL is determined using one of the methods in the sections below. Once set it's available in your formulas and functions via context.endpoint.

pack.addFormula({
  // ...
  execute: async function ([], context) {
    // Retrieve the endpoint that the user set.
    let endpoint = context.endpoint;
    let url = endpoint + "/api/v1/users/me";
    // ...
  },
});

Alternatively you can use a relative URL, and it will have the endpoint URL automatically prepended.

pack.addFormula({
  // ...
  execute: async function ([], context) {
    // The endpoint URL will be automatically prepended.
    let url = "/api/v1/users/me";
    // ...
  },
});

Entering manually

To require the user to enter the endpoint URL, set the requiresEndpointUrl field to true. If you are expecting a subdomain, for additional validation set the endpointDomain field to the root domain that all subdomains share.

pack.setUserAuthentication({
  // ...
  requiresEndpointUrl: true,
  endpointDomain: "example.com",
});

When the user connects to their account they will now also be asked to provide the endpoint URL for their account.

Setting the endpoint URL

View Sample Code

Not compatible with OAuth2

Packs that use OAuth2 authentication don't support a manual prompt for the endpoint URL. They should instead use one of the alternate options listed below.

Extracting from token exchange

Some APIs that use OAuth2 authentication return the endpoint URL during the token exchange, as a custom property within the JSON body:

{
  "access_token": "...",
  "token_type": "bearer",
  "site_url": "https://vandelay.example.com"
}

You can automatically extract this value and use it as the endpoint URL by setting the endpointKey field of the authentication configuration.

pack.setUserAuthentication({
  type: coda.AuthenticationType.OAuth2,
  // ...
  endpointKey: "site_url",
});

View Sample Code

Prompting for user selection

If the service allows the same user account to access multiple endpoints, and those endpoints can be determined with an API call, then you can prompt the user to select the endpoint they wish to connect to. This is accomplished using a postSetup step of the type SetEndpoint.

pack.setUserAuthentication({
  // After approving access, the user should select which instance they want to
  // connect to.
  postSetup: [{
    type: coda.PostSetupType.SetEndpoint,
    name: "SelectEndpoint",
    description: "Select the site to connect to:",
    // Generate the list of endpoint options.
    getOptions: async function (context) {
      // Make a request to the API to retrieve the sites they can access.
      let response = await context.fetcher.fetch({
        method: "GET",
        url: "https://api.example.com/my/sites",
      });
      let sites = response.body.sites;
      return sites.map(site => {
        return { display: site.name, value: site.url };
      });
    },
  }],
});

The step's getOptions works like dynamic autocomplete, fetching the list of available endpoints and returning a display name and value for each. After the user enters their credentials they will be prompted to select one of the endpoints in a dialog.

Selecting from a list of endpoints

View Sample Code

Connection name generated twice

When using this feature the getConnectionName function is run multiple times: once before the endpoint is selected and again after. Make sure your code works correctly in both cases. Before the endpoint is available you can return a generic name, which will get overwritten later.

Network domains

Typically a Pack can only make HTTP requests to the network domains it declares. If the selected endpoint URL is outside of the Pack's declared domains then requests to it will fail. The endpointDomain field in the authentication settings can be used to validate manually entered endpoints provided by users, helping to prevent mistakes.

There are services however where each account is associated with a distinct domain, instead of a sub-domain of a common root domain. This makes it impossible to declare them ahead of time as network domains. In these cases you can omit network domain declaration from your Pack, which will allow it to make requests to the account's endpoint URL (and only that URL) regardless of domain.