Skip to main content

Overview

Modal provides multiple ways to expose HTTP endpoints from your functions, ranging from simple single-route handlers to full ASGI/WSGI applications.

FastAPI endpoint

The @modal.fastapi_endpoint decorator creates a simple web endpoint backed by FastAPI:
import modal

app = modal.App()

@app.function()
@modal.fastapi_endpoint(method="GET")
def hello():
    return {"message": "Hello, world!"}

Configuration options

@app.function()
@modal.fastapi_endpoint(
    method="POST",  # HTTP method (GET, POST, PUT, DELETE, etc.)
    label="my-endpoint",  # Custom subdomain label
    docs=True,  # Enable /docs endpoint
    requires_proxy_auth=False,  # Require Modal-Key and Modal-Secret headers
    custom_domains=["api.example.com"],  # Use custom domain(s)
)
def handler(data: dict):
    return {"processed": data}
Endpoints created with @modal.fastapi_endpoint automatically have CORS enabled and support FastAPI features like request validation and dependency injection.

Custom domains

Deploy to your own domain:
@app.function()
@modal.fastapi_endpoint(
    method="GET",
    custom_domains=["api.mycompany.com", "api-v2.mycompany.com"]
)
def api_handler():
    return {"version": "2.0"}

Proxy authentication

Require Modal authentication headers:
@app.function()
@modal.fastapi_endpoint(method="POST", requires_proxy_auth=True)
def secure_endpoint(data: dict):
    # Requires Modal-Key and Modal-Secret headers
    return {"status": "authenticated"}

ASGI app

The @modal.asgi_app decorator allows you to deploy full ASGI applications (FastAPI, Starlette, Quart, etc.):
from fastapi import FastAPI
import modal

app = modal.App()

@app.function()
@modal.asgi_app()
def create_app():
    web_app = FastAPI()
    
    @web_app.get("/")
    def index():
        return {"message": "Welcome"}
    
    @web_app.post("/items")
    def create_item(item: dict):
        return {"created": item}
    
    return web_app

Configuration

@app.function()
@modal.asgi_app(
    label="my-api",  # Custom subdomain
    custom_domains=["api.example.com"],  # Custom domain(s)
    requires_proxy_auth=False,  # Require authentication
)
def create_app():
    # Return an ASGI application
    ...
Use @modal.asgi_app when you need multiple routes, middleware, or advanced web framework features.

Requirements

  • The decorated function must be synchronous (not async)
  • The function must take no parameters
  • The function must return an ASGI application
# ✓ Correct
@app.function()
@modal.asgi_app()
def create_asgi():
    return fastapi_app

# ✗ Incorrect - async not allowed
@app.function()
@modal.asgi_app()
async def create_asgi():
    return fastapi_app

# ✗ Incorrect - parameters not allowed
@app.function()
@modal.asgi_app()
def create_asgi(config: dict):
    return fastapi_app

WSGI app

The @modal.wsgi_app decorator supports WSGI applications (Flask, Django, etc.):
from flask import Flask
import modal

app = modal.App()

@app.function()
@modal.wsgi_app()
def create_app():
    flask_app = Flask(__name__)
    
    @flask_app.route("/")
    def index():
        return "Hello from Flask!"
    
    @flask_app.route("/api/data")
    def api_data():
        return {"data": [1, 2, 3]}
    
    return flask_app

Configuration

@app.function()
@modal.wsgi_app(
    label="flask-app",  # Custom subdomain
    custom_domains=["www.example.com"],  # Custom domain(s)
    requires_proxy_auth=False,  # Require authentication
)
def create_app():
    # Return a WSGI application
    ...
WSGI is a synchronous standard. For modern async frameworks, use @modal.asgi_app instead.

Web server

The @modal.web_server decorator exposes arbitrary HTTP servers running inside the container:
import subprocess
import modal

app = modal.App()

@app.function()
@modal.web_server(8000)
def file_server():
    # Start Python's built-in HTTP server
    subprocess.Popen(["python", "-m", "http.server", "-d", "/", "8000"], shell=False)

Configuration

@app.function()
@modal.web_server(
    8000,  # Port number (required, first argument)
    startup_timeout=5.0,  # Seconds to wait for server startup
    label="my-server",  # Custom subdomain
    custom_domains=["files.example.com"],  # Custom domain(s)
    requires_proxy_auth=False,  # Require authentication
)
def start_server():
    # Start any HTTP server listening on port 8000
    ...

Use cases

1
Rust/Go HTTP servers
2
Run compiled binaries that serve HTTP:
3
@app.function()
@modal.web_server(3000)
def rust_server():
    subprocess.Popen(["/app/target/release/server"])
4
Non-ASGI frameworks
5
Integrate frameworks like aiohttp or Tornado:
6
@app.function()
@modal.web_server(8080)
def aiohttp_server():
    from aiohttp import web
    app = web.Application()
    # Configure app...
    web.run_app(app, port=8080)
7
Development tools
8
Host development servers:
9
@app.function()
@modal.web_server(8000)
def dev_server():
    subprocess.Popen(["npm", "run", "dev"])

Method decorator for classes

All web decorators work with class methods:
app = modal.App()

@app.cls()
class MyAPI:
    @modal.fastapi_endpoint(method="GET")
    def health(self):
        return {"status": "healthy"}
    
    @modal.fastapi_endpoint(method="POST")
    def process(self, data: dict):
        # Access instance state
        return {"processed": data}

Web endpoint (deprecated)

The @modal.web_endpoint decorator is deprecated. Use @modal.fastapi_endpoint instead:
# Old (deprecated)
@modal.web_endpoint(method="GET")
def handler():
    return {"data": "value"}

# New (recommended)
@modal.fastapi_endpoint(method="GET")
def handler():
    return {"data": "value"}
@modal.web_endpoint was renamed to @modal.fastapi_endpoint in v0.73.82. The old name still works but will be removed in a future version.

Comparison

DecoratorUse caseMultiple routesFramework
@modal.fastapi_endpointSimple single endpointNoFastAPI
@modal.asgi_appFull ASGI appYesAny ASGI
@modal.wsgi_appFull WSGI appYesAny WSGI
@modal.web_serverCustom HTTP serverYesAny

Best practices

  1. Choose the right decorator: Use fastapi_endpoint for simple cases, ASGI/WSGI for full applications
  2. Enable CORS: fastapi_endpoint has CORS enabled by default; configure it manually for ASGI/WSGI apps
  3. Use custom domains: Set up custom domains for production deployments
  4. Monitor performance: Web endpoints auto-scale based on traffic
  5. Secure sensitive endpoints: Use requires_proxy_auth or implement your own authentication

Build docs developers (and LLMs) love