Analyze Contracts and Automate Decision-Making

Seamlessly surface deal insights to contract requesters from internal systems or data sources.

Contract requesters and approvers can often benefit from additional business context. Using Ironclad's Workflow APIs, you can automatically query proprietary data sources or run data-driven or AI-driven analysis, and surface these insights to your users.

Our "Contract Analyzer"

As a simple example, we will run the counterparty name and contract document through the following "contract analyzer."

// Analyze the contract, based on the counterparty and contract contents.
function analyzeContract(counterpartyName, draftBuffer) {
  // We could query an external system for information about the counterparty.
  // Instead, we will evaluate them based on the length of their name.
  const counterpartyScore = counterpartyName.length;

  // We could run the draft contract DOCX through an internal system.
  // Instead, we evaluate the size of the contract file.
  const contractSize = `${Number(draftBuffer.length * 0.001, 1).toFixed(1)} kB`;

  // We could devise a risk score based on contract information.
  // Instead, we choose a random number between 1 and 3.
  const riskScore = Math.floor(Math.random() * 3) + 1;

  return {
    counterpartyScore,
    contractSize,
    riskScore,
  }
}

exports.analyzeContract = analyzeContract;

In practice, this kind of analysis will be much more sophisticated, likely parsing out document contents and pulling many more attributes from the contract workflow.

Giving Users Context with Comments

Context from our contract analyzer in a comment.Context from our contract analyzer in a comment.

In the code sample below, we listen for workflow_launched webhook events from a certain contract template, run them through our analyzer, and add a comment with the results.

require('dotenv').config();
const express = require('express');
const axios = require('axios').default;
const {analyzeContract} = require('./analyze-contract');

const app = express();

// Be sure to store your API securely!
const apiKey = process.env.IRONCLAD_API_KEY;

// Your host URL may vary based on the implementation.
const hostUrl = (process.env.HOST_URL ? process.env.HOST_URL : 'ironcladapp');
const apiUrl = `https://${hostUrl}.com/public/api/v1`;

const userEmail = process.env.IRONCLAD_BOT_USER_EMAIL;
const templateId = process.env.IRONCLAD_WORKFLOW_TEMPLATE_ID;

app.use(express.json());

app.post('/', async (request, response) => {
  try {
    const payload = request.body.payload;

    if (payload.event !== 'workflow_launched') {
      return response.json({});
    }

    if (payload.templateID !== templateId) {
      console.log('ignored workflow from non-matching template');
      return response.json({});
    }

    // Retrieve workflow information through GET endpoint
    const workflowId = payload.workflowID;
    const workflowResponse = await axios.get(
      `${apiUrl}/workflows/${workflowId}`,
      {
        headers: {'Authorization': `Bearer ${apiKey}`},
      },
    );

    const {data} = workflowResponse;

    if (data.attributes.draft) {
      const draftDocResponse = await axios.get(
        `https://${hostUrl}.com${data.attributes.draft[0].download}`, {
          headers: {'Authorization': `Bearer ${apiKey}`},
          responseType: 'arraybuffer',
        },
      );

      // Run custom analysis of the counterparty and the contract.
      const {counterpartyScore, contractSize, riskScore} = analyzeContract(
        data.attributes.counterpartyName,
        draftDocResponse.data,
      );

      await axios.post(
        `${apiUrl}/workflows/${workflowId}/comment`,
        {
          creator: {
            type: 'email',
            email: userEmail,
          },
          comment: `Contract analyzed.
            Counterparty Score: ${counterpartyScore}
            Contract Size: ${contractSize}
            Risk Score: ${riskScore}
            Contract will be ${riskScore < 2 ? 'approved' : 'denied'}`,
        },
        {
          headers: {'Authorization': `Bearer ${apiKey}`},
        },
      );

      console.log(`analyzed new workflow: ${data.title}`);
    } else {
      console.log(`no draft attached to the workflow: ${data.title}`);
    }
    response.sendStatus(200);
  } catch (err) {
    console.log(err);
    response.sendStatus(400);
  }
});

app.listen(8000, () => console.log('Listening for webhook on port 8000'));

If you reviewed the Hello World of Contracts guide, much of this will look familiar, from the webhook event processing, to the retrieval and download of a document.

Beyond this, there is one main addition: the POST comment endpoint. This endpoint allows us to post comments as our bot user, who we reference by email. It also allows us to @-mention other users, which will send them a notification. In this case, no notification is necessary, since the requester will already be on the workflow page.

Automating Decision-Making with Bot Approvals

We can also approve contracts via API. In this case, we will approve contracts with a risk score of 1, but in practice, this mechanism can be used in for many different things. For example, if the contract is tied to a business process with its own approval system, you can sync approval over from that system in order to block or release the contract from going out for signature.

The result of our "contract analyzer" auto-approving a contract with a risk score of 1 is shown below.

Auto-approving a contract with a risk score of 1.Auto-approving a contract with a risk score of 1.

Let's look at the specific code that will drive this approval, right after the comment is sent.

if (riskScore < 2) {
  // Automatically approve if the risk score is below 2.
  const approvalStatusResponse = await axios.get(
    `${apiUrl}/workflows/${workflowId}/approvals`,
    {
      headers: {'Authorization': `Bearer ${apiKey}`},
    },
  );

  const approvalId = approvalStatusResponse.data.approvalGroups[0].reviewers[0].role;

  await axios.patch(
    `${apiUrl}/workflows/${workflowId}/approvals/${approvalId}`,
    {
      user: {
        type: 'email',
        email: userEmail,
      },
      status: 'approved',
    },
    {
      headers: {'Authorization': `Bearer ${apiKey}`},
    },
  );
}

In the code above, we first retrieve the workflows approvals, and get the approval ID. This approval ID is the same for each launched workflow, so we could change it into a variable. The approval status also contains information about who is assigned to each approval, so we could have also searched for the approval corresponding to our bot user.

Then, we use the POST approve endpoint.

Putting it All Together

The full code is shown below.

require('dotenv').config();
const express = require('express');
const axios = require('axios').default;
const {analyzeContract} = require('./analyze-contract');

const app = express();

// Be sure to store your API securely!
const apiKey = process.env.IRONCLAD_API_KEY;

// Your host URL may vary based on the implementation.
const hostUrl = (process.env.HOST_URL ? process.env.HOST_URL : 'ironcladapp');
const apiUrl = `https://${hostUrl}.com/public/api/v1`;

const userEmail = process.env.IRONCLAD_BOT_USER_EMAIL;
const templateId = process.env.IRONCLAD_WORKFLOW_TEMPLATE_ID;

app.use(express.json());

app.post('/', async (request, response) => {
  try {
    const payload = request.body.payload;

    if (payload.event !== 'workflow_launched') {
      return response.json({});
    }

    if (payload.templateID !== templateId) {
      console.log('ignored workflow from non-matching template');
      return response.json({});
    }

    // Retrieve workflow information through GET endpoint
    const workflowId = payload.workflowID;
    const workflowResponse = await axios.get(
      `${apiUrl}/workflows/${workflowId}`,
      {
        headers: {'Authorization': `Bearer ${apiKey}`},
      },
    );

    const {data} = workflowResponse;

    if (data.attributes.draft) {
      const draftDocResponse = await axios.get(
        `https://${hostUrl}.com${data.attributes.draft[0].download}`, {
          headers: {'Authorization': `Bearer ${apiKey}`},
          responseType: 'arraybuffer',
        },
      );

      // Run custom analysis of the counterparty and the contract.
      const {counterpartyScore, contractSize, riskScore} = analyzeContract(
        data.attributes.counterpartyName,
        draftDocResponse.data,
      );

      await axios.post(
        `${apiUrl}/workflows/${workflowId}/comment`,
        {
          creator: {
            type: 'email',
            email: userEmail,
          },
          comment: `Contract analyzed.
            Counterparty Score: ${counterpartyScore}
            Contract Size: ${contractSize}
            Risk Score: ${riskScore}
            Contract will be ${riskScore < 2 ? 'approved' : 'denied'}`,
        },
        {
          headers: {'Authorization': `Bearer ${apiKey}`},
        },
      );

      if (riskScore < 2) {
        // Automatically approve if the risk score is below 2.
        const approvalStatusResponse = await axios.get(
          `${apiUrl}/workflows/${workflowId}/approvals`,
          {
            headers: {'Authorization': `Bearer ${apiKey}`},
          },
        );

        const approvalId = approvalStatusResponse.data.approvalGroups[0].reviewers[0].role;

        await axios.patch(
          `${apiUrl}/workflows/${workflowId}/approvals/${approvalId}`,
          {
            user: {
              type: 'email',
              email: userEmail,
            },
            status: 'approved',
          },
          {
            headers: {'Authorization': `Bearer ${apiKey}`},
          },
        );
      }

      console.log(`analyzed new workflow: ${data.title}`);
    } else {
      console.log(`no draft attached to the workflow: ${data.title}`);
    }
    response.sendStatus(200);
  } catch (err) {
    console.log(err);
    response.sendStatus(400);
  }
});

app.listen(8000, () => console.log('Listening for webhook on port 8000'));
// Analyze the contract, based on the counterparty and contract contents.
function analyzeContract(counterpartyName, draftBuffer) {
  // We could query an external system for information about the counterparty.
  // Instead, we will evaluate them based on the length of their name.
  const counterpartyScore = counterpartyName.length;

  // We could run the draft contract DOCX through an internal system.
  // Instead, we evaluate the size of the contract file.
  const contractSize = `${Number(draftBuffer.length * 0.001, 1).toFixed(1)} kB`;

  // We could devise a risk score based on contract information.
  // Instead, we choose a random number between 1 and 3.
  const riskScore = Math.floor(Math.random() * 3) + 1;

  return {
    counterpartyScore,
    contractSize,
    riskScore,
  }
}

exports.analyzeContract = analyzeContract;
IRONCLAD_API_KEY="YOUR_API_KEY"
IRONCLAD_BOT_USER_EMAIL="[email protected]"
IRONCLAD_WORKFLOW_TEMPLATE_ID="YOUR_WORKFLOW_TEMPLATE_ID"

To run this example, you'll need to npm install express axios dotenv for the correct dependencies, and then fill in the details of the .env file. For the IRONCLAD_WORKFLOW_TEMPLATE_ID, pull up the workflow launch form, and copy the ID from the end of the URL. This will look something like https://demo.ironcladapp.com/workflows/launch/YOUR_WORKFLOW_TEMPLATE_ID.

Finally, you'll want to run node server.js, create a tunnel to port 8000, and set up a webhook. See Hello World of Contracts for a more in-depth description of these steps.


Did this page help you?