M. Patel12 min read7 views

Claude rate limit headers measure two things two ways

The anthropic-ratelimit headers are not one meter. Input is an estimate that gets corrected, output is a running tally, and the tokens header reports whichever limit currently binds.

Flat schematic on deep navy: three horizontal meter tracks filled to three different lengths, with a lime vertical line marking the shortest, showing one axis carrying three different readings.
Flat schematic on deep navy: three horizontal meter tracks filled to three different lengths, with a lime vertical line marking the shortest, showing one axis carrying three different readings.
On this page

Quick answer

As of September 23, 2026, the anthropic-ratelimit-* response headers on the Claude API are not one meter with one clock. Input and output limits are measured by opposite methods, the tokens-* trio reports whichever limit currently binds rather than a fixed quantity, and the configured ceiling you would need in order to turn any of it into a percentage is not in the response at all. It lives behind a separate admin-only endpoint.

Most writing about Claude API rate limits is about what to do after a 429 arrives. This is about what the telemetry says before one does, and about the four places where the readout means something other than it appears to.

The moment

I was staring at a dashboard panel that graphed anthropic-ratelimit-tokens-remaining over time for a batch worker. Sensible thing to graph. It had been a flat, boring sawtooth for two weeks.

Then it stepped down by roughly an order of magnitude and stayed there. No traffic change. No deploy. No 429s either, which was the confusing part. I spent an afternoon looking for the new workload before I read the header definition properly and realised the header had not changed value so much as changed subject.

The panel had been averaging two different quantities on one axis for a fortnight, and I had been reading the average as a trend.

Finding 1: Input and output limits are measured by opposite methods

This is the piece I had assumed was symmetric, and it is not. From Anthropic's rate limits documentation:

On input, verbatim: "ITPM rate limits are estimated at the beginning of each request, and the estimate is adjusted during the request to reflect the actual number of input tokens used."

On output, verbatim: "OTPM rate limits are evaluated in real time as output tokens are produced, counting only the actual tokens generated."

So input is a forecast that gets corrected, and output is a running tally. One of those two numbers is provisional at the instant you read it and the other is not.

The practical consequence lands on max_tokens. The same page continues, verbatim: "The max_tokens parameter does not factor into OTPM rate limit calculations, so there is no rate limit downside to setting a higher max_tokens value."

That sentence retires a habit a lot of us picked up: trimming max_tokens to avoid burning output quota on a request that will not use it. Under the documented behaviour there is nothing to burn, because nothing is reserved. max_tokens remains a real ceiling on the response, and it remains a reason to stream rather than block, but it is not a rate-limit lever. If you are carrying a comment in your codebase that says otherwise, it is describing a mechanism that the current documentation does not describe. I had one.

Note what this does to the two halves together: the meter you can influence up front (input, via caching) is the estimated one, and the meter you cannot influence up front (output) is the exact one.

Finding 2: The tokens header reports whichever limit binds

Here is the sentence that explains my dashboard. From the same page, verbatim:

On what the header contains: "The anthropic-ratelimit-tokens-* headers display the values for the most restrictive limit currently in effect. For instance, if you have exceeded the Workspace per-minute token limit, the headers will contain the Workspace per-minute token rate limit values. If Workspace limits do not apply, the headers will return the total tokens remaining, where total is the sum of input and output tokens."

Read that twice. The tokens-remaining header is a combined input-plus-output figure in one response and a workspace-scoped figure in another, under the identical header name, with nothing in the response marking the switch. Which one you get depends on which constraint currently binds.

That is fine as a design. It is the right value to show a human who wants to know what is about to stop them. It is a trap for anything that stores the number, because a time series of it is a time series of a changing definition.

Two things follow.

First, if you want a stable series, graph the explicitly scoped headers instead. Those exist and they do not move around:

text
anthropic-ratelimit-input-tokens-limit
anthropic-ratelimit-input-tokens-remaining
anthropic-ratelimit-input-tokens-reset
anthropic-ratelimit-output-tokens-limit
anthropic-ratelimit-output-tokens-remaining
anthropic-ratelimit-output-tokens-reset

The docs describe input-tokens-remaining as "The number of input tokens remaining (rounded to the nearest thousand) before being rate limited." Note the rounding. At a small limit, a thousand-token quantum is a coarse instrument, and it is coarse on both the input and output series.

Second, if you keep the tokens-* series anyway, you need to know which scope produced each sample, and there is a header for exactly that. The rate-limits page points at anthropic-workspace-id, describing it as carrying "the ID of the Workspace that your API key or access token resolved to." Capturing it alongside the sample is what makes the series interpretable after the fact. Without it, a step change in the graph is genuinely ambiguous between "we got busier" and "a different limit started binding", and I could not distinguish those two for an afternoon.

Anthropic Reading response headers is not exotic in any SDK. In Python Python it is with_raw_response; in curl curl it is -D -. The Claude errors documentation notes that the same mechanisms "read any other response header too, such as anthropic-organization-id and anthropic-workspace-id." The problem was never access. It was that I never asked what the number was scoped to.

Finding 3: The denominator is not in the response

A remaining count is only useful against a ceiling, and the -limit headers do give you one. But they give you the ceiling of whichever limit is currently binding, which inherits the problem above.

If you want the configured limits, as configured, there is a separate endpoint. Anthropic's Rate Limits API describes itself as providing "programmatic access to the rate limits configured for your organization and its workspaces", and names the use case directly: "Keep gateways and proxies in sync: Read your current limits at startup and on a schedule instead of hardcoding values that drift when Anthropic adjusts them."

Three properties of it that changed how I would wire it up.

It is admin-scoped. The docs are explicit that "workspace API keys don't work". You need an Admin API key, an OAuth token with org:admin, or an unscoped personal or service account key. That means the credential your worker uses to call the Messages API is very likely not the credential that can read its own limits, so this is a separate startup path with a separate secret, not a line you add to an existing client.

It is read-only. The page's own FAQ: "Can I update rate limits with this API? No."

Absence means inherited, not unlimited. This is the one I would have got wrong. The workspace endpoint returns only overrides, and the docs spell out the consequence: "A group that is absent from data has no workspace override at all. The workspace inherits the organization-level limits for that group (it is not unlimited)." A naive sync that treats a missing group as "no constraint here" builds a gateway that believes it has infinite headroom on exactly the groups nobody bothered to override.

bash
curl "https://api.anthropic.com/v1/organizations/rate_limits?model=claude-opus-5" \
  -H "x-api-key: $ANTHROPIC_ADMIN_KEY" \
  -H "anthropic-version: 2023-06-01"

The response groups models, and the grouping is not what the model names suggest. The documented footnotes say the Opus 4.x limit "applies to combined traffic across Claude Opus 4.8, Opus 4.7, Opus 4.6, and Opus 4.5", while "Claude Opus 5.5 and Claude Opus 5 each have a separate rate limit and are not part of this combined bucket." Sonnet 5 likewise sits outside the Sonnet 4.x pool. If you are load-shedding across model versions on the assumption that they share a bucket, the grouping is worth reading from the API rather than inferring from the version numbers, because it does not follow them.

Finding 4: The Messages headers do not cover everything you spend

The anthropic-ratelimit-* headers on a Messages response describe the Messages API. Several other things you are probably doing draw on pools those headers never mention. From the rate limits page and its own sections:

  • Message Batches has "its own set of rate limits which are shared across all models", including a cap on how many batch requests can sit in the processing queue at once.
  • Managed Agents endpoints are "rate-limited per organization" and "separate from the Messages API rate limits", documented at 300 requests per minute for create operations and 1,200 for reads.
  • Files API requests have "their own per-organization limit".
  • Fast mode carries "dedicated rate limits that apply that are separate from standard Opus rate limits", and reports through a different header family, anthropic-fast-*.

The Rate Limits API exposes these as group types, and the documented set is model_group, batch, token_count, files, skills, and web_search. Six categories. A monitoring setup built entirely from Messages response headers is watching one of them.

One more scoping note that surprised me: the docs state that "Rate limits are currently shared across all inference_geo values", so splitting traffic between us and global does not split the pool.

Finding 5: The failure with no status code and no headers

Everything above assumes you got a response with headers on it. There is one common case where you do not, and it is the case that gets more common the more you stream.

From the errors documentation, verbatim: "When receiving a streaming response over server-sent events (SSE), an error can occur after the API returns a 200 response. In that case, error handling doesn't follow these standard mechanisms."

The headers were sent with that 200. They were accurate when sent. The failure arrives afterwards, inside the event stream, and a retry policy that branches on HTTP status is structurally unable to see it. If your pipeline streams, the mid-stream error path is a separate branch from the status-code path, not a subset of it. My colleagues at Claude AgentNotebook have gone deep on the mechanics of consuming that event stream in their tool-call streaming tutorial, which is adjacent to this piece rather than overlapping it: they cover how to read the stream correctly, and this covers what the rate-limit layer is and is not telling you while it runs.

The other half of this finding is about retries you did not write. The errors page states that the official SDKs "automatically retry transient failures (such as connection errors, rate limits, and 5xx server errors) with exponential backoff, twice by default, honoring the retry-after header when present."

That is good default behaviour and it is wrong in one specific case. Not every 429 is a rate limit. The rate-limits page documents a 429 returned when an organization reaches its tier spend cap, and notes of it: "The error type is rate_limit_error, the same as for a rate limit, but the response has no retry-after header. Retrying, including the SDKs' automatic retries, fails until access resumes." The discriminator it gives is error.details.error_code being enforced_spend_limit_reached.

So the SDK will quietly spend two extra requests against a condition that cannot clear before the month does. Two requests is nothing. Two requests multiplied by every worker in a fleet that all hit the cap in the same minute is the shape of an incident, and the only signal separating it from an ordinary rate limit is a field one level down in the error body.

There is also a 529, which is a different thing entirely: overloaded_error, documented simply as "The API is temporarily overloaded." That one is about capacity across all users and has nothing to do with your limits, so your headers will look perfectly healthy while it happens.

What I am carrying forward

The thing I actually changed is smaller than this article. I stopped graphing anthropic-ratelimit-tokens-remaining and started graphing the input and output series separately, with anthropic-workspace-id captured on every sample so I can tell later which scope each point came from. That is a ten-line diff and it would have saved me the afternoon.

The broader habit is one I have written about before in a different context, when I found that a task budget caps a turn rather than a task: with this API, the unit a number is measured over is usually the thing I have assumed rather than checked, and it is usually the thing that makes the number confusing.

What I did not verify

Being explicit, because this piece is documentation-derived rather than incident-derived.

  • I did not reproduce a live 429 on my own account and inspect the headers on it. Every header semantic above is quoted from Anthropic's published documentation as of September 23, 2026, not observed in a failure.
  • I did not call the Rate Limits API. It needs Admin API credentials that this account does not have, so the endpoint shape, the grouping behaviour and the overrides-only response are quoted from the docs rather than exercised.
  • I did not check whether the -reset timestamps are accurate in practice, or how they behave under the token-bucket replenishment the docs describe.
  • I did not test the Priority Tier header family (anthropic-priority-input-tokens-* and the output equivalents). Those are documented and I have no Priority Tier access to confirm them against.
  • I did not measure the rounding behaviour. The docs say remaining token counts are rounded to the nearest thousand; I have not checked what that does to a low-limit account in practice.
  • Rate limit values change, and the tier tables in the docs are worth reading fresh rather than trusting from any article, including this one. The mechanisms are what I would expect to outlast the numbers.

Postscript: the header was never wrong. It answered the question it was designed to answer, which was not the question my dashboard was asking it.

M

Written by

M. Patel

Frequently asked questions

Does lowering max_tokens help avoid Claude API rate limits?

No. Anthropic's rate limits documentation states that OTPM limits are evaluated in real time counting only the tokens actually generated, and that max_tokens does not factor into OTPM rate limit calculations, so there is no rate limit downside to setting a higher value. It is still a real ceiling on the response and still a reason to stream, but it is not a rate-limit lever.

Why did anthropic-ratelimit-tokens-remaining change by an order of magnitude with no traffic change?

Because that header reports the most restrictive limit currently in effect, not a fixed quantity. If a workspace limit binds, it carries workspace values; if not, it carries the combined input-plus-output total. The header name does not change when the scope does. Graph the explicitly scoped input-tokens and output-tokens series instead, and capture anthropic-workspace-id alongside each sample.

Are ITPM and OTPM measured the same way?

No, they are measured by opposite methods. The documentation says ITPM is estimated at the beginning of each request and the estimate is adjusted during the request, while OTPM is evaluated in real time as output tokens are produced. The input figure is provisional at the instant you read it and the output figure is not.

How do I read my configured Claude API rate limits programmatically?

Through the Rate Limits API at /v1/organizations/rate_limits, which returns the limits configured for your organization and workspaces. It requires Admin API credentials, workspace API keys do not work, and it is read-only. On the workspace endpoint a group that is absent has no override and inherits the organization limit rather than being unlimited.

Will an SDK retry fix a 429 from the spend cap?

No. The SDKs retry transient failures twice by default honoring retry-after, but the tier spend-cap 429 carries the same rate_limit_error type with no retry-after header, and the documentation states that retrying, including the SDKs' automatic retries, fails until access resumes. The discriminator is error.details.error_code being enforced_spend_limit_reached.

Your JSON schema is a second cached artifact

Structured outputs and strict tool use turn your JSON schema into a separate cached artifact. It has its own 24 hour lifetime, invalidation rules that invert what you would guess, and a retention boundary that zero data retention does not cover.

10 min read18

Context engineering is four mechanisms, not one

Context engineering on the Claude platform is not a setting. It is four mechanisms at four points in the pipeline, and the one everybody enables first pays for context window with prompt cache.

14 min read20