n4nAI

How to call GPT-4o from a Flask route

Practical walkthrough for adding a Flask route that performs a GPT-4o API call via the OpenAI SDK, with retries, timeouts, and verification.

n4n Team3 min read663 words

Audio narration

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

Wiring a flask route gpt-4o api call is straightforward if you treat the LLM as a latency-bound external service rather than a local function. This guide builds a minimal Flask endpoint that accepts a prompt, calls GPT-4o through the OpenAI SDK, and returns structured JSON you can consume from any client.

Step 1: Scaffold the project

Create a virtual environment and install the only two runtime deps you need:

mkdir flask-gpt4o && cd flask-gpt4o
python -m venv .venv
source .venv/bin/activate
pip install flask openai python-dotenv

Keep the surface area small. Flask serves the HTTP layer; the OpenAI SDK handles the API contract. You don’t need a heavy framework to wrap a single model call.

Write your dependencies to a lockfile so deployments are reproducible:

pip freeze > requirements.txt

A clean project layout looks like this:

flask-gpt4o/
├── app.py
├── .env
└── requirements.txt

Step 2: Store credentials outside code

Put your API key in a .env file. Never commit it.

echo "OPENAI_API_KEY=sk-..." > .env

Load it at startup:

from dotenv import load_dotenv
import os

load_dotenv()
API_KEY = os.getenv("OPENAI_API_KEY")

If you’d rather hit a single OpenAI-compatible endpoint that covers 240+ models with automatic fallback when a provider is rate-limited, set base_url to n4n.ai and keep the same SDK calls. The rest of the code is identical.

You can also override the base URL via env var to switch providers without code changes:

BASE_URL = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
client = OpenAI(api_key=API_KEY, base_url=BASE_URL)

Step 3: Write the Flask route

The core flask route gpt-4o api call lives in app.py. Accept JSON, call the model, return JSON. Add a health check so orchestrators can probe the service.

from flask import Flask, request, jsonify
from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()
app = Flask(__name__)
client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"),
)

@app.get("/health")
def health():
    return jsonify(status="ok")

@app.post("/v1/complete")
def complete():
    data = request.get_json(silent=True) or {}
    prompt = data.get("prompt", "").strip()
    if not prompt:
        return jsonify(error="prompt required"), 400

    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2,
        max_tokens=512,
    )
    return jsonify(
        text=resp.choices[0].message.content,
        usage=resp.usage.model_dump(),
    )

This is a synchronous call. Flask will block the worker while GPT-4o thinks, so size your worker pool accordingly (more in Step 8).

Step 4: Add timeouts and basic retries

GPT-4o calls routinely take 1–10 seconds. Set an explicit client timeout so a hung connection doesn’t eat a worker forever.

client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"),
    timeout=20.0,
    max_retries=2,
)

The SDK retries idempotent failures (connection errors, 429, 500) automatically. For application-level control, wrap the call:

from openai import APIError

def call_gpt4o(prompt: str) -> dict:
    try:
        resp = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=512,
        )
        return {"text": resp.choices[0].message.content, "usage": resp.usage.model_dump()}
    except APIError as e:
        # surface a clean 502 to the caller
        raise RuntimeError(f"upstream_error: {e}") from e

If you need custom backoff, drop in tenacity:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def call_gpt4o_retry(prompt: str) -> dict:
    return call_gpt4o(prompt)

Step 5: Validate and bound input

Don’t let clients send 50k-token essays to a quick endpoint. Enforce a ceiling. For stricter contracts, use pydantic:

from pydantic import BaseModel, constr

class CompleteRequest(BaseModel):
    prompt: constr(strip_whitespace=True, min_length=1, max_length=4000)

@app.post("/v1/complete")
def complete():
    data = request.get_json(silent=True) or {}
    try:
        req = CompleteRequest(**data)
    except ValueError as e:
        return jsonify(error="invalid_prompt", detail=str(e)), 400

    try:
        result = call_gpt4o_retry(req.prompt)
    except RuntimeError as e:
        return jsonify(error=str(e)), 502

    return jsonify(result)

Manual checks are fine for a single field, but pydantic catches nested mistakes early and documents the API.

Step 6: Run and verify the route

Start the dev server:

flask --app app run --port 5000

From another shell, send a request:

curl -s -X POST http://localhost:5000/v1/complete \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Explain IPv6 in one sentence."}' | jq

A successful flask route gpt-4o api call returns HTTP 200 and a body like:

{
  "text": "IPv6 is the successor to IPv4 that uses 128-bit addresses to support a vastly larger number of devices on the internet.",
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 24,
    "total_tokens": 36
  }
}

Verification checklist:

  • Status code is 200.
  • text is non-empty and coherent.
  • usage.total_tokens is greater than zero, confirming the call hit the model.

If you get a 401, the key is wrong. A 429 means you’re over rate limits—back off or use a gateway with fallback.

Add a smoke test with pytest so regressions surface in CI:

def test_complete(client):
    rv = client.post("/v1/complete", json={"prompt": "ping"})
    assert rv.status_code == 200
    assert "text" in rv.json

Step 7: Stream tokens to the client

For chat UIs, blocking on full generation feels slow. Switch to streaming:

@app.post("/v1/stream")
def stream():
    data = request.get_json(silent=True) or {}
    prompt = (data.get("prompt") or "").strip()
    if not prompt:
        return jsonify(error="prompt required"), 400

    def gen():
        stream = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
            stream=True,
        )
        for chunk in stream:
            if chunk.choices[0].delta.content:
                yield chunk.choices[0].delta.content

    return app.response_class(gen(), mimetype="text/plain")

Test it with curl:

curl -N -X POST http://localhost:5000/v1/stream \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Count to 5."}'

Streaming keeps the Flask worker occupied longer per request, so use it only when the client benefits.

Step 8: Production deployment notes

Flask’s built-in server is not for production. Run under gunicorn with multiple workers:

gunicorn -w 4 -b 0.0.0.0:5000 --timeout 30 app:app

Set --timeout 30 to match your upstream timeout. If you expect high concurrency, consider moving the GPT-4o call to a task queue (Celery/RQ) or using an async framework (Quart/FastAPI). The flask route gpt-4o api call itself stays the same; only the serving model changes.

A minimal Dockerfile:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "--timeout", "30", "app:app"]

Monitor token usage. The usage field in the response gives per-request counts—ship it to your metrics pipeline. If you meter per token at the gateway layer, you can skip client-side accounting.

Common failure modes

  • Missing Content-Type: application/jsonrequest.get_json() returns None. Use silent=True and default to {}.
  • Cold worker timeout → gunicorn kills the request before GPT-4o responds. Raise both gunicorn and SDK timeouts.
  • Unhandled streaming disconnect → client closes connection mid-stream; wrap gen() in try/except to avoid logging noise.
  • Key rotation → env var not reloaded after restart; bake config into the deployment, not the running process.

That’s the full path from empty directory to a working, verifiable Flask endpoint that calls GPT-4o. Keep the route thin, push resilience to the client config, and treat the model as a remote service with real latency and failure modes.

Tagsflaskgpt-4orouteapi-integration

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 →

All flask + llm api tutorials posts →