A Claude Pro weekly quota tracker is one popup row, plus a few lines of normalization
Open the ClaudeMeter popup on a Pro plan and you see two rows: 5-hour and 7-day. The second row is the weekly quota tracker. Behind it is one HTTP GET against /api/organizations/{org}/usage, a percent normalization that handles two scales the server ships in the same JSON, and a countdown band that almost always lands on Nd. This page walks every pixel of that row.
Direct answer (verified 2026-04-30)
The free, open-source Claude Pro weekly quota tracker is ClaudeMeter (MIT licensed, Rust core, browser extension). It polls GET /api/organizations/{org}/usage on claude.ai every 60 seconds with your existing logged-in cookies, reads seven_day.utilization and seven_day.resets_at, and renders a row labeled 7-day · Nd with a percent bar. Source verified at extension/popup.js.
What the popup looks like on a Pro account
Two rows. That is the entire surface for a Pro user. The first row is the 5-hour bucket; the second row is the weekly quota.
On a plan that ships per-model weekly buckets in the same JSON, the popup grows to four rows automatically; the extension does not have to know which plan you are on:
The popup does not consult a plan flag. It looks at the JSON response, asks “is this field present?”, and renders a row only if the answer is yes. Plan tier is observable, never configurable.
The conditional rows, in code
The render block is ten lines. Two unconditional row() calls (5-hour, 7-day) and two ternary expressions for the per-model weekly buckets that some plans ship:
When Anthropic added seven_day_sonnet and seven_day_opus to the response, this code did not need a release. The fields show up; the rows show up. If the server adds an eighth bucket tomorrow, you would need one more ternary line and one more entry in the Rust struct, both small. The shape of the change Anthropic ships first is new fields, not renamed fields, which is why this works.
Why the percent has to be normalized per call
The same JSON ships the utilization field on two different scales. Most buckets (five_hour, seven_day, seven_day_sonnet, seven_day_opus, seven_day_oauth_apps) come back as a float between 0 and 1. The two newer ones, seven_day_omelette and seven_day_cowork, come back as 0 to 100. Mixing those without a normalization step gives you a 0.72% bar where the Settings page shows 72%.
One condition handles both. If the value is at most 1, it gets multiplied by 100; otherwise it is already on the percent scale. The rule is per-bucket, per-poll, picked from the value itself rather than from a hardcoded list of which buckets ship which scale.
The countdown almost always says “Nd”
The label format is 7-day · {countdown}, and the countdown comes out of the same humanization function the 5-hour row uses:
The window is 168 hours long. After your first heavy session, resets_at is somewhere between 5 and 7 days in the future, so the band falls to the days output. You see “5d”, then “4d”, then “3d”, and only in the last 48 hours does it switch to “Nh”. The minute band on the 7-day row is essentially theoretical; you would have to be staring at the popup at the exact second the window resets.
The toolbar badge ignores the weekly number on purpose
The browser toolbar gives you one badge slot. Whichever cap is most likely to fire next gets the slot. That is the 5-hour bucket. The weekly percent goes into the icon tooltip on the second line, and the popup is the place you look when you actually want both at once.
Color thresholds: green under 80, orange 80 to 99, red 100 and up. Same thresholds for the bar inside the popup row, controlled by cls = v >= 100 ? "hot" : v >= 80 ? "warn" : "" on line 31 of popup.js. So your 7-day row turns orange when you cross 80 percent and red when you hit the cap, which is the moment a tracker exists for: not the moment of failure, the moment your decision shifts from “keep going” to “switch to Sonnet” or “wait for reset”.
The typed struct that makes the JSON safe to read
On the macOS side the same JSON gets deserialized into a typed Rust struct. Every bucket is wrapped in Option<Window>, so a missing field is a clean None and not a parser error:
The struct also rejects unknown drift loudly. If Anthropic renames a field tomorrow, deserialization fails on that field and the menu bar shows an error chip instead of silently mapping the new name into a missing key. That is the loud failure mode you want from a typed schema; quiet failures lead to dashboards that read “0% weekly” when the actual response shape moved.
Tracker vs the Settings page on the weekly bucket
Both read the same JSON. The difference is what they render and how fresh the render stays.
| Feature | claude.ai/settings/usage | ClaudeMeter (browser + menu bar) |
|---|---|---|
| Weekly percent | Drawn bar without a numeric label on the 7-day row | Rounded fraction next to a clamped bar (Math.round on raw float) |
| Weekly countdown | Mostly absent; small text on reset reads 'usage will reset at X' | '7-day · 5d' label, banded by fmtResets |
| Refresh cadence | On full page reload | Every 60 seconds in the background while the browser is awake |
| Where you read it | claude.ai/settings/usage page | Browser toolbar popup, macOS menu bar, CLI |
| Multi-org coverage | One organization per visible page | Worst-case across every account.membership in one badge |
| Source | Both buckets rendered server-side by the same JSON | Both buckets pulled from the same /usage JSON, normalized in popup.js |
| Cost | Bundled with Pro/Max | Free, MIT licensed Rust + JavaScript |
Six invariants the weekly row holds
What a Pro weekly quota tracker has to get right
- Two rows always render: '5-hour' and '7-day'. The 7d Sonnet and 7d Opus rows render only when the JSON ships those fields. Plan tier is inferred from the response, never hardcoded in the extension.
- The percent normalization runs per call: u <= 1 ? u * 100 : u. Five buckets ship 0-1, two ship 0-100 in the same JSON object. Skipping this step makes 0.72 percent on the bar look like 0.72 percent of the cap.
- The countdown next to the 7-day label lives in the 'Nd' band for almost all of the 168-hour window. You see 'Nh' only in the last 24 to 48 hours, and 'Nm' essentially never.
- The toolbar badge text is the 5-hour percent. The weekly percent goes into the icon tooltip (chrome.action.setTitle) on the second line. The badge has one slot; the cap most likely to fire next gets it.
- The bar width is clamped at Math.min(100, v ?? 0). Overage on the weekly bucket can return a value above 100, and an unclamped bar would push past the 280-pixel popup column and break the row layout.
- Multi-account coverage runs through worstPct(snaps, 'seven_day') (and 'five_hour'). The badge surfaces the hottest membership; the popup itemizes per email so a team org and a personal org keep their breakdowns visible.
The numbers behind the row
Every one is readable out of the source. None are invented benchmarks.
Install in four steps
Step 1: brew install the menu bar app
brew install --cask m13v/tap/claude-meter. The cask installs ClaudeMeter.app under /Applications and registers a launch agent so the menu bar icon comes back after reboot.
Step 2: load the unpacked extension
Clone github.com/m13v/claude-meter, open chrome://extensions (or arc://extensions, brave://extensions, edge://extensions), enable Developer mode, click 'Load unpacked', select the extension/ folder. The browser pins the icon next to the URL bar.
Step 3: visit claude.ai once
If you are not already logged in, visit claude.ai and sign in. The extension reads your existing session cookie via fetch with credentials: 'include'; you do not paste anything. Within one minute the badge lights up with a percent.
Step 4: open the popup
Click the ClaudeMeter icon in the toolbar. The popup shows one block per logged-in account with the 5-hour row, the 7-day row, and any per-model weekly rows the server returned. The 'updated Ns ago' footer confirms the polling loop is alive.
Why this matters for someone watching a weekly quota
The Pro weekly quota tightened on Anthropic's side over the last six months. The 5-hour cap fires first on a daily basis; the 7-day cap fires on a 168-hour rhythm that hardly anyone feels until late Tuesday. A tracker that surfaces both at the same time means the moment the 7-day row crosses 80 you can switch to lighter models for the rest of the week, instead of hitting a 429 mid-Wednesday-refactor and discovering the weekly bucket was the one to watch all along.
Local-log tools (ccusage, Claude-Code-Usage-Monitor) cannot do this. They read ~/.claude/projects/<project>/<session>.jsonl and sum tokens. Server-side weights, peak-hour multipliers, and browser-chat usage are not in those files. The number that matters for a 429 is the float in the JSON, and the JSON only comes through the cookie-authenticated GET on /api/organizations/{org}/usage. Run them together; ccusage tells you what your local Claude Code session weighed in tokens, ClaudeMeter tells you what fraction of the cap Anthropic counted that against.
The honest caveat
The endpoint is internal and undocumented. Anthropic can rename any field in any release. The conditional ternary pattern in popup.js is additive-friendly (new fields show up as new rows) but a rename breaks it for the duration between the rename and the next brew release. If the field shape changes the macOS app shows an error chip and the popup hides the offending row; the rest of the surface keeps rendering against the fields that still parse. That is the tradeoff for reading server truth instead of a published API.
Building your own weekly tracker and want to compare bucket math?
Send a 15 minute call. Happy to swap notes on the rolling-bucket edges, the per-model weekly fields, and the moments the JSON shape shifts.
Frequently asked questions
What is a Claude Pro weekly quota tracker?
It is a tool that polls /api/organizations/{org_uuid}/usage on claude.ai with your existing browser cookies, reads the seven_day.utilization fraction and seven_day.resets_at timestamp the server returns, and renders them as a percent bar plus a countdown label so you can see how much of the weekly bucket you have spent without opening claude.ai/settings/usage. ClaudeMeter is the open-source one (MIT, free); it ships as a browser extension plus a macOS menu bar app.
Why does claude.ai not just show me the weekly percent itself?
It does, on the Settings page, but only for the 5-hour bucket. The seven_day field has a bar drawn from the same JSON, but the small auxiliary label collapses the continuous fraction into 'usage is low' or 'usage will reset at X'. You can read the precise fraction by opening DevTools on the Settings page and reading the JSON response, but a tracker exists so you do not have to do that every five minutes during a heavy session.
How often does the tracker repoll the weekly number?
Once per 60 seconds. extension/background.js sets POLL_MINUTES = 1 and registers a chrome.alarms tick on install and on browser startup. The seven_day bucket moves slowly compared to five_hour, so a faster cadence gains nothing; a slower cadence misses the moment a long Opus run lands on the bucket. One minute is the cadence claude.ai's own Settings page recomputes against, so matching it keeps the numbers in lockstep.
Why does the toolbar badge show the 5-hour percent instead of the weekly one?
Because the 5-hour bucket fires first. The badge has room for one number; that number has to be the cap most likely to throw a 429 on your next prompt. background.js calls worstPct(snaps, 'five_hour') for the badge text and only puts the weekly number in the icon's tooltip title (the second line: '7d: NN%'). Hover the icon and you see both. Click the icon and the popup shows them side by side with the countdown attached.
Does the popup show 7d Sonnet and 7d Opus rows on Pro?
Conditionally. popup.js lines 62 and 63 are ternary expressions: `${u.seven_day_sonnet ? row('7d Sonnet', u.seven_day_sonnet) : ''}`. They render only when the API response actually contains those fields. On a typical Pro account you see two rows (5-hour and 7-day). Plans that include per-model buckets get the extra rows automatically, no setting to flip; the extension just trusts whatever the server hands back.
What does the countdown next to the 7-day row look like?
Almost always 'Nd', e.g. '7-day · 5d'. The fmtResets function in popup.js (lines 17-27) bands the resets_at timestamp into 'now' (negative diff), 'Nm' (under one hour), 'Nh' (one to forty-eight hours), or 'Nd' (forty-eight hours and up). The seven_day reset is roughly 168 hours away after a fresh window, so it lands in the days band for almost the whole life of the bucket. You only see 'Nh' on the 7-day row in the last day or two before reset.
Why does the percent sometimes look weirdly off compared to claude.ai?
Two reasons that both come back to normalization. The server returns utilization as a float between 0 and 1 on most buckets, but the seven_day_omelette and seven_day_cowork buckets ship 0 to 100 instead. pctFromWindow in popup.js handles this with one line: `return u <= 1 ? u * 100 : u;`. If you wrote your own scraper and skipped the normalization step, you would see 0.78 percent next to a 'usage is low' label and assume the server was lying. The second reason is that claude.ai rounds the bar visually; the tracker shows the rounded percent (Math.round) but uses the raw float for the bar width, so the bar can look 1 to 2 percent tighter than the displayed number.
Does ccusage track the weekly Pro quota?
No. ccusage reads ~/.claude/projects/<project>/<session>.jsonl on disk and sums tokens per turn. That is a faithful local-log signal for Claude Code traffic only, and it does not have access to the per-model weights, peak-hour multiplier, or browser-chat usage that Anthropic folds into seven_day.utilization. ccusage at 8 percent next to claude.ai at 71 percent is normal; they are different ledgers. ClaudeMeter complements ccusage rather than replacing it: ccusage tells you what your Claude Code session weighed in tokens, ClaudeMeter tells you what fraction of the cap Anthropic counted that against.
Do I have to paste a cookie or grant any other permission?
Not on the extension route. The Chrome extension calls fetch with credentials: 'include' against /api/organizations/{org}/usage, so your existing claude.ai session cookie travels automatically. The macOS menu bar app reads Chrome Safe Storage out of keychain to do the same thing without the extension; that route triggers one keychain prompt the first time you launch it. Either way, no manual cookie paste, no second login, no API key.
What if I am on Claude Pro across two organizations?
background.js iterates account.memberships and calls /usage once per org. The badge runs worstPct across all of them, so the percent you see is the worst-case 5-hour bucket of any org you belong to. The popup splits accounts into separate sections labeled by email, so the 7-day rows are itemized per account. If you sit in one personal Pro org and one team Pro org, the badge tells you which one is hottest and the popup tells you the breakdown.
Is the endpoint going to keep working?
It is the same endpoint claude.ai/settings/usage already calls when you load that page. Anthropic does not document it as a public API, so the field names can shift in any release. The Rust struct in src/models.rs declares each known field as Option<Window> so a missing field deserializes cleanly; that is a forward-compat hedge, not a guarantee. If a field gets renamed, the open-source repo gets a same-day patch and you pull the next brew release.
Keep reading
5-hour window tracker: the countdown math most guides skip
Walking the resets_at humanization function (now / Nm / Nh / Nd) and why a fixed 60-second poll is what makes the countdown honest.
Why the weekly limit hits by Tuesday: it is a 168-hour clock
The seven_day window is rolling, not calendar-aligned. resets_at points at the moment the oldest weighted prompt ages out 168 hours later.
Local counter vs server quota: why ccusage and claude.ai disagree
Why ccusage at 8 percent and claude.ai at 71 percent are both correct. Two ledgers, two sources, neither replaces the other.