Error Handling and HTTP Codes
On this page
Updated November 3, 2026 based on OpenAI documentation:
temperatureandtop_pare only accepted with a reasoning effort ofnone, whereas GPT-5.6 models reason by default (medium). Calls that set these parameters must therefore specifyreasoning={"effort": "none"}(Responses API) orreasoning_effort="none"(Chat Completions). Sources: Using GPT-6 · GPT-5.6 Terra.
Clarified November 3, 2026 based on OpenAI’s Deprecations page: the removed model example previously used
gpt-4o, which remains available until January 14, 2027; it now usesgpt-4-turbo, which was discontinued on October 23, 2026 (indicated replacement:gpt-5.6-sol). Source: Deprecations.
Error Handling and HTTP Codes
In production, errors are inevitable. The network drops, an API key expires, a colleague deploys an invalid parameter, or the API itself suffers a three-minute outage. Understanding the HTTP codes returned by the API and handling them correctly is essential for building robust applications. The entire challenge boils down to one question you must ask for every exception: will this disappear if I retry, or will I loop indefinitely on a problem that only a human can fix?
OpenAI API HTTP Codes
The API returns standard HTTP codes to indicate the result of each request. The Python SDK doesn’t let you handle these codes manually; instead, it translates each one into a typed exception that you can catch separately. This allows you to write precise except blocks rather than a catch-all except Exception that would obscure the nature of the problem.
from openai import OpenAI
from openai import (
APIError,
AuthenticationError,
BadRequestError,
RateLimitError,
APIConnectionError,
InternalServerError,
PermissionDeniedError,
NotFoundError,
UnprocessableEntityError,
)
client = OpenAI()
401 — AuthenticationError
A 401 indicates that the API key is invalid or missing. You typically see this during the first deployment when the environment variable hasn’t been propagated to the container. Retrying is useless: the key will be just as invalid ten seconds from now.
try:
# Invalid or missing API key
bad_client = OpenAI(api_key="sk-invalid")
bad_client.responses.create(
model="gpt-5.6-terra",
input="Test"
)
except AuthenticationError as e:
print(f"Code: {e.status_code}") # 401
print(f"Message: {e.message}")
# Action: verify the API key, regenerate if necessary
400 — BadRequestError
A 400 tells you the request is malformed. Here, a temperature of 5.0 is provided when the maximum accepted value is 2.0. The error message almost always names the faulty parameter, making this the quickest error to fix—provided you log it correctly rather than swallowing it silently.
try:
# Invalid parameters
client.responses.create(
model="gpt-5.6-terra",
reasoning={"effort": "none"}, # temperature requires effort none
input="Test",
temperature=5.0 # Invalid value (max 2.0)
)
except BadRequestError as e:
print(f"Code: {e.status_code}") # 400
print(f"Message: {e.message}")
# Action: correct the request parameters
429 — RateLimitError
A 429 indicates you are sending too many requests or have exceeded your quota. Unlike the previous two, this error is temporary by nature: the counter resets. This is the textbook case for a retry, but a retry that waits—and waits longer with each attempt.
try:
# Too many requests or quota exceeded
client.responses.create(
model="gpt-5.6-terra",
input="Test"
)
except RateLimitError as e:
print(f"Code: {e.status_code}") # 429
print(f"Message: {e.message}")
# Action: wait and retry with exponential backoff
500/503 — InternalServerError
500 and 503 errors come from OpenAI, not from you. Your request was valid; the infrastructure simply couldn’t process it. Retry after a delay: in the vast majority of cases, the second or third attempt succeeds.
try:
client.responses.create(
model="gpt-5.6-terra",
input="Test"
)
except InternalServerError as e:
print(f"Code: {e.status_code}") # 500 or 503
print(f"Message: {e.message}")
# Action: retry after a delay; the error is on OpenAI's side
Comprehensive Error Handler
Let’s bring these behaviors together in a single function. The logic is triage: fatal errors are converted to RuntimeError and raised immediately, transient errors trigger a wait that doubles each turn, and if the attempt budget is exhausted, the call fails cleanly rather than returning an empty value that the caller might misinterpret.
from openai import OpenAI, APIError, AuthenticationError, RateLimitError
from openai import BadRequestError, APIConnectionError, InternalServerError
import time
client = OpenAI()
def robust_api_call(prompt: str, model: str = "gpt-5.6-terra",
max_retries: int = 3) -> str:
"""API call with comprehensive error handling."""
for attempt in range(max_retries):
try:
response = client.responses.create(
model=model,
input=prompt
)
return response.output_text
except AuthenticationError:
# Do not retry — the key is invalid
raise RuntimeError("Invalid API key. Check OPENAI_API_KEY.")
except BadRequestError as e:
# Do not retry — the request is malformed
raise RuntimeError(f"Invalid request: {e.message}")
except RateLimitError:
# Retry with exponential backoff
delay = 2 ** attempt
print(f"Rate limit reached. Waiting {delay}s...")
time.sleep(delay)
except InternalServerError:
# Retry — server-side error
delay = 2 ** attempt
print(f"Server error. Attempt {attempt + 1}/{max_retries}...")
time.sleep(delay)
except APIConnectionError:
# Retry — network issue
delay = 2 ** attempt
print(f"Connection error. Attempt {attempt + 1}/{max_retries}...")
time.sleep(delay)
raise RuntimeError(f"Failed after {max_retries} attempts")
# Usage
try:
result = robust_api_call("Hello!")
print(result)
except RuntimeError as e:
print(f"Fatal error: {e}")
Responses API Specific Errors
Two situations deserve separate treatment because they appear under a generic code but call for a very specific fix.
Context Too Long
A prompt that exceeds the context window returns a 400, just like a typo in a parameter. To distinguish between the two, inspect the message: the presence of context_length_exceeded points you toward truncating the text or switching to a model with a larger context window, rather than reviewing your parameters.
try:
response = client.responses.create(
model="gpt-5.6-terra",
input="x " * 300000 # Exceeds context window
)
except BadRequestError as e:
if "context_length_exceeded" in str(e.message):
print("The prompt is too long for this model.")
print("Solutions: truncate the text, or switch to a model")
print("with a larger context window.")
Model Unavailable
Model identifiers aren’t eternal. A deployment that still references a retired model fails with a 404, causing the entire application to crash even though it worked the day before. Handle this case explicitly: it’s an automatic reminder to update your configuration.
try:
response = client.responses.create(
model="gpt-4-turbo", # retired from the API on October 23, 2026
input="Test"
)
except NotFoundError as e:
print(f"Model not found: {e.message}")
print("Use gpt-5.6-sol, the replacement indicated by OpenAI, or a newer model.")
Structured Error Logging
An unlogged error is an error you’ll discover via a customer ticket. Logging in JSON rather than free text changes everything when you need to aggregate: you can count 429s per hour, filter by response_id, or track consumption drift, which is impossible with hand-written log lines.
import logging
import json
from datetime import datetime
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("openai_client")
def call_with_log(prompt: str) -> str:
"""API call with structured logging."""
request_id = None
try:
response = client.responses.create(
model="gpt-5.6-terra",
input=prompt
)
logger.info(json.dumps({
"event": "api_success",
"model": response.model,
"tokens": response.usage.total_tokens,
"response_id": response.id,
"timestamp": datetime.now().isoformat()
}))
return response.output_text
except APIError as e:
logger.error(json.dumps({
"event": "api_error",
"status_code": e.status_code,
"message": e.message,
"timestamp": datetime.now().isoformat()
}))
raise
result = call_with_log("Hello!")
Summary Table
Keep this grid handy: it summarizes the decision to make for each code.
| Code | Exception | Retry? | Action |
|---|---|---|---|
| 400 | BadRequestError | No | Correct the request |
| 401 | AuthenticationError | No | Verify API key |
| 403 | PermissionDeniedError | No | Verify permissions |
| 404 | NotFoundError | No | Verify model/endpoint |
| 422 | UnprocessableEntityError | No | Correct the format |
| 429 | RateLimitError | Yes | Exponential backoff |
| 500 | InternalServerError | Yes | Wait and retry |
| 503 | InternalServerError | Yes | Service temporarily unavailable |
Key Takeaways
- Categorize errors: retryable (429, 500, 503) vs fatal (400, 401, 403)
- Use exponential backoff for retryable errors
- Never retry authentication or invalid request errors
- Log every error with context (code, message, timestamp)
- The SDK automatically handles 2 retries — configure
max_retriesif needed