Quotamux
Route your coder to the subscription with the most quota left.
多模型多订阅额度调度器 —— 让任意 coder 自动落到余量最多的那个订阅上。
The problem
You pay for several AI coding subscriptions. Then this happens:
kimi-A weekly 7% left ← burned out on Tuesday
kimi-B weekly 46% left
minimax-1 weekly 73% left
minimax-2 weekly 99% left ← never touched
One subscription is exhausted while another sits idle, because nothing tells your tooling which pool still has room. You find out when a job dies with a 429.
Worse, the usual reflex is to estimate usage from your own logs. Local logs are a proxy: they miss calls that bypass them, their accounting differs from the provider's, and your own grouping code can be wrong. Every conclusion built on a proxy inherits its bias. (Ask us how we know — a full day of modelling, all of it wrong.)
What it does
Asks each provider how much is actually left, then hands your coder the environment for the pool with the most quota remaining — not merely one that is non-empty. "Non-empty is good enough" is exactly how one subscription burns out while another idles.
$ quotamux
池 周剩 窗剩 重置
🔴kimi-A 7% 99% 2026-08-04 00:39
🟢kimi-B 46% 100% 2026-08-04 05:25
🟢minimax-1 73% 98% 2026-08-03 01:00
🟢minimax-2 99% 100% 2026-08-03 01:00 ←选它
🟡claude-max 22% 86% 2026-07-31 23:59
# Works with ANY coder — quotamux only emits environment variables
eval "$(quotamux --export)" && claude -p "fix the failing test"
eval "$(quotamux --export)" && aider --message "..."
eval "$(quotamux --export)" && your-own-agent
Install
pip install quotamux # or: pipx install quotamux
Configure
Providers ship built in. Declare your subscriptions in ~/.quotamux/config.yaml
(YAML or JSON). You only ever write environment-variable names — never secrets:
providers:
kimi:
subscriptions:
# Multiple keys under one subscription share ONE quota pool.
# (Verified: same-account keys return byte-identical usage.)
# So rotating to another key of the same account when throttled is a no-op.
- name: "kimi-team"
keys: [KIMI_CODE_API_KEY, KIMI_CODE_API_KEY_1, KIMI_CODE_API_KEY_2]
- name: "kimi-personal"
keys: [KIMI_CODE_API_KEY_5]
Point elsewhere with QUOTAMUX_CONFIG=/path/to/config.yaml.
Supported providers
| Provider | Weekly | Rate window | Usage endpoint |
|---|---|---|---|
| Kimi Code | ✅ | ✅ | {base}/usages |
| MiniMax | ✅ | ✅ | /v1/token_plan/remains |
| Anthropic (Claude Max/Pro) | ✅ | ✅ | /api/oauth/usage |
| OpenAI | ⚠️ config only, untested | — | /v1/organization/costs |
| DeepSeek | pay-as-you-go, no pool | — | — |
Two of these endpoints are not in any public documentation. We found them by reading the vendors' own clients — Kimi's from its CLI source, Anthropic's from
stringson the Claude Code binary. If a dashboard can show a number, something serves that number; follow the signpost back to the machine.
Add a provider — config only, no code
Adding a vendor means adding a block to your config. Two parse modes cover every provider we have met:
providers:
acme:
display: ACME AI
usage:
url: "https://api.acme.example/v1/quota"
auth: bearer # or: x-api-key
headers: {api-version: "2026-01-01"}
parse:
mode: ratio # vendor returns absolute remaining/limit
week: {remaining: "quota.remaining", limit: "quota.limit", reset: "quota.reset_at"}
window: {list: "windows", remaining: "detail.remaining", limit: "detail.limit"}
lane:
base_url: "https://api.acme.example/anthropic"
model: "acme-1"
auth_env: ANTHROPIC_AUTH_TOKEN
subscriptions:
- name: "acme-seat-1"
keys: [ACME_KEY_1]
| mode | when the vendor returns | fields |
|---|---|---|
ratio |
absolute remaining / limit |
week.{remaining,limit,reset}, window.{list,remaining,limit} |
percent |
remaining percent | select.{path,where}, week.{pct,reset_ms}, window.{pct} |
percent_used |
used percent (utilization) | week.{used_pct,reset}, window.{used_pct} |
Declare the models a provider serves so --model can find it:
models:
- {id: "acme-1", aliases: [acme, a1]}
# usage_row: only when the vendor reports quota per model — names the row to read
- {id: "acme-turbo", aliases: [turbo], usage_row: turbo}
A test in this repo proves the claim: a fully fictional provider that appears nowhere in the source runs end to end from config alone.
"Most quota left" — three levels
All three levels pick the emptiest-loaded pool, never just "one that still has something". What changes between levels is which pools are eligible, and that depends on what the task needs:
quotamux --pick # any model will do → most weekly quota left, full stop
quotamux --model opus --pick # needs this model → only pools that serve it
quotamux --model k3,m3 --pick # either is fine → pools serving k3 OR m3,
# then the emptiest across vendors
The third form is the one you want most days: "this job runs fine on Kimi or MiniMax".
It keeps the job runnable (right model) and stops one subscription burning out while
another idles (most quota left). Order expresses preference — with --model k3,m3, a
pool serving both gets k3.
Where a vendor reports quota per model (MiniMax returns one row per model), the per-model figure wins. A subscription can look 90 % free overall while the model you need is down to 5 %.
Selection rule
- If
--modelis given, drop pools that do not serve it. Weekly quota is worthless on a pool that will not run your model. - A pool is eligible when its tightest rate window has ≥
--min-windowpercent left (default 15). Weekly quota is useless if you are about to get throttled. - Among eligible pools, pick the one with the most quota remaining — per-model if the vendor reports it, otherwise subscription-level. This is the rule that stops one subscription burning out while another idles.
- Pay-as-you-go pools are excluded unless you pass
--allow-metered— spending money should be an explicit decision. - Nothing eligible → exit code 1. Queue and wait for the window. Buying another subscription rarely fixes what is a scheduling problem.
Safety
- Secrets are never printed by
quotamux,--pickor--json. Only--exportemits keys — that is its entire job — and it writes them to stdout so you canevalthem. Do not pipe--exportinto logs or CI output. - Config holds environment-variable names, never key material.
- Native subscriptions (Claude Max OAuth) emit no environment at all. Injecting a base URL or bearer token there downgrades OAuth and kills the subscription channel — a mistake we made in production so you do not have to.
Notes
- Queries run concurrently and are cached for 60 s (
--freshto bypass), so launching a batch of workers does not hammer the usage endpoints. quotamux --jsonis stable machine output; build your own policy on top of it.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file quotamux-0.1.1.tar.gz.
File metadata
- Download URL: quotamux-0.1.1.tar.gz
- Upload date:
- Size: 24.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc3c9d6e11f8229e8be0447d5f0a04ad9214d3a0b23eca02e431fb721af39794
|
|
| MD5 |
8ebf4a9cf65f954131297190fff973cc
|
|
| BLAKE2b-256 |
5e99addb0e443efb19bc81748ad0a095258252c79cf58ff687655d78e620def5
|
Provenance
The following attestation bundles were made for quotamux-0.1.1.tar.gz:
Publisher:
release.yml on yandie-AI/quotamux
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quotamux-0.1.1.tar.gz -
Subject digest:
cc3c9d6e11f8229e8be0447d5f0a04ad9214d3a0b23eca02e431fb721af39794 - Sigstore transparency entry: 2281079970
- Sigstore integration time:
-
Permalink:
yandie-AI/quotamux@5a7503b4f80bd1c86781ced1244ece563f242cab -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/yandie-AI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5a7503b4f80bd1c86781ced1244ece563f242cab -
Trigger Event:
push
-
Statement type:
File details
Details for the file quotamux-0.1.1-py3-none-any.whl.
File metadata
- Download URL: quotamux-0.1.1-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63aa7fb994ebf458248d93fc0ce7563c769aa417c0d69cf43e5d92864b6dd8bd
|
|
| MD5 |
3f15b80622a5801bd9cfec794c2c7857
|
|
| BLAKE2b-256 |
b727cad0d284da94dda5f8d3ca1e8ce4e172b248e0617b2c89b0916eaa26804a
|
Provenance
The following attestation bundles were made for quotamux-0.1.1-py3-none-any.whl:
Publisher:
release.yml on yandie-AI/quotamux
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quotamux-0.1.1-py3-none-any.whl -
Subject digest:
63aa7fb994ebf458248d93fc0ce7563c769aa417c0d69cf43e5d92864b6dd8bd - Sigstore transparency entry: 2281080006
- Sigstore integration time:
-
Permalink:
yandie-AI/quotamux@5a7503b4f80bd1c86781ced1244ece563f242cab -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/yandie-AI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5a7503b4f80bd1c86781ced1244ece563f242cab -
Trigger Event:
push
-
Statement type: