Skip to main content

Documentation Index

Fetch the complete documentation index at: https://vanta.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Every Vanta API request is authenticated with a short-lived OAuth 2.0 bearer token. Which OAuth flow you use — and the shape of the credentials you exchange — depends on the application type you create in the Developer Console. This page explains the underlying model so you can pick the right flow, understand the tradeoffs, and avoid the foot-guns that aren’t obvious from the per-quickstart instructions. Use this page when you’re deciding how to authenticate or debugging an auth failure. For step-by-step setup, see the per-persona quickstarts: Manage Vanta, Build a Private Integration, Build a Public Integration, and Conduct an Audit.

Overview

Every Vanta API client is registered in the Developer Console as an Application. Each Application has a client_id and a client_secret, plus an app type that determines which OAuth grant types and scopes it can use. Clients exchange those credentials at a single token endpoint — POST https://api.vanta.com/oauth/token — for an access_token, then send that token as Authorization: Bearer <access_token> on every API request. Tokens last one hour. After that you either request a fresh one (machine-to-machine flows) or refresh it (per-user OAuth flows).
Vanta does not support API keys, basic auth, or session cookies for the API. Every authenticated request goes through the OAuth bearer token model below.

Application types and grant types

Vanta exposes four application types, mapped to two OAuth 2.0 grant types. The grant type — not the app type — determines the shape of the auth flow.
Application typeGrant typeToken is scoped toRefresh tokensTypical caller
Manage Vantaclient_credentialsYour own Vanta tenantNoYour own automation
Build Integrations Privateclient_credentialsYour own Vanta tenantNoYour own integration code
Build Integrations Publicauthorization_codeA specific customer’s tenantYesYour customers (per user)
Conduct an Auditclient_credentialsYour auditor firm’s auditsNoYour auditing tools
The big split is between client_credentials (you act as yourself, single tenant) and authorization_code (a customer authorizes you to act on their behalf, per-user tokens).

client_credentials — server-to-server, single tenant

Used by Manage Vanta, Build Integrations → Private, and Auditor apps. There is no end-user; your server holds the client_id and client_secret and exchanges them directly for an access token.
POST https://api.vanta.com/oauth/token
Content-Type: application/json

{
  "client_id": "vci_...",
  "client_secret": "vcs_...",
  "scope": "vanta-api.all:read",
  "grant_type": "client_credentials"
}
Response:
{
  "access_token": "vat_...",
  "expires_in": 3599,
  "token_type": "Bearer"
}
Notice there’s no refresh_token — when the access token expires, you just exchange your client credentials again for a fresh one. Most integrations request a new token at the top of each scheduled run.

authorization_code — per-user, multi-tenant

Used only by Build Integrations → Public apps (i.e. integrations published in the Vanta marketplace). A customer clicks Allow in their browser, Vanta redirects them back to you with a short-lived code, and your server exchanges that code for a per-customer access_token plus a refresh_token. The flow:
  1. Your server redirects the customer’s browser to https://app.vanta.com/oauth/authorize?... with client_id, scope, state, redirect_uri, source_id, and response_type=code.
  2. The customer authorizes; Vanta redirects to your redirect_uri with code and state query parameters.
  3. Your server POSTs to /oauth/token with grant_type=authorization_code to receive both an access_token and a refresh_token.
  4. When the access token expires, your server POSTs to /oauth/token with grant_type=refresh_token to rotate.
POST https://api.vanta.com/oauth/token
Content-Type: application/json

{
  "client_id": "vci_...",
  "client_secret": "vcs_...",
  "code": "vac_...",
  "source_id": "acct1234",
  "redirect_uri": "https://partner-app.com/oauth/callback",
  "grant_type": "authorization_code"
}
Response:
{
  "access_token": "...",
  "refresh_token": "...",
  "expires_in": 3600,
  "token_type": "Bearer"
}
This is the only authentication request that touches app.vanta.com (the browser-facing authorization page). All token exchanges and API calls go to api.vanta.com.
For the full request shape — including the redirect URI rules, error codes, and the source_id parameter — see the Build a Public Integration quickstart.

Choosing an auth model

Pick by who’s making the call and whose data you’re touching.
If you’re…Use
Automating your own Vanta tenant (scripts, internal jobs)Manage Vanta
Pushing data from a homegrown or unsupported system into your own tenantBuild Integrations → Private
Building an integration that customers install from the Vanta marketplaceBuild Integrations → Public
An audit partner pulling evidence from customer auditsConduct an Audit
If you’re unsure, default to the smallest scope of access — start with Manage Vanta (read-only) or Private, and only move to a Public integration when you actually need per-customer OAuth.

Scopes

All applications authenticate against the same /oauth/token endpoint, but the scopes you can request depend on your app type. Requesting a scope that doesn’t match your app type returns an invalid_scope error. The full per-app scope matrix lives in the API reference. At a high level:
  • Manage Vanta appsvanta-api.all:read, vanta-api.all:write, vanta-api.documents:upload, plus vendor-specific scopes.
  • Build Integrations apps (private and public) — connectors.self:read-resource, connectors.self:write-resource, self:read-document, self:write-document.
  • Auditor appsauditor-api.audit:read|write, auditor-api.auditor:read|write.
Always request the minimum scopes your tool needs. Scopes are validated at token-exchange time, so an over-scoped token is a real attack surface even when most of your code paths only read.

Token lifecycle and the foot-guns

The OAuth flows themselves are standard. The behavior around those flows is where most production integrations get bitten. The rules below apply to every app type.

One active token per Application

Vanta only allows a single active access_token per Application. Requesting a new token with the same client_id and client_secret immediately revokes the previous one — any in-flight requests using the old token will fail with 401 Unauthorized.
Concretely, this means:
  • Don’t run two processes that both mint tokens for the same Application. They’ll race, mutually invalidate each other, and you’ll see intermittent 401s.
  • For client_credentials apps, request the token at the start of a sync run and reuse it for the entire run. Don’t refetch per request.
  • For authorization_code apps, this rule applies per source_id (per customer authorization), not globally — different customers get independent tokens.

Tokens expire after one hour

expires_in is 3599 seconds. After that, requests return 401. The recovery path differs by grant type:
  • client_credentials — re-exchange your client_id and client_secret for a new token. There’s no refresh token; the credentials are the refresh.
  • authorization_code — exchange the refresh_token you received during initial authorization for a new access_token / refresh_token pair.

Refresh tokens rotate — and have a 3-hour reuse window

For Public Build Integrations only, every successful refresh returns a new refresh_token. The previous refresh token stays valid for 3 hours after first use, then expires.
Persist the new refresh_token immediately on every refresh. If your refresh handler crashes after issuing the request but before saving the result, the old token still works for up to 3 hours — long enough to recover without forcing the customer to re-authorize, but only if you actually retry.
The 3-hour window is specifically designed to tolerate transient failures. Configure automatic retries on 5xx responses and network errors during refresh; if you don’t, a single bad request can lock a customer out and force a full re-auth.

Authorization codes expire in 30 seconds

For Public integrations: after Vanta redirects the customer back to your redirect_uri with a code, you have 30 seconds to exchange it at /oauth/token. After that the code is dead and the customer has to start the OAuth flow over. In practice this is only a problem if your callback handler is slow (cold-start Lambdas, blocking on a database write before the token exchange). Do the token exchange first, then persist.

/oauth/token accepts JSON, not form-encoded

Vanta’s /oauth/token endpoint expects a Content-Type: application/json body. Most off-the-shelf OAuth client libraries default to application/x-www-form-urlencoded per RFC 6749 — you’ll need to override that. If you see invalid_request or invalid_client errors despite using correct credentials, check the body encoding first.

Rate limit on the token endpoint

/oauth/token is limited to 5 requests per minute across all app types. This is shared across token issuance, refresh, and (for Public) the suspend endpoint. It’s deliberately low because a healthy integration rarely needs more than one token per hour — if you’re hitting the limit, you’re probably re-minting per request rather than per run.

Per-user vs per-app token scoping

Where the tokens live in your system depends on the grant type:
  • client_credentials tokens are scoped to your Application. One token at a time, stored in your service’s secrets manager or in-memory cache. There’s no per-user concept.
  • authorization_code tokens are scoped to a (client_id, source_id) pair — i.e. one of your customer accounts. You’ll have one access token and one refresh token per customer, and you should store them encrypted, keyed on your internal customer identifier.
The source_id you pass during the authorize redirect is the bridge: it’s a string you choose (e.g. your internal accountId) that lets the same Vanta tenant connect multiple of your accounts. Make it human-readable so customers can tell which account is connected.

Revoking access (Public integrations)

When a customer disconnects your integration on your side (uninstall, account deletion, support escalation), you must call the Suspend API so Vanta cleans up its side and revokes the token:
POST https://api.vanta.com/v1/oauth/token/suspend
Content-Type: application/json

{
  "token": "<access_or_refresh_token>",
  "client_id": "vci_...",
  "client_secret": "vcs_..."
}
Suspend is idempotent — calling it on an already-revoked token returns 200. Calling it with a token that doesn’t belong to your client_id returns 401. For client_credentials apps, there’s no Suspend API. To revoke access, rotate the client_secret in the Developer Console (which immediately invalidates any active token issued with the old secret) or delete the Application entirely.

Credential hygiene

The same rules apply across every app type:
  • Never put client_secret, access_token, or refresh_token in source control or client-side code. All token exchanges must happen on a server you control.
  • Rotate client_secret when team members leave or any time you suspect a leak. Rotation is supported from the Developer Console — for Public integrations, it does not invalidate active customer access or refresh tokens, only new token mints.
  • Store per-user tokens encrypted at rest, scoped per customer. A breach of one customer’s tokens shouldn’t compromise others.
  • Treat state validation as mandatory for Public integrations. Compare the returned state against the value you stored in the user’s session before exchanging the code; abort if they don’t match. This is your CSRF protection.

Vanta Gov

Vanta Gov customers authenticate against a separate base URL: https://api.vanta-gov.com. Everything else — grant types, scopes, token lifetimes, refresh semantics — is identical. If you’re not a Vanta Gov customer, use https://api.vanta.com.

Common error patterns

SymptomLikely cause
401 invalid_client on token exchangeWrong client_id / client_secret, or rotated secret with stale value cached.
400 invalid_request on token exchangeBody sent as application/x-www-form-urlencoded instead of application/json.
400 invalid_scopeRequested scope isn’t allowed for this app type.
Intermittent 401 mid-runTwo processes minting tokens for the same Application — they’re invalidating each other. Centralize token issuance.
401 after token refreshYour refresh handler isn’t persisting the new refresh_token. Without persistence you fall back to the old one, which expires 3 hours after first reuse.
Customer “uninstalled” but data still flowingYou didn’t call the Suspend API on disconnect. Wire it into your uninstall handler.
429 on /oauth/tokenYou’re minting more than 5 tokens/minute — request once per run, not per request.

Manage Vanta quickstart

Get a client_credentials token and call your first endpoint.

Build a Public Integration

The full authorization_code flow, including refresh and Suspend.

Build a Private Integration

Single-tenant client_credentials flow for homegrown systems.

Conduct an Audit quickstart

Auditor-scoped client_credentials flow against customer audits.

API scopes reference

The full per-app-type scope matrix.

Postman setup

Import the collection and start testing requests in seconds.