Token-Mint Integrations
Exchange a vaulted credential for a short-lived token your VM can use
Some vendors don't take an API key on every request — they want an OAuth-style
**token dance**: present a durable credential at a token endpoint, get back a
short-lived access token, use that. The token-mint integrations run that dance
for you: exe.dev vaults the durable credential (a client secret, a service
account key, a refresh token) and your VM exchanges it for the short-lived
token without ever seeing it.
The catalog services with this shape today: `googlesa`, `keycloak`, `twitch`,
and `reddit-ads`. (Container registries use a GET-based cousin of this flow —
see [Container Registry Integrations](integrations-oci-registries).)
## How it works
1. `POST` to the vendor's documented token path on the integration hostname.
Any body you send is ignored — the proxy discards it and synthesizes the
real token request from the vaulted credential:
```
curl -X POST http://<name>.int.exe.xyz/oauth2/token
```
2. The vendor's response comes back to you verbatim: a short-lived
`access_token` (typically ~1 hour) with `expires_in`.
3. Use it as `Authorization: Bearer <token>` — depending on the service,
either through the integration or directly against the vendor (see each
section below).
4. When the token expires (the vendor answers 401), mint a fresh one. React
to the 401 rather than tracking TTLs.
The durable credential never reaches the VM; only the scoped, expiring token
does. If a vendor echoes a durable secret back in the token response (Reddit
does), the proxy strips that field.
## Google service account (`googlesa`)
- Credential: a service-account JSON key
([create one](https://console.cloud.google.com/iam-admin/serviceaccounts)).
- Mint: `POST /token` with an empty body. The proxy signs the JWT assertion
with the private key server-side and exchanges it at Google's token
endpoint.
- Use the ~1h access token **directly against the Google API** (e.g.
`Authorization: Bearer <token>` to `sheets.googleapis.com`) — those calls
do not go through the integration.
- Consumer-style use: share the target resource (e.g. a Sheet) with the
service account's `client_email`.
- Domain-wide delegation: pass `--subject` with the user to impersonate.
## Keycloak (`keycloak`)
- Credential: a confidential client's id + secret.
- Self-hosted: point `--base-url` at your Keycloak install.
- Mint: `POST /realms/<realm>/protocol/openid-connect/token` — the
client-credentials grant. The integration is path-gated to `/realms/`, so
the secret can only ever be presented to a token endpoint.
- Use the bearer token directly against your own API.
- Verify runs a client-credentials mint against `--realm` (default
`master`); the id/secret ride HTTP Basic, so the body carries only
`grant_type`.
## Twitch (`twitch`)
- Credential: an application's client_id + client_secret
([register an app](https://dev.twitch.tv/console/apps), then "New Secret"
on its Manage page).
- Mint: `POST /oauth2/token` — Twitch's client-credentials grant. Twitch
only accepts the credentials as form-body fields (HTTP Basic is rejected
with `missing client id`), which is exactly why the body is synthesized
server-side.
- Use the app access token on `/helix/...` requests **through the
integration** — the proxy adds the required `Client-Id` header for you.
- App access tokens are server-to-server and carry no scopes. Endpoints that
need a user context (e.g. a user's email) require a *user* access token,
which this integration does not mint.
## Reddit Ads (`reddit-ads`)
- Credential: your app's client credentials plus a **refresh token**.
- Getting the refresh token: authorize your app once at
`https://www.reddit.com/api/v1/authorize?client_id=...&response_type=code&state=...&redirect_uri=...&duration=permanent&scope=adsread`
(add `adsedit` / `adsconversions` as needed), then exchange the code at
Reddit's token endpoint; the response's `refresh_token` is what you paste
into the integration.
- Scopes: `adsread` covers reporting and read endpoints; campaign writes
need `adsedit`; conversion uploads need `adsconversions`.
- Mint: `POST /api/v1/access_token` (any body ignored). The proxy performs
the refresh-token grant server-side and returns the ~1h `access_token`.
Reddit echoes the permanent refresh token back in refresh responses — the
proxy strips that field.
- Use the token on `/api/v3/...` requests, through the integration or
directly against `ads-api.reddit.com`.
- Reddit requires a descriptive User-Agent; the proxy sets one on proxied
requests.