Token-Mint Integrations

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.)

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).
  • 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, 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.