docs: Improved openAI compatible provider OAuth documentation

2026-07-21 11:32:55 -06:00
parent 07ad1418e0
commit fde10e818c
+192 -56
@@ -210,20 +210,165 @@ clients:
auth: oauth
```
### OAuth for OpenAI-Compatible Providers
## OAuth for OpenAI-Compatible Providers
For any provider that speaks the OpenAI API shape, you can enable OAuth on an `openai-compatible` client. Coyote resolves
OAuth configuration in two layers:
For any provider that speaks the OpenAI API shape (`type: openai-compatible`), you can enable OAuth by setting
`auth: oauth` and configuring an `oauth:` block. This section is your reference for what fields each OAuth flow needs.
1. **Bundled defaults** in `models.yaml` under `provider: <name>` (Coyote ships defaults for providers like xAI).
2. **Inline overrides** in your `config.yaml` under `clients[i].oauth`. You can either supply the whole block (for
providers not shipped with Coyote) or override individual fields (e.g. to point at a corporate token endpoint).
**Not needed for built-in providers** (Claude, Gemini, OpenAI, xAI-when-using-bundled-defaults): those work with just
`auth: oauth`, meaning no fields are required. See the [provider-specific notes above](#openai-oauth-note) for details.
For each optional field, the user's `config.yaml` value wins over the `models.yaml` default. If neither source provides
a required field (`client_id`, `token_url`), Coyote will bail with a clear error when you attempt to authenticate.
### Which flow does my provider use?
**Example: inline OAuth for a private gateway:**
Look at your provider's own login docs or CLI for signal words:
| What your provider does | Flow to configure | Section |
|---|---|---|
| Opens your browser, redirects to a `http://localhost` or `http://127.0.0.1` URL | Authorization Code + PKCE (loopback callback) | [Flow 1, Mode A](#flow-1-authorization-code--pkce) |
| Opens your browser, redirects to a `https://...` URL you'd copy from the address bar | Authorization Code + PKCE (browser-paste) | [Flow 1, Mode B](#flow-1-authorization-code--pkce) |
| Displays a short user code you enter at a URL, with no browser callback | Device Authorization Grant (RFC 8628) | [Flow 2](#flow-2-device-authorization-grant-rfc-8628) |
| No user at all; just `client_id` + `client_secret` exchanged headlessly | Client Credentials (RFC 6749 §4.4) | [Flow 3](#flow-3-client-credentials-m2m) |
If your provider's OAuth app is already registered with a specific redirect URI, Coyote's routing follows it: put the
value in `redirect_uri` and Coyote does the right thing based on the URL shape (loopback vs public).
### Flow 1: Authorization Code + PKCE
Standard OAuth 2.0 with PKCE (RFC 7636). Coyote opens the authorize URL in your browser, receives back an authorization
code, and exchanges it for tokens. Refresh tokens are supported (silently refreshed on expiry).
Coyote picks between two delivery modes automatically based on what you set for the callback:
#### Mode A: Loopback callback (Coyote listens on your machine)
Coyote binds a TCP listener on 127.0.0.1 and the browser redirects there directly. This is the smoothest UX (i.e. no manual
copy/paste), but requires that the provider's OAuth app allows a loopback redirect URI.
**Required fields:**
- `flow: pkce` (or omit. This is the default)
- `client_id`: your OAuth app's client ID
- `authorize_url`: where the browser goes to authorize
- `token_url`: where Coyote exchanges the code for tokens
**Callback config (pick one):**
- `redirect_port: <u16>`: if the provider registered a specific port (e.g. `56121` for xAI). Coyote synthesizes
`http://127.0.0.1:<port>/callback`.
- `redirect_uri: http://127.0.0.1:<port>/<path>`: full custom loopback URL (overrides `redirect_port`).
- Omit both: Coyote picks an ephemeral port on 127.0.0.1 (only works if the provider allows arbitrary loopback ports).
**Optional fields:**
- `scopes: [scope1, scope2]`: space-joined into the `scope=` query param.
- `extra_authorize_params: { key: value }`: extra query params on the authorize URL (e.g. xAI needs `plan: generic`).
- `echo_pkce_in_token_exchange: true`: some providers (like xAI) require re-sending `code_challenge` on token exchange.
- `include_state_in_token_exchange: false`: some providers reject `state` on the token endpoint (default `true`).
- `token_request_format: form_url_encoded`: some providers require form-encoded token bodies (default is
`form_url_encoded` for openai-compatible).
- `client_secret`: rare with PKCE, but supported for hybrid setups.
**Example:**
```yaml
clients:
- type: openai-compatible
name: my-provider
api_base: https://api.provider.com/v1
auth: oauth
oauth:
client_id: '{{PROVIDER_CLIENT_ID}}'
authorize_url: https://auth.provider.com/oauth/authorize
token_url: https://auth.provider.com/oauth/token
redirect_port: 8765
scopes: [read, write]
```
#### Mode B: Browser-paste (you paste the callback back into the terminal)
For providers whose OAuth app is registered with a **public HTTPS URL** (not loopback). The browser redirects to that
URL, the provider's page displays the code (or you copy it from the URL bar), and you paste it back into Coyote.
Coyote detects Mode B automatically when `redirect_uri` is set to a **non-loopback URL** (anything other than 127.0.0.1,
localhost, or ::1). No opt-in flag needed.
**Required fields:** same as Mode A above, except the callback config:
- `redirect_uri: https://<provider-callback-host>/<path>`: the exact URL the provider is registered with.
**What Coyote accepts at the paste prompt:**
1. **Full callback URL from the browser bar** (e.g. `https://provider.example.com/callback?code=abc&state=xyz`).
Coyote parses `code` and `state` from the query string.
2. **`<code>#<state>` fragment** (Anthropic-style; supported for provider pages that display both concatenated).
3. **Bare code** (just the authorization code, no state). Accepted with a warning. CSRF state validation is
skipped. Use this for providers whose callback page shows only the code.
**Example:**
```yaml
clients:
- type: openai-compatible
name: my-hosted-provider
api_base: https://api.provider.com/v1
auth: oauth
oauth:
client_id: '{{PROVIDER_CLIENT_ID}}'
authorize_url: https://auth.provider.com/oauth/authorize
token_url: https://auth.provider.com/oauth/token
redirect_uri: https://provider.com/oauth/callback # public URL -> paste mode
scopes: [read]
```
### Flow 2: Device Authorization Grant (RFC 8628)
For CLIs and headless environments: **no browser callback required**. Coyote displays a short `user_code` and a
`verification_uri`. You visit the URL on any device (phone, another laptop, or the same browser Coyote tries to open)
and enter the code to approve. Coyote polls the token endpoint until you approve or the code expires. Refresh tokens
are supported.
**Required fields:**
- `flow: device_code`
- `client_id`: your OAuth app's client ID
- `device_authorization_url`: where Coyote requests the initial `user_code`
- `token_url`: where Coyote polls for the token
**Optional fields:**
- `scopes: [scope1, scope2]`
- `use_pkce_in_device_flow: true`: some providers (like MiniMax) require PKCE with device flow. Coyote generates a
fresh `code_verifier` + `code_challenge` (S256) on each authentication.
- `token_request_format: form_url_encoded`: usually needed for device flow (default for openai-compatible).
- `extra_token_headers: { key: value }`: custom headers applied to both device and token endpoints.
- `client_secret`: rare but supported.
**Example:**
```yaml
clients:
- type: openai-compatible
name: my-device-flow-provider
api_base: https://api.example.com/v1
auth: oauth
oauth:
client_id: '{{MY_CLIENT_ID}}'
device_authorization_url: https://auth.example.com/oauth/device_authorization
token_url: https://auth.example.com/oauth/token
flow: device_code
scopes: [openid, profile]
```
**Sandbox bonus:** when `IS_SANDBOX=1` (set automatically by `coyote --sandbox`), Coyote renders a scannable QR code
in the terminal so you can point your phone camera at the verification URL and complete the flow in seconds.
### Flow 3: Client Credentials (M2M)
For headless machine-to-machine authentication. No user, no browser. Coyote POSTs directly to the token endpoint with
`client_id` + `client_secret`. Best for CI, servers, agent runtimes.
**Required fields:**
- `flow: client_credentials`
- `client_id`
- `client_secret`
- `token_url`
**Optional fields:**
- `scopes: [scope1, scope2]`
- `token_request_format`
- `extra_token_headers: { key: value }`
**Example:**
```yaml
clients:
- type: openai-compatible
@@ -238,49 +383,32 @@ clients:
flow: client_credentials
```
### client_credentials Flow
**Note on refresh:** RFC 6749 forbids refresh tokens for this flow. When the access token expires, Coyote
silently re-runs the client credentials exchange (still no browser or user interaction).
For headless machine-to-machine (M2M) authentication (i.e. no browser, no user interaction), set `flow: client_credentials`
in your `oauth` block. Coyote will `POST` directly to the token endpoint with `client_id` and `client_secret` and cache
the returned access token.
### Providers with Bundled OAuth Defaults
RFC 6749 forbids refresh tokens for the client credentials flow, so when the token expires Coyote silently
re-runs the client credentials exchange (still no browser). This makes it suitable for CI, servers, and other
non-interactive environments.
Coyote ships OAuth defaults for the following providers, so you only need `auth: oauth` in your config, with no fields
required:
### Device Authorization Grant (Device Flow, RFC 8628)
* **xAI:** SuperGrok / X Premium+ subscribers. See [xAI OAuth Note](#xai-oauth-note) above for behavior details.
For CLIs and headless environments; no browser callback required. Coyote displays a short
`user_code` and a `verification_uri`. You visit the URL on any device (your phone, another
computer, or the same browser Coyote tries to open) and enter the code to approve. Coyote
polls the token endpoint until you approve or the code expires.
To bundle defaults for additional providers locally, edit your `models.yaml` (see [Local Models Override](#local-models-override))
and add an `oauth:` block under the provider entry using the field reference for the appropriate flow above.
To enable, set `flow: device_code` in your `oauth:` block and provide a `device_authorization_url`:
### Configuration Precedence
clients:
- type: openai-compatible
name: my-device-flow-provider
api_base: https://api.example.com/v1
auth: oauth
oauth:
client_id: '{{MY_CLIENT_ID}}'
device_authorization_url: https://auth.example.com/oauth/device_authorization
token_url: https://auth.example.com/oauth/token
flow: device_code
Coyote resolves OAuth configuration in two layers:
**Optional:** some providers require PKCE with device flow. Set `use_pkce_in_device_flow: true` to opt
in. Coyote generates a fresh `code_verifier` and `code_challenge` on each authentication.
1. **Bundled defaults** in `models.yaml` under `provider: <name>`.
2. **Inline overrides** in your `config.yaml` under `clients[i].oauth`.
Token refresh works the same as the Auth Code + PKCE flow: `refresh_token` is silently
exchanged for a new `access_token` when the current one expires.
Per-field: `config.yaml` wins over `models.yaml`. If neither source provides a required field for your flow (see the
"Required fields" list in each flow section above), Coyote bails with a clear error at authentication time telling you
which field is missing.
**Sandbox / headless environments:** when Coyote runs inside a sandbox (`coyote --sandbox`),
your host OAuth tokens (`~/.cache/coyote/oauth/`) are copied into the container so agents can
call OAuth-authenticated providers without re-authenticating. If you do need to authenticate
from inside a sandbox, Device Flow works out of the box: Coyote prints a QR code you can
scan with your phone camera to visit the verification URL. Auth Code + PKCE flows (like xAI's)
generally cannot complete inside a sandbox because they require an inbound callback listener;
authenticate on the host first and let the token copy carry you through.
You can override individual fields. For example, if the bundled default has the wrong token URL for your corporate
gateway, just set `oauth.token_url` inline and the rest of the bundled defaults still apply.
### Known Limitation: Token Namespacing
@@ -288,27 +416,35 @@ OAuth tokens are stored on disk at `~/.cache/coyote/oauth/{client_name}_oauth_to
field only (not by provider type). Two clients with the same `name`, even if they use different provider types, will
overwrite each other's tokens.
To prevent accidental collisions, Coyote now rejects configuration files with duplicate `name` values across all
To prevent accidental collisions, Coyote rejects configuration files with duplicate `name` values across all
`clients[]` entries at load time. Fixing the underlying single-namespace layout is future work.
### OpenAI-Compatible Providers with Bundled OAuth Defaults
### Sandbox and headless environments
Coyote ships OAuth defaults for the following providers, so you only need `auth: oauth` in your config:
When Coyote runs inside a sandbox (`coyote --sandbox`), your host OAuth tokens (`~/.cache/coyote/oauth/`) are copied
into the container so agents can call OAuth-authenticated providers without re-authenticating. If you do need to
authenticate from inside a sandbox:
* **xAI:** SuperGrok / X Premium+ subscribers
- **Flow 2 (Device Grant)** works out of the box. Coyote prints a QR code you can scan with your phone camera.
- **Flow 3 (Client Credentials)** works fine (no browser or callback needed).
- **Flow 1 Mode A (loopback callback)** generally cannot complete because the loopback listener binds inside the
sandbox's network namespace, unreachable from the host browser.
- **Flow 1 Mode B (browser-paste)** works if you can open the authorize URL in a browser somewhere and paste back.
To bundle defaults for additional providers locally, edit your `models.yaml` (see [Local Models Override](#local-models-override))
and add an `oauth:` block under the provider entry.
Recommendation: authenticate on the host first and let the sandbox token copy carry you through.
## Providers That Support OAuth
* Claude
* Gemini
* OpenAI (requires ChatGPT Plus or Pro subscription)
* xAI (requires SuperGrok or X Premium+ subscription; configured via `type: openai-compatible`)
* Any OpenAI-compatible provider via an inline `oauth:` block (see [OAuth for OpenAI-Compatible Providers](#oauth-for-openai-compatible-providers))
> Additional providers using Device Authorization Grant (RFC 8628) can be configured inline via
> `flow: device_code` in the `oauth:` block. See [Device Authorization Grant](#device-authorization-grant-device-flow-rfc-8628).
**Built-in providers:** (just set `auth: oauth`, no fields required):
* **Claude:** See [Claude OAuth Note](#oauth-authentication)
* **Gemini:** See [Gemini OAuth Note](#gemini-oauth-note)
* **OpenAI:** See [OpenAI OAuth Note](#openai-oauth-note) (requires ChatGPT Plus or Pro subscription)
* **xAI:** See [xAI OAuth Note](#xai-oauth-note) (requires SuperGrok or X Premium+ subscription; configured via `type: openai-compatible`, uses bundled defaults)
**Any OpenAI-compatible provider:** Configured inline via an `oauth:` block. Coyote supports all four common flows:
Authorization Code + PKCE (loopback callback OR browser-paste), Device Authorization Grant (RFC 8628), and Client
Credentials. See [OAuth for OpenAI-Compatible Providers](#oauth-for-openai-compatible-providers) for the field reference
for each flow.
# Extra Settings
Coyote also lets you customize some extra settings for interacting with APIs: