> ## Documentation Index
> Fetch the complete documentation index at: https://developer.vanta.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Integrations API

> Build Integrations API — push resource data and evidence into Vanta tenants. For partner built integrations and private integrations.

The Build Integrations API is for applications that send data **into** Vanta on behalf of a customer. It powers Vanta's marketplace integrations and any private integration you build for your own tenant. Use it to ingest user accounts, devices, vulnerabilities, training records, background checks, and arbitrary custom resources.

## Who is this API for?

This API is the **only** Vanta API available to partners and is the right choice for any app that ships data into Vanta.

| You are...                                                     | Use this API to...                                                                                                                                                                                          |
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **A partner / ISV** building a marketplace integration         | Push your product's data (user accounts, devices, vulnerabilities, etc.) into your customers' Vanta tenants. This is the **only** API partners can use — partners cannot access the Manage Vanta API.       |
| **A Vanta customer** building a private integration            | Connect a homegrown app, on-prem system, or unsupported SaaS tool to your own Vanta tenant.                                                                                                                 |
| **A Vanta customer** syncing data Vanta doesn't natively cover | Define a [custom resource](/docs/concepts/resources#custom-resources) and feed it into Vanta to layer [Custom Tests](https://help.vanta.com/hc/en-us/articles/28012720726036-Creating-Custom-Tests) on top. |

<Info>
  Automating workflows inside your **own** Vanta tenant — assigning control owners, querying tests, managing vendors and personnel? Use the [Manage Vanta API](/reference/manage-vanta/overview) instead. (Note: the Manage Vanta API is not available to partners.)
</Info>

## When to use this API

Reach for Build Integrations endpoints when you want to:

* **Publish a public integration** to the Vanta marketplace so any Vanta customer can connect your tool.
* **Build a private integration** for a homegrown app, on-prem system, or unsupported SaaS tool inside your own tenant.
* **Sync resources Vanta doesn't natively support** via [custom resources](/docs/concepts/resources#custom-resources), then layer [Custom Tests](https://help.vanta.com/hc/en-us/articles/28012720726036-Creating-Custom-Tests) on top.
* **Upload file-based evidence** on a customer's behalf to satisfy evidence requests.

New here? Walk through the [Build a Private Integration quickstart](/docs/quickstart/build-private-integration) to get OAuth-authorized and push your first resource end to end.

## Authentication

All Build Integrations apps authenticate with OAuth 2.0 at the same token endpoint. The grant type depends on how your app is distributed:

* **Private integration** — single-tenant, used only inside your own Vanta account. Uses `client_credentials`. Start here if you're building for your own tenant.
* **Public integration** — listed in the Vanta marketplace, installable by any customer. Uses `authorization_code` with refresh tokens, one set per customer authorization.

|                  |                                                         |
| ---------------- | ------------------------------------------------------- |
| **Endpoint**     | `POST /oauth/token`                                     |
| **Base URL**     | `https://api.vanta.com`  ·  `https://api.vanta-gov.com` |
| **Content-Type** | `application/json`                                      |

### Private integrations

Grant Type: `client_credentials`

Your server holds the `client_id` / `client_secret` and exchanges them directly for an `access_token` scoped to your own Vanta tenant.

**Request body**

| Field           | Type   | Required | Description                                                   |
| --------------- | ------ | -------- | ------------------------------------------------------------- |
| `client_id`     | string | yes      | OAuth client ID from your Build Integrations app.             |
| `client_secret` | string | yes      | OAuth client secret from your app.                            |
| `scope`         | string | yes      | Space-separated list of [Build Integrations scopes](#scopes). |
| `grant_type`    | string | yes      | `client_credentials`                                          |

<CodeGroup>
  ```bash cURL theme={"system"}
  curl --request POST 'https://api.vanta.com/oauth/token' \
    --header 'Content-Type: application/json' \
    --data '{
      "client_id": "vci_your_client_id",
      "client_secret": "vcs_your_client_secret",
      "scope": "connectors.self:write-resource connectors.self:read-resource",
      "grant_type": "client_credentials"
    }'
  ```

  ```javascript Node.js theme={"system"}
  const response = await fetch("https://api.vanta.com/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.VANTA_CLIENT_ID,
      client_secret: process.env.VANTA_CLIENT_SECRET,
      scope: "connectors.self:write-resource connectors.self:read-resource",
      grant_type: "client_credentials",
    }),
  });
  const { access_token } = await response.json();
  ```

  ```python Python theme={"system"}
  import requests

  response = requests.post(
      "https://api.vanta.com/oauth/token",
      json={
          "client_id": client_id,
          "client_secret": client_secret,
          "scope": "connectors.self:write-resource connectors.self:read-resource",
          "grant_type": "client_credentials",
      },
  )
  access_token = response.json()["access_token"]
  ```
</CodeGroup>

**Response**

```json theme={"system"}
{
  "access_token": "vat_...",
  "expires_in": 3600,
  "token_type": "Bearer"
}
```

No `refresh_token` is issued. Requesting a new token with the same `client_id` / `client_secret` immediately revokes the previous one — re-mint just before each sync run.

To revoke access, rotate the `client_secret` in the Developer Console (this invalidates active tokens immediately) or delete the application. For the full step-by-step setup, see the [Build a Private Integration quickstart](/docs/quickstart/build-private-integration).

### Public integrations

Grant Type: `authorization_code`

A customer authorizes your app in the browser at `/oauth/authorize` on **their region's** app host (`app.vanta.com`, `app.eu.vanta.com`, or `app.aus.vanta.com`), and Vanta redirects back to your `redirect_uri` with a short-lived `code` (valid for **30 seconds**). Your server then exchanges the code for a per-customer `access_token` + `refresh_token` pair.

<Info>
  The authorize redirect is the only flow that touches the browser-facing app host — and unlike `api.vanta.com`, that host is **regional**: sending an EU or AU customer to `app.vanta.com` instead of their own region's host can fail. All token exchanges and API calls still go to `api.vanta.com` regardless of the customer's region. For Vanta Gov, use `app.vanta-gov.com` and `api.vanta-gov.com`. See [Base URLs and regions](/docs/concepts/authentication#base-urls-and-regions) for the full picture, and the [Build a Public Integration quickstart](/docs/quickstart/build-integration) for the full authorize-redirect flow (including `state` validation and `source_id` semantics).
</Info>

**Request body**

| Field           | Type   | Required | Description                                                                                                                        |
| --------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `client_id`     | string | yes      | OAuth client ID from your public app.                                                                                              |
| `client_secret` | string | yes      | OAuth client secret from your application.                                                                                         |
| `code`          | string | yes      | Authorization code from the redirect callback. Expires **30 seconds** after issuance.                                              |
| `source_id`     | string | yes      | Must match the `source_id` you sent on the initial authorize redirect — your internal account identifier for the user.             |
| `redirect_uri`  | string | yes      | Must exactly match the `redirect_uri` you sent on the initial authorize redirect, and must be registered in the Developer Console. |
| `grant_type`    | string | yes      | `authorization_code`                                                                                                               |

<CodeGroup>
  ```bash cURL theme={"system"}
  curl --request POST 'https://api.vanta.com/oauth/token' \
    --header 'Content-Type: application/json' \
    --data '{
      "client_id": "vci_your_client_id",
      "client_secret": "vcs_your_client_secret",
      "code": "vac_authorization_code_from_callback",
      "source_id": "acct1234",
      "redirect_uri": "https://partner-app.com/oauth/callback",
      "grant_type": "authorization_code"
    }'
  ```

  ```javascript Node.js theme={"system"}
  const response = await fetch("https://api.vanta.com/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.VANTA_CLIENT_ID,
      client_secret: process.env.VANTA_CLIENT_SECRET,
      code,
      source_id: accountId,
      redirect_uri: "https://partner-app.com/oauth/callback",
      grant_type: "authorization_code",
    }),
  });
  const tokens = await response.json();
  ```

  ```python Python theme={"system"}
  import requests

  response = requests.post(
      "https://api.vanta.com/oauth/token",
      json={
          "client_id": client_id,
          "client_secret": client_secret,
          "code": code,
          "source_id": account_id,
          "redirect_uri": "https://partner-app.com/oauth/callback",
          "grant_type": "authorization_code",
      },
  )
  tokens = response.json()
  ```
</CodeGroup>

**Response**

```json theme={"system"}
{
  "access_token": "...",
  "refresh_token": "...",
  "expires_in": 3600,
  "token_type": "Bearer"
}
```

Store both tokens encrypted at rest, keyed on your internal customer identifier (the same value you used as `source_id`). Each customer authorization is independent — re-running the flow for the same `source_id` revokes that customer's previous tokens, but different customers get separate, isolated token pairs.

#### Refreshing the token

When the access token expires (1 hour), exchange the `refresh_token` for a new pair. Every refresh rotates the `refresh_token`; the previous one stays valid for **3 hours** to tolerate transient failures — persist the new one immediately.

| Field           | Type   | Required | Description                                  |
| --------------- | ------ | -------- | -------------------------------------------- |
| `client_id`     | string | yes      | OAuth client ID.                             |
| `client_secret` | string | yes      | OAuth client secret.                         |
| `refresh_token` | string | yes      | The current refresh token for this customer. |
| `grant_type`    | string | yes      | `refresh_token`                              |

```bash cURL theme={"system"}
curl --request POST 'https://api.vanta.com/oauth/token' \
  --header 'Content-Type: application/json' \
  --data '{
    "client_id": "vci_your_client_id",
    "client_secret": "vcs_your_client_secret",
    "refresh_token": "your_refresh_token",
    "grant_type": "refresh_token"
  }'
```

Returns the same `200 OK` response shape as the initial exchange (new `access_token` + new `refresh_token`).

<Warning>
  Configure automatic retries on `5xx` responses and network errors during refresh. If your refresh handler crashes after issuing the request but before saving the new `refresh_token`, the old one still works for up to 3 hours — but only if you actually retry.
</Warning>

#### Revoking access

When a customer disconnects your integration on your side, call the Suspend API so Vanta cleans up its side and revokes the tokens.

|                  |                                                         |
| ---------------- | ------------------------------------------------------- |
| **Endpoint**     | `POST /v1/oauth/token/suspend`                          |
| **Base URL**     | `https://api.vanta.com`  ·  `https://api.vanta-gov.com` |
| **Content-Type** | `application/json`                                      |

| Field           | Type   | Required | Description                                                        |
| --------------- | ------ | -------- | ------------------------------------------------------------------ |
| `token`         | string | yes      | The access or refresh token to revoke.                             |
| `client_id`     | string | yes      | OAuth client ID. Must match the application that minted the token. |
| `client_secret` | string | yes      | OAuth client secret.                                               |

```bash cURL theme={"system"}
curl --request POST 'https://api.vanta.com/v1/oauth/token/suspend' \
  --header 'Content-Type: application/json' \
  --data '{
    "token": "access_or_refresh_token_to_revoke",
    "client_id": "vci_your_client_id",
    "client_secret": "vcs_your_client_secret"
  }'
```

Returns `200 OK` with an empty body `{}`. Idempotent — calling it on an already-revoked token still returns `200`. Returns `401` if the token doesn't belong to this `client_id`.

For grant-type tradeoffs, credential hygiene, and the rest of the foot-guns, see [Authentication concepts](/docs/concepts/authentication).

## Scopes

| Scope                            | Grants                                                       |
| -------------------------------- | ------------------------------------------------------------ |
| `connectors.self:write-resource` | Push resources into customer Vanta accounts.                 |
| `connectors.self:read-resource`  | Read resources you previously pushed (useful for debugging). |
| `self:write-document`            | Upload file-based evidence on a customer's behalf.           |
| `self:read-document`             | Query evidence requests you previously responded to.         |

Most integrations request the first two. Add the document scopes only if your integration uploads evidence files.

## Base URL

Use `https://api.vanta.com`, or `https://api.vanta-gov.com` if you're on Vanta Gov. See [Base URLs](/reference/overview#base-urls) for details.

## Resource model

Build Integrations endpoints follow a `resource_type` pattern:

| Method | Endpoint               | Use                                                                                                                |
| ------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `PUT`  | `/v1/resources/<type>` | Idempotent upsert by `uniqueId`. Push the full set of resources you own on a periodic schedule (typically hourly). |
| `GET`  | `/v1/resources/<type>` | Read back resources you previously pushed. See [Pagination](#pagination) for response shape.                       |

`PUT` is a "state of the world" sync — any resource you previously pushed but omit from a later `PUT` is treated as deleted. There is no separate `DELETE` endpoint. `PUT` is safe to retry on network errors; to sync a large dataset, batch resources into multiple `PUT` calls.

See the resource endpoints in the sidebar for the full list of supported types and their schemas. For anything not natively supported, use the [custom resource](/docs/concepts/resources#custom-resources) type.

## Pagination

Build Integrations `GET /v1/resources/<type>` endpoints are **not paginated**. Each request returns the full list of resources for the given type in a single `resources` array:

```json theme={"system"}
{
  "resources": [ /* ... */ ]
}
```

There are no `pageSize`, `pageCursor`, or `pageInfo` fields. To sync a large dataset, use the corresponding `PUT /v1/resources/<type>` endpoint to push resources to Vanta in batches.

## Payload limits

Each `PUT /v1/resources/<type>` request is capped at:

| Limit                       | Value  |
| --------------------------- | ------ |
| Maximum resources per `PUT` | 10,000 |
| Maximum request body size   | 10 MB  |

Because `PUT` is a "state of the world" sync, every resource you want Vanta to retain for a given `resource_type` must arrive in a **single** request — a follow-up `PUT` to the same type replaces the previous payload rather than appending to it. You can split work *across* resource types (a separate `PUT` per type), but not *within* one.

If a payload approaches either cap, scope the sync down to fewer resources to avoid the payload limit.

* **Sync only the records you need to act on.** This is most relevant for vulnerability resource types (`PackageVulnerabilityConnectors`, `ApiEndpointVulnerabilityConnectors`, `StaticAnalysisCodeVulnerabilityConnectors`), where scanners often emit thousands of findings spanning Critical, High, Medium, and Low CVEs alongside Known malware and Protestware / potentially unwanted behavior alerts. Customers overwhelmingly take action on Critical and High in Vanta, and those are the severities that drive compliance monitoring. Sending only Critical and High keeps payloads well under the limit and matches what Vanta's own native scanner integrations do.
* **Let customers pick severities at install time.** For public integrations, expose a severity selector in your install or settings UI so customers opt into what they want synced. Defaulting to Critical and High covers the common case; customers with specific framework requirements can broaden the set themselves.

The same scope-down pattern applies to `Computer` resources when devices carry large installed-application inventories — sync only the security-relevant applications (password managers, antivirus, etc.) rather than the full list.

## Rate limits

| Endpoint group               | Limit                        |
| ---------------------------- | ---------------------------- |
| Build Integrations endpoints | 20 / minute per access token |
| OAuth (`/oauth/token`)       | 5 / minute                   |

Exceeding a limit returns `429 Too Many Requests`. Back off and retry after a short delay.

## Common workflows

<CardGroup cols={2}>
  <Card title="Build a private integration" href="/docs/quickstart/build-private-integration">
    Single-tenant integration for your own Vanta account, end to end.
  </Card>

  <Card title="Build a public integration" href="/docs/quickstart/build-integration">
    Partner flow: register, OAuth, push resources, list in the marketplace.
  </Card>

  <Card title="Resources" href="/docs/concepts/resources">
    Deep dive on the resource lifecycle, idempotency model, and custom resources.
  </Card>
</CardGroup>

## OpenAPI specification

<a href="/reference/build-integrations.json" download>
  <Card title="Download the Build Integrations OpenAPI spec" icon="download">
    Generate clients, import into your favorite tool, or browse the schema offline.
  </Card>
</a>

## Tools

<CardGroup cols={2}>
  <Card title="Postman Collection" icon="paper-plane" href="/docs/postman-setup">
    Import the collection to explore endpoints quickly.
  </Card>

  <Card title="AI Skills" icon="wand-magic-sparkles" href="/docs/ai-skills">
    Add skills that give Cursor, Claude Code, and other AI agents Vanta-specific context.
  </Card>
</CardGroup>
