Use Muse Code (muse) with any OpenAI-compatible API without creating a Meta account or using muse login.
Muse Code's meta provider defaults to https://api.meta.ai/v1, but you can point it at your own gateway. The meta label is just a provider name — storing a key with muse auth set --provider meta does not require a Meta OAuth login. muse login is optional and only for Meta's hosted auth; API-key auth is enough.
⚠️ Muse Code 1.0.1 changed things. The old per-invocation--base-urlflag no longer sends your auth header (the bearer is "withheld" unless the base URL is pinned in settings). If you're on 1.0.1+, use theendpoint_transportsettings method below. The old--base-urlwrapper only works on 0.2.x — see the legacy section.
- Route Muse Spark (
muse-spark-1.2, etc.) through a gateway/proxy (e.g., self-hosted, OpenCode, Helicone, Cloudflare AI Gateway, LiteLLM) - Avoid
muse login/ browser OAuth entirely - Keep the same CLI (
muse exec,museTUI) while swapping the backend
curl -fsSL https://dev.meta.ai/install.sh | sh
muse --version # Muse Code 1.0.1 (or newer)This does NOT create a Meta account. It just saves a key under the
metaprovider name.
# paste your gateway/proxy API key on stdin (never as an argv)
echo -n "YOUR_GATEWAY_API_KEY" | muse auth set --provider meta --api-key-stdin
# verify
cat ~/.config/muse/auth.json # { "schema_version": 1, "providers": { "meta": { "api_key": "..." } } }
# alternative: env var (takes priority over file)
export META_API_KEY="YOUR_GATEWAY_API_KEY"You never need to run muse login.
Muse Code 1.0.1 hardens endpoint overrides: a bare --base-url flag is treated as "off the sanctioned front door" and your Authorization header will not be sent to it. The sanctioned opt-in is a settings pin — edit ~/.config/muse/settings.json:
{
"schema_version": 1,
"model": "muse-spark-1.2",
"provider": "meta",
"endpoint_transport": {
"base_url": "http://127.0.0.1:8914/v1",
"auth": "bearer"
}
}base_url— your gateway / proxy root (same value you used to pass to--base-url)auth: "bearer"— required. This pin is what makes Muse attach your stored API key to requests against that base URL. Without it the key is withheld.
That's it — no wrapper needed. Every command (muse, muse exec, subagents, workflows) now goes through your gateway:
muse exec "say hi in 5 words"
muse --image ./photo.jpg exec "describe this image"Verify it's actually hitting your gateway:
muse exec "Reply with exactly: GATEWAY_OK"
# and/or watch requests land in your proxy logIf you see an auth error mentioning "Meta bearer withheld: base_url is off the sanctioned front door", the settings pin is missing or malformed — re-check endpoint_transport.auth is exactly "bearer".
Multiple gateways? Swap config dirs with XDG_CONFIG_HOME
Muse Code honors XDG_CONFIG_HOME / XDG_DATA_HOME. Keep one settings dir per gateway and switch by env:
# ~/.config-muse-a/muse/settings.json -> gateway A
# ~/.config-muse-b/muse/settings.json -> gateway B
XDG_CONFIG_HOME=~/.config-muse-a muse exec "hi via gateway A"
XDG_CONFIG_HOME=~/.config-muse-b muse exec "hi via gateway B"If your custom provider is a local proxy (e.g., translating APIs), run it as a LaunchAgent:
<!-- ~/Library/LaunchAgents/com.muse-proxy.plist -->
<dict>
<key>Label</key><string>com.muse-proxy</string>
<key>ProgramArguments</key><array><string>/usr/bin/python3</string><string>/Users/you/.local/bin/my-muse-proxy.py</string></array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
</dict>launchctl load ~/Library/LaunchAgents/com.muse-proxy.plist
curl -s http://127.0.0.1:8914/v1/models | headForward Muse's POST /v1/responses to your gateway, with optional fallback to /v1/chat/completions for gateways that don't speak the Responses API. Rewrite model names if needed. See examples/proxy.py — run it with:
GATEWAY_BASE_URL="https://your-gateway.example.com/v1" \
GATEWAY_API_KEY="YOUR_GATEWAY_API_KEY" \
python3 examples/proxy.pyEnv vars: GATEWAY_BASE_URL (upstream), GATEWAY_API_KEY (bearer key), PROXY_PORT (default 8914), PROXY_UPSTREAM_PATH (default /responses; set /chat/completions for chat-style gateways). PROXY_USER_AGENT (default Muse/1.0.1 — some CDNs like Cloudflare in front of opencode.ai block python-urllib's default UA with error 1010, so the proxy mimics the Muse client).
Deprecated since 1.0.1 — kept for completeness. On 1.0.1+ this wiring still connects, but Muse withholds your auth header (401 from your gateway). Use the settings method instead.
BIN="$HOME/.local/bin/muse-bin-0.2.1-R1215.1" # or wherever `muse` is installed
BASE="https://your-gateway.example.com/v1"
cat > ~/.local/bin/muse-wrapper << SH
#!/bin/bash
BIN="$BIN"
BASE="$BASE"
case "\$1" in
exec|resume|config|export|trace|skills|sandbox|session-message|auth|login|logout|init)
exec "\$BIN" "\$1" --base-url "\$BASE" "\${@:2}"
;;
*)
exec "\$BIN" --base-url "\$BASE" "\$@"
;;
esac
SH
chmod +x ~/.local/bin/muse-wrapper
ln -sf ~/.local/bin/muse-wrapper ~/.local/bin/museNote on
execordering:muse execexpects options afterexec(muse exec --base-url ...), not before. The wrapper above handles this.
If you use Muse's native subagents (subagent_spawn etc.) with a custom gateway, two things must be set or the tools stay hidden:
- Enable delegation in
~/.config/muse/settings.json:"run": { "subagent_delegation_mode": "auto" }
- Run inside a trusted workspace —
git inityour project and either trust it when prompted, or pass--trust-workspacetomuse exec. Untrusted workspaces hide the delegation tools entirely.
Both settings coexist fine with endpoint_transport.
OpenCode also supports custom OpenAI-compatible providers via opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"model": "my-gateway/muse-spark-1.2",
"provider": {
"my-gateway": {
"npm": "@ai-sdk/openai-compatible",
"name": "My Gateway",
"options": { "baseURL": "https://your-gateway.example.com/v1" },
"models": {
"muse-spark-1.2": { "name": "Muse Spark 1.2", "limit": { "context": 1048576, "output": 131072 } }
}
}
}
}opencode run -m my-gateway/muse-spark-1.2 "fix tests"Do I need a Meta account? No. muse auth set + the settings pin is enough. muse login is only for Meta-hosted OAuth.
Does provider: meta mean I'm calling Meta directly? No — it's just the provider key. With endpoint_transport.base_url it calls your gateway instead. Your Authorization: Bearer ... header is your gateway key.
I get "Meta bearer withheld: base_url is off the sanctioned front door." You passed --base-url (or set the base URL somewhere) without the settings pin. Add endpoint_transport with "auth": "bearer" to ~/.config/muse/settings.json — see step 3.
What about images/PDFs? muse-spark-1.2 supports image, pdf, video as input modalities (1M context). Pass --image (repeatable) to muse exec.
Can I use this with OpenCode Go / other gateways? Yes — any OpenAI-compatible /v1/responses endpoint works as GATEWAY_BASE_URL. For /v1/chat/completions-only gateways, set PROXY_UPSTREAM_PATH=/chat/completions in the proxy example.
My gateway only speaks chat completions, not responses. Run the proxy example locally and set PROXY_UPSTREAM_PATH=/chat/completions; Muse talks Responses to the proxy, the proxy forwards chat-style.
My gateway is behind Cloudflare and returns error 1010 / browser_signature_banned. The proxy mimics the Muse client's User-Agent by default (PROXY_USER_AGENT, default Muse/1.0.1) because Cloudflare blocks python-urllib's default signature. If your gateway blocks that too, set PROXY_USER_AGENT to whatever your CDN allowlists.
- Never paste API keys as CLI args (they appear in
ps). Use--api-key-stdinor env vars. - Don't commit
~/.config/muse/auth.jsonoropencodeauth files. - Gateways should use
Bearertokens over HTTPS only.
MIT — PRs welcome. Not affiliated with Meta.