How does extra usage work with Claude: the eight-field rate_limit event behind the billing toggle
Every help page about Claude extra usage describes the feature. This one describes the wire shape. When a Pro or Max user burns through their cap, the Claude Agent SDK emits an eight-field rate_limit event and an error whose text literally contains the phrase out of extra usage. Fazm decodes every field. This page shows where.
What help pages leave out
If you search for “how does extra usage work with claude” the first four results are all Anthropic help-center articles. They tell you the right things in the right order. Extra usage is prepaid. It activates after the five-hour cap or the seven-day cap. You set a monthly ceiling. It is billed at standard API rates. That is the product description.
None of them tells you what the API actually sends your client when one of those limits fires. That matters if you are building anything that wraps Claude: a Mac agent, a VS Code extension, a shell script, a browser extension. You need the on-the-wire shape, not the product description, or your error UX will treat a five-hour session rejection the same way it treats a monthly ceiling rejection, and your user will wait three days instead of three hours.
Fazm is the Mac Claude wrapper. Its bridge lives at acp-bridge/src/index.ts. The bridge already decodes every field the SDK emits. The rest of this page reproduces the code that does it and the wire messages it is decoding. All of it is grep-verifiable.
The eight fields you never see in a help article
This is the complete TypeScript interface for the rate_limit message Fazm forwards out of the Claude Agent SDK, verbatim from the public source file. Every field maps to one behavior on claude.ai. You will not find this shape documented on any help page.
“rateLimitType: string | null; // five_hour | seven_day | etc.”
acp-bridge/src/protocol.ts line 249
What each of the eight fields actually tells you
The SDK emits one of these per Claude turn. Taken together they tell a wrapper app three things: how much of your window is burned, whether extra usage is active, and when the window resets.
status
Four-value enum: "allowed" (under threshold), "allowed_warning" (crossed a surpassedThreshold), "rejected" (the window is out of budget), "unknown" (malformed). Rejected does NOT always mean the turn fails; the SDK may still retry against overage if overageStatus is allowed.
resetsAt
Unix timestamp in seconds. null when the limit is non-time-based (e.g. hard monthly cap). Fazm formats it at formatResetTime (ChatProvider.swift line 549) using the user's local timezone and a h:mm a pattern.
rateLimitType
"five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet", "overage". Fazm maps the first four to plan labels and the fifth to "extra usage limit" (ChatProvider.swift line 543). This is the field that tells you whether a reset is hours or days away.
utilization
Float 0 to 1. 0.42 means 42 percent of the current cap used. Fazm fires an analytics event on allowed_warning with the exact percentage (ChatProvider.swift line 523) so you can see in aggregate where your users are burning extra-usage spend.
overageStatus
"allowed" means extra usage is on and under its monthly ceiling. "rejected" means either the toggle is off or the ceiling was hit. null means the plan does not support overage (Free tier, some Enterprise seats). This is the single most important field for a wrapper app to branch on.
overageDisabledReason
String explanation when overageStatus is rejected. Common values include "disabled_by_user", "monthly_ceiling_reached", and "plan_not_eligible". Fazm logs it but does not yet surface it to the user; the ChatProvider rate-limit handler (ChatProvider.swift line 513) has an `_` placeholder for when it starts to.
isUsingOverage
Boolean. True means the current turn is being billed against extra usage, not against the plan. When this flips from false to true mid-session, a wrapper app has a great moment to show a small badge so the user knows they are spending prepaid balance.
surpassedThreshold
Float 0 to 1, the warning threshold you just crossed. Anthropic commonly fires warnings at 0.5, 0.8, and 0.95. Fazm uses this to decide whether to update the UI progress bar silently or to fire a notification.
Every string value your client will see
Five rateLimitType values, three status values, two overage outcomes, plus the two HTTP status codes that gate all of it.
Where the eight fields come from
The bridge sees one update object per event from the SDK and unrolls it into the typed message above. No transformation, no filtering. Even when status is allowed the event is forwarded so the Swift UI can keep its progress bar live.
The literal string Claude sends when extra usage dries up
Structured events are the happy path. The fallback path is a regex on the raw error text, because the SDK is sometimes slower than the HTTP response and the structured api_retry event can arrive after the error message. Line 1903 of the bridge is the single regex that catches every way Anthropic can tell you you are out of budget.
The grep-verifiable claim: the literal string out of extra usage appears on line 1903 of /Users/matthewdi/fazm/acp-bridge/src/index.ts. Run grep -n 'out of extra usage' index.ts against the public 2772-line file and it is there. Seven other alternatives in the same regex catch the other failure modes. None of them appear on any help-center page about extra usage because Anthropic does not publish the error text; you have to catch it at your own edge.
The whole extra-usage surface in four numbers
These are counts from the source, not invented benchmarks. Every number resolves to a line in a public file.
Plus 0 total lines in the bridge, 0 in protocol.ts, and 0 in the Swift ACPBridge that decodes the messages on the other side.
The eight substrings the regex matches
The regex at index.ts:1903 is case-insensitive and runs against the raw errMsg. If any one of these substrings matches, Fazm treats the failure as a credit or limit exhaustion and refuses to retry.
alternatives in the credit-exhausted regex
- credit balance is too low
- insufficient credit, funds or balance
- you've hit your limit
- you have hit your limit
- hit your.*limit
- rate.?limit.*rejected
- out of extra usage
- unable to verify.*membership
Six states a Claude turn can end in
The product description calls it a toggle. The wire says otherwise. Here are the six distinct outcomes a single Claude message can produce, mapped to the exact combination of structured fields Fazm looks at.
From allowed to out-of-extra-usage, with the field values that define each case
allowed, isUsingOverage=false
Plain happy path. The turn completes on plan budget. Fazm keeps the progress bar ticking up.
allowed_warning, isUsingOverage=false
The user just crossed a threshold (50, 80, or 95 percent). Turn still completes on plan budget.
allowed, isUsingOverage=true
Plan budget exhausted but overage is on. Turn completes, billed to extra usage.
rejected, rateLimitType="five_hour"
Session cap hit with no overage room. Fazm does NOT throw (ACPBridge.swift line 879) because the SDK may retry against the weekly window or overage.
rejected, rateLimitType="seven_day"
Weekly cap. Reset is days away. Fazm formats the reset with a weekday-aware string.
rejected, overageStatus="rejected", error matches "out of extra usage"
The terminal case. Overage was on; the monthly ceiling was hit. There is no reset time.
Feature description vs. wire reality
Same concept, two different levels of specificity. The left pane is the help-article framing. The right pane is what your client actually parses.
"Claude extra usage" in two forms
"After you reach your plan's usage limit, your usage will roll over to extra usage. You can control extra usage from Settings > Usage > Manage extra usage. Extra usage is billed at standard API rates. You can set a monthly spending cap."
- Describes the feature, not the API contract
- No mention of rate_limit events
- No mention of HTTP status codes
- No error strings
- No distinction between five_hour and seven_day rejections
How an extra-usage rejection travels through Fazm
Every failure mode converges on one Swift enum case. The bridge is doing the decoding. The app is just rendering the result.
Extra-usage rejection, end to end
One full turn, one hit on extra usage
The thirteen messages that pass between you, Fazm, the bridge, the SDK, and the Anthropic API for a single failed turn when your extra-usage ceiling is hit.
Send a message, hit the extra-usage ceiling
The five rateLimitType values, with their user-facing labels
The raw string arrives at the Swift side and is mapped through a static function at ChatProvider.swift line 537. The five cases cover every Anthropic window: a five-hour session, a full weekly cap, a per-model weekly cap (Opus and Sonnet are tracked separately), and the overage bucket itself.
What the user actually reads, and where that string is built
The final translation from structured event to human sentence happens at ACPBridge.swift line 1599. The error message arrives containing a clause like resets 11pm (America/Santiago). The regex on line 1601 extracts just that fragment so the UI can drop it into the “You've hit Claude's usage limit (…)” sentence unchanged.
Three greps that verify every claim on this page
The Fazm source is public. Run these on the checked-out repo yourself. Nothing on this page is reasoning about what Fazm might do; it is reasoning from lines you can open.
Help-center docs vs. Fazm source
A line-by-line accounting of which extra-usage facts a third-party app can actually verify from each source.
| Feature | support.claude.com | Fazm source |
|---|---|---|
| Documents the rate_limit event structure | no (feature-level language only) | yes, eight fields from protocol.ts:244-256 |
| Names the literal error substrings | no | yes, eight substrings in the regex at index.ts:1903 |
| Distinguishes 402 billing_error from 429 rate_limit | no | yes, index.ts:1898-1902 |
| Maps rateLimitType string to user label | no | yes, ChatProvider.swift:537-546 covers 5 types |
| Distinguishes five_hour vs seven_day reset UX | partial (mentions 5h and 7d caps) | yes, reset time extracted via regex at ACPBridge.swift:1601 |
| Shows the isUsingOverage boolean branching | no | yes, protocol.ts:253, handled through rate_limit handler |
| Tells you what overageDisabledReason values look like | no | yes, protocol.ts:252 documents the null/rejected shape |
| Describes retry policy for billing errors | no | yes, non-retryable branch at index.ts:1963-2000 |
| Ships grep-verifiable source | n/a (help-center article) | yes, 2772-line public bridge file |
Eleven claims you can grep for
Each of these is a single-line assertion tied to an exact file and line number. If any one fails to verify, file an issue; the page is wrong.
independently verifiable against the fazm repo
- RateLimitMessage has eight fields at protocol.ts:244-256
- rateLimitType covers five_hour, seven_day, seven_day_opus, seven_day_sonnet, overage
- The literal string "out of extra usage" is in the regex at index.ts:1903
- The literal string "unable to verify.*membership" is in the same regex
- 402 and 429 both trigger isStructuredCreditError at index.ts:1901-1902
- Billing errors are non-retryable via the branch at index.ts:1963-2000
- The rate-limit update handler forwards all eight fields verbatim (index.ts:2443-2465)
- Reset time is extracted from the error message via regex at ACPBridge.swift:1601
- UI shows "session limit" for five_hour, "weekly limit" for seven_day (ChatProvider.swift:539-540)
- UI shows "extra usage limit" for overage (ChatProvider.swift:543)
- Mode A and Mode B log lines differ at index.ts:535
Want to watch Fazm log the rate_limit event in real time on your own Mac?
Thirty minutes on a call. We tail /tmp/fazm-dev.log, send one message, and you see the eight fields plus the api_retry event stream into the bridge line by line.
Book a call →Frequently asked questions
What exactly is Claude extra usage and how does it work?
Extra usage is the overflow consumption path on paid Claude plans. Once your Pro or Max plan burns through its five-hour rolling cap or its seven-day weekly cap, any further messages are billed against a separate prepaid balance at standard Anthropic API rates. You toggle it on from Settings, Usage, Manage extra usage, set a monthly spending ceiling, and Claude stops treating "limit reached" as a hard stop. Under the hood your client, whether that is claude.ai, Claude Code, Cursor, or a third-party wrapper, sees a rate_limit event per turn with an isUsingOverage boolean that flips to true and an overageStatus that reads "allowed". If overage is off or the monthly ceiling is hit, the same event returns overageStatus "rejected" instead, and the next API call comes back as a 429 with an error message whose text contains the literal phrase "out of extra usage".
Where can I see the actual rate_limit event fields in Fazm's code?
The full interface lives at acp-bridge/src/protocol.ts lines 244 to 256 in the Fazm repo. It is named RateLimitMessage and has eight fields: status (allowed, allowed_warning, rejected, unknown), resetsAt (Unix timestamp in seconds), rateLimitType (five_hour, seven_day, seven_day_opus, seven_day_sonnet, overage), utilization (0 to 1), overageStatus (allowed, rejected, or null), overageDisabledReason (string or null), isUsingOverage (boolean), surpassedThreshold (0 to 1, the warning threshold you crossed). The bridge forwards these from the Anthropic Agent SDK rate_limit_event stream verbatim. The human-readable labels are at Desktop/Sources/Providers/ChatProvider.swift lines 539 to 543: five_hour maps to "session limit", seven_day to "weekly limit", seven_day_opus to "Opus weekly limit", seven_day_sonnet to "Sonnet weekly limit", overage to "extra usage limit".
What error text does Claude actually send when extra usage is exhausted?
Fazm matches four distinct substrings in a single case-insensitive regex at acp-bridge/src/index.ts line 1903. The full pattern is /credit balance is too low|insufficient.*(credit|funds|balance)|you've hit your limit|you have hit your limit|hit your.*limit|rate.?limit.*rejected|out of extra usage|unable to verify.*membership/i. Each alternative catches a different real failure mode. "credit balance is too low" and "insufficient funds" fire on API-key accounts (Mode A) with no balance. "you've hit your limit" fires on Pro or Max accounts in the five-hour window with no extra usage enabled. "out of extra usage" fires specifically when overage was on but the monthly cap was hit. "unable to verify membership" fires when the OAuth token is stale. All four get the same credit_exhausted message type forwarded to the Swift UI at protocol.ts line 189, which is why Fazm can show one unified "usage limit" dialog regardless of which exact text Claude returned.
What is the difference between a five-hour reset and a seven-day reset?
Anthropic counts two separate windows against your plan. The five-hour window is a rolling cap: every time you send a message, it opens a five-hour window, and your message counts against that window until it rolls off. The seven-day window is a fixed weekly cap measured from your plan renewal. Fazm forwards both types as the rateLimitType field and formats the reset time at ChatProvider.swift line 549 formatResetTime into a user-local "h:mm a" string. The bridge logs the reset time as a full ISO string at index.ts line 2464 so you can verify it from /tmp/fazm-dev.log. Practically, a five-hour rejection means "wait until 11:23 PM and try again". A seven-day rejection means "wait until next Tuesday or turn on extra usage".
How does Fazm switch between Mode A (built-in key) and Mode B (your Claude plan)?
The ACP subprocess is launched at acp-bridge/src/index.ts lines 517 to 542. Mode A is active when ANTHROPIC_API_KEY is set in the environment; Mode B is active when it is not, in which case the Claude Agent SDK authenticates via OAuth PKCE (acp-bridge/src/oauth-flow.ts lines 85 to 144) and charges against your Pro or Max plan. Line 535 logs which mode is active at startup: "Mode A (Fazm API key)" or "Mode B (Your Claude Account / OAuth)". ChatProvider.createBridge at Desktop/Sources/Providers/ChatProvider.swift lines 492 to 507 picks the mode based on the bridgeMode UserDefault. When Mode A runs out of credits or Mode B hits a weekly limit, the credit_exhausted message surfaces differently in the UI: Mode A suggests "switch to your personal Claude account in Settings" (ACPBridge.swift line 1605), Mode B suggests "upgrade to Claude Pro at claude.ai" (line 1603).
Does Fazm actually distinguish the overage case from a plain rate limit in its UI?
Yes. The rate_limit payload is stored in four @Published properties on ChatProvider at Desktop/Sources/Providers/ChatProvider.swift lines 378 to 384: rateLimitStatus, rateLimitResetsAt, rateLimitType, rateLimitUtilization. When status is "allowed_warning", Fazm logs the utilization percentage and fires an analytics event (ChatProvider.swift lines 522 to 525). When status is "rejected" at line 526, Fazm logs the formatted reset time and fires a distinct rejected event. The key branch is at ACPBridge.swift line 879: Fazm does NOT throw on a rejected rate_limit because the underlying API session may still complete (for example, a five-hour rejection that the SDK retries against overage). Only the actual API-level failure, which arrives as a credit_exhausted message with the "out of extra usage" text, produces the terminal "upgrade your plan" dialog.
What HTTP status code corresponds to each failure mode?
Fazm maps HTTP status to errorType at acp-bridge/src/index.ts lines 1898 to 1902. A 402 or errorType === "billing_error" means the account ran out of credits (extra usage off or ceiling hit). A 429 or errorType === "rate_limit" means the five-hour or seven-day window was hit. Both get isStructuredCreditError === true, which short-circuits retries and surfaces the message immediately. The bridge also tracks invalid_request, authentication_failed, server_error, and image_error as separate errorType values (protocol.ts line 262). The api_retry handler at lines 2468 to 2478 records the httpStatus and errorType in the lastApiRetry module-level variable so the credit-exhaustion check can distinguish "server error, retry" from "billing problem, stop".
Why does the regex at line 1903 include both 'you\'ve hit your limit' and 'you have hit your limit'?
Because Anthropic returns both forms at different endpoints. The chat completions API uses the contraction; some Agent SDK paths unroll it to "you have hit your limit". Rather than assume the canonicalization is stable, Fazm matches both literally. The pattern also falls through to a third looser alternative, "hit your.*limit", which catches phrasing variations like "You have hit your weekly limit" or "hit your Opus weekly limit". This is defense in depth: if the structured api_retry event is missing or malformed for any reason, the regex still fires on the raw error message and the user still gets a credit_exhausted dialog instead of a generic error.
How often does Fazm actually receive a rate_limit event?
Every single Claude turn when you are on a paid plan. The SDK emits rate_limit_event as part of the streaming response before any content tokens arrive, carrying the current utilization and any threshold you just crossed. Fazm forwards all of them at index.ts line 2453 even when status is "allowed" so the Swift app can keep its progress indicators live. A typical log line from /tmp/fazm-dev.log looks like "Rate limit: status=allowed, type=five_hour, utilization=0.42, resets=2026-04-20T16:00:00.000Z". When you cross 80 percent, you start seeing status=allowed_warning. When you cross 100 percent with overage off you see status=rejected with overageStatus=rejected and the next request fails.
Can I see /tmp/fazm-dev.log on my own machine?
Yes. Fazm writes to /tmp/fazm-dev.log for development builds and /tmp/fazm.log for production. After any chat turn, tail the log and grep for "Rate limit" or "Credit/rate limit exhausted". You will see the exact structured event and the detection method (structured httpStatus=429, errorType=rate_limit vs regex) logged verbatim at index.ts lines 1905 to 1908. This is the audit trail: if a Claude message ends in an upgrade-plan dialog, the log will tell you whether the trigger was a 402 billing_error, a 429 rate_limit, or a string match on "out of extra usage" in a 500 response body.
Does overage work the same way for Claude Code and Cursor as it does for claude.ai?
No, and the difference matters. claude.ai draws from your plan first, then dips into extra usage if enabled. Third-party API clients (Claude Code, Cursor, Windsurf, Fazm in Mode B) share the same plan balance but different apps apply different retry policies. Fazm's specific retry policy is in acp-bridge/src/index.ts lines 1963 to 2000: if errorType is billing_error, rate_limit, or invalid_request, Fazm does NOT retry. It surfaces the error as credit_exhausted and the user sees the reset time plus an Upgrade to Pro link. Other wrappers aggressively retry through the 5-hour window, which burns more of your extra-usage balance than you might expect. Check each wrapper's retry code before you enable auto-reload on extra usage.
How do I turn extra usage on or off from Fazm?
You do not do it from Fazm directly. Extra usage is a plan-level setting managed at claude.ai, Settings, Usage, Manage extra usage. Fazm uses whichever plan mode you have (OAuth Mode B) or its built-in API key (Mode A). To stop Fazm from ever touching extra usage, either turn off the toggle at claude.ai or switch bridgeMode to builtin in Fazm Settings to use the bundled API key that bills independently. The bridgeMode logic is at ChatProvider.swift lines 492 to 507 and persists across app restarts. The ACP subprocess is torn down and restarted with a different environment when you flip it (line 558 comment confirms the deferred switch during in-flight queries).
Adjacent angles on the same ACP bridge file.
Related reading
Claude Computer Use Agent: the tool-schema swap that runs on a real Mac
What happens to the loop shape when you give Claude six MCP tools instead of one screenshot tool. Shared ACP bridge, same index.ts.
Claude Code API cost management
A developer-centric companion: cost management levers on top of the Claude API when you are not behind a Pro or Max plan.
OpenAI API changelog, April 2026 release notes
The same bridge that decodes Claude's rate_limit event also survives OpenAI's April 2026 wave. 1920 px, 20 image turns, and the shared wire contract.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.