Vendor Assessments API

A common use case for Transcend's Assessments module is a Vendor Risk Assessment. What is often unique about a Vendor Assessment compared to other privacy assessments like DPIAs or TIAs is that the trigger for a new Vendor Assessment happens at a lifecycle stage before Transcend's Data Discovery modules may have discovered or scanned that Vendor systems in concept. For example, you may have a procurement tool that encodes that a Vendor is being evaluated and it would be preferrable to send that vendor a risk assessment before you've even integrated that system into your stack.

Transcend separates out this concept in the Data Inventory using the "Vendors" vs "Data Silos" tabs. The "Vendor" is the parent company that you may be doing business with (e.g. Amazon.com Inc.), and there may be many "Data Silos" that roll up to any given "Vendor" e.g. Amazon Ads, Redshift DB 1, MySQL DB 2, Amazon S3, ...

The recommended workflow for handling Vendor Assessments is as follows: Step 1). Decide on the trigger for creating the Vendor Assessment - e.g. when Salesforce record hits certain stage, or a row is added to your procurement tool. Step 2). Call the Transcend API with basic information about that vendor to create a row for the Vendor in your Data Inventory Step 3). Call the Transcend API to trigger your pre-defined Vendor Assessment that syncs data back to the Data Inventory upon final approval of the assessment Step 4). Sync the approved Vendor metadata back into your procurement tool

This guide assumes that you have already created a Vendor Assessment Template & Assessment Group. If you have not done that yet, the steps are:

  1. Create or Import an Assessment Template
  2. Configure the Assessment Template to Sync to Data Inventory
  3. Create a new Assessment Group - no need to assign the assessment yet

Once you create the Assessment Group, you will want to grab the UUID to use when making API calls to create new Assessments. To find the Assesment Group UUID:

  • Go to Assessments -> Assessments
  • Select your Assessment Group, to go to a URL like https://app.transcend.io/assessments/groups/59b11511-bd5f-4868-84b2-7f6142086a81
  • Your Assessment Group ID is the UUID at the end of the URL: 59b11511-bd5f-4868-84b2-7f6142086a81

In order to complete the following actions in this guide, you will need to create an API key with the following scopes:

  • Manage Data Inventory
  • Manage Assessments

You will then need to construct a GraphQL client using your language of choice. In JavaScript, this may look like:

import { GraphQLClient } from 'graphql-request';

const transcendUrl = 'https://api.transcend.io'; // for EU hosting
// const transcendUrl = 'https://api.us.transcend.io'; // for US hosting

const client = new GraphQLClient(`${transcendUrl}/graphql`, {
  headers: {
    Authorization: `Bearer ${process.env.TRANSCEND_API_KEY}`,
  },
});

When you go to trigger a new Assessment, you will need to associate that assessment to a Vendor. Each Vendor in your Data Inventory must have a unique title, so before creating a new Vendor, you should first check if there is a Vendor with an existing title:

query ($title: String!) {
  vendors(first: 10, filterBy: { text: $title }, useMaster: false) {
    nodes {
      title
      id
    }
  }
}

This will return a respose like this:

{
  "data": {
    "vendors": {
      "nodes": [
        {
          "title": "Adobe Inc.",
          "id": "1f6f06fb-0df9-4728-bd71-0af45229f6a9"
        }
      ]
    }
  }
}

If a vendor exists, you will want to grab the vendor ID like:

const searchTitle = 'Adobe Inc.';
const lowerSearchTitle = searchTitle.toLowerCase();
const vendorId = data.vendors.nodes.find(
  (vendor) => vendor.title.toLowerCase() === lowerSearchTitle,
);

If no Vendor already exists with that title, you will want to create a new Vendor:

mutation ($input: CreateVendorInput!) {
  createVendor(input: $input) {
    vendor {
      title
      id
    }
  }
}

Returning a result like:

{
  "data": {
    "createVendor": {
      "vendor": {
        "title": "Adobe Inc.",
        "id": "1f6f06fb-0df9-4728-bd71-0af45229f6a9"
      }
    }
  }
}

The minimal information needed for the CreateVendorInput is just a title and description:

{
  "title": "Adobe Inc.",
  "description": "Adobe is a software company that provides its users with digital marketing and media solutions."
}

However additional optional parameters can be specified:

{
  "dataProcessingAgreementLink": "https://acme.com/data-processing-agreement",
  "contactName": "Alice Smith",
  "contactEmail": "alice@acme.com",
  "websiteUrl": "https://acme.com",
  "dataProcessingAgreementStatus": "ONLINE_DPA",
  "headquarterCountry": "US",
  "headquarterSubDivision": "US-CA",
  "ownerEmails": ["joe@mycompany.com", "rebecca@mycompany.com"],
  "businessEntitityTitle": "Affiliate Co.",
  "teamNames": ["Marketing"],
  "attributes": [
    {
      "key": "Tags",
      "values": ["Salesforce Import"]
    },
    {
      "key": "Quarter",
      "values": ["Q1-2025"]
    }
  ]
}

The latest documentation and enum typing on each field can be found using the GraphQL API Explorer.

Once you have the newly created Vendor and Assessment Group, the next step is to trigger a new Assessment and then associate the Vendor with that Assessment.

mutation ($inputs: [CreateAssessmentFormInput!]!) {
  createAssessmentForms(input: { assessmentForms: $inputs }) {
    assessmentForms {
      id
      title
    }
  }
}

Returning a result like:

{
  "data": {
    "createAssessmentForms": {
      "assessmentForms": [
        {
          "id": "e558af83-2907-410b-8c2d-6b83fa8abec1",
          "title": "Adobe Vendor Assessment"
        }
      ]
    }
  }
}

Note - if an existing assessment title exists with the same name, the assessment is still created but with a number appended to the title. e.g. Adobe Vendor Assessment 2 instead of Adobe Vendor Assessment.

The minimal information needed for the CreateAssessmentFormInput would be:

[
  {
    "assessmentGroupId": "59b11511-bd5f-4868-84b2-7f6142086a81",
    "title": "Adobe Vendor Assessment",
    "syncRows": [
      {
        "syncModel": "vendor",
        "syncRowIds": ["1f6f06fb-0df9-4728-bd71-0af45229f6a9"]
      }
    ]
  }
]

The value of assessmentGroupId is taken from the value in the Create Your Assessment section above. The syncRowIds value is the ID of the vendor from Creating a new Vendor in the Data Inventory.

The full set of input parameters that you can provide include:

[
  {
    "assessmentGroupId": "35f34832-9886-4229-bf24-4d1f52b3d42c",
    "title": "Adobe Vendor Assessment",
    "assigneeIds": ["11e6569a-85df-4a18-b88e-c01119394c43"],
    "externalAssigneeEmails": ["jill@acme.com"],
    "syncRows": [
      {
        "syncModel": "vendor",
        "syncRowIds": ["1f6f06fb-0df9-4728-bd71-0af45229f6a9"]
      }
    ],
    "sectionAssignees": [
      {
        "index": 0,
        "assigneeIds": [],
        "externalAssigneeEmails": ["jill@acme.com"]
      }
    ],
    "titleIsInternal": true
  }
]

You will want to set the externalAssigneeEmails to the email addresses of the vendor that will need to fill out the assessment. If the assessment is going to individuals on your team that also have seats in Transcend, then you can specify the assigneeIds parameter using the Transcend user ID. To find the User ID you would call the users query:

query ($email: String!) {
  users(filterBy: { text: $email }, useMaster: false) {
    nodes {
      id
      email
    }
  }
}

Which returns a result like:

{
  "data": {
    "users": {
      "nodes": [
        {
          "id": "11e6569a-85df-4a18-b88e-c01119394c43",
          "email": "joe@acme.com"
        }
      ]
    }
  }
}

Where you would grab the resulting user matching the email address exactly

const searchEmail = 'joe@acme.com'.toLowerCase();
const userId = data.users.nodes.find((vendor) => vendor.email === searchEmail);

Ultimately, you may want to sync the information that was approved in the Vendor Assessment back into the original source that triggered the Assessment. By following the steps in the section above to Create Your Assessment, the information that you sync back will likely be a subset of the responses in the Assessment that have gone through review and approval.

You can then query the vendors query to fetch the necessary information about that Vendor:

query (first: $Int!, $offset: Int!) {
  vendors(
    first: $first
    offset: $offset
    useMaster: false
    orderBy: [
      { field: createdAt, direction: ASC }
      { field: title, direction: ASC }
    ]
  ) {
    nodes {
      id
      title
      description
      dataProcessingAgreementLink
      contactName
      contactEmail
      contactPhone
      address
      headquarterCountry
      headquarterSubDivision
      websiteUrl
      businessEntity {
        title
      }
      teams {
        name
      }
      owners {
        email
      }
      attributeValues {
        attributeKey {
          name
        }
        name
      }
    }
  }
}

This will return a result like:

{
  "data": {
    "vendors": {
      "nodes": [
        {
          "id": "756df8fc-5250-4845-b140-1b0fec870baf",
          "title": "Twilio Inc.",
          "description": "Twilio is a cloud communication company that enables users to use standard web languages to build voice, VoIP, and SMS apps via a web API.",
          "dataProcessingAgreementLink": null,
          "contactName": null,
          "contactEmail": null,
          "contactPhone": "",
          "address": "",
          "headquarterCountry": null,
          "headquarterSubDivision": "US-CA",
          "websiteUrl": null,
          "businessEntity": {
            "title": "Sneaker Supply - Germany"
          },
          "teams": [
            {
              "name": "Engineering"
            }
          ],
          "owners": [
            {
              "email": "test@acme.io"
            }
          ],
          "attributeValues": []
        },
        {
          "id": "0ca4d374-6596-4ee1-a7c7-c55937ca01d3",
          "title": "Shopify Inc.",
          "description": "Shopify is a cloud-based, multi-channel commerce platform designed for small and medium-sized businesses.",
          "dataProcessingAgreementLink": null,
          "contactName": null,
          "contactEmail": null,
          "contactPhone": "",
          "address": "",
          "headquarterCountry": null,
          "headquarterSubDivision": "CA-ON",
          "websiteUrl": null,
          "businessEntity": {
            "title": "Payroll "
          },
          "teams": [
            {
              "name": "Marketing"
            }
          ],
          "owners": [
            {
              "email": "test@acme.io"
            }
          ],
          "attributeValues": [
            {
              "attributeKey": {
                "name": "ROPA - Art. 30 Status"
              },
              "name": "ROPA - Martech"
            },
            {
              "attributeKey": {
                "name": "DPIA Status Tracker"
              },
              "name": "DPIA - Marketing Team"
            },
            {
              "attributeKey": {
                "name": "Vendor Assessment Status"
              },
              "name": "NEEDED"
            }
          ]
        }
      ]
    }
  }
}

You will likely need to paginate over the Vendors as the maximum limit per page is 100. For example you could paginate in JavaScript like:

  const vendors: Vendor[] = [];
  let offset = 0;
  let pageSize = 100;

  // Whether to continue looping
  let shouldContinue = false;
  do {
    const {
      vendors: { nodes },
    } = await client.request(VENDORS, {
      first: pageSize,
      offset,
    });
    vendors.push(...nodes);
    offset += pageSize;
    shouldContinue = nodes.length === pageSize;
  } while (shouldContinue);

In order to connect a Vendor in Transcend's Data Inventory back to your source system, the best way to store the mapping is using a non-changing unique identifier. This can be done by:

  1. Storing the Transcend Vendor ID in your source system
  2. Storing your remote system ID in Transcend using a Custom Field on the Transcend Vendor table