Home

Chapter 3 · Project

Build an AI-powered REST API

Estimated time: 2–3 hours · Portfolio-worthy

Overview

You have three lessons of theory — routes, Pydantic validation, and async. Now you build the real thing: a production-shaped FastAPI backend that exposes AI capabilities as a clean HTTP API.

This is the foundation of every project in Chapter 3. You will extend it with Docker, PostgreSQL, and Celery in the lessons ahead. Start clean.

Prerequisites

  • Lesson 18: FastAPI routes, Pydantic request/response models, dependency injection
  • Lesson 19: Field constraints, validators, LLM output parsing
  • Lesson 20: async/await, AsyncOpenAI, asyncio.gather

Specification

Build a FastAPI application with the following endpoints:

Required endpoints

Method Path Description
GET /health Returns {"status": "ok", "version": "1.0.0"}
POST /chat Single-turn chat with an LLM
POST /chat/batch Process up to 10 messages concurrently
POST /summarize Summarize text with configurable length
POST /classify Classify text into caller-provided categories

/chat request and response

// Request
{
  "message": "What is the capital of France?",
  "model": "gpt-4o-mini",        // optional, default: gpt-4o-mini
  "temperature": 0.7             // optional, default: 0.7, range: 0.0–2.0
}

// Response
{
  "reply": "The capital of France is Paris.",
  "model_used": "gpt-4o-mini",
  "tokens_used": 42
}

/chat/batch request and response

// Request
{
  "messages": ["What is 2+2?", "Name a planet.", "Who wrote Hamlet?"],
  "model": "gpt-4o-mini"         // optional
}

// Response
{
  "replies": ["4", "Mars", "William Shakespeare"],
  "total_messages": 3,
  "failed": 0
}

/summarize request and response

// Request
{
  "text": "Long document text here...",
  "max_words": 150,              // optional, default: 150, range: 50–500
  "focus_on": "key findings"    // optional hint to guide the summary
}

// Response
{
  "summary": "The document describes...",
  "original_word_count": 843,
  "summary_word_count": 147
}

/classify request and response

// Request
{
  "text": "My server has been returning 500 errors since the last deploy.",
  "labels": ["billing", "technical support", "feature request", "feedback"],
  "include_reasoning": true      // optional, default: false
}

// Response
{
  "label": "technical support",
  "confidence": "high",
  "reasoning": "The text describes a server error following a deployment."
  // reasoning field omitted when include_reasoning is false
}

Technical requirements

Completion checklist

Testing your API

Use the /docs UI for manual testing. For scripted tests, use curl:

# Test /chat
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "What is the speed of light?"}'

# Test /chat/batch
curl -X POST http://localhost:8000/chat/batch \
  -H "Content-Type: application/json" \
  -d '{"messages": ["Hello", "What year is it?", "Name a colour"]}'

# Test /classify
curl -X POST http://localhost:8000/classify \
  -H "Content-Type: application/json" \
  -d '{"text": "I cant log in", "labels": ["billing", "access", "feature request"]}'

# Test validation (should return 422)
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"temperature": 99}'

Extension challenges

  1. Streaming chat: Add a POST /chat/stream endpoint that streams the LLM response token-by-token using FastAPI’s StreamingResponse and the OpenAI streaming API. The response should be a Server-Sent Events stream.

  2. API key authentication: Add a bearer token authentication dependency (use an environment variable API_SECRET_KEY). All endpoints except /health should require the correct token.

  3. Request logging middleware: Add FastAPI middleware that logs every request (method, path, status code, duration in ms) to stdout. This is the foundation of the observability you will build in Chapter 5.

  4. Multi-provider support: Add an Anthropic provider option to /chat. When the caller sets "provider": "anthropic", use the Anthropic SDK instead of OpenAI. Use a dependency that returns either client based on the request.

Community Showcase

Finished this project? Share your GitHub repo link in the discussion below. Browse what others built to get inspiration and give feedback.