Skip to content
~/smaje.net

CLIProxyAPI: sharing Claude Max across machines

I run CLIProxyAPI on a Raspberry Pi at home so Claude Code and Codex on every machine can use my accounts without logging in and out. Here are the account rules, the cache fix it needs and the setup over Tailscale.

13 min read

Updated

Tags: ai · agents · tools · self-hosting

On this page

Claude Code and Codex each sign in to one account at a time. If you have more than one Claude account, using the other one means logging out and back in, then doing the same on every other machine you code on. I run my sessions on a MacBook and a Mac mini, managed through T3 Code and Conductor, and the switching got tedious.

For a while I used a small command-line tool to switch accounts. Now CLIProxyAPI runs on a Raspberry Pi at home, and Claude Code and Codex on both Macs send their requests to it. It spreads conversations across my Claude accounts and keeps each conversation on one of them, so there's nothing to switch. Tailscale made connecting the machines the easy part: once the Pi is on my tailnet, each Mac reaches it at the same private address, at home or away, without opening a port on the router.

Before you start: the account rules

Follow this at your own risk

Anthropic and OpenAI haven't approved running subscriptions through a proxy like this, and either could suspend or ban an account that does. This is how I run my own setup, not a promise that yours is safe or allowed. If losing an account would really hurt, don't do it.

Putting your subscriptions behind a proxy carries some risk of losing an account. My view is that bans almost always come from what people do with their accounts, not from having more than one. Thariq Shihipar from the Claude Code team said in February that "it's not against terms of service to have multiple MAX accounts, though sometimes people are using multiple MAX accounts to do things like resell tokens which of course is against terms of service".

These are the rules I keep to:

  • Don't share your accounts. Anthropic's Consumer Terms say "You may not share your Account login information, Anthropic API key, or Account credentials with anyone else". The hub counts: nobody else gets its access key.
  • Only use the accounts in real Claude Code and Codex sessions. Tools that run those CLIs for you, such as T3 Code, Conductor or Orca, are fine. Don't point SDK scripts, chat apps or other agent harnesses at the hub.
  • Keep to one Codex account. Several Claude accounts should be fine, but stacking ChatGPT subscriptions for more Codex isn't. OpenAI's Terms of Use ban "circumventing any rate limits or restrictions", and OpenAI now limits how many paid accounts one device or card can use.
  • Be careful what you do with them. Reselling tokens is a sure way to get banned, and so is trying to reverse engineer or distil Anthropic's or OpenAI's models.

Anthropic's Claude Code docs describe subscription sign-in as meant for Claude Code and Anthropic's own apps, and here CLIProxyAPI holds the login and forwards the requests. I'm comfortable with that because I'm the only user and Claude Code and Codex are the only clients.

Keep the traffic on your home connection

The hub's requests should leave from your own home broadband, the same address your normal use comes from. Don't run it on a VPS, and don't send it through a commercial VPN or someone else's exit node: data-centre addresses and constantly changing IPs can look like a shared or resold account. With the hub at home, a laptop in a hotel still sends its requests over Tailscale to the Pi and out through your home connection.

If your router sends some sites through a VPN, make sure Anthropic and OpenAI aren't among them. On the hub, this should name your broadband provider:

curl -s https://ipinfo.io/org

Don't lose the prompt cache

Claude Code caches each conversation's context, so later turns cost little of your limit. Writing the cache again is what eats into it, and a proxy can cause that in two ways.

First, the cache drops to five minutes. Signed in to a subscription directly, Claude Code asks for a one-hour cache, but through a custom base URL it falls back to five minutes, which I saw too. Any pause longer than that, such as reading a diff, then writes the whole conversation again. Setting promptCacheTtl to "1h" brings the hour back. It needs Claude Code 2.1.242 or later.

Second, each account has its own cache. Caches are kept per organisation, and every subscription is its own organisation, so a conversation that moves to another account starts its cache again. CLIProxyAPI's session affinity keeps a conversation on one account until that account runs out.

Both settings are in the steps below, and the checks at the end show whether they're working.

Choose where the hub runs

A Raspberry Pi suits this. CLIProxyAPI is a single Go binary that relays requests, so a Pi 5 has plenty of headroom, and it's always on and already on your home connection. Any always-on Linux machine at home can follow the same steps with the release for its processor.

If you only code on one Mac, you can run the hub there with Homebrew and skip Tailscale. It only answers while the Mac is awake, other machines can't use it, and its requests leave from wherever the Mac is.

Where will the hub run?

You can follow the steps below yourself or hand them to an agent. I had Claude Code do most of mine over SSH. The whole setup is also in one Markdown file, setup-prompt.md, written as instructions for an agent: it asks which setup you want, keeps the keys out of its replies and stops for every sign-in you need to approve in a browser. Paste this into Claude Code or Codex on the machine you code on:

Raspberry Pi

Download https://smaje.net/blog/cliproxyapi-raspberry-pi-tailscale/setup-prompt.md
with curl, read all of it and follow it. Use the Raspberry Pi setup.
The Pi is at [email protected].

This Mac

Download https://smaje.net/blog/cliproxyapi-raspberry-pi-tailscale/setup-prompt.md
with curl, read all of it and follow it. Use the This Mac setup.

Downloading it with curl gives the agent the exact file. A web-fetch tool may hand it a summary instead, and summaries drop commands.

Install CLIProxyAPI

Raspberry Pi

These steps assume 64-bit Raspberry Pi OS (uname -m prints aarch64) with SSH working. Run the commands on the Pi unless a step says otherwise.

Set V to the latest version on the releases page. The block downloads the linux_aarch64 release, checks it against the published checksums and stops at the first error, so a bad download installs nothing:

(
set -eu
V=8.0.51
cd "$(mktemp -d)"
B=https://github.com/router-for-me/CLIProxyAPI/releases/download/v$V
curl -fsSLO "$B/CLIProxyAPI_${V}_linux_aarch64.tar.gz"
curl -fsSLO "$B/checksums.txt"
grep "CLIProxyAPI_${V}_linux_aarch64.tar.gz$" checksums.txt | sha256sum -c -
tar -xzf "CLIProxyAPI_${V}_linux_aarch64.tar.gz"
mkdir -p ~/cliproxyapi
install -m 755 cli-proxy-api ~/cliproxyapi/
cp config.example.yaml ~/cliproxyapi/
install -m 600 config.example.yaml ~/cliproxyapi/config.yaml
install -d -m 700 ~/.cli-proxy-api
)

The last line makes a private folder for your account logins. Keep config.example.yaml: comparing it with the next release's copy shows which settings changed.

This Mac

brew install cliproxyapi
chmod 600 "$(brew --prefix)/etc/cliproxyapi.conf"
install -d -m 700 ~/.cli-proxy-api

Homebrew's config file starts readable by other users, so the chmod locks it down before your access key goes in. The last line makes a private folder for your account logins.

Configure it

Generate two keys and save both in your password manager. Claude Code and Codex send the access key with every request, and the management key opens the hub's web panel:

openssl rand -hex 32   # access key
openssl rand -hex 24   # management key

Raspberry Pi

Edit ~/cliproxyapi/config.yaml and change these settings, leaving the rest as they are:

server:
  host: "127.0.0.1"
  port: 8317
  trusted-proxies: ["127.0.0.1", "::1"]
 
management:
  allow-remote: true
  secret-key: "<management key>"
  disable-auto-update-panel: true
 
access:
  api-keys:
    - "<access key>"
 
routing:
  session-affinity: true
  session-affinity-rebind-on: "quota"
 
requests:
  proxy-url: "direct"
 
observability:
  usage:
    usage-statistics-enabled: true

The hub listens only on the Pi itself. Tailscale will forward requests to it, which is why it trusts proxies on localhost and allows remote management.

This Mac

Edit $(brew --prefix)/etc/cliproxyapi.conf and change these settings, leaving the rest as they are:

server:
  host: "127.0.0.1"
  port: 8317
 
management:
  allow-remote: false
  secret-key: "<management key>"
  disable-auto-update-panel: true
 
access:
  api-keys:
    - "<access key>"
 
routing:
  session-affinity: true
  session-affinity-rebind-on: "quota"
 
requests:
  proxy-url: "direct"
 
observability:
  usage:
    usage-statistics-enabled: true

Binding to 127.0.0.1 means only programs on this Mac can reach the hub.

With session-affinity on, a conversation stays on the account that took it. Setting session-affinity-rebind-on to quota means it only moves when that account runs out, its login fails or it doesn't support the model. New conversations alternate between accounts. proxy-url: "direct" stops the hub picking up proxy settings from its environment. It doesn't get around a VPN on the machine or router, which is what the ipinfo.io check is for. The hub replaces the management key in the file with a hash when it first starts.

Keep it running

Raspberry Pi

Run it as a systemd user service. Create the folder with mkdir -p ~/.config/systemd/user, then save this as ~/.config/systemd/user/cliproxyapi.service:

[Unit]
Description=CLIProxyAPI
After=network-online.target
Wants=network-online.target
 
[Service]
WorkingDirectory=%h/cliproxyapi
ExecStart=%h/cliproxyapi/cli-proxy-api -config %h/cliproxyapi/config.yaml
Restart=always
RestartSec=5
CPUQuota=150%
Nice=5
NoNewPrivileges=yes
UMask=0077
 
[Install]
WantedBy=default.target

Let your user's services run without a login session, then start it:

sudo loginctl enable-linger "$USER"
systemctl --user daemon-reload
systemctl --user enable --now cliproxyapi
journalctl --user -u cliproxyapi -n 20 --no-pager

My Pi also runs Home Assistant and the network's DNS, and the CPU cap stops the hub from starving them. Don't rely on MemoryMax here: Raspberry Pi kernels boot with cgroup_disable=memory (check /proc/cmdline), so systemd accepts the setting but doesn't enforce it.

This Mac

brew services start cliproxyapi

That starts it now and at every login.

Sign in to your accounts

Run umask 077 first, in the same terminal, so new login files start out private. Sign in one account at a time. For each Claude account, the browser that approves the login has to be signed in to claude.ai as that account; a separate browser profile for each account saves logging out and back in.

Raspberry Pi

The Claude login redirects your browser to localhost:54545, but the hub is waiting on the Pi, not your laptop. On the laptop whose browser you'll use, forward that port and leave it running:

ssh -N -L 54545:127.0.0.1:54545 [email protected]

Then, in another terminal on the Pi, start the login. Open the URL it prints, check which account claude.ai is signed in as and approve. Ignore the ssh command it suggests; the tunnel replaces it. Repeat for each Claude account:

cd ~/cliproxyapi
./cli-proxy-api -config config.yaml -claude-login -no-browser

Codex uses a device code, so it needs no tunnel. If ChatGPT says device-code login is turned off, turn it on in your account's security settings:

./cli-proxy-api -config config.yaml -codex-device-login -no-browser

Claude's login files can still come out readable by others, so tighten them afterwards:

chmod 600 ~/.cli-proxy-api/*.json

This Mac

cliproxyapi -claude-login

It opens the authorisation page and catches the callback itself. Repeat for each Claude account, then sign in to Codex with cliproxyapi -codex-device-login. If ChatGPT says device-code login is turned off, turn it on in your account's security settings. Afterwards, run chmod 600 ~/.cli-proxy-api/*.json so only you can read the login files.

The running hub picks up new logins without a restart. Those files are your accounts, so treat them like passwords and keep them out of unencrypted backups.

Reach it from your other machines

Raspberry Pi

This is the part Tailscale makes easy. Your tailnet is the private network of devices signed in to your Tailscale account. Install Tailscale on the Pi and sign in with the same account as your Macs:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

Then publish the hub to your tailnet over HTTPS:

sudo tailscale serve --bg --https=8317 http://127.0.0.1:8317

If Tailscale asks you to turn on HTTPS certificates, follow its link. The hub is now at https://<pi>.<tailnet>.ts.net:8317, reachable only from devices on your tailnet. If other people share your tailnet, use its access rules to limit this port to your own machines.

In the Tailscale admin console, turn off key expiry for the Pi so it doesn't drop off the tailnet after six months.

This Mac

Nothing to do here. Claude Code and Codex reach the hub at http://127.0.0.1:8317.

Point Claude Code and Codex at it

Raspberry Pi

Do this on each machine you code on, not on the Pi.

In ~/.claude/settings.json, add the hub's address and your access key inside env, and promptCacheTtl at the top level, keeping any settings you already have:

Raspberry Pi

{
  "env": {
    "ANTHROPIC_BASE_URL": "https://<pi>.<tailnet>.ts.net:8317",
    "ANTHROPIC_AUTH_TOKEN": "<access key>"
  },
  "promptCacheTtl": "1h"
}

This Mac

{
  "env": {
    "ANTHROPIC_BASE_URL": "http://127.0.0.1:8317",
    "ANTHROPIC_AUTH_TOKEN": "<access key>"
  },
  "promptCacheTtl": "1h"
}

promptCacheTtl is the one-hour cache fix from earlier. Subagents still get five minutes unless you also set subagentPromptCacheTtl.

For Codex, add a provider to ~/.codex/config.toml. model_provider has to sit above any [table] in the file:

Raspberry Pi

model_provider = "cliproxy"
 
[model_providers.cliproxy]
name = "CLIProxyAPI"
base_url = "https://<pi>.<tailnet>.ts.net:8317/v1"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = true
experimental_bearer_token = "<access key>"

This Mac

model_provider = "cliproxy"
 
[model_providers.cliproxy]
name = "CLIProxyAPI"
base_url = "http://127.0.0.1:8317/v1"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = true
experimental_bearer_token = "<access key>"

Apps that run these CLIs for you pick up the same settings, as long as they use the installed CLI rather than their own sign-in. Leave your direct logins signed in for when the hub is down.

Check it works

Run these on a machine you've just pointed at the hub. Set its address:

Raspberry Pi

H=https://<pi>.<tailnet>.ts.net:8317

This Mac

H=http://127.0.0.1:8317

Then ask it for its models. The key is read without echoing it or saving it in your shell history, and a 200 means the address and key work:

printf 'Access key: ' && read -rs K && echo
printf 'Authorization: Bearer %s\n' "$K" | curl -s -o /dev/null -w '%{http_code}\n' -H @- "$H/v1/models"
unset K

Next, the cache. This uses jq (brew install jq). The first command starts a conversation and shows which cache it wrote to; the second resumes it and shows how much it read back:

claude -p "Reply with just ok" --output-format json > first.json
jq '.usage.cache_creation' first.json
claude -p --resume "$(jq -r .session_id first.json)" "Reply with just ok again" --output-format json | jq '.usage.cache_read_input_tokens'

The first should show tokens under ephemeral_1h_input_tokens and none under ephemeral_5m_input_tokens, and the second a large number. Mine wrote 34,222 tokens to the one-hour cache and read 16,300 back. If a machine keeps writing large amounts turn after turn, check its promptCacheTtl.

Send Codex a test with codex exec --skip-git-repo-check "Reply with just ok". Then open $H/management.html in a browser and enter the management key. It lists each account with its recent requests, failures and the usage the hub last saw, so you can confirm both tools went through the account you expected.

Living with it

When the hub is unreachable, every session pointed at it stops, so keep a way back. Remove ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN from Claude Code's settings and the model_provider line from Codex's config, and new sessions use your direct logins. I have a small script on each Mac for this: hub-off removes those lines and hub-on puts them back.

Raspberry Pi

Take care updating Tailscale on a machine that uses the hub. When I updated it on my MacBook, Tailscale stayed offline for more than three minutes, probably while macOS waited for me to allow the new network extension. The agent doing the update was itself running through the hub, so it went quiet too. Switch to direct logins before updating Tailscale.

Watch the SD card. Even with request logging off, the hub spools large request bodies to ~/.cli-proxy-api/logs while requests run, and a coding agent's requests are large. On busy days that came to several gigabytes of writes, and it briefly left whole prompts on the card. I mounted that folder in RAM instead. This version also keeps it out of swap (noswap needs Linux 6.4 or later, which current Raspberry Pi OS has) and only restarts the hub if the mount worked:

(
set -eu
systemctl --user stop cliproxyapi
mkdir -p ~/.cli-proxy-api/logs
printf 'tmpfs %s tmpfs rw,noswap,nosuid,nodev,noexec,noatime,size=512M,uid=%s,gid=%s,mode=0700 0 0\n' \
  "$HOME/.cli-proxy-api/logs" "$(id -u)" "$(id -g)" | sudo tee -a /etc/fstab
sudo systemctl daemon-reload
sudo mount ~/.cli-proxy-api/logs
findmnt --mountpoint ~/.cli-proxy-api/logs --types tmpfs
systemctl --user start cliproxyapi
)

Run it once, since it adds a line to /etc/fstab. The mount hides anything already in the folder without deleting it, so clear out old files first if that matters to you.

To upgrade, download and verify the new release as before, compare its config.example.yaml with your saved copy, keep the old binary as cli-proxy-api.previous, install the new one and restart the service. Then rerun the checks.

This Mac

brew upgrade cliproxyapi updates it. Restart it with brew services restart cliproxyapi, then rerun the checks.

When a Claude account reaches its limit, the hub moves that account's conversations to another one that still has allowance, and each moved conversation builds a new cache there. The hub's panel is where to see what's left across all of them.

Share this post

← All posts