P5 - Ultimate OpenClaw CLIProxy Setup (Free Multi API)
Step 0) Objectives
• Run CLIProxyAPI locally on VPS: 127.0.0.1:8317
• Add multiple Codex/ChatGPT accounts (device login) → proxy automatically rotates accounts
• OpenClaw points the openai-codex provider to CLIProxyAPI (instead of chatgpt.com)
• CLIProxyAPI runs as a service (systemd user), automatically starts on reboot
Step 1) Install Go 1.22.5
cd /tmp
wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/go /usr/local/bin/go
sudo ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt
Check the version
which go
go version
Step 2) Clone CLIProxyAPI and build the binary
cd ~
rm -rf CLIProxyAPI
git clone https://github.com/router-for-me/CLIProxyAPI.git
cd ~/CLIProxyAPI
go build -o cli-proxy-api ./cmd/server
./cli-proxy-api -h
Step 3) Create the CLIProxyAPI configuration
Suggested way to create a proxy API key (create using Python):
python3 - <<'PY'
import secrets
print(secrets.token_urlsafe(32))
PY
Key: sMPIdzRqbikhhHZ04QD9DHhvwKo32SbinpkkvVNQ9Mo
nano ~/CLIProxyAPI/config.yaml
Content
host: "127.0.0.1"
port: 8317
tls:
enable: false
cert: ""
key: ""
remote-management:
allow-remote: false
secret-key: ""
disable-control-panel: true
panel-github-repository: "https://github.com/router-for-me/Cli-Proxy-API-Management-Center"
auth-dir: "/home/bao/.cli-proxy-api"
api-keys:
- "sMPIdzRqbikhhHZ04QD9DHhvwKo32SbinpkkvVNQ9Mo"
debug: true
usage-statistics-enabled: true
routing:
strategy: "round-robin"
Save file : Ctrl + O enter, Ctrl X exit
Note: must be in the correct format
Step 5) Add Codex/ChatGPT account Go to CLIProxyAPI (device login)
Enable Codex in Chatgpt settings for accounts
Setting => Security => Enable device code author...
Add account #1
cd ~/CLIProxyAPI
./cli-proxy-api -codex-device-login -config ./config.yaml -no-browser
It will print out:
• URL: https://auth.openai.com/codex/device
• CODE: XXXX-XXXXX
Open the URL on the local computer → enter code → login account #1.
Add account #2 (repeat)
Run the correct command again:
cd ~/CLIProxyAPI
./cli-proxy-api -codex-device-login -config ./config.yaml -no-browser
After success, the proxy will create the auth file at:
ls -la ~/.cli-proxy-api
# There will be 2 codex-*.json files
# Test model list
cd ~/CLIProxyAPI
nohup ./cli-proxy-api -config ./config.yaml > proxy.log 2>&1 &
curl -sS -H "Authorization: Bearer sMPIdzRqbikhhHZ04QD9DHhvwKo32SbinpkkvVNQ9Mo" http://127.0.0.1:8317/v1/models
Step 6) Point OpenClaw to CLIProxyAPI
OpenClaw is using the openai-codex provider. We edit the conf file
Use python to insert files
python3 - <<'PY'
import json
p = "/home/bao/.openclaw/openclaw.json"
obj = json.load(open(p))
obj.setdefault("models", {})
obj["models"]["mode"] = "merge"
providers = obj["models"].setdefault("providers", {})
providers["openai-codex"] = {
"baseUrl": "http://127.0.0.1:8317/v1",
"api": "openai-completions",
"headers": {
"Authorization": "Bearer sMPIdzRqbikhhHZ04QD9DHhvwKo32SbinpkkvVNQ9Mo"
},
"models": []
}
with open(p, "w") as f:
json.dump(obj, f, indent=2)
f.write("\n")
print("updated", p)
PY
Validate config
openclaw config validate --json
Should output {"valid":true,...}.
Restart the gateway to regenerate agents/main/agent/models.json
systemctl --user restart openclaw-gateway
Check
cat ~/.openclaw/agents/main/agent/models.json
Step 7) Restart OpenClaw Gateway
systemctl --user restart openclaw-gateway
Then test normal Telegram messaging.
Step 8) Run CLIProxyAPI as a service (run automatically after reboot)
Create service file:
sudo nano ~/.config/systemd/user/cliproxyapi.service
Content
[Unit]
Description=CLIProxyAPI (local OpenAI-compatible proxy)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/CLIProxyAPI
ExecStart=%h/CLIProxyAPI/cli-proxy-api -config %h/CLIProxyAPI/config.yaml
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=default.target
Enable + start:
systemctl --user daemon-reload
systemctl --user enable --now cliproxyapi.service
Log Follow:
journalctl --user -u cliproxyapi -f
Step 9) Option Note
routing.strategy: What is round-robin, and where is it located?
It's a strategy for selecting accounts/credentials when a proxy has multiple valid accounts for the same model.
• round-robin = alternating (A → B → A → B…)
• fill-first = use up the priority account first, then move on to the next account
It's located in the CLIProxyAPI configuration file:
nano ~/CLIProxyAPI/config.yaml
# Restart proxy
systemctl --user restart cliproxyapi
# Restart gateway
systemctl --user restart openclaw-gateway
Step 10) Enable usage quota checking
#0 Create secret key
Create a secret key (you can use py)
(python creates plaintext key)
python3 - <<'PY'
import secrets
print(secrets.token_urlsafe(32))
PY
Key: rDdnKJu4JYNMnlVp8WRr-7tGgXSBtzMib2c_KQqzs4I
nano ~/CLIProxyAPI/config.yaml
Then restart the service:
systemctl --user restart cliproxyapi
When the service starts, it will automatically hash the secret (bcrypt) and overwrite the secret key in the file.
Restart service:
systemctl --user restart cliproxyapi
#1 Create key management file (to avoid copying the key every time)
echo 'rDdnKJu4JYNMnlVp8WRr-7tGgXSBtzMib2c_KQqzs4I' > ~/.cli-proxy-api/.mgmt_key
chmod 600 ~/.cli-proxy-api/.mgmt_key
#2 Create main Python script:
Create folder
mkdir -p ~/bin
Run python command to write file
cat > ~/bin/cliproxy_stats.py <<'PY'
#!/usr/bin/env python3
import argparse
import json
from datetime import datetime, timezone
try:
from zoneinfo import ZoneInfo
except Exception:
ZoneInfo = None
from urllib.request import Request, urlopen
DEFAULT_URL = "http://127.0.0.1:8317/v0/management/usage"
DEFAULT_AUTHFILES_URL = "http://127.0.0.1:8317/v0/management/auth-files"
DEFAULT_KEY_FILE = "/home/bao/.cli-proxy-api/.mgmt_key"
def fetch_json(url: str, key: str) -> dict:
req = Request(url, headers={"X-Management-Key": key})
with urlopen(req, timeout=10) as resp:
body = resp.read().decode("utf-8", errors="replace")
return json.loads(body)
def iter_details(usage_json: dict):
apis = usage_json.get("usage", {}).get("apis", {})
for _api_key, api in apis.items():
for model, model_info in (api.get("models", {}) or {}).items():
for det in (model_info.get("details", []) or []):
yield model, det
def auth_status_map(auth_files_json: dict) -> dict:
out = {}
files = auth_files_json.get("files", []) or []
for f in files:
email = f.get("email") or f.get("account") or f.get("label")
if not email:
continue
out[email] = {
"status": f.get("status", ""),
"disabled": bool(f.get("disabled", False)),
}
return out
def print_table(rows, headers, limit=None):
if limit is not None:
rows = rows[-limit:]
widths = []
for i, h in enumerate(headers):
w = len(h)
for r in rows:
w = max(w, len(str(r[i])))
widths.append(w)
fmt = " ".join("{:<%d}" % w for w in widths)
print(fmt.format(*headers))
print(fmt.format(*["-" * w for w in widths]))
for r in rows:
print(fmt.format(*[str(x) for x in r]))
def cmd_recent(usage: dict, n: int):
rows = []
for model, det in iter_details(usage):
t = det.get("tokens", {}) or {}
rows.append([
det.get("timestamp", ""),
model,
det.get("source", ""),
det.get("latency_ms", ""),
t.get("input_tokens", 0) or 0,
t.get("output_tokens", 0) or 0,
t.get("cached_tokens", 0) or 0,
t.get("total_tokens", 0) or 0,
"Y" if det.get("failed") else "N",
])
rows.sort(key=lambda r: r[0])
headers = ["time", "model", "account", "lat_ms", "in_tok", "out_tok", "cached", "total", "fail"]
print_table(rows, headers, limit=n)
def cmd_today(usage: dict, authfiles: dict, tz_name: str = "Asia/Ho_Chi_Minh"):
tz = ZoneInfo(tz_name) if ZoneInfo else timezone.utc
today = datetime.now(tz).date().isoformat()
status = auth_status_map(authfiles)
agg = {}
for model, det in iter_details(usage):
ts = det.get("timestamp", "")
if not ts.startswith(today):
continue
acct = det.get("source", "unknown")
t = det.get("tokens", {}) or {}
a = agg.setdefault(acct, {"req": 0, "in": 0, "out": 0, "cached": 0, "total": 0, "fail": 0})
a["req"] += 1
a["in"] += int(t.get("input_tokens", 0) or 0)
a["out"] += int(t.get("output_tokens", 0) or 0)
a["cached"] += int(t.get("cached_tokens", 0) or 0)
a["total"] += int(t.get("total_tokens", 0) or 0)
a["fail"] += 1 if det.get("failed") else 0
print(f"date({tz_name}) = {today}")
accounts = sorted(status.keys() | agg.keys())
rows = []
for acct in accounts:
v = agg.get(acct, {"req": 0, "in": 0, "out": 0, "cached": 0, "total": 0, "fail": 0})
s = status.get(acct, {})
st = s.get("status", "")
dis = "Y" if s.get("disabled") else "N"
rows.append([acct, st, dis, v["req"], v["in"], v["out"], v["cached"], v["total"], v["fail"]])
headers = ["account", "status", "disabled", "req", "in_tok", "out_tok", "cached", "total", "fail"]
print_table(rows, headers)
def main():
ap = argparse.ArgumentParser(description="CLIProxyAPI management usage viewer")
ap.add_argument("--url", default=DEFAULT_URL)
ap.add_argument("--key-file", default=DEFAULT_KEY_FILE)
sub = ap.add_subparsers(dest="cmd", required=True)
p1 = sub.add_parser("recent", help="show last N requests")
p1.add_argument("-n", type=int, default=40)
p2 = sub.add_parser("today", help="show today's totals per account")
p2.add_argument("--tz", default="Asia/Ho_Chi_Minh")
args = ap.parse_args()
with open(args.key_file, "r", encoding="utf-8") as f:
key = f.read().strip()
usage = fetch_json(args.url, key)
authfiles = fetch_json(DEFAULT_AUTHFILES_URL, key)
if args.cmd == "recent":
cmd_recent(usage, args.n)
else:
cmd_today(usage, authfiles, tz_name=args.tz)
if __name__ == "__main__":
main()
PY
Permissions
chmod +x ~/bin/cliproxy_stats.py
#3 Create a short bash wrapper: ~/bin/cliproxy-stats
cat > ~/bin/cliproxy-stats << 'SH'
#!/usr/bin/env bash
set -euo pipefail
exec /home/bao/bin/cliproxy_stats.py "$@"
SH
Permissions
chmod +x ~/bin/cliproxy-stats
#4 Execute
• View today's total + which account is active/disabled:
~/bin/cliproxy-stats today
• View recent requests running on which account (to ensure it "fill-first" correctly):
~/bin/cliproxy-stats recent -n 30
In conclusion: If you want it to be faster, just copy and paste my steps and let the AI do it automatically.
See also related articles
P10 – Uninstall OpenClaw Windows Fast
P10 – Uninstall OpenClaw Windows Fast https://youtu.be/1ljEMzohiSY 🚀 AI Tutorial – P10: Uninstall OpenClaw on Windows (Clean Removal & Fix Issues) If you’re facing issues with OpenClaw or simply want to remove it completely, performing a proper Uninstall OpenClaw Windows process is essential. A partial uninstall may leave behind background...
Read MoreP9 – Build Local AI Telegram Bot Fast (Ollama Guide)
P9 – Build Local AI Telegram Bot Fast (Ollama Guide) https://youtu.be/YuiLJDLIVr0 🚀 AI Tutorial – P9: Create a Local AI Telegram Bot with Ollama in Minutes Building a Local AI Telegram Bot is one of the fastest ways to bring AI into real-world usage. By combining Ollama with a simple...
Read MoreP8 – Ultimate OpenClaw Local AI Setup
P8 – Ultimate OpenClaw Local AI Setup 🚀 AI Tutorial – P8: Complete Guide to Running Local AI with Ollama, Qwen & Open WebUI Running openclaw local AI is one of the most powerful ways to build a private, fast, and cost-efficient AI system. By combining Ollama, Qwen models, and...
Read More