Troubleshooting
Common errors and FAQ.
Getting your muk- API key
cli2api authenticates to MuleRun's gateway with a muk- API key — a
stable, long-lived per-account key. Three ways to get one, ordered from
laziest to most explicit:
Option A — Zero-config (just mulerun login)
Since v0.2.0, cli2api auto-handles OAuth. Just log in once:
mulerun login # browser OAuth, writes ~/.config/mulerun/oauth_cache.json
cli2api # reads the cache, exchanges JWT → muk- on startupOn every startup cli2api will:
- read the cached JWT
- refresh it (and write back the new tokens) if it's expired
- POST it to
mulerun.com/api/platform/users/cli-tokento get yourmuk-key - use that for all gateway calls
You'll see one of exchanged OAuth JWT for muk- API key or
refreshed expired OAuth tokens in the startup log when this fires.
Option B — Capture once, paste forever
If you'd rather not depend on the cache (e.g. for Docker/CI where you only
have one secret slot), capture the muk- key once with the bundled script:
export MULERUN_TOKEN=$(curl -fsSL \
https://raw.githubusercontent.com/yeagoo/MuleRunCLI2API/master/scripts/get-muk-key.sh \
| bash)Prerequisites: mulerun CLI installed (npm i -g @mulerunai/cli) and
logged in. The key is stable across runs so store it in your .env /
systemd unit and forget about it.
Option C — Pass the JWT directly
If you have a JWT but no muk- key, just set it — cli2api will exchange it
on startup (same flow as Option A):
export MULERUN_TOKEN=eyJhbGci... # JWT
cli2api # exchanges to muk- automaticallyWhich to pick
- Long-lived host / systemd → Option A (one-time
mulerun login, no secrets to rotate; JWT refresh is automatic). - Docker / Zeabur / CI → Option B (single static secret, no
mulerunCLI in the image, no writeback needed). - Bridging from a third-party OAuth flow → Option C.
Switching MuleRun accounts (or rotating the key)
The muk- key is tied to one account. To point cli2api at a different account
(e.g. one with credits) you re-login, capture the new key, and update the env.
# 1. Log into the account you want (browser OAuth).
mulerun logout
mulerun login
mulerun user balance # confirm balance > 0 on the new account
# 2. Capture the new account's muk- key with the one-liner.
NEWMUK=$(curl -fsSL https://raw.githubusercontent.com/yeagoo/MuleRunCLI2API/master/scripts/get-muk-key.sh | bash)
# 3. Update MULERUN_TOKEN in place (keeps your other config), then restart.
sudo sed -i "s|^MULERUN_TOKEN=.*|MULERUN_TOKEN=$NEWMUK|" /etc/cli2api/env
sudo systemctl restart cli2api
# 4. Verify — token_source should be env, no JWT warning, and a call succeeds.
journalctl -u cli2api -n2 --no-pagerIf you already have the new account's muk- key, skip steps 1–2 and just run
step 3 with it. Running as a plain process instead of systemd? Replace the env
edit with export MULERUN_TOKEN=muk-... and restart the process.
Common errors
no mulerun credentials found
Set export MULERUN_TOKEN=muk-... (see above). The startup log's
token_source field shows which file/env it read.
502 upstream HTTP 401 / Invalid API Key format on any endpoint
Means the value you set as MULERUN_TOKEN reached the gateway as a non-muk-
string — either auto-exchange was disabled by an unexpected token shape, or
the exchange itself failed at startup (look for JWT → muk- exchange failed
in the log). Fix paths:
- v0.2.0+: just
mulerun loginand let cli2api handle it (Option A in "Getting yourmuk-API key"). - Or set
MULERUN_TOKENto yourmuk-key directly (Option B).
502 upstream HTTP 402 — out of MuleRun credits
402 Payment Required means your MuleRun account balance is exhausted, not
a rate limit (that would be 429) and not a cli2api bug — the gateway just
relays the upstream's 402. Check your balance:
mulerun user balance # data.balance — if ~0, you're out
mulerun user wallets # daily / monthly / longterm credits breakdownTop up at mulerun.com (the account you logged in with), then retry — no restart needed. Symptoms when nearly drained:
- Premium models fail first (
gpt-image-2,nano-banana*,midjourneycost more per image) while the cheapest (wan2.6-t2i) still works for a couple more calls, then it 402s too. - To stretch a small balance, prefer
wan2.6-t2i; savegpt-image-2/midjourneyfor images that truly need them.
/v1/chat/completions returns Model 'gpt-5' is not supported
Auth passed, but the studio-plane muk- key doesn't expose chat models. Text
endpoints (/v1/chat/completions, /v1/messages, /v1/responses) need a
separate LLM-gateway key from MuleRun; the muk- studio key covers
image/video/audio only.
vendor_error: code 3005 / 3006 / ...
A real upstream error (the MiniMax/Seedance/Wan service itself failed). cli2api is working — it surfaces the structured upstream error to the client. Retry or switch models.
404 unknown image model: dall-e-3
cli2api does not alias OpenAI names to MuleRun. Use real names
(gpt-image-2 / wan2.6-t2i / midjourney). Run
curl localhost:51222/v1/models | jq '.data[].id' for the full list.
Video / music job stuck at queued
Check CLI2API_JOB_RETENTION and CLI2API_JOB_HARD_CAP_MULT: too-short
retention + small multiplier means the reaper deletes the job before polling
finishes. Defaults (7d / 3×) are fine; only shrink them for short-lived tests.
Job ID gone after restart
The in-memory store is lost on restart. Use CLI2API_JOBSTORE_DSN=file:... or
a remote libsql to persist.
request body too large (400)
Per-request cap is 64 MB. This is chi's RequestSize middleware, wrapped as an
OpenAI-style 400.
SSE streaming shows no increments
Your reverse proxy didn't disable proxy_buffering — nginx / Caddy each need it
configured. Also confirm your client uses stream=True, not json().
FAQ
Why a proxy instead of calling MuleRun directly?
/v1/chat/completions and /v1/messages are already compatible — call them
directly if you want. But image/video/audio use a /vendors/{vendor}/... async
job shape unlike OpenAI's. cli2api hides that so existing SDK code runs unchanged.
Does it cache results?
No. Every call is a fresh MuleRun task. Cache at the application or CDN layer.
Can it run in Lambda / Cloud Functions?
Text and image sync endpoints, yes. Video/music are explicitly async — the client polls, instances can restart freely. Pair with a libsql persistent store.
Multi-tenancy?
No built-in users/quota. CLI2API_API_KEYS is a flat allow-list. Put quota
behind an API gateway.
How well-reviewed is this code?
The project went through 6 rounds of reviewer/reviewee iteration (codex ×3 + cc ×2 + live e2e), fixing 26 real bugs — credential leaks, async jobs stuck forever, the reaper deleting live jobs, upstream schema nesting. 50+ unit tests, each bug carries a regression. See DEVELOPMENT.md.