FastAPI

Build lightweight Python APIs for AI model serving and workflows.

A trained model becomes useful to other software when it has a stable, secure, and observable interface. FastAPI can provide that interface by validating HTTP requests, invoking preprocessing and inference code, and returning versioned response schemas.

What Is FastAPI?

FastAPI is a Python framework for building web APIs using standard type hints. It integrates request and response validation, serialization, dependency injection, asynchronous request handling, and OpenAPI documentation.

FastAPI is the application framework, while an ASGI server such as Uvicorn runs the application. A production deployment may place that server behind a reverse proxy, load balancer, container platform, or API gateway.

FastAPI's Role in MLOps

Output
Client request
-> Gateway authentication and limits
-> FastAPI request validation
-> Feature transformation
-> Model inference
-> Output validation and policy
-> Versioned JSON response
-> Logs, metrics, and trace
-> Production monitoring and feedback

FastAPI is one layer of the serving system. Model versioning, artifact storage, deployment, autoscaling, monitoring, data quality, feedback, retraining, and governance require additional MLOps components.

Install and Run

Terminal
python -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn
uvicorn app.main:app --reload

Reload mode is for local development. Production uses tested runtime configuration, controlled worker and process management, timeouts, graceful shutdown, and deployment health checks.

A Minimal API

Python
from fastapi import FastAPI

app = FastAPI(
    title="Ticket Classification API",
    version="1.0.0",
)

@app.get("/")
def home() -> dict[str, str]:
    return {"message": "Ticket Classification API"}

FastAPI generates an OpenAPI schema and interactive documentation from routes, models, and metadata. Documentation is useful but may need authentication or restricted exposure in production.

HTTP Methods and Status Codes

MethodTypical UseExample
GETRetrieve a resource or statusRead model metadata
POSTSubmit data or create workRequest a prediction
PUTReplace a resourceReplace a complete configuration
PATCHUpdate selected fieldsChange deployment metadata
DELETERemove a resourceDelete an allowed temporary job
StatusMeaning
200 OKSuccessful request with a response body
201 CreatedA new resource was created
202 AcceptedWork was accepted for asynchronous processing
400 Bad RequestRequest is invalid beyond schema parsing
401 UnauthorizedAuthentication is missing or invalid
403 ForbiddenIdentity lacks permission
404 Not FoundResource does not exist
422 Unprocessable EntityRequest validation failed
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorUnexpected server failure
503 Service UnavailableService is temporarily unable to serve safely

Request and Response Models

Python
from typing import Literal
from pydantic import BaseModel, Field

class PredictionRequest(BaseModel):
    text: str = Field(min_length=1, max_length=10_000)
    language: Literal["en", "hi"] = "en"

class PredictionResponse(BaseModel):
    label: Literal["billing", "technical", "sales", "needs_review"]
    confidence: float = Field(ge=0.0, le=1.0)
    model_version: str
    request_id: str

Explicit schemas reject malformed input early, document the contract, and prevent accidental leakage of internal fields. Validation ensures shape and allowed values; it does not prove that input is truthful, safe, authorized, or representative.

A Prediction Endpoint

Python
from uuid import uuid4
from fastapi import FastAPI, HTTPException

app = FastAPI()
MODEL_VERSION = "ticket-classifier-17"

@app.post("/v1/predict", response_model=PredictionResponse)
def predict(payload: PredictionRequest) -> PredictionResponse:
    request_id = str(uuid4())

    try:
        features = preprocess(payload.text, payload.language)
        label, confidence = model_predict(features)
    except KnownInputError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc

    if label not in {"billing", "technical", "sales"}:
        label = "needs_review"

    return PredictionResponse(
        label=label,
        confidence=confidence,
        model_version=MODEL_VERSION,
        request_id=request_id,
    )

The model, preprocessing functions, and domain exception are placeholders. Production code should load an approved artifact, enforce compatibility, validate model output, avoid exposing internal errors, and record request and model metadata.

Load Models Once

Loading a model on every request wastes time and memory. Use FastAPI's lifespan support to initialize expensive resources during startup and release them during shutdown.

Python
from contextlib import asynccontextmanager
from fastapi import FastAPI

resources: dict[str, object] = {}

@asynccontextmanager
async def lifespan(app: FastAPI):
    model, metadata = load_approved_model(version="17")
    validate_model_compatibility(model, metadata)
    resources["model"] = model
    resources["metadata"] = metadata
    yield
    resources.clear()

app = FastAPI(lifespan=lifespan)

If several worker processes are started, each may load its own model copy. Account for multiplied CPU or GPU memory before increasing worker count.

Sync vs Async Endpoints

Endpoint StyleBest FitCaution
defBlocking libraries and CPU-bound codeThroughput depends on thread and process behavior
async defAwaitable network, database, and file I/OBlocking code inside it can stall the event loop
Background queueSlow or durable work not suitable for a requestNeeds status, retries, idempotency, and result storage

Declaring a function async does not make CPU-heavy inference nonblocking. Benchmark the model and use suitable worker processes, separate inference servers, accelerator scheduling, or job queues according to the workload.

Asynchronous Jobs

Long document processing, video inference, or large batch scoring should usually return 202 Accepted with a job ID and process work in a durable queue rather than holding one HTTP connection open.

JSON
{
  "job_id": "job_8f31",
  "status": "queued",
  "status_url": "/v1/jobs/job_8f31"
}

Dependency Injection

FastAPI dependencies can provide authentication, authorization, database sessions, tenant context, rate-limit checks, and shared services. Keep dependencies explicit and testable, and avoid hidden global state that makes behavior difficult to reason about.

Python
from typing import Annotated
from fastapi import Depends, Header, HTTPException

async def require_api_key(
    x_api_key: Annotated[str | None, Header()] = None,
) -> str:
    if not x_api_key or not verify_api_key(x_api_key):
        raise HTTPException(status_code=401, detail="Invalid credentials")
    return x_api_key

@app.post("/v1/predict")
def predict(
    payload: PredictionRequest,
    _: Annotated[str, Depends(require_api_key)],
) -> PredictionResponse:
    ...

For production identity, use an approved authentication design such as short-lived tokens, OAuth, or service identity. Never hard-code keys, compare secrets unsafely, or log authorization values.

Authentication and Authorization

  • Authenticate callers at an API gateway, the application, or both according to architecture.
  • Authorize the specific model, endpoint, tenant, and action after authentication.
  • Use least-privilege service identities and short-lived credentials where possible.
  • Keep tenant and customer context server-controlled and verify resource ownership.
  • Separate public, internal, operator, health, and administrative endpoints.
  • Audit sensitive prediction and management actions without storing unnecessary input data.

Error Handling

Error TypeResponse
Schema validationReturn a clear client error without invoking the model
Domain validationReturn an appropriate 4xx response with safe details
Rate limitReturn 429 and retry guidance where appropriate
Temporary model dependencyReturn 503 or use a defined fallback
TimeoutCancel or abandon work safely and return a controlled error
Unexpected exceptionLog with request ID and return a generic 500 response

Do not return stack traces, filesystem paths, SQL details, prompts, secrets, or internal model errors to clients. Map known failures consistently and monitor unknown ones.

Health Endpoints

Python
@app.get("/health/live")
def liveness() -> dict[str, str]:
    return {"status": "alive"}

@app.get("/health/ready")
def readiness() -> dict[str, str]:
    if "model" not in resources:
        raise HTTPException(status_code=503, detail="Model not ready")
    return {"status": "ready"}

Liveness should answer whether the process is alive. Readiness should answer whether the instance can safely receive traffic, including whether the expected model is loaded. Do not expose sensitive configuration through health endpoints.

API Versioning

Version client-facing contracts independently from the model artifact. A /v1/predict endpoint can serve a newer compatible model while preserving the request and response schema. Introduce a new API version for breaking contract changes and publish a migration and deprecation plan.

Model Metadata

Return or log enough metadata to trace behavior without exposing sensitive internals. Useful fields can include API version, model version, request ID, prediction timestamp, and a review flag. Avoid revealing filesystem paths, registry credentials, training records, or proprietary details unnecessarily.

Preprocessing and Postprocessing

  • Package feature transformation with the model or as a versioned compatible component.
  • Validate units, categories, missing values, encoding, and shape before inference.
  • Prevent training-serving skew by reusing and testing feature definitions.
  • Validate prediction types, numeric ranges, allow-listed labels, and calibration assumptions.
  • Apply deterministic business policy after prediction rather than embedding all decisions in model output.
  • Route out-of-distribution, low-confidence, or policy-sensitive results to fallback or review.

Batching and Concurrency

Batching can improve accelerator throughput but may increase waiting time. Limit request and batch size, define maximum queue delay, preserve per-item errors, and measure latency at realistic concurrency. Do not let an oversized request exhaust memory for other clients.

Rate Limiting and Abuse Protection

  • Set request, concurrency, body-size, file-size, and inference-cost limits.
  • Rate-limit by an appropriate authenticated identity rather than only IP address when possible.
  • Reject unsupported media types and validate file signatures and dimensions.
  • Use timeouts and circuit breakers for model and downstream calls.
  • Protect expensive endpoints from automated abuse, model extraction, and denial of service.
  • Place edge protections in a gateway or proxy while retaining application-level safeguards.

File Uploads

Image, audio, and document APIs need strict file limits and safe parsing. Validate declared and actual type, size, dimensions or duration, archive contents, malware policy, and retention. Store uploads only when required and use generated object identifiers rather than trusting client filenames.

CORS

Cross-Origin Resource Sharing controls which browser origins may call an API. Configure an explicit allow-list of trusted origins, methods, and headers. CORS is a browser mechanism, not authentication, and a permissive wildcard can be unsafe with credentials.

Structured Logging

JSON
{
  "timestamp": "2026-07-20T12:15:00Z",
  "level": "info",
  "service": "ticket-classifier-api",
  "request_id": "req_8f31",
  "trace_id": "trace_2aa7",
  "route": "/v1/predict",
  "status_code": 200,
  "duration_ms": 84,
  "model_version": "17",
  "prediction_label": "technical"
}

Do not log full sensitive request bodies by default. Redact tokens, personal data, confidential features, prompts, and model outputs according to data policy.

Metrics and Tracing

LayerMetrics
HTTPRequest count, status, latency, body rejections, and active requests
ApplicationQueue time, preprocessing, inference, and postprocessing latency
ModelVersion, prediction distribution, confidence, fallback, and review rate
SystemCPU, memory, GPU, worker saturation, and restarts
BusinessCorrectness, conversion, resolution, and customer outcome
CostCompute, model calls, and cost per successful prediction

Propagate trace context through gateways, queues, feature services, model servers, and downstream systems. A healthy API does not prove the model is accurate or the business outcome is good.

Testing FastAPI

Python
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_predict_rejects_empty_text():
    response = client.post(
        "/v1/predict",
        json={"text": "", "language": "en"},
        headers={"X-API-Key": "test-key"},
    )
    assert response.status_code == 422

def test_liveness():
    response = client.get("/health/live")
    assert response.status_code == 200
  • Unit-test preprocessing, postprocessing, authorization, and domain rules.
  • Test valid, missing, malformed, oversized, extreme, and adversarial inputs.
  • Use dependency overrides or fakes for external systems and expensive models.
  • Run contract tests against request and response schemas.
  • Test model-artifact compatibility, evaluation thresholds, and fallback behavior.
  • Test timeouts, concurrency, rate limits, startup failure, graceful shutdown, and dependency outages.
  • Load-test latency, throughput, memory, and error rate with representative traffic.

Project Structure

Output
app/
 main.py
 api/
    routes.py
    dependencies.py
 schemas/
    requests.py
    responses.py
 services/
    inference.py
    preprocessing.py
 core/
    config.py
    security.py
    logging.py
 monitoring/
     metrics.py
tests/
configs/
Dockerfile
pyproject.toml

Keep route functions thin. Place model and business behavior in testable service modules, centralize validated configuration, and isolate infrastructure dependencies.

Configuration

  • Read environment-specific settings from validated runtime configuration.
  • Keep secrets in a protected secret manager or platform connection.
  • Fail startup if required model, endpoint, or credential configuration is missing.
  • Avoid environment-dependent defaults that can accidentally connect tests to production.
  • Record safe resolved configuration and artifact versions for traceability.
  • Separate development, staging, and production data, identities, and endpoints.

Docker Deployment

DOCKERFILE
FROM python:3.11-slim
RUN useradd --system --create-home app
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app app/ ./app/
USER app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Pin dependencies, use a trusted minimal image, run as non-root, keep secrets outside the image, scan the artifact, and promote the same immutable image through staging and production.

Worker and Scaling Strategy

  • Benchmark one process before selecting worker count.
  • Account for one model copy per process and accelerator context.
  • Scale horizontally with container replicas when workload and platform support it.
  • Set resource requests and limits based on measured memory and CPU or GPU use.
  • Use readiness checks and warm-up so traffic reaches only loaded instances.
  • Limit in-flight requests and queue work rather than allowing latency collapse under overload.
  • Use a specialized model server when dynamic batching, multi-model serving, or accelerator optimization justifies it.

Deployment Lifecycle

Output
Versioned API and model code
-> Unit, integration, contract, security, and AI evaluation tests
-> Build and scan immutable container
-> Deploy to staging
-> Smoke, compatibility, and load tests
-> Shadow or canary production traffic
-> Monitor HTTP, system, model, quality, outcome, and cost
-> Promote, pause, or roll back by image and model version

Security Checklist

  • Authenticate and authorize every non-public endpoint.
  • Validate body, path, query, header, file, and model output data.
  • Use TLS and secure proxy-header configuration behind trusted infrastructure.
  • Apply rate, concurrency, size, timeout, and cost limits.
  • Keep secrets outside source and images and redact logs.
  • Run containers with least privilege and maintain dependencies.
  • Protect documentation, metrics, debug, and administrative endpoints.
  • Test tenant isolation, injection, unsafe deserialization, file parsing, and denial-of-service cases.

Common Mistakes

MistakeBetter Practice
Load model on every requestLoad and validate once during application lifespan
Accept dict for every bodyUse explicit request and response models
Use async for blocking inferenceMatch endpoint and serving architecture to workload
Return raw exceptionsMap failures to safe consistent responses
Run slow jobs in request processQueue durable work and return a job ID
Start many workers blindlyMeasure model memory, concurrency, and throughput
Expose docs and metrics publiclyRestrict operational endpoints
Monitor only status codesMonitor model quality, drift, outcomes, and cost too

Practical Exercise

  • Create a /v1/predict endpoint with typed request and response schemas.
  • Load a model or deterministic test double through lifespan.
  • Add authentication, request IDs, safe errors, liveness, and readiness.
  • Record structured logs and request, inference, and fallback metrics.
  • Write unit, integration, schema, failure, and load tests.
  • Containerize as a non-root service with pinned dependencies.
  • Deploy to a safe staging environment and perform a canary test.
  • Document the API contract, model version, limitations, monitoring, and rollback.

Best Practices

  • Keep endpoints small, versioned, typed, and explicit about status behavior.
  • Load approved model artifacts once and verify preprocessing and schema compatibility.
  • Use deterministic validation before and after inference and route uncertainty safely.
  • Design authentication, tenant authorization, rate limits, timeouts, and payload limits from the start.
  • Separate long-running work through durable queues and status resources.
  • Emit traceable logs and metrics while protecting sensitive data.
  • Test contracts, model behavior, failures, concurrency, resources, and shutdown.
  • Deploy immutable containers progressively and monitor service, model, outcome, and cost together.