Request timeouts

Web endpoint (a.k.a. webhook) requests should complete quickly, ideally within a few seconds. All web endpoint function types (web_endpoint, asgi_app, wsgi_app) have a maximum HTTP request timeout of 150 seconds enforced. However, the underlying Modal function can have a longer timeout.

In case the function takes more than 150 seconds to complete, a HTTP status 303 redirect response is returned pointing at the original URL with a special query parameter linking it that request. This is the result URL for your function. Most web browsers allow for up to 20 such redirects, effectively allowing up to 50 minutes (20 * 150 s) for web endpoints before the request times out.

(Note: This does not work with requests that require CORS, since the response will not have been returned from your code in time for the server to populate CORS headers.)

Some libraries and tools might require you to add a flag or option in order to follow redirects automatically, e.g. curl -L ... or http --follow ....

The result URL can be reloaded without triggering a new request. It will block until the request completes.

Polling solutions

Sometimes it can be useful to be able to poll for results rather than wait for a long running HTTP request. The easiest way to do this is to have your web endpoint spawn a modal.Function call and return the function call id that another endpoint can use to poll the submitted function’s status. Here is an example:

import fastapi
from modal import App, asgi_app
from modal.functions import FunctionCall


app = App()  # Note: prior to April 2024, "app" was called "stub"

web_app = fastapi.FastAPI()


@app.function()
@asgi_app()
def fastapi_app():
    return web_app


@app.function()
def slow_operation():
    ...


@web_app.post("/accept")
async def accept_job(request: fastapi.Request):
    call = slow_operation.spawn()
    return {"call_id": call.object_id}


@web_app.get("/result/{call_id}")
async def poll_results(call_id: str):
    function_call = FunctionCall.from_id(call_id)
    try:
        return function_call.get(timeout=0)
    except TimeoutError:
        http_accepted_code = 202
        return fastapi.responses.JSONResponse({}, status_code=http_accepted_code)

Document OCR Web App is an example that uses this pattern.