Use any provider in Goose
Goose loads each custom provider from its own JSON file, and the key itself lives in your OS keyring or in secrets.yaml, never in that file. yoink writes custom_providers/custom_<provider>.json, sets GOOSE_PROVIDER and GOOSE_MODEL, and stores the key only when the keyring is off. Start with yoink connect <name> --to goose --default <model>.
On this page
At a glance
| Fact | Value |
|---|---|
| Harness id | goose |
| Config yoink writes | ~/.config/goose/custom_providers/custom_<provider>.json and config.yaml |
| Format | JSON and YAML |
| Protocols, in order of preference | openai-chat, anthropic-messages |
| Default model | GOOSE_PROVIDER: custom_<provider> and GOOSE_MODEL in config.yaml |
| Path override | GOOSE_PATH_ROOT |
| Providers at once | many, side by side |
| Experimental | yes |
| Presets that reach it | 7 of 7: openai, kimi-code, moonshot, openrouter, deepseek, zai, ollama |
| Connect command | yoink connect <name> --to goose |
Generated from the yoink CLI source and docs/harnesses.md.
What yoink writes
The provider file follows the declarative format in Goose's custom provider docs. For the OpenRouter preset it looks like this:
{
"name": "custom_openrouter",
"engine": "openai",
"display_name": "OpenRouter",
"description": "OpenRouter via yoink",
"api_key_env": "CUSTOM_OPENROUTER_API_KEY",
"base_url": "https://openrouter.ai/api/v1/chat/completions",
"models": [{ "name": "z-ai/glm-4.7", "context_limit": 204800 }],
"supports_streaming": true
}Two details differ from the other harnesses:
- For an OpenAI Chat endpoint,
base_urlis the full/chat/completionsURL, because that is how Goose'sopenaiengine expects it. An Anthropic endpoint gets theanthropicengine and a base URL with no/v1. - Each model carries only
nameandcontext_limit; Goose has no output-limit or image field here.
--default writes two lines into config.yaml:
GOOSE_PROVIDER: custom_openrouter
GOOSE_MODEL: z-ai/glm-4.7Where the API key goes
This is why Goose is marked experimental. yoink checks GOOSE_DISABLE_KEYRING, in your environment first and then in config.yaml. The values true, 1, yes and on count as set.
| Keyring | What yoink does | What you do |
|---|---|---|
| disabled | merges CUSTOM_<PROVIDER>_API_KEY: <key> into secrets.yaml (0600) | nothing |
| enabled (the default) | writes no key anywhere, prints a notice | export the variable, or enter the key once with goose configure |
yoink connect openrouter --to goose --default z-ai/glm-4.7
export CUSTOM_OPENROUTER_API_KEY="<your key>"The variable name is the profile id in upper snake case between CUSTOM_ and _API_KEY. An id that does not fit lowercase-and-hyphens gets a short hash suffix so two ids cannot collide on one name. yoink never reads or writes the OS keyring.
Which providers work
Goose takes OpenAI Chat first, then Anthropic Messages. The five dual-endpoint presets land on their Chat endpoint, openai lands on Chat Completions at https://api.openai.com/v1/chat/completions, and ollama works too. A gateway that answered only the Responses probe cannot connect.
Paths
- Linux and macOS:
~/.config/goose, or$XDG_CONFIG_HOME/goose. - Windows:
%APPDATA%\Block\goose\config. GOOSE_PATH_ROOTset:$GOOSE_PATH_ROOT/config.
yoink counts Goose as installed when the goose binary is on your PATH, the config folder exists, or /Applications/Goose.app is present.
The provider file is yoink's
custom_<provider>.json is owned by yoink: it is overwritten on every connect with no backup and deleted on disconnect. Fields you add to it by hand, such as headers, are carried over on re-sync, but do not keep a hand-made file under the same name. config.yaml and secrets.yaml are shared files and get the usual one-time .yoink.bak copy.
yoink disconnect openrouter --from goose deletes the provider file, removes CUSTOM_OPENROUTER_API_KEY from secrets.yaml if it is there, and clears GOOSE_PROVIDER and GOOSE_MODEL only when they point at custom_openrouter.
FAQ
Goose says the provider has no API key. What did I miss?
Your keyring is on, so yoink did not store the key. Export CUSTOM_<PROVIDER>_API_KEY in the shell that starts Goose, or run goose configure and enter it once. Setting GOOSE_DISABLE_KEYRING and connecting again makes yoink write it to secrets.yaml instead.
Can yoink import a Goose provider I set up myself?
Only when its key sits in secrets.yaml under the name in its api_key_env. A key kept in the keyring is invisible to yoink, so that provider is skipped.
Related
- Harnesses
What yoink writes into pi, omp, opencode, codex, Claude Code, Qwen Code, Kilo, Droid, Crush, Continue, Goose, Zed and Claude Desktop: paths and keys.
- Security
Atomic owner-only writes, kept symlinks, one-time backups, seven managed Claude Code keys, a git-tracked config guard, and keys passed on stdin, never argv.
- Providers
Add an API key once with yoink, from a preset (OpenAI, Kimi, OpenRouter, DeepSeek, z.ai, Ollama) or any compatible URL it probes, and pick its models.
- Use any provider in pi
Connect any OpenAI- or Anthropic-compatible provider to pi. yoink writes ~/.pi/agent/models.json with model limits from models.dev and sets the default.
- Use any provider in omp
Connect a provider to omp. yoink writes ~/.omp/agent/models.yml (or models.yaml), carries over a legacy models.json and sets modelRoles.default in config.yml.
- Use any provider in opencode
Connect OpenRouter, DeepSeek, Ollama or any compatible API to opencode. yoink edits opencode.json or .jsonc in place, keeping your comments and formatting.