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.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.
Overview
Every Vanta API client is registered in the Developer Console as an Application. Each Application has aclient_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 type | Grant type | Token is scoped to | Refresh tokens | Typical caller |
|---|---|---|---|---|
| Manage Vanta | client_credentials | Your own Vanta tenant | No | Your own automation |
| Build Integrations Private | client_credentials | Your own Vanta tenant | No | Your own integration code |
| Build Integrations Public | authorization_code | A specific customer’s tenant | Yes | Your customers (per user) |
| Conduct an Audit | client_credentials | Your auditor firm’s audits | No | Your auditing tools |
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.
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:
- Your server redirects the customer’s browser to
https://app.vanta.com/oauth/authorize?...withclient_id,scope,state,redirect_uri,source_id, andresponse_type=code. - The customer authorizes; Vanta redirects to your
redirect_uriwithcodeandstatequery parameters. - Your server
POSTs to/oauth/tokenwithgrant_type=authorization_codeto receive both anaccess_tokenand arefresh_token. - When the access token expires, your server
POSTs to/oauth/tokenwithgrant_type=refresh_tokento rotate.
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 tenant | Build Integrations → Private |
| Building an integration that customers install from the Vanta marketplace | Build Integrations → Public |
| An audit partner pulling evidence from customer audits | Conduct an Audit |
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 apps —
vanta-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 apps —
auditor-api.audit:read|write,auditor-api.auditor:read|write.
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
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_credentialsapps, request the token at the start of a sync run and reuse it for the entire run. Don’t refetch per request. - For
authorization_codeapps, this rule applies persource_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 yourclient_idandclient_secretfor a new token. There’s no refresh token; the credentials are the refresh.authorization_code— exchange therefresh_tokenyou received during initial authorization for a newaccess_token/refresh_tokenpair.
Refresh tokens rotate — and have a 3-hour reuse window
For Public Build Integrations only, every successful refresh returns a newrefresh_token. The previous refresh token stays valid for 3 hours after first use, then expires.
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 yourredirect_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_credentialstokens 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_codetokens 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.
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: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, orrefresh_tokenin source control or client-side code. All token exchanges must happen on a server you control. - Rotate
client_secretwhen 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
statevalidation as mandatory for Public integrations. Compare the returnedstateagainst 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
| Symptom | Likely cause |
|---|---|
401 invalid_client on token exchange | Wrong client_id / client_secret, or rotated secret with stale value cached. |
400 invalid_request on token exchange | Body sent as application/x-www-form-urlencoded instead of application/json. |
400 invalid_scope | Requested scope isn’t allowed for this app type. |
Intermittent 401 mid-run | Two processes minting tokens for the same Application — they’re invalidating each other. Centralize token issuance. |
401 after token refresh | Your 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 flowing | You didn’t call the Suspend API on disconnect. Wire it into your uninstall handler. |
429 on /oauth/token | You’re minting more than 5 tokens/minute — request once per run, not per request. |
Related
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.