From f1d4ffc0a7b2d7f804139ec627eda0153e6620d2 Mon Sep 17 00:00:00 2001 From: 72004 Date: Fri, 21 Aug 2026 01:26:59 +0500 Subject: [PATCH 1/2] fix: map Anthropic API usage-limit errors to RateLimitError (429) not BadRequest (400) --- .../litellm_core_utils/exception_mapping_utils.py | 8 ++++++++ .../test_exception_mapping_utils.py | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index d23466938f2..f8b2bd24e5c 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -68,6 +68,14 @@ class ExceptionCheckers: if "service tier capacity exceeded" in _error_str_lower: return True + ####################################### + # Anthropic returns spend/usage-limit exhaustion typed as an + # invalid_request_error (HTTP 400), e.g. "You have reached your specified + # API usage limits." It is a usage limit and should map to 429. (#37599) + ####################################### + if "api usage limit" in _error_str_lower: + return True + return False @staticmethod diff --git a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py index d5676aaf288..605d6991567 100644 --- a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py @@ -167,6 +167,18 @@ class TestExceptionCheckers: is True ) + def test_anthropic_usage_limit_is_treated_as_rate_limit(self): + """Anthropic reports spend/usage-limit exhaustion as an invalid_request_error + (HTTP 400), but it is a usage limit and should map to 429, not 400 (#37599). + """ + error_str = ( + "litellm.BadRequestError: AnthropicException - " + '{"type":"error","error":{"type":"invalid_request_error",' + '"message":"You have reached your specified API usage limits. ' + 'You will regain access on 2026-09-01 at 00:00 UTC."}}' + ) + assert ExceptionCheckers.is_error_str_rate_limit(error_str, status_code=400) is True + def test_is_azure_content_policy_violation_error_with_policy_violation_text(self): """Test detection of Azure content policy violation with explicit policy violation text""" From a6ac6097f56ac0e3e4de33f70383dc4b9e0f6a3d Mon Sep 17 00:00:00 2001 From: 72004 Date: Fri, 21 Aug 2026 22:11:38 +0500 Subject: [PATCH 2/2] fix(anthropic): route usage-limit 400 through rate-limit detection The helper change alone was not on the Anthropic exception path: _map_anthropic_exception maps the invalid_request_error (400) usage-limit response to BadRequestError before any rate-limit check runs. Call is_error_str_rate_limit inside the Anthropic mapper, ahead of the 400 branches, so it surfaces as RateLimitError (429) as documented. Add an end-to-end test driving exception_type for the reported payload. --- .../exception_mapping_utils.py | 11 ++++++++ .../test_exception_mapping_utils.py | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index f8b2bd24e5c..df297ef0881 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -544,6 +544,17 @@ def _map_anthropic_exception( model=model, llm_provider="anthropic", ) + # Anthropic returns usage/spend-limit exhaustion typed as invalid_request_error (400), + # but per the exception-mapping docs it should surface as a RateLimitError (429). Check + # before the generic 400 branches below, which would otherwise map it to BadRequestError. + if ExceptionCheckers.is_error_str_rate_limit( + error_str, getattr(original_exception, "status_code", None) + ): + raise RateLimitError( + message=f"AnthropicError - {error_str}", + model=model, + llm_provider="anthropic", + ) if "Client error '400 Bad Request'" in error_str: raise BadRequestError( message=f"AnthropicError - {error_str}", diff --git a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py index 605d6991567..606ca5b971f 100644 --- a/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py +++ b/tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py @@ -179,6 +179,31 @@ class TestExceptionCheckers: ) assert ExceptionCheckers.is_error_str_rate_limit(error_str, status_code=400) is True + def test_anthropic_usage_limit_maps_to_rate_limit_error_end_to_end(self): + """Production path: an Anthropic usage-limit error is typed invalid_request_error + (HTTP 400). Driving it through exception_type must surface a RateLimitError (429), + not a BadRequestError (#37599) — the helper alone is not on the Anthropic mapper. + """ + message = ( + "AnthropicException - " + '{"type":"error","error":{"type":"invalid_request_error",' + '"message":"You have reached your specified API usage limits. ' + 'You will regain access on 2026-09-01 at 00:00 UTC."},' + '"request_id":"req_011CeDfeyYs7N6neZrLwFxPJ"}' + ) + original_exception = httpx.HTTPStatusError( + message=message, + request=httpx.Request("POST", "https://api.anthropic.com/v1/messages"), + response=httpx.Response(400), + ) + + with pytest.raises(litellm.RateLimitError): + exception_type( + model="claude-haiku-4-5-20251001", + original_exception=original_exception, + custom_llm_provider="anthropic", + ) + def test_is_azure_content_policy_violation_error_with_policy_violation_text(self): """Test detection of Azure content policy violation with explicit policy violation text"""