Add owners/descriptions to resources
Use Vanta's API to update the Owner and Descriptions on your Resources
Add Owners/Description to Resource Kinds
In this guide we'll walkthrough the various API Endpoints needed to assign Owners and Descriptions to your inventory resources on your Vanta instance.
In Vanta, your inventory resources are required to be assigned an active user as an Owner in order pass certain compliance tests such as the Inventory items have active owners test:
Similarly, Inventory resources are also required to have a text Description via the Inventory items have descriptions test:
This helps Auditors understand the purpose of the resource and show you're handling them responsibly.
In order to perform these actions through the Vanta API, we'll need to do the following.
Steps:
- GET our list of connected integrations, or a known connected integration
- GET a list resources for that resource kind
- PATCH a single resource, or multiple resources to set their owner and description
List Integrations Endpoint - List connected integrations
The integrations endpoint can be used to list all the integration ids, the resource kinds associated with that specific integration, as well as the integration's connectionIds
. We'll also need the resourceKind
and integrationId
later on to filter what kind of resources we assign Owner and Descriptions for.
If you don't know the IntegrationId
of the integration that the resources belong to, use this endpoint to return a list of connected integrations, along with their resource kinds.
Endpoint:
/integrations
Query Parameters:
pageSize
(<number>
): An integer value that specifies the number of resources to return per page. This parameter controls the size of each page in paginated responses.pageCursor
(<string>
): A string value used to navigate through pages of results. This parameter holds the cursor for the current page, allowing the API to fetch the next set of results.
List Connected Integrations - Code Example
curl --location 'https://api.vanta.com/v1/integrations?pageSize=100&pageCursor=<string>' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer your_token'
const myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Authorization", "Bearer your_token");
const requestOptions = {
method: "GET",
headers: myHeaders,
redirect: "follow"
};
fetch("https://api.vanta.com/v1/integrations?pageSize=100&pageCursor=<string>", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
const axios = require('axios');
let config = {
method: 'get',
maxBodyLength: Infinity,
url: 'https://api.vanta.com/v1/integrations?pageSize=100&pageCursor=<string>',
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer your_token'
}
};
axios.request(config)
.then((response) => {
console.log(JSON.stringify(response.data));
})
.catch((error) => {
console.log(error);
});
import requests
url = "https://api.vanta.com/v1/integrations?pageSize=100&pageCursor=<string>"
payload = {}
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer your_token'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
List Connected Integrations - Example Response
{
"results": {
"pageInfo": {
"endCursor": "em9vbQ==",
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "YWRkaWd5"
},
"data": [
{
"integrationId": "snowflake",
"displayName": "Snowflake",
"resourceKinds": [
"SnowflakeAccount",
"SnowflakeDatabase"
],
"connections": [
{
"connectionId": "64dbda1a6491f66d234bfd41",
"isDisabled": true,
"connectionErrorMessage": "Vanta hit an unknown issue with Snowflake. Please reconnect the Snowflake integration to fix this."
}
]
}, {
"integrationId": "snyk",
"displayName": "Snyk",
"resourceKinds": [
"SnykAccount",
"SnykProject",
"SnykVulnerabilityV2"
],
"connections": [
{
"connectionId": "65a0caf8b4c7501f891e2183",
"isDisabled": true,
"connectionErrorMessage": "Vanta failed to access some information in Snyk. Please reconnect the Snyk integration to fix this."
}
]
},
{
"integrationId": "sumologic",
"displayName": "Sumo Logic",
"resourceKinds": [
"SumoLogicMonitor",
"SumoLogicUser"
],
"connections": [
{
"connectionId": "645ab076f2e7f0df0be2da36",
"isDisabled": false,
"connectionErrorMessage": null
}
]
},
]
}
}
Get a List of Resources - By Resource-Kind and ConnectionId
Using the :resourceKind/resources
endpoint, we can use the integrationId
, connectionId
, and resourceKind
to fetch a list of resources from Vanta. This endpoint has several query parameters that can help filter the list down based on certain properties such as hasDescription
, hasOwner
, and isInScope
.
Endpoint
/integrations/:integrationId/resource-kinds/:resourceKind/resources
Path Variables
integrationId
(<string>
- Required): The unique identifier for the integration. This parameter is necessary to specify which integration the request is targeting, and usually is just the integration name.resourceKind
(<string>
- Required): The type of resource being requested. This parameter is necessary to specify the kind of resource for which details or a list of resources are being requested.
Query Parameters
connectionId
(<string>
): The unique identifier for the connection. This parameter is used to target a specific connection within an integration.hasDescription
(<boolean>
): A boolean value to filter resources that have a description. When set to true, only resources with descriptions will be included in the response.hasOwner
(<boolean>
): A boolean value to filter resources that have an owner. When set to true, only resources with owners will be included in the response.isInScope
(<boolean>
): A boolean value to filter resources that are in scope. When set to true, only resources that are considered in scope will be included in the response.pageSize
(<number>
): An integer value that specifies the number of resources to return per page.pageCursor
(<string>
): A string value used to navigate through pages of results.
Since snowflakeDatabase
resource types don't have descriptions, we'll only filter by isInScope
and hasOwner
:
snowflakeDatabase
resource types don't have descriptions, we'll only filter by isInScope
and hasOwner
:/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources?connectionId=64dbda1a6491f66d234bfd41&hasOwner=false&isInScope=true&pageSize=10
Get List of Resources - Code Example
curl --location 'https://api.vanta.com/v1/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources?connectionId=64dbda1a6491f66d234bfd41&hasOwner=false&isInScope=true&pageSize=10' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer vat_YOUR_TOKEN'
const myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Authorization", "Bearer vat_YOUR_TOKEN");
const requestOptions = {
method: "GET",
headers: myHeaders,
redirect: "follow"
};
fetch("https://api.vanta.com/v1/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources?connectionId=64dbda1a6491f66d234bfd41&hasOwner=false&isInScope=true&pageSize=10", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
const axios = require('axios');
let config = {
method: 'get',
maxBodyLength: Infinity,
url: 'https://api.vanta.com/v1/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources?connectionId=64dbda1a6491f66d234bfd41&hasOwner=false&isInScope=true&pageSize=10',
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer vat_YOUR_TOKEN'
}
};
axios.request(config)
.then((response) => {
console.log(JSON.stringify(response.data));
})
.catch((error) => {
console.log(error);
});
import requests
url = "https://api.vanta.com/v1/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources?connectionId=64dbda1a6491f66d234bfd41&hasOwner=false&isInScope=true&pageSize=10"
payload = {}
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer vat_YOUR_TOKEN'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
Get List of Resources - Response Example
{
"results": {
"pageInfo": {
"endCursor": "eyJfaWQiOnsiJG9pZCI6IjY0ZGM4NDhiNjQ5MWY2NmQyM2M2NGUyNiJ9fQ==",
"hasNextPage": false,
"hasPreviousPage": false,
"startCursor": "eyJfaWQiOnsiJG9pZCI6IjY0ZGM4NDhiNjQ5MWY2NmQyM2M2NGRmNiJ9fQ=="
},
"data": [
{
"responseType": "Database",
"resourceKind": "SnowflakeDatabase",
"resourceId": "64dc848b6491f66d23c64df6",
"connectionId": "64dbda1a6491f66d234bfd41",
"displayName": "SNOWFLAKE",
"owner": null,
"inScope": true,
"description": "Snowflake Database",
"creationDate": "2023-08-16T08:10:51.150Z",
"account": "",
"areBackupsEnabled": true,
"isEncrypted": true,
"containsEphi": null,
"containsUserData": null
},
{
"responseType": "Database",
"resourceKind": "SnowflakeDatabase",
"resourceId": "64dc848b6491f66d23c64e26",
"connectionId": "64dbda1a6491f66d234bfd41",
"displayName": "SNOWFLAKE_SAMPLE_DATA",
"owner": null,
"inScope": true,
"description": "Snowflake Database",
"creationDate": "2023-08-16T08:10:51.156Z",
"account": "",
"areBackupsEnabled": true,
"isEncrypted": true,
"containsEphi": null,
"containsUserData": null
}
]
}
}
Get List of Resources - Response Schema
The response contains an array of resource objects, each object representing a separate resource. Below is the breakdown of each field within the objects:
-
responseType
(string
): Indicates the type of the resource. In this case, it is "Database". -
resourceKind
(string
): Specifies the kind of resource. Here, it is "SnowflakeDatabase". -
resourceId
(string
): The unique identifier for the resource. -
connectionId
(string
): The unique identifier for the connection associated with the resource. -
displayName
(string
): The display name of the resource. -
owner
(string
ornull
): The ownerId of the resource. It can benull
if no owner is assigned. -
inScope
(boolean
): Indicates if the resource is in scope. Whentrue
, the resource is considered in scope. -
description
(string
): A description of the resource. -
creationDate
(string
): The date and time when the resource was created, in ISO 8601 format. -
account
(string
): The account associated with the resource. It can be an empty string if no account is specified. -
areBackupsEnabled
(boolean
): Indicates if backups are enabled for the resource. Whentrue
, backups are enabled. -
isEncrypted
(boolean
): Indicates if the resource is encrypted. Whentrue
, the resource is encrypted. -
containsEphi
(boolean
ornull
): Indicates if the resource contains electronic protected health information (ePHI). It can benull
if this information is not specified. -
containsUserData
(boolean
ornull
): Indicates if the resource contains user data. It can benull
if this information is not specified.
Patch Resource-Kinds - Add Owner and Description
By using the Patch Resource-Kinds endpoint, We can update the resources with an appropriate Description
and OwnerId
of the Vanta User who's to own the resources. If you don't know the owner Id, you can find them via the List People endpoint, or from the URL on their profile via the People Page in Vanta:
Endpoint:
/integrations/:integrationId/resource-kinds/:resourceKind/resources
Path Variables
integrationId
(required):<string>
The integration identifier that the resources belong to.resourceKind
(required):<string>
The string identifier of the different kinds of resources associated with this integration.
Request Body:
{
"resourceIds": [
"<string>"
],
"inScope": "<boolean>",
"description": "<string>",
"ownerId": "<string>"
}
Patch Resource-Kinds - Code Example
curl --location --request PATCH 'https://api.vanta.com/v1/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer vat_YOUR_TOKEN' \
--data '{
"resourceIds": [
"64dc848b6491f66d23c64df6",
"64dc848b6491f66d23c64e26"
],
"description": "Test Description",
"ownerId": "5fc82421a228f6b6f713547d"
}'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Accept", "application/json");
myHeaders.append("Authorization", "Bearer vat_YOUR_TOKEN");
const raw = JSON.stringify({
"resourceIds": [
"64dc848b6491f66d23c64df6",
"64dc848b6491f66d23c64e26"
],
"description": "Test Description",
"ownerId": "5fc82421a228f6b6f713547d"
});
const requestOptions = {
method: "PATCH",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://api.vanta.com/v1/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
const axios = require('axios');
let data = JSON.stringify({
"resourceIds": [
"64dc848b6491f66d23c64df6",
"64dc848b6491f66d23c64e26"
],
"description": "Test Description",
"ownerId": "5fc82421a228f6b6f713547d"
});
let config = {
method: 'patch',
maxBodyLength: Infinity,
url: 'https://api.vanta.com/v1/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer vat_YOUR_TOKEN'
},
data : data
};
axios.request(config)
.then((response) => {
console.log(JSON.stringify(response.data));
})
.catch((error) => {
console.log(error);
});
import requests
import json
url = "https://api.vanta.com/v1/integrations/snowflake/resource-kinds/SnowflakeDatabase/resources"
payload = json.dumps({
"resourceIds": [
"64dc848b6491f66d23c64df6",
"64dc848b6491f66d23c64e26"
],
"description": "Test Description",
"ownerId": "5fc82421a228f6b6f713547d"
})
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer vat_YOUR_TOKEN'
}
response = requests.request("PATCH", url, headers=headers, data=payload)
print(response.text)
Patch Resource-Kinds- Example Response
204 No Content
Viewing the updated resources in Vanta
Now that we've successfully added an Active owner to the SNOWFLAKE and SNOWFLAKE_SAMPLE_DATA databases, they're no longer failing the Inventory items have active owners test:
We've also added a Description to these resources, but since they already had one it simply updated it instead. You can see that reflected on the Inventory Page:
Updated 4 months ago