Table of Contents
- Supported Clients
- Client Configuration
- Authentication
- API Key Authentication
- OAuth Authentication
- OAuth for OpenAI-Compatible Providers
- Which flow does my provider use?
- Flow 1: Authorization Code + PKCE
- Mode A: Loopback callback (Coyote listens on your machine)
- Mode B: Browser-paste (you paste the callback back into the terminal)
- Flow 2: Device Authorization Grant (RFC 8628)
- Flow 3: Client Credentials (M2M)
- Providers with Bundled OAuth Defaults
- Configuration Precedence
- Known Limitation: Token Namespacing
- Sandbox and headless environments
- Providers That Support OAuth
- Extra Settings
Coyote supports a large number of model providers (referred to as clients since Coyote is a client of these providers). In
order to use them, you must configure each one in the clients array in the global Coyote configuration file.
The location of the global Coyote configuration file varies between systems, so you can use the following command to locate your configuration file:
coyote --info | grep 'config_file' | awk '{print $2}'
Supported Clients
Coyote supports the following model client types:
- Azure AI Foundry
- AWS Bedrock
- Anthropic Claude
- Cohere
- Google Gemini
- OpenAI
- OpenAI-Compatible (e.g. Ollama)
- GCP Vertex AI
In addition to the settings detailed below, each client may have additional settings specific to the provider. Check the example global configuration file to verify that your client has all the necessary fields defined.
Client Configuration
Each client in Coyote has the same configuration settings available to them, with only special authentication fields added
for specific clients as necessary. They are each placed under the clients array in your global configuration file:
clients:
- name: client1
# ... client configuration ...
- name: client2
# ... client configuration ...
Metadata
The client metadata uniquely identifies the client in Coyote so you can reference it across your configurations. The available settings are listed below:
| Setting | Description |
|---|---|
name |
The name of the client (e.g. openai, gemini, etc.) |
auth |
Authentication method: oauth for OAuth, or omit to use api_key (see Authentication) |
models |
See the model settings documentation below |
patch |
See the client patch configuration documentation |
extra |
See the extra settings documentation below |
Be sure to also check provider-specific configurations for any extra fields that are added for authentication purposes.
Model Settings
The models array lists the available models from the model client. Each one has the following settings:
| Setting | Required | Model Type | Description |
|---|---|---|---|
name |
* | all |
The name of the model |
real_name |
all |
You can define model aliases via the name field. However, Coyote still needs to know the real name of the model so it can query it. For example: If you have name: gpt-alias, then you must also define real_name: gpt-oss:latest |
|
type |
* | all |
The type of model. Coyote supports only 3 types of models:
|
input_price |
all |
The cost in USD per 1M tokens for each input sequence; Coyote will keep track of usage costs if this is defined | |
output_price |
all |
The cost in USD per 1M tokens of the model output; Coyote will keep track of usage costs if this is defined | |
patch |
all |
See the model-specific patch configuration documentation | |
max_input_tokens |
all |
The maximum number of input tokens for the model | |
max_output_tokens |
chat |
The maximum number of output tokens for the model | |
require_max_tokens |
chat |
Whether to enforce the max_output_tokens constraint. |
|
supports_vision |
chat |
Indicates if the model supports multimodal queries that would require vision (i.e. image recognition) | |
supports_function_calling |
chat |
Indicates if the model supports function calling | |
reasoning_levels |
chat |
A list of named reasoning effort levels the model accepts (e.g. [low, medium, high]). When non-empty, .reasoning and .set reasoning_effort become available in the REPL for this model |
|
default_reasoning_effort |
chat |
The effort level applied by default when reasoning_levels is non-empty and no explicit override is set. Must be one of the values listed in reasoning_levels |
|
no_stream |
chat |
Enable or disable streaming API responses | |
no_system_message |
chat |
Controls whether the model supports system messages | |
system_prompt_prefix |
chat |
An additional prefix prompt to add to all system prompts to ensure consistent behavior across all interactions | |
max_tokens_per_chunk |
embedding |
The maximum chunk size supported by the embedding model | |
default_chunk_size |
embedding |
The default chunk size to use with the given model | |
max_batch_size |
embedding |
The maximum batch size that the given embedding model supports |
Authentication
Coyote clients support two authentication methods: API keys and OAuth. Each client entry in your configuration must use one or the other.
API Key Authentication
Most clients authenticate using an API key. Simply set the api_key field directly or inject it from the
Coyote vault:
clients:
- type: claude
api_key: '{{ANTHROPIC_API_KEY}}'
API keys can also be provided via environment variables named {CLIENT_NAME}_API_KEY (e.g. OPENAI_API_KEY,
GEMINI_API_KEY). See the environment variables documentation
for details.
OAuth Authentication
For providers that support OAuth, you can authenticate using your existing subscription instead of an API key. This uses the OAuth 2.0 PKCE flow.
Step 1: Configure the client
Add a client entry with auth: oauth and no api_key:
clients:
- type: claude
name: my-claude-oauth
auth: oauth
Step 2: Authenticate
Run the --authenticate flag with the client name:
coyote --authenticate my-claude-oauth
Or if you have only one OAuth-configured client, you can omit the name:
coyote --authenticate
Alternatively, you can use the REPL command .authenticate.
This opens your browser for the OAuth authorization flow. Depending on the provider, Coyote will either start a
temporary localhost server to capture the callback automatically (e.g. Gemini, OpenAI) or ask you to paste the authorization
code back into the terminal (e.g. Claude). Coyote stores the tokens in <coyote_cache_dir>/coyote/oauth and automatically refreshes
them when they expire.
Step 3: Use normally
Once authenticated, the client works like any other. Coyote uses the stored OAuth tokens automatically:
coyote -m my-claude-oauth:claude-sonnet-4-20250514 "Hello!"
Note: You can have multiple clients for the same provider. For example: you can have one with an API key and another with OAuth. Use the
namefield to distinguish them.
OpenAI OAuth Note
OpenAI OAuth requires a ChatGPT Plus or Pro subscription. It uses the shared Codex CLI OAuth app registered with OpenAI, so no additional API credentials or app registration are needed.
When authenticating, Coyote starts a localhost server on port 1455 to capture the OAuth callback automatically. This port is
fixed by OpenAI's app registration and cannot be changed. Make sure port 1455 is available when you run
--authenticate/.authenticate.
When auth: oauth is set and no api_base is configured, Coyote automatically routes all requests through
chatgpt.com/backend-api/codex/responses (the OpenAI Responses API), which is what ChatGPT Plus/Pro subscribers have
access to. If you later add an api_key or a custom api_base, Coyote will use the standard api.openai.com/v1
endpoint instead.
Example configuration:
clients:
- type: openai
name: openai-oauth
auth: oauth
Gemini OAuth Note
Coyote uses the following scopes for OAuth with Gemini:
- https://www.googleapis.com/auth/generative-language.peruserquota
- https://www.googleapis.com/auth/userinfo.email
- https://www.googleapis.com/auth/generative-language.retriever (Sensitive)
Since the generative-language.retriever scope is a sensitive scope, Google needs to verify Coyote, which requires full
branding (logo, official website, privacy policy, terms of service, etc.). The Coyote app is open-source and is designed
to be used as a simple CLI. As such, there's no terms of service or privacy policy associated with it, and thus Google
cannot verify Coyote.
So, when you kick off OAuth with Gemini, you may see a page similar to the following:

Simply click the Advanced link and click Go to Coyote (unsafe) to continue the OAuth flow.
xAI OAuth Note
xAI OAuth requires a SuperGrok or X Premium+ subscription. Coyote ships with the required OAuth endpoints and
client ID bundled in models.yaml, so you can enable OAuth by just adding auth: oauth to your xAI client entry. No
additional endpoints or app registration needed.
When authenticating, Coyote starts a localhost server on port 56121 to capture the OAuth callback automatically.
This port matches what xAI's OAuth app expects and cannot be changed. Make sure port 56121 is available when you run
--authenticate/.authenticate.
If the authorization succeeds but the token exchange returns 403, that indicates your account does not have a valid SuperGrok / X Premium+ subscription. This is not a Coyote configuration error.
Example configuration:
clients:
- type: openai-compatible
name: xai
api_base: https://api.x.ai/v1
auth: oauth
OAuth for OpenAI-Compatible Providers
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.
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 for details.
Which flow does my provider use?
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 |
Opens your browser, redirects to a https://... URL you'd copy from the address bar |
Authorization Code + PKCE (browser-paste) | Flow 1, Mode B |
| Displays a short user code you enter at a URL, with no browser callback | Device Authorization Grant (RFC 8628) | Flow 2 |
No user at all; just client_id + client_secret exchanged headlessly |
Client Credentials (RFC 6749 §4.4) | Flow 3 |
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 IDauthorize_url: where the browser goes to authorizetoken_url: where Coyote exchanges the code for tokens
Callback config (pick one):
redirect_port: <u16>: if the provider registered a specific port (e.g.56121for xAI). Coyote synthesizeshttp://127.0.0.1:<port>/callback.redirect_uri: http://127.0.0.1:<port>/<path>: full custom loopback URL (overridesredirect_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 thescope=query param.extra_authorize_params: { key: value }: extra query params on the authorize URL (e.g. xAI needsplan: generic).echo_pkce_in_token_exchange: true: some providers (like xAI) require re-sendingcode_challengeon token exchange.include_state_in_token_exchange: false: some providers rejectstateon the token endpoint (defaulttrue).token_request_format: form_url_encoded: some providers require form-encoded token bodies (default isform_url_encodedfor openai-compatible).client_secret: rare with PKCE, but supported for hybrid setups.
Example:
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:
- Full callback URL from the browser bar (e.g.
https://provider.example.com/callback?code=abc&state=xyz). Coyote parsescodeandstatefrom the query string. <code>#<state>fragment (Anthropic-style; supported for provider pages that display both concatenated).- 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:
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_codeclient_id: your OAuth app's client IDdevice_authorization_url: where Coyote requests the initialuser_codetoken_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 freshcode_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:
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_credentialsclient_idclient_secrettoken_url
Optional fields:
scopes: [scope1, scope2]token_request_formatextra_token_headers: { key: value }
Example:
clients:
- type: openai-compatible
name: acme-gateway
api_base: https://gateway.acme.com/v1
auth: oauth
oauth:
client_id: '{{ACME_CLIENT_ID}}'
client_secret: '{{ACME_CLIENT_SECRET}}'
token_url: https://auth.acme.com/oauth/token
scopes: [openai.chat]
flow: client_credentials
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).
Providers with Bundled OAuth Defaults
Coyote ships OAuth defaults for the following providers, so you only need auth: oauth in your config, with no fields
required:
- xAI: SuperGrok / X Premium+ subscribers. See xAI OAuth Note above for behavior details.
To bundle defaults for additional providers locally, edit your models.yaml (see Local Models Override)
and add an oauth: block under the provider entry using the field reference for the appropriate flow above.
Configuration Precedence
Coyote resolves OAuth configuration in two layers:
- Bundled defaults in
models.yamlunderprovider: <name>. - Inline overrides in your
config.yamlunderclients[i].oauth.
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.
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
OAuth tokens are stored on disk at ~/.cache/coyote/oauth/{client_name}_oauth_tokens.json, keyed by the client's name
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 rejects configuration files with duplicate name values across all
clients[] entries at load time. Fixing the underlying single-namespace layout is future work.
Sandbox and 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:
- 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.
Recommendation: authenticate on the host first and let the sandbox token copy carry you through.
Providers That Support OAuth
Built-in providers: (just set auth: oauth, no fields required):
- Claude: See Claude OAuth Note
- Gemini: See Gemini OAuth Note
- OpenAI: See OpenAI OAuth Note (requires ChatGPT Plus or Pro subscription)
- xAI: See 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 for the field reference
for each flow.
Extra Settings
Coyote also lets you customize some extra settings for interacting with APIs:
| Setting | Description | Default |
|---|---|---|
proxy |
Set a proxy to use | - |
connect_timeout |
Timeout in seconds for establishing a connection to the API | 10 |
read_timeout |
Stall detector: maximum seconds allowed between successive reads from the API before the request is aborted. Streaming-safe; the clock resets on every chunk received, so long streaming responses are fine as long as data keeps flowing. Set to 0 to disable entirely (e.g. for very slow local models or large non-streaming embedding calls). |
300 |
clients:
- type: openai
extra:
proxy: socks5://127.0.0.1:1080
connect_timeout: 10
read_timeout: 300
Note
Before
read_timeoutexisted, a connection that stalled after connecting (e.g. a streaming response that silently stopped mid-stream) would hang Coyote indefinitely. The default now aborts any request that goes 300 seconds without receiving a single byte.

