Customize your authorization flow with the Unified API

In this tutorial, you'll build a Javascript app that asks users to grant access to their third-party accounts with the Unified API. Instead of using the Authorization embedded component as in the starter tutorial, we'll implement this authorization flow from scratch. This work flow is intended for experienced developers who want more control over the authorization process or need to customize the user experience.

By the end of this tutorial, you'll be able to:

  1. Fetch and display a list of available integrations from Unified.to
  2. Create an authorization URL for a selected integration
  3. Handle the authorization callback and create a connection
  4. Use the connection to fetch data from an API provider

Prerequisites

Before we begin, make sure you have:

  1. Signed up for a Unified.to account.
  2. Completed our Get Started guide in the app.
  3. Downloaded a code editor of your choice, like VS Code.
  4. Installed NPM and Node.js on your system.

Project setup

We'll use the same project structure as the previous tutorial. Start by cloning the repository and installing dependencies:

shell
git clone git@github.com:unified-to/unified-javascript-tutorial.git
cd unified-javascript-tutorial
npm i

The app should now be available on localhost:1234. Open the folder in your preferred code editor.

Step 1: Retrieve your workspace ID and API token

  1. Log in to your Unified.to account
  2. Go to Settings > API Keys
  3. Copy your Workspace ID and API Token
  4. Open the .env file in your project and add these values:
.env
UNIFIED_WORKSPACE_ID = your_workspace_id_here;
UNIFIED_API_KEY = your_api_token_here;

Now that you've updated .env, we can start our app. Run the following from the command line:

shell
npm run start

You can view your app on localhost:1234.

Step 2: Activate an integration in the Sandbox environment

If you've already activated some ATS integrations in the Sandbox environment, skip ahead to step 3.

The Sandbox environment is where you can activate, explore, and try out integrations in a safe and non-destructive environment. We're going to call the ATS endpoint by the end of this tutorial, so let's activate an ATS integration.

Check that you are in the Sandbox environment

  1. Navigate to Integrations
  2. Look for the environment dropdown near the top of the page. Click on the dropdown that contains 'ENV' and select 'ENV: SANDBOX'. Any subsequent actions you take will happen in the Sandbox environment.

Activate Lever

  1. On the same page (Active Integrations), search for 'Lever' by typing it into the search box.
  2. Click on the Lever card to open its details page.
  3. Leave the default settings as-is. Notice that since we’re in the Sandbox environment, the credentials are pre-filled with mock values. Lever uses OAuth 2 by default for authorization.
  4. At the bottom of the page, click 'Activate’.

The Lever integration is now activated in your Sandbox environment. This allows you to make test API calls and get a feel for the Unified API without needing to connect to a real Lever account.

Step 3: Fetch and display available integrations

Time to get coding! First, let's write a function to fetch all of the available and activated integrations from your workspace. Add the following to app.js:

app.js
async function fetchIntegrations() {
    try {
        const response = await axios.get('https://api.unified.to/unified/integration/workspace', {
            params: {
                workspace_id: UNIFIED_WORKSPACE_ID,
                env: 'Sandbox',
                categories: 'ats',
            },
            headers: {
                Authorization: `Bearer ${UNIFIED_API_KEY}`,
            },
        });
        return response.data;
    } catch (error) {
        console.error('Error fetching integrations:', error);
        return [];
    }
}

API reference: Get all integrations

Render integrations in the UI

We also need to display the integrations in the UI. We'll do this programatically by passing the response from the above function to displayIntegrations(). When the app loads, we'll fetch the integrations and then render them on the page. Add the following to app.js:

app.js
function displayIntegrations(integrations) {
    const integrationsListElement = document.getElementById('integrations-list');
    integrations.forEach((integration) => {
        const listItem = document.createElement('div');
        listItem.innerHTML = `<button class="integration-btn" data-integration-type="${integration.integration_type}">
        <span>${integration.integration_type}</span>
      </button>
  `;
        integrationsListElement.appendChild(listItem);
    });
}

document.addEventListener('DOMContentLoaded', async () => {
    const integrations = await fetchIntegrations();
    displayIntegrations(integrations);
});

Now, let's update the view by creating a container to hold the integrations. In index.html, add the following under <section class="integrations-section"> e.g:

index.html
<section class="integrations-section">
    <h2 class="section-title">Available Integrations</h2>
    <ul id="integrations-list" class="integrations-grid"></ul>
</section>

At this point, you should see a list of integrations displayed on the page (if you're just starting out, then the list may only contain one item, but that's OK!)

Step 4: Create an authorization URL

Now that we're displaying our integrations to our users, let's ask them to grant us access to their third-party accounts when they click on an integration. A few things will happen:

  • When a user clicks on an integration, they will be redirected to the authorization page for that integration.
  • The user will be asked to authorize your app with the third-party provider.
  • After granting access to your app, a connection will be created. The user will be redirected to your success_redirect URL.

Add the following to app.js:

app.js
function createAuthLink(integrationType) {
    const baseUrl = 'https://api.unified.to/unified/integration/auth';
    const params = new URLSearchParams({
        redirect: '1',
        env: 'Sandbox',
        success_redirect: window.location.href,
        failure_redirect: window.location.href,
        scopes: 'ats_candidate_read',
        state: 'abc-123-def-456',
    });

    return `${baseUrl}/${UNIFIED_WORKSPACE_ID}/${integrationType}?${params.toString()}`;
}

API reference: Create authorization URL

Breaking down the authorization URL API call

Let's review the structure of the API call we made to create an authorization URL:

https://api.unified.to/unified/integration/auth/{workspace_id}/{integration_type}?{query_parameters}

  • workspace_id: Your Unified.to workspace ID
  • integration_type: The name of the integration (e.g., 'lever')
  • Query parameters:
    • redirect=1: Indicates that this is a redirect request. The server will return a 302 status code and redirect the user. We recommend that you use this method as it is more secure and robust.
    • env=Sandbox: Specifies the environment (use 'Sandbox' for testing)
    • success_redirect: URL to redirect after successful authorization
    • failure_redirect: URL to redirect after failed authorization
    • scopes: Comma-separated list of required scopes (e.g., 'ats_candidate_read'). It's a best practice to always include the scopes you need.
    • state: Data that you want to send back to your success URL in the URL parameters. A common use case is to put your user ID here so that after they have completed the auth flow, you can use the user ID to associate them with the connection you just made.

Update the display logic for integrations

We need to update the displayIntegrations() function we created earlier. Replace it with the following:

app.js
function displayIntegrations(integrations) {
    const integrationsListElement = document.getElementById('integrations-list');
    integrations.forEach((integration) => {
        const listItem = document.createElement('div');
        const authLink = createAuthLink(integration.integration_type);
        listItem.innerHTML = `
            <a href="${authLink}" class="integration-btn">
                <span>${integration.integration_type}</span>
            </a>
        `;
        integrationsListElement.appendChild(listItem);
    });
}

API reference: Create an authorization URL (a.k.a. create a connection indirectly).

When you click on one of your integrations now, you'll be taken to an authorization page. In the next step, we'll handle what happens after an integration is authorized.

Step 5: Handle the authorization callback

After a user successfully authorizes your application, they'll be redirected to your app with some new parameters in the URL. One of these parameters is id, which represents the ID of the newly created connection. We will need to store this connection ID in order to use it in future API calls.

Create a function to handle the connection ID:

app.js
function handleAuthCallback(connectionId) {
    localStorage.setItem('unifiedConnectionId', connectionId);
    // Clear the URL parameters
    window.history.replaceState({}, document.title, window.location.pathname);
}

Update the DOMContentLoaded event listener to call this function when the page is loaded and a connection ID is present i.e. when the user is redirected to your app.

app.js
// Add the following to the `DOMContentLoaded` event listener
const urlParams = new URLSearchParams(window.location.search);
const connectionId = urlParams.get('id');

if (connectionId) {
    handleAuthCallback(connectionId);
}

Note: In a "real" app, you should store the connection ID in your own database.

Step 6: Use the connection to call the Unified API

Let's update our code to fetch candidates using the connection we just created. (Fun fact: From this point onwards, the code you're seeing is the same as the code from the starter tutorial).

app.js
async function fetchATSCandidates(connectionId) {
    const options = {
        method: 'GET',
        url: `https://api.unified.to/ats/${connectionId}/candidate`,
        headers: {
            authorization: `bearer ${UNIFIED_API_KEY}`,
        },
        params: {
            limit: 20,
            offset: 0,
        },
    };

    try {
        const response = await axios.request(options);
        return response.data;
    } catch (error) {
        console.error('Error fetching ATS candidates:', error);
        return null;
    }
}

API Reference: List all candidates

Notice that we don't need to specify Sandbox as an environment parameter in the API call - the connection we created is part of the Sandbox environment, so any data that comes back from it will be synthetic.

Finally, let's display the candidates in the UI. Add the following code to your app:

app.js
function displayCandidates(candidates) {
    const candidatesList = document.getElementById('candidates-list');
    candidatesList.innerHTML = '<h2 class="section-title">Candidates:</h2>';

    candidates.forEach((candidate) => {
        const candidateElement = document.createElement('li');
        candidateElement.className = 'candidate-item';
        candidateElement.innerHTML = `
            <div class="candidate-avatar-container">
                <img class="candidate-avatar" src="https://ui-avatars.com/api/?name=${encodeURIComponent(
                    candidate.name || 'na'
                )}&background=random" alt="${candidate.name || 'Candidate'} avatar">
                <div class="candidate-info">
                    <p class="candidate-name">${candidate.name || 'N/A'}</p>
                    <p class="candidate-email">${
                        candidate.emails?.[0]?.email || 'No email provided'
                    }</p>
                </div>
            </div>
        `;
        candidatesList.appendChild(candidateElement);
    });
}

document.getElementById('fetchCandidatesBtn').addEventListener('click', async () => {
    const connectionId = localStorage.getItem('unifiedConnectionId');

    if (!connectionId) {
        console.error('No connection ID found. Please connect to Lever first.');
        return;
    }

    const candidates = await fetchATSCandidates(connectionId);

    if (candidates) {
        displayCandidates(candidates);
    } else {
        console.log('No candidates found or error occurred');
        document.getElementById('candidatesList').innerHTML =
            '<p>No candidates found or an error occurred.</p>';
    }
});

In index.html, add the following lines under <section class="candidates-section"> e.g:

index.html
<section class="candidates-section">
    <button id="fetchCandidatesBtn" class="fetch-button">Fetch candidates</button>
    <ul id="candidates-list" class="candidates-list"></ul>
</section>

With all the pieces in place, you can now give this auth flow a try. Click on an integration, authorize your app, and then click on Fetch candidates to see what happens.

Summary

Congratulations! You've successfully built your own custom authorization flow to creates connections with the Unified API. This tutorial demonstrated how to:

  1. Fetch and display available integrations
  2. Redirect users to an authorization page for a selected integration
  3. Handle the auth callback and create a connection
  4. Use the connection to fetch and display data from a third-party API provider

By implementing this authorization flow yourself, you have more control over the user experience and can customize it to fit your application's needs.

Happy building!

Are we missing anything? Let us know
Was this page helpful?