cli2api

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 startup

On 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-token to get your muk- 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- automatically

Which 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 mulerun CLI 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-pager

If 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 login and let cli2api handle it (Option A in "Getting your muk- API key").
  • Or set MULERUN_TOKEN to your muk- 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 breakdown

Top 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*, midjourney cost 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; save gpt-image-2 / midjourney for 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.

On this page