Skip to content

About

Self-hosted gateway for Claude Code: give each of your own machines its own key, model access, limits and usage tracking on your Claude subscription.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DeviceGate

CI License: MIT Website

A self-hosted gateway that gives each of your machines its own key, model access, limits and usage history for Claude Code, all on your one Claude subscription.

Your Claude login stays on a single machine on your LAN. Every other PC connects through DeviceGate with its own revocable device token. You decide which models each one may use and how much, and you can see exactly where your subscription's capacity went.

your PCs ──(device token)──► DeviceGate :3000 ──► TeamClaude :3456 (holds your Claude login) ──► Anthropic

DeviceGate demo: status page, adding a device, per-device limits and the usage explorer

Important

DeviceGate is for your own machines on your own subscription. Don't use it to share a Claude plan with other people. See Terms of use.

DeviceGate is an independent project. It isn't affiliated with or endorsed by Anthropic. "Claude" and "Claude Code" are trademarks of Anthropic, PBC.

Why

Once Claude Code runs on more than one of your machines (a laptop, a desktop, an always-on box running agents), they all draw from one shared 5-hour and weekly allowance, and you can't see which one used it. DeviceGate gives you:

  • One login, many machines. The subscription credential lives only on the gateway machine. Each PC holds its own device token, which you can revoke from the admin at any time.
  • Two-minute device setup. Add a device in the admin and run the one-line command it shows on the PC (macOS, Linux or Windows). After that, plain claude works.
  • Per-device model access. Allow Opus on your workstation and only Haiku on the automation box. Models match by family prefix, so new model versions are covered without code changes.
  • Per-device limits. Daily and weekly request, token and spend (USD, at API rates) quotas, requests per minute, and concurrent requests. Set defaults once on the Limits page; each device uses them for any limit it leaves empty. When a limit is hit, Claude Code shows a readable error with the reset time.
  • Usage and cost tracking. Input, output and cache tokens for every request, plus what they would have cost at API rates. Break it down by device, model and day, filter it, and bookmark or share the view as a URL.
  • Subscription capacity at a glance. The 5-hour and weekly utilization of every Claude account TeamClaude holds, plus the total across them, read from TeamClaude's quota endpoint.
  • More than one subscription, if you need it. When one plan isn't enough, sign TeamClaude in to more of your own accounts. It moves to the next one as an account nears its limit, and your devices keep working. See More than one Claude account.
  • Audit log. Every admin action and sign-in attempt, with before/after diffs.
  • Private by design. Prompts and responses are never stored. Tokens and setup codes are kept only as hashes.
  • Small. One Next.js process and one SQLite file. No Redis, Postgres or Docker required.
Usage explorer Device limits
Usage explorer Device detail

Terms of use

Read this before deploying.

  • DeviceGate is built for one person and their own devices. Anthropic's Consumer Terms and the Claude Code legal and compliance page don't allow routing other people's requests through your Free, Pro or Max credentials. They also state that advertised plan limits assume ordinary, individual use.
  • Never give a device token to anyone else, whether coworkers, family, clients or customers. If several people need Claude Code, each should sign in with their own subscription, or your organization should use a Team/Enterprise plan or API keys from the Claude Console.
  • Several subscriptions must all be yours: bought by you, used by you, through the real Claude Code. Claude Code itself offers to switch to another account you own when you hit a limit, and TeamClaude automates that switch. Anthropic hasn't explicitly approved automatic switching, so weigh it against the current terms yourself. TeamClaude's compliance notes cover this in more detail.
  • Claude sign-in is handled by TeamClaude through Anthropic's own OAuth flow. DeviceGate never sees your Claude password or OAuth tokens.
  • Anthropic may enforce these terms without notice. Deciding whether your setup complies is your responsibility. Nothing here is legal advice.

How it works

  1. You sign in to your Claude account once, with TeamClaude, on the gateway machine (or to several of your own accounts, which TeamClaude switches between). TeamClaude listens only on 127.0.0.1.
  2. DeviceGate runs next to it on your LAN. It authenticates each PC's device token and applies that device's model allow-list and limits. It then forwards the request to TeamClaude with the only credential it holds.
  3. Responses stream back unchanged. As they pass through, DeviceGate reads the token usage out of the stream and records it.

Claude Code needs no patches. The setup script only sets ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN in the PC's ~/.claude/settings.json. For the full design (request pipeline, data model, security model), see docs/architecture.md.

Requirements

  • A gateway machine: Linux or macOS, always on, with a fixed LAN IP (reserve it in your router).
  • Node.js ≥ 20.9, Bun and git on the gateway machine.
  • A Claude subscription (Pro or Max).
  • Client PCs: macOS, Linux or Windows with Claude Code installed.

Quick start

1. Get the code.

git clone https://github.com/jkugsiya/DeviceGate.git
cd DeviceGate
bun install

2. Sign TeamClaude in to your Claude account, then start it.

bun add -g @karpeleslab/teamclaude     # or: npm install -g @karpeleslab/teamclaude
teamclaude login                        # choose 1 (Claude subscription), finish in the browser
teamclaude probe 300                    # keep the capacity figures fresh while your PCs are idle
teamclaude server --headless            # keep this running (see "Run as a service")

3. Configure DeviceGate.

bun run init-env                  # or pass the LAN IP: bun run init-env 192.168.1.50

This writes .env.local with TeamClaude's key, a fresh auth secret, the gateway's LAN URL and your timezone. It won't overwrite an existing file unless you add --force. See Configuration for each value.

4. Create your admin login. You'll be asked for a password of at least 12 characters.

bun run admin create you@example.com "Your Name"

5. Build and start.

bun run build
bun run start      # serves on 0.0.0.0:3000; the database is created and migrated on start

6. Allow port 3000 from your LAN if a firewall is on. For example, sudo ufw allow from 192.168.0.0/16 to any port 3000 proto tcp, or with firewalld, sudo firewall-cmd --permanent --add-port=3000/tcp && sudo firewall-cmd --reload.

7. Connect your PCs. Open http://<gateway-ip>:3000/admin, sign in and click Add device. The page shows a one-time setup code and the exact command to run on that PC:

# macOS / Linux
curl -fsSL http://<gateway-ip>:3000/setup.sh | sh -s -- K7QM-2XPA
# Windows (PowerShell)
& ([scriptblock]::Create((irm http://<gateway-ip>:3000/setup.ps1))) K7QM-2XPA

Codes are single-use and expire after 15 minutes. After that, plain claude on that PC goes through the gateway.

Warning

DeviceGate serves plain HTTP and is meant for your LAN. Don't expose port 3000 to the internet. To reach it from elsewhere, use a VPN such as Tailscale, or put a TLS reverse proxy in front and restrict who can reach it.

Configuration

Everything is set in .env.local. .env.example documents the same values.

Variable Required Description
UPSTREAM_API_KEY yes TeamClaude's proxy.apiKey, from ~/.config/teamclaude.json. Also used to read its quota endpoint.
UPSTREAM_URL no TeamClaude's address. Default http://127.0.0.1:3456. Keep it on loopback.
BETTER_AUTH_SECRET yes Signs admin sessions. Generate with openssl rand -base64 32.
BETTER_AUTH_URL yes The gateway's LAN URL, e.g. http://192.168.1.50:3000.
ADMIN_TRUSTED_ORIGINS yes Comma-separated origins you open the admin from.
DATABASE_PATH no SQLite file. Default ./data/gateway.db.
TIMEZONE no IANA zone for quota windows and admin times, e.g. Europe/Berlin. Days start at local midnight and weeks on Monday. Default: the server's zone.
GATEWAY_PUBLIC_URL no The URL written into each device's ANTHROPIC_BASE_URL. Defaults to BETTER_AUTH_URL.

Run with Docker

DeviceGate can run in a container instead of steps 5 and 6 of the Quick start. TeamClaude still runs on the host (steps 1 and 2), because it holds your Claude login and listens only on 127.0.0.1. The container uses host networking to reach it, which works on Linux, or on Docker Desktop with host networking turned on.

bun run init-env                  # writes .env.local, as in step 3
docker compose up -d --build
docker compose exec -it devicegate bun run admin create you@example.com "Your Name"

The database lives in the devicegate-data volume, and migrations run on start. To update, run git pull && docker compose up -d --build. To move the database to another machine, stop the container and copy it out with docker compose cp devicegate:/app/data ./data.

Run as a service

On Linux, systemd user services keep both processes running across reboots.

~/.config/systemd/user/teamclaude.service:

[Unit]
Description=TeamClaude
[Service]
ExecStart=%h/.bun/bin/teamclaude server --headless
Restart=on-failure
[Install]
WantedBy=default.target

~/.config/systemd/user/devicegate.service (adjust WorkingDirectory):

[Unit]
Description=DeviceGate
After=teamclaude.service
[Service]
WorkingDirectory=%h/DeviceGate
ExecStart=/usr/bin/env bun run start
Restart=on-failure
[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now teamclaude devicegate
sudo loginctl enable-linger $USER     # keep them running while you're logged out

On macOS, a launchd agent with the same two commands does the job.

More than one Claude account

If you keep running out on one subscription, TeamClaude can hold several of your own accounts and switch between them. DeviceGate needs no extra setup: it still talks to the one TeamClaude.

teamclaude login                 # once per account
teamclaude priority <name> 1     # optional: the order to use them in, lowest first
teamclaude accounts              # check they're all there

TeamClaude stays on one account until it nears its 5-hour or weekly limit (98% by default), then moves to the next. A request refused for quota is resent on another account, so Claude Code never sees the limit while any account has room left. Set "distributeSessions": "adaptive" in ~/.config/teamclaude.json to spread parallel sessions across accounts while keeping each conversation on one of them. The routing docs cover this and the other settings.

Each personal Max login is its own organization, so a conversation that moves to another account starts again with a cold prompt cache. TeamClaude only switches near a limit, so this is rare.

The admin overview lists every account with its plan, status and 5-hour and weekly use, plus totals across the accounts in rotation. Pause takes an account out of TeamClaude's rotation and Resume puts it back; TeamClaude saves this to its config, so it survives a restart. The totals are weighted by plan size, so a Max 20x counts four times as much as a Max 5x, and paused accounts are left out. The status page shows the same totals and each account's plan, status and remaining capacity, so a PC that gets a 429 can see whether the pool is out. Accounts appear there as "Account 1", "Account 2"…, never by name.

DeviceGate reads these figures from TeamClaude's GET /teamclaude/quota (TeamClaude 1.1.25 or newer) at most every 30 seconds. TeamClaude only learns an account's quota from traffic through it, so keep teamclaude probe 300 on, or idle accounts show stale figures. If the endpoint fails, DeviceGate falls back to the rate-limit headers on the last response, which describe only the account that served it. The admin says when this happens.

Updating

git pull && bun install && bun run build
systemctl --user restart devicegate

Pending database migrations and any missing request costs are applied on startup, so a restart is all an update needs. Watch it come up with journalctl --user -u devicegate -n 30.

Day to day

Task How
Add or reconnect a PC Admin → Add device, or device page → Generate setup code
Set models and limits for a PC Device page → Access & limits
Set limits for every PC Admin → Limits
Delete a Claude account Overview → account row → Delete. Needs "mcp": "full" in the proxy section of teamclaude.json.
Block a PC right now Device page → Disable device
See who did what Admin → Audit log
See where the capacity went Admin → Usage
See which PCs used a model Admin → Usage → click the model
Usage between two dates Admin → Usage → From / To
Reset the admin password bun run admin password you@example.com
Manage devices from the shell bun run device list | create <name> | setup <id> | disable <id> | …
Re-price past requests bun run recompute-costs (after editing lib/pricing.ts)

Usage explorer

/admin/usage is the tracking view. Pick a preset range or explicit From / To dates, narrow by device, model or outcome, or search across model id, device name, client IP and request id. Every table sorts by its headers, and the request log pages through the whole filtered set. All of it lives in the URL, so any view is a link you can bookmark.

Outcomes are split three ways. Blocked means DeviceGate refused the request (it never reached Anthropic). Upstream error means it reached Anthropic and failed there. Succeeded is everything else.

Cost tracking

Each request records input, output, cache-write and cache-read tokens, plus an API-equivalent cost: what those tokens would have cost at Anthropic's pay-as-you-go rates. Your subscription already covers this traffic, so nothing is billed. The figure is there to compare devices and spot one burning through your quota.

Rates live in lib/pricing.ts. Costs are computed when a request is recorded, so old rows keep the rate that applied at the time. After changing a rate, or adding a model the admin flags as "no price", run bun run recompute-costs.

Status page

Public status page

/ needs no login. It shows the subscription capacity left in the current 5-hour and weekly windows (across all your accounts, if TeamClaude has several), tokens and API-equivalent cost all time and today, a chart of either over the last 24 hours (by hour) or 7, 30, 90 days or all time (by day), and this week's devices ranked by tokens and by cost. Only device names and totals appear there (lib/public-queries.ts selects nothing else). If the gateway can be reached beyond your LAN, restrict / at your reverse proxy.

Opened on a connected PC, the page also shows a This PC card with that device's usage today and this week against its limits, and the models it may use. The match is by IP address: the browser's address against the one the device last sent a request from, so it appears after the PC's first claude request. It's a convenience, not a login, so anyone on your LAN who forges the address could see the same figures.

Moving to a new gateway machine

  • To keep devices, usage, admins and the audit log: stop the old gateway, then copy data/gateway.db (plus gateway.db-wal / -shm if present) into data/ on the new machine before starting it. Copy BETTER_AUTH_SECRET too if you want admin sessions to survive. Update the IP values in .env.local.
  • Each PC still points at the old IP. On each device page, click Generate setup code and run the command on that PC again.
  • Run only one DeviceGate and one TeamClaude at a time. Counters live in memory, so a second instance would enforce limits on its own.

FAQ

Do the per-device token numbers match my subscription usage? Not exactly. Anthropic doesn't publish how subscription usage is weighted, so DeviceGate shows its own per-device token counts and Anthropic's reported 5-hour and weekly utilization separately. Cache reads are tracked but don't count toward DeviceGate's token quotas, because long sessions would otherwise burn quota almost entirely on them.

A request went over the limit. Is that a bug? No. Quotas are checked before each request, against usage so far, so a single request can overshoot a limit.

Claude Code updated and requests started failing. In token mode, Claude Code leaves out two beta flags that the subscription upstream needs, and DeviceGate adds them back (lib/proxy/betas.ts). If a Claude Code release changes this, see Protocol notes for how to check, and please open an issue.

Can I run it in Docker or on Windows? Docker, yes: see Run with Docker. A Windows gateway isn't packaged yet (Windows PCs work fine as clients). The gateway is tested on Linux. Contributions are welcome.

Contributing

Issues and pull requests are welcome. Start with CONTRIBUTING.md. To report a vulnerability, follow SECURITY.md and please don't open a public issue.

Acknowledgements

TeamClaude by Karpeles Lab does the hard upstream work: OAuth, token refresh and rate-limit handling. DeviceGate is built on top of it.

License

MIT © Jayesh Kugsiya

About

Self-hosted gateway for Claude Code: give each of your own machines its own key, model access, limits and usage tracking on your Claude subscription.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages