Skip to main content
Lesson 12 of 22 · Responses API: OpenAI Fundamentals

Error Handling and HTTP Codes

Lesson 12 of 22 Updated November 3, 2026 Developer
On this page
  1. Error Handling and HTTP Codes
  2. OpenAI API HTTP Codes
  3. Comprehensive Error Handler
  4. Responses API Specific Errors
  5. Structured Error Logging
  6. Summary Table
  7. Key Takeaways

Updated November 3, 2026 based on OpenAI documentation: temperature and top_p are only accepted with a reasoning effort of none, whereas GPT-5.6 models reason by default (medium). Calls that set these parameters must therefore specify reasoning={"effort": "none"} (Responses API) or reasoning_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 uses gpt-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.

CodeExceptionRetry?Action
400BadRequestErrorNoCorrect the request
401AuthenticationErrorNoVerify API key
403PermissionDeniedErrorNoVerify permissions
404NotFoundErrorNoVerify model/endpoint
422UnprocessableEntityErrorNoCorrect the format
429RateLimitErrorYesExponential backoff
500InternalServerErrorYesWait and retry
503InternalServerErrorYesService 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_retries if needed