OAuth with PKCE

Discover the authorization server, register a public client, sign the owner in and renew or revoke the connection.

Envoy is an OAuth protected resource. A separate authorization server signs the user in and issues the bearer token; Envoy checks the token and the owner's approval on every call. Use authorization code with S256 PKCE, scope agent and resource https://api.envoy.money/mcp, for MCP and HTTP alike.

Support revision: envoy@2026-10. Last updated: 28 October 2026.

Discover the authorization server

Read Envoy's protected-resource metadata without a token:

https://api.envoy.money/.well-known/oauth-protected-resource/mcp

It names the resource, the authorization_servers (one issuer, the one to sign in with) and scopes_supported: ["agent"]. Always read the issuer from this document rather than storing it: it can change when Central Auth moves host, and Envoy keeps accepting tokens from the previous issuer until they expire. An unauthenticated call to /mcp or /tools/{tool} returns 401 with this metadata URL in WWW-Authenticate.

Envoy's OAuth issuer is gateway.joinelixir.club. Its metadata names auth.envoy.money for authorization, and gateway.joinelixir.club for token, registration and revocation calls. The connected tools remain at api.envoy.money; use the advertised endpoints if they change.

Then read /.well-known/oauth-authorization-server on that issuer and use the endpoints it advertises: authorization, token, registration, device authorization and revocation. Do not hard-code them, and do not send Envoy's own origin as the issuer.

Register a public client

Clients are public: the issuer advertises token-endpoint authentication none, and PKCE replaces a client secret. The registration endpoint rejects any other token_endpoint_auth_method, such as client_secret_basic, so the host's connect flow fails before sign-in. Either register through the issuer's registration endpoint, or use an HTTPS client ID metadata document when the issuer advertises client_id_metadata_document_supported. A registration body looks like this, with a callback your application controls:

register-client.json
{
  "client_name": "Example account assistant",
  "redirect_uris": ["https://client.example/oauth/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}

Keep the returned client_id. A client name is what the client says about itself; it does not prove which platform or model is calling.

Authorize and exchange

Generate a random state and a PKCE verifier, and keep both privately until the callback. Open the issuer's authorization endpoint with:

response_type=code
client_id=YOUR_CLIENT_ID
redirect_uri=YOUR_EXACT_REGISTERED_CALLBACK
scope=agent
state=YOUR_RANDOM_STATE
code_challenge=YOUR_S256_CHALLENGE
code_challenge_method=S256
resource=https://api.envoy.money/mcp

The user signs in on the first-party page, reviews the client, Envoy's permissions and its limit, and approves the connection. Connecting lets this assistant read your Envoy account and ask to buy. The user must complete each purchase's returned approval or payment-link task. See purchase routes.

At your callback, reject a missing or mismatched state, then exchange the code once at the token endpoint with grant_type=authorization_code, your client_id, the code, the original code_verifier, the exact redirect_uri and the same resource. Use the expires_in the issuer returns; this page does not promise a token lifetime.

Store the access and refresh tokens in private storage, never in chat, source code or logs. Send Authorization: Bearer <access token> on every call. Your first call is in the API quickstart.

A client without a browser callback can use the issuer's device authorization endpoint with an installation key instead. See request protection.

Renew without replaying

Renew at the issuer's token endpoint with grant_type=refresh_token, your client_id and the current refresh token. Serialize renewal per connection and save the rotated tokens before any other request uses them. Never resend a refresh token that may already have been used: if a renewal reply is lost, reconnect instead.

If the issuer refuses renewal with refresh_resource_mismatch, the connection was authorized before Envoy's resource identifier changed on 2026-10-06 and can never renew. Reconnect once; do not retry renewal.

Every UNAUTHENTICATED reply has next.action reconnect, including one caused by a plain expired access token. Renew once with your refresh token if you hold one; if renewal fails or you have none, reconnect: the user signs in again. See Reconnect and resume your task.

Disconnect

To end the agent's access, call POST /disconnect on Envoy with the current bearer (and installation proof, for a key-bound connection), or revoke the token at the issuer's revocation endpoint. Disconnecting ends access; it does not cancel a purchase or a merchant order. No card is released after a disconnect. Details are in HTTP routes.

On this page