From 5a74d1350825b508a06f7bcc54ebf4ab90aee238 Mon Sep 17 00:00:00 2001 From: majianhan Date: Wed, 1 Apr 2026 22:55:49 +0800 Subject: [PATCH] feat: add InsufficientQuotaError for 429 insufficient_quota responses Add a new InsufficientQuotaError exception that subclasses RateLimitError, allowing users to distinguish between temporary rate limits and quota exhaustion (insufficient_quota error code). This is backward-compatible since InsufficientQuotaError extends RateLimitError. Closes #1671 --- src/openai/__init__.py | 2 ++ src/openai/_client.py | 4 ++++ src/openai/_exceptions.py | 11 +++++++++++ 3 files changed, 17 insertions(+) diff --git a/src/openai/__init__.py b/src/openai/__init__.py index b2093ada68..c62401236f 100644 --- a/src/openai/__init__.py +++ b/src/openai/__init__.py @@ -21,6 +21,7 @@ NotFoundError, APIStatusError, RateLimitError, + InsufficientQuotaError, APITimeoutError, BadRequestError, APIConnectionError, @@ -62,6 +63,7 @@ "ConflictError", "UnprocessableEntityError", "RateLimitError", + "InsufficientQuotaError", "InternalServerError", "LengthFinishReasonError", "ContentFilterFinishReasonError", diff --git a/src/openai/_client.py b/src/openai/_client.py index aadf3601f2..1421e3159b 100644 --- a/src/openai/_client.py +++ b/src/openai/_client.py @@ -451,6 +451,8 @@ def _make_status_error( return _exceptions.UnprocessableEntityError(err_msg, response=response, body=data) if response.status_code == 429: + if is_mapping(data) and data.get("code") == "insufficient_quota": + return _exceptions.InsufficientQuotaError(err_msg, response=response, body=data) return _exceptions.RateLimitError(err_msg, response=response, body=data) if response.status_code >= 500: @@ -826,6 +828,8 @@ def _make_status_error( return _exceptions.UnprocessableEntityError(err_msg, response=response, body=data) if response.status_code == 429: + if is_mapping(data) and data.get("code") == "insufficient_quota": + return _exceptions.InsufficientQuotaError(err_msg, response=response, body=data) return _exceptions.RateLimitError(err_msg, response=response, body=data) if response.status_code >= 500: diff --git a/src/openai/_exceptions.py b/src/openai/_exceptions.py index 09016dfedb..f2d7208232 100644 --- a/src/openai/_exceptions.py +++ b/src/openai/_exceptions.py @@ -21,6 +21,7 @@ "ConflictError", "UnprocessableEntityError", "RateLimitError", + "InsufficientQuotaError", "InternalServerError", "LengthFinishReasonError", "ContentFilterFinishReasonError", @@ -129,6 +130,16 @@ class RateLimitError(APIStatusError): status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride] +class InsufficientQuotaError(RateLimitError): + """Raised when the API returns a 429 status code with an 'insufficient_quota' error code. + + This is distinct from a rate limit error — it indicates that the account's + quota has been exhausted rather than a temporary rate limit being hit. + """ + + status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride] + + class InternalServerError(APIStatusError): pass