Deploying a Python Function on Vercel for a Model Call
10 min read · updated August 11, 2026
The Python handler is the easy part. What decides whether a Python function deploys and stays deployable on Vercel is the entrypoint convention, the version pin and the bundle — because Vercel documents that there is no automatic tree-shaking for Python and everything reachable at build time is included.
Where Vercel looks for your code
Vercel’s Python runtime reference documents two distinct shapes, and mixing them up is the first thing that goes wrong.
A single serverless function
A .py file inside an /api directory that defines a handler inheriting from BaseHTTPRequestHandler becomes one Vercel Function on its own:
# api/complete.py
import json
import os
import urllib.request
from http.server import BaseHTTPRequestHandler
class handler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("content-length", 0))
payload = json.loads(self.rfile.read(length) or b"{}")
key = os.environ.get("OPENAI_API_KEY")
if not key:
self.send_response(500)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b'{"error":"OPENAI_API_KEY not set"}')
return
request = urllib.request.Request(
"https://api.openai.com/v1/responses",
data=json.dumps(
{"model": "gpt-4o-mini", "input": payload.get("prompt", "")}
).encode("utf-8"),
headers={
"Content-Type": "application/json",
"Authorization": "Bearer " + key,
},
method="POST",
)
with urllib.request.urlopen(request, timeout=100) as response:
body = response.read()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)The class must be named handler in lower case. That is not a convention, it is the name Vercel looks for. Note also the explicit timeout=100 on urlopen — like Node’s fetch, it has no useful default, and without it a stalled provider consumes the whole function duration.
A whole framework application
Alternatively Vercel loads an ASGI or WSGI app. It looks for app.py, index.py, server.py, main.py, wsgi.py or asgi.py — in the project root or inside src/, app/ or api/ — and inside that file expects a top-level name of app for most ASGI or WSGI frameworks including FastAPI and Flask, application for Django and other WSGI applications, or handler for the BaseHTTPRequestHandler form above. For a module elsewhere, point at it explicitly:
# pyproject.toml [tool.vercel] entrypoint = "my_package.api:app"
Vercel notes that the older [project.scripts] app = "module:variable" form is still supported for existing projects but that new projects should use tool.vercel.entrypoint. Crucially, a framework app builds into a single function from its resolved entrypoint — which is why the duration configuration later keys on that file rather than on a route.
Deploying one
- Create
api/complete.pywith the handler above, or anapp.pyexporting a FastAPIapp. - Declare dependencies. Vercel accepts
pyproject.toml(with or without auv.lock),requirements.txt, or aPipfilewith a correspondingPipfile.lock, and detects your framework by finding a matching dependency in one of them. The example above needs nothing beyond the standard library, which is itself a bundling decision — see below. - Add the provider key as an environment variable and redeploy, per the environment variable tutorial. Python functions read it from
os.environ. - Deploy with
vercel --prod, then exercise it:curl -X POST https://your-app.vercel.app/api/complete \ -H 'Content-Type: application/json' \ -d '{"prompt":"Summarise the following in one sentence: ..."}'
Pinning the Python version
Vercel documents three available versions — 3.12 (default), 3.13 and 3.14 — set through pyproject.toml, a .python-version file, or Pipfile.lock. If the required version is undefined or unsupported, Vercel falls back to the default.
That fallback is silent, and it is the reason to pin explicitly. A project that has been building against 3.12 by default will move when the default moves, and the first symptom is a dependency that no longer has a wheel for the new interpreter. One line prevents it:
# .python-version 3.13
python3.12, python3.13 and python3.14 at the time of writing.The bundling step most guides skip
Vercel states it plainly: by default Python functions include all files from your project that are reachable at build time, and there is no automatic tree-shaking for Python. A Node function ships what the bundler traced. A Python function ships what is there.
This is why an AI Python function is the one most likely to breach a bundle limit. The uncompressed limit is 250 MB for most runtimes and 500 MB for Python; a single ML library with its transitive numerical dependencies can approach that on its own, before any of your test fixtures or sample data.
- List only runtime dependencies. Vercel is explicit that
pyproject.tomlorrequirements.txtshould contain only what is needed at runtime. A test framework, a notebook kernel or a linter in the same file is dead weight in every invocation’s cold start. - Exclude what the tracer cannot know is unused. Configure
excludeFilesunder thefunctionskey invercel.json, as a glob relative to the project root:{ "$schema": "https://openapi.vercel.sh/vercel.json", "functions": { "api/**/*.py": { "excludeFiles": "{tests/**,**/test_*.py,fixtures/**,testdata/**,static/**,assets/**}" } } } - If you genuinely need more room, Vercel documents large functions supporting uncompressed bundles up to 5 GB on the Node.js and Python runtimes, requiring fluid compute with Active CPU. New projects are eligible by default; existing ones opt in by setting the
VERCEL_SUPPORT_LARGE_FUNCTIONSenvironment variable to1. Vercel notes that large functions are not yet supported for projects using Secure Compute or Static IPs. excludeFilesdoes not work in Next.js. Vercel states thatincludeFilesandexcludeFilesare not supported there and that you should useoutputFileTracingIncludesin the Next.js configuration instead. Setting them in a Next.js project is silently inert, which is the same failure shape as the unmatched glob above.- Relative paths resolve against the project root, not the directory containing the file — Vercel documents this specifically, and it is the standard cause of a
FileNotFoundErrorfor a prompt template or config file that opens perfectly on a laptop. - Be careful with entrypoint names. Vercel searches for
app.py,index.py,server.py,main.py,wsgi.pyandasgi.pyacross the root and three subdirectories. In a repository that also contains a CLI or a worker, more than one of those names can plausibly exist. If there is any ambiguity, remove it withtool.vercel.entrypointrather than relying on the search order.
Vercel also notes that it compiles Python sources to bytecode during the build and includes the .pyc files when space allows, which reduces initialisation time — one more reason to keep the bundle small enough that there is space.
Duration and streaming
Python has no in-code maxDuration hook. Vercel documents that for Python, Go, Rust and Ruby the value goes in the functions object of vercel.json, keyed on the file:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"functions": {
"api/complete.py": { "maxDuration": 300 }
}
}For a framework application the key is the resolved entrypoint file — app/main.py or myproject/wsgi.py — not an /api route, because the whole app is one function. Getting this wrong is the same silent failure as the /src/ prefix in the Node case: the glob matches nothing, no error is raised, and the default quietly stays in force. See the duration limits page for the values each plan permits.
Streaming responses are supported on the Python runtime, and Vercel notes that a streaming function produces larger and more frequent runtime log entries — worth knowing before you point a log drain at it.