n4nAI

API integration

Guide

Error handling for OpenAI API calls in Flask

Practical flask error handling openai api patterns for production: timeouts, retries, rate limits, and structured responses to build reliable LLM apps.

n4n Team3 min read674 words

Audio narration

Coming soon — every post will get a voice note here.

Wrapping LLM calls in a web service means the unstable part is the outbound request, not your Flask handler. Good flask error handling openai api begins with treating every call as a fallible network operation that can fail for reasons outside your code: packet loss, provider outages, token limits, or a malformed prompt.

1. Start with a naive route and its gaps

A minimal Flask endpoint that calls the OpenAI Chat Completions API looks like this:

from flask import Flask, jsonify, request
from openai import OpenAI

app = Flask(__name__)
client = OpenAI()  # uses OPENAI_API_KEY from env

@app.post("/v1/summarize")
def summarize():
    text = request.json.get("text", "")
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"Summarize: {text}"}],
    )
    return jsonify(summary=resp.choices[0].message.content)

This works in a demo. In production it will hang, crash, or leak secrets. There is no timeout, no retry, and any SDK exception becomes a 500 with a stack trace.

2. Classify the failures you will actually see

The OpenAI Python SDK (v1.x) raises specific exceptions. Catch them by type, not by string matching.

from openai import (
    APIConnectionError,   # DNS, TCP, TLS failure
    APITimeoutError,      # request exceeded client timeout
    RateLimitError,       # HTTP 429
    AuthenticationError,  # HTTP 401
    BadRequestError,      # HTTP 400 (bad params, context length)
    InternalServerError,  # HTTP 500/502/503 from provider
)

Connection and timeout errors are transient. Rate limits are semi-transient: wait and retry. Authentication and bad request errors are permanent for that exact call—retrying wastes quota and latency.

A common pitfall is catching Exception broadly and retrying everything. That turns a bad prompt into three failed requests and a confused user.

3. Set explicit timeouts

The default OpenAI client has no aggressive timeout. A stuck connection can block your Flask worker until the OS gives up.

client = OpenAI(timeout=8.0)  # total request timeout in seconds

If your generation can take longer, set timeout per request:

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[...],
    timeout=20.0,
)

Tradeoff: too short a timeout on a slow model yields false failures. Measure p95 latency for your model and set timeout at roughly 2× that, capped by your inbound reverse proxy’s limit (e.g., Nginx proxy_read_timeout).

4. Retry only the right things

Use a retry decorator that filters on exception type. tenacity is the standard library for this.

from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type

@retry(
    retry=retry_if_exception_type((
        APIConnectionError, APITimeoutError,
        InternalServerError, RateLimitError
    )),
    wait=wait_exponential(multiplier=0.5, min=1, max=8),
    stop=stop_after_attempt(3),
    reraise=True,
)
def chat_complete(prompt):
    return client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
    )

reraise=True ensures the final exception still propagates to your Flask error handler. Retrying RateLimitError is safe because the provider already rejected the call—no tokens were spent.

Pitfall: do not retry BadRequestError. If the context length is exceeded, resending the same payload will fail identically.

5. Honor rate-limit signals

When OpenAI returns 429, the response often includes a Retry-After header (in seconds). Respect it instead of blind exponential backoff.

from flask import current_app

try:
    chat_complete(prompt)
except RateLimitError as e:
    ra = e.response.headers.get("Retry-After") if e.response else None
    wait_s = float(ra) if ra else 2.0
    current_app.logger.warning("openai 429, retry_after=%s", wait_s)
    # either sleep here or push to a queue

If you serve many users, a single Flask process sleeping on time.sleep(wait_s) holds a worker. Prefer a task queue (RQ, Celery) for anything beyond a few hundred ms of expected delay.

6. Return structured JSON errors

Never return the SDK exception string to the browser. Map exceptions to a stable schema:

@app.post("/v1/summarize")
def summarize():
    try:
        text = request.json.get("text", "")
        resp = chat_complete(text)
        return jsonify(summary=resp.choices[0].message.content)
    except RateLimitError:
        return jsonify(error="rate_limited", detail="Upstream limit reached"), 429
    except (APIConnectionError, APITimeoutError, InternalServerError):
        return jsonify(error="upstream_unavailable", detail="LLM provider error"), 502
    except AuthenticationError:
        return jsonify(error="auth_failed", detail="Invalid key"), 500
    except BadRequestError as e:
        return jsonify(error="bad_request", detail=str(e)), 400
    except Exception:
        current_app.logger.exception("unexpected")
        return jsonify(error="internal"), 500

This keeps your API contract clean and lets frontends distinguish “try later” from “fix your input”.

7. Move long generations off the request path

Flask’s synchronous model means a 30-second LLM call ties up a worker. If you expect long completions, accept the request, enqueue a job, and poll or webhook.

from rq import Queue
from redis import Redis

q = Queue("llm", connection=Redis())

@app.post("/v1/summarize-async")
def summarize_async():
    job = q.enqueue(chat_complete, request.json["text"])
    return jsonify(job_id=job.id), 202

Tradeoff: you now run Redis, a worker, and a status endpoint. For low traffic, a threaded Flask dev server with timeout=30 might suffice, but don’t ship that to production.

8. Consider an OpenAI-compatible gateway

If you route through an OpenAI-compatible endpoint such as n4n.ai, automatic fallback when a provider is rate-limited or degraded handles part of the reliability story. The gateway may also honor client routing directives and provider cache-control hints. You still must set client timeouts and catch APITimeoutError—the gateway can’t save a dead local network interface.

9. Log what matters

Capture the error class, model, and latency. Avoid logging full prompts if they contain PII.

import time
from flask import current_app

def logged_complete(prompt, model):
    start = time.monotonic()
    try:
        return chat_complete(prompt)
    except Exception as e:
        current_app.logger.error(
            "openai_call_failed model=%s err=%s ms=%.0f",
            model, type(e).__name__, (time.monotonic()-start)*1000
        )
        raise

Dashboards on error rate by type will tell you whether you’re fighting rate limits or bad inputs.

10. Test the unhappy paths

Mock the SDK to assert your flask error handling openai api logic actually triggers.

from unittest.mock import patch
from openai import APITimeoutError

@patch("openai.OpenAI.chat.completions.create")
def test_timeout_returns_502(mock_create, client):
    mock_create.side_effect = APITimeoutError("timed out")
    resp = client.post("/v1/summarize", json={"text": "hi"})
    assert resp.status_code == 502
    assert resp.json["error"] == "upstream_unavailable"

Without these tests, the first time you see a real 429 is in a production incident.

Common pitfalls summary

  • No timeout: worker exhaustion.
  • Retrying 4xx: wasted quota, confusing loops.
  • Leaking exceptions: information disclosure.
  • Sleeping on 429 in request: latency spikes and cascading failures.
  • Ignoring Retry-After: avoidable throttling.

Solid flask error handling openai api is not about catching errors—it’s about classifying them, retrying the right ones, and returning a contract your clients can code against. Do that, and your LLM features stay up when the provider hiccups.

Tagsflaskerror-handlingopenai-apireliability

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →