Claude Pro's 5-hour window quota is one float on a sliding clock, not 45 messages

Anthropic does not publish a 5-hour message counter. The server tracks one opaque utilization fraction in a single field, on a window that slides forward every time you send a prompt. If you have ever been surprised by a 429 while you thought you had “hours left,” this is the field you wanted to be watching.

M
Matthew Diakonov
8 min read
4.9from Sourced from the live claude.ai usage endpoint
Field names from src/models.rs, lines 3-7
Verifiable in 30 seconds with one curl
Same JSON the Settings page itself fetches

The myth, in one sentence

Almost every article on this topic gives you the same shape: Pro equals roughly 45 messages per 5 hours, the window starts when you send your first prompt, and at the 5-hour mark you get a fresh batch. That description is wrong on three counts. There is no 45-message counter on the server. The window is not anchored to your first prompt. The 5-hour mark is not a fresh batch.

The shape that actually exists on the server is one number plus one timestamp, in one place.

Mental model swap

Claude Pro gives you about 45 messages per 5 hours. The window starts when you send your first message and ends 5 hours later. When the timer runs out, you get a fresh batch.

  • Implies a fixed message count, which the server does not track
  • Implies the window is anchored to your first prompt, which is wrong
  • Implies a single fixed reset time, which is not what resets_at returns
  • Names no field, no endpoint, and no scale

The anchor fact: 0 field, 0 timestamp, 0 endpoint

Hit GET /api/organizations/{org_uuid}/usage with your logged-in claude.ai cookies. The relevant slice for this topic is the five_hour object on top. Verbatim shape (formatted for readability):

claude.ai/api/organizations/{org_uuid}/usage

That is the entire 5-hour quota surface. One utilization float. One resets_at ISO 8601 string. There is no message count. There is no remaining tokens field. There is no model breakdown inside this bucket. The rate limiter trips on utilization >= 1.0 (or 100.0, depending on which scale your payload picked).

The struct, verbatim

ClaudeMeter deserializes the response into this. If Anthropic ever changes the field shape, this is where you will see it break first:

claude-meter/src/models.rs

What “rolling” really means

A rolling window is not a timer. It is a moving boundary. At any moment, the window covers the last 5 hours of your activity. The server is keeping a list of recent messages and totaling the (weighted) cost of every one inside that boundary.

When you send a new message, the cost lands inside the boundary and utilization goes up. When the earliest unexpired message ages out (its timestamp becomes older than 5 hours), its cost falls out of the boundary and utilization goes down. The resets_at field is the wall-clock time of that next age-out event.

Which means: every prompt you send pushes resets_at forward. If you keep sending steadily, your “reset” timestamp is always five hours from your earliest unexpired message, which is always five hours from a moment that keeps moving. People read this and say the window “never resets,” but the mechanic is just that the boundary slid with them.

One prompt, one bucket increment

The exact handshake between you, the browser, the claude.ai server, and the rate limiter for a single 5-hour bucket request.

five_hour update path

YouBrowserclaude.ai serverRate limitersend a promptPOST /completionsincrement five_hour bucketutilization += weight(prompt, model, attachments)stream responseGET /api/organizations/{org}/usage{ five_hour: { utilization, resets_at } }next message: 429 if utilization >= 1

The 0-to-1 vs 0-to-100 trap

One ugly detail if you call the endpoint yourself: utilization arrives on inconsistent scales. We have seen the same payload come back with five_hour.utilization at 0.72 and a sibling bucket at 94.0. ClaudeMeter normalizes with one clamp:

claude-meter/extension/popup.js

Without that clamp, a bucket at 0.94 renders as “less than 1%” and your next prompt 429s.

The sliding clock, in code

ClaudeMeter does not start a 5-hour timer when you open the popup. It reads resets_at straight from the server, every poll, and converts it into a human-readable countdown:

claude-meter/extension/popup.js

That label (“5-hour · 2h”, “5-hour · 47m”, and so on) updates whenever the server returns a new resets_at, which in practice is every minute the extension polls. The reason the countdown sometimes “ticks back up” instead of down is exactly the rolling-window behavior: you sent a message, the earliest-unexpired-message pointer moved, the timestamp moved with it.

Reproduce it yourself in one curl

You do not need ClaudeMeter to verify any of this. Open DevTools on claude.ai/settings/usage, copy the cookie header from the Network panel, and call the endpoint directly:

claude.ai/api/organizations/{org_uuid}/usage

What every prompt actually does

Inputs to the five_hour float

Prompt length
Model picked
Attachments
Tool calls
Peak-hour multiplier
five_hour.utilization
Settings page bar
Rate limiter at >= 1.0
ClaudeMeter menu bar
ClaudeMeter CLI --json

The whole verification path, end to end

1

Open claude.ai/settings/usage

This is the only first-party surface that renders your live quota. The page itself calls /api/organizations/{org_uuid}/usage to draw the bar.

2

Open DevTools → Network → XHR

Reload the Settings page. You will see the request to /api/organizations/{your-org-uuid}/usage. The response is a JSON object with a five_hour key on top.

3

Read five_hour.utilization

If it is between 0 and 1, multiply by 100 for a percent. If it is between 0 and 100 already, leave it. ClaudeMeter handles both at popup.js:6-11. The bar Anthropic draws is the same number, rounded.

4

Watch resets_at slide

Send a message in another tab, refresh, look again. resets_at advances. That is the rolling window: the earliest unexpired message just moved forward in time.

5

Confirm the rate-limit ceiling

When utilization hits 100, the next request from the same org returns 429. The bucket does not name itself in the error, so live monitoring is the only way to know which one tripped.

Why the wrong mental model gets you 429’d

What you miss by counting messages

  • Quoted message counts (45 per 5 hours, 225 for Max 5x, 900 for Max 20x) are average estimates over an unknown prompt distribution. Long prompts or attachments break them on the first message.
  • If you assume the window is anchored to your first prompt, you will think you have hours left when the rolling clock has actually pulled forward and you are seconds from the wall.
  • If you read your local Claude Code logs (ccusage, Claude-Code-Usage-Monitor), you can see tokens you spent. You cannot see the server's five_hour utilization, because the server applies weighting (peak-hour multiplier, attachment cost, tool calls) you do not have locally.
  • If you skip the 0-to-1 vs 0-to-100 normalization, a bucket at 0.94 renders as 'less than 1%' and you walk straight into a 429.
  • The only number that matches what Anthropic enforces is usage.five_hour.utilization on /api/organizations/{org_uuid}/usage.

The numbers that matter

From the implementation. No invented benchmarks.

0five_hour fields the server returns
0sClaudeMeter poll cadence
0Localhost bridge port
0Cookies you have to paste

Common myths to drop

Myth: 45 messages per 5 hoursMyth: window starts at first promptMyth: window expires all at onceMyth: utilization is on a fixed scaleMyth: local token counts equal server quotaMyth: peak-hour throttling is a separate field

Why local-token tools cannot give you this number

ccusage and Claude-Code-Usage-Monitor read ~/.claude/projects/**/*.jsonl and total the tokens recorded in those files. That tells you what Claude Code burned on disk. It does not tell you what the server counts in your five_hour bucket.

The reason is not philosophical. It is concrete. The server applies weighting that local logs do not see: a peak-hour multiplier (announced by Anthropic in late 2025), an attachment-cost factor, a per-model factor, and a tool-call factor. None of those land in your local JSONL file. They all land on utilization. The single endpoint that returns the post-weighting number is /api/organizations/{org_uuid}/usage.

ClaudeMeter exists because that endpoint is the only one that matches what the rate limiter checks, and because a browser extension is the cleanest way to hit it (your existing session cookies, no paste, no scrape, no reverse-engineering).

The honest caveat

The endpoint is internal and undocumented. The field names listed here have been stable for many months but Anthropic could rename or reshape them in any release. ClaudeMeter deserializes into a strict struct, so if five_hour changes shape the menu bar surfaces a parse error and we ship a patch the same day. Until then, this is the field. This is what the rate limiter checks.

Watch your 5-hour float live

ClaudeMeter sits in your macOS menu bar and refreshes every 60 seconds. Free, MIT licensed, no cookie paste, reads the same JSON claude.ai/settings/usage reads.

Install ClaudeMeter

Frequently asked questions

Is Claude Pro's 5-hour limit really 45 messages?

No. The number 45 you see quoted everywhere is a one-sample average that the Help Center used as an example range. The server does not track a remaining-message count for you. It tracks one utilization fraction in the five_hour bucket of /api/organizations/{org_uuid}/usage, weighted internally by prompt length, attachments, model, and tool calls. A short Sonnet message and a long Opus message with a PDF attached do not consume the same slice. Anything that quotes a fixed message count is averaging across an unknown distribution.

When does the 5-hour window start?

It does not start from your first prompt and run for a fixed five hours. The window is rolling. At any moment, it covers the last five hours of activity. The five_hour.resets_at field on the server tells you when the earliest unexpired message will age out, which is the next time your utilization number changes for free without you sending anything. Each new message you send pushes resets_at forward, because the earliest unexpired message keeps moving.

Why does my window seem to never reset?

Because rolling is not the same as expiring. If you send messages steadily, the earliest unexpired message keeps moving forward in real time, so resets_at keeps moving with it. You have to actually stop sending and let the oldest message age out for the bucket to drain. ClaudeMeter renders this directly: the 'resets · Xh' label in the popup is computed from the live resets_at the server returns, not from a five-hour timer started locally.

Where is this number on claude.ai?

Open claude.ai/settings/usage with DevTools' Network tab open. The page calls GET /api/organizations/{your-org-uuid}/usage. The response includes a five_hour object with a utilization float and a resets_at ISO timestamp. The bar drawn on the page is rendered from those same two values. ClaudeMeter calls the same endpoint with your existing session cookies, every 60 seconds, and surfaces the raw numbers without re-interpretation.

Why is utilization sometimes 0.94 and sometimes 94.0?

Because the API is internal and the scale is inconsistent across buckets and across releases. We have seen the same payload come back with five_hour at 0.94 and seven_day_opus at 94.0. ClaudeMeter normalizes with one clamp at popup.js:6-11: `u <= 1 ? u * 100 : u`. If you write your own client and skip that clamp, a bucket at 0.94 will render as 'less than 1 percent' and you will get rate-limited the next message.

Can ccusage or Claude-Code-Usage-Monitor tell me my five_hour utilization?

No. Those tools read JSONL files under ~/.claude/projects, count tokens locally, and infer cost. That is a different question from what the server is counting. The server's weighting for the five_hour bucket is not exposed and includes signal you do not have locally (peak-hour multiplier, attachment cost, tool calls, browser-chat usage). The only number that matches what the rate limiter enforces is the one returned by /api/organizations/{org_uuid}/usage.

What happens at 100 percent?

You get throttled. The Settings page bar pins, and any further request from the same org gets a 429 with a generic message. The bar does not name which bucket tripped, so if you are over on five_hour but fine on the weekly buckets, the error message looks identical to the inverse case. Watching utilization live is the only way to know whether to keep working or stop.

Does Anthropic publish the formula?

No. The endpoint is undocumented, the field names are not in any public spec, and the weighting is opaque. What we know comes from reading the JSON the Settings page itself fetches. ClaudeMeter deserializes that JSON into a strict Rust struct (src/models.rs), so when Anthropic adds, renames, or removes a field, the deserializer fails loudly and we ship a release.

Does peak-hour traffic actually consume more of my quota?

Anthropic announced in late 2025 that during weekday peak windows the five_hour bucket fills faster for free, Pro, and Max accounts. We see the effect directly in the live utilization numbers: the same workload that consumes 30 percent of five_hour at 6am Pacific consumes more at 1pm Pacific. There is no separate peak-hour field in the response, only the utilization float moves faster.

How do I read the number without ClaudeMeter?

GET https://claude.ai/api/organizations/{your-org-uuid}/usage from a browser session that is logged into claude.ai. Find your org_uuid by looking at the cookie or by reading any URL on claude.ai/settings. The response is plain JSON. ClaudeMeter exists because doing this every 60 seconds by hand, and pasting cookies into curl, is annoying. The browser extension forwards your existing session to the menu bar app over localhost so you never type a cookie.

Seeing a different shape on five_hour?

If your payload returns extra fields, a different scale, or a resets_at that does not slide, send it. We map every variant we see.

Book a 15-minute call