Token Vault Endpoints
Manage third-party OAuth2 connections (Google, GitHub, Slack, etc.) on behalf of users. Bulwark stores the encrypted refresh token, runs the OAuth dance, and vends short-lived access tokens to authorized agents.
Refresh tokens are NEVER returned via the API. Only short-lived access tokens are exposed, and each retrieval refreshes the token if it's near expiry. The refresh token stays encrypted-at-rest in the vault.
Authentication
All management endpoints (/connections, /providers) require:
| Header | Description |
|--------|-------------|
| Authorization | Bearer <jwt> — Bulwark JWT (admin role required for create/delete). |
| X-Bulwark-Tenant | Tenant UUID. |
The OAuth callback endpoint (/callback) is public — it's hit by the OAuth provider and authenticates via a signed state parameter, not headers.
List Available Providers
GET /api/v1/token-vault/providers
Returns the registry of OAuth2 providers Bulwark knows how to talk to (e.g. google, github, slack). Use this to populate provider pickers in admin tooling.
Response 200
{
"providers": [
{
"name": "google",
"display_name": "Google",
"default_scopes": ["openid", "email", "profile"]
}
]
}
Create Connection
POST /api/v1/token-vault/connections
Registers a new OAuth2 provider connection for the tenant. The client_id and client_secret are encrypted before storage. After this call, kick off the OAuth dance via the /authorize endpoint below.
Body
{
"provider_name": "google",
"client_id": "<oauth-client-id-from-google>",
"client_secret": "<oauth-client-secret-from-google>",
"scopes": ["openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly"],
"display_name": "Google Calendar (read-only)"
}
| Field | Required | Description |
|-------|----------|-------------|
| provider_name | Yes | Provider key from /providers. |
| client_id | Yes | OAuth2 client ID issued by the provider. |
| client_secret | Yes | OAuth2 client secret. Encrypted before storage. |
| scopes | No | Override the provider's default scope set. |
| display_name | No | Human-readable label for the admin UI. |
Response 201
{
"id": "01j...",
"tenant_id": "01j...",
"provider_name": "google",
"display_name": "Google Calendar (read-only)",
"scopes": ["openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly"],
"has_token": false,
"created_at": "2026-05-07T00:00:00Z"
}
has_token: false means the OAuth dance hasn't completed yet. Hit /authorize next to start it.
List Connections
GET /api/v1/token-vault/connections
Response 200
{
"connections": [
{
"id": "01j...",
"tenant_id": "01j...",
"provider_name": "google",
"display_name": "Google Calendar (read-only)",
"scopes": ["openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly"],
"has_token": true,
"token_expiry": "2026-05-07T01:00:00Z",
"created_at": "2026-05-07T00:00:00Z"
}
],
"count": 1
}
Delete Connection
DELETE /api/v1/token-vault/connections/{id}
Removes the connection and its stored tokens. Cannot be undone.
Response 200
{ "status": "deleted" }
Start OAuth Dance
GET /api/v1/token-vault/connections/{id}/authorize
Redirects the browser to the provider's authorization URL. Bulwark builds the URL with the right client_id, redirect_uri, scope, and a signed state parameter that ties the eventual callback back to this connection.
This endpoint returns a 302 Found; the browser follows it to the provider, the user grants consent, and the provider redirects back to /api/v1/token-vault/callback with code + state.
OAuth Callback
GET /api/v1/token-vault/callback
Public endpoint. Hit by the OAuth provider after the user grants consent. Bulwark verifies the state HMAC, exchanges the authorization code for tokens, encrypts the refresh token, and stores both. On success returns a small success page.
You should NOT call this endpoint directly. It exists so the OAuth provider can redirect to it.
Get Access Token
GET /api/v1/token-vault/connections/{id}/token
Returns a fresh access token for the connection. If the cached token is near expiry, Bulwark refreshes it transparently using the stored refresh token before returning. The refresh token itself is NEVER returned.
Response 200
{
"access_token": "<short-lived-provider-access-token>",
"expires_at": "2026-05-07T01:00:00Z",
"provider": "google"
}
Errors
404— connection not found, orhas_token: false(OAuth dance never completed)503— refresh failed (provider rejected the refresh token, e.g. user revoked access). Re-run the OAuth dance via/authorize.
Typical flow
- Admin calls
POST /connectionsto register the provider's client_id / client_secret. - Admin clicks "Connect" in the UI, which navigates to
GET /connections/{id}/authorize. - User is redirected to the provider, grants consent.
- Provider redirects to
/callbackwithcode+state. Bulwark exchanges and stores. - Connection now has
has_token: true. - Agent code reads
GET /connections/{id}/tokenwhenever it needs a fresh access token to call the provider's API. Bulwark handles refresh internally.