Build a Fintech REST API with FastAPI

Build a REST API backend for a fintech app with CRUD endpoints.

Introduction

30 Second Summary

Every fintech app on your phone talks to a backend server to fetch transactions, validate payments, and keep your data in sync. That backend is powered by an API.

In this project, you will build a REST API backend for a personal finance app using Python and FastAPI. Your API will handle creating, reading, updating, and deleting financial transactions with automatic data validation.

What You'll Build

A FastAPI server with five RESTful endpoints that manage financial transactions for a mobile fintech app.

By the end of this project, you'll have:

  • A FastAPI server with full CRUD operations for managing financial transactions.
  • Pydantic data models that automatically validate monetary amounts and transaction types.
  • Auto-generated API documentation (Swagger UI) your frontend team can use as a contract.
  • Secret Mission: Add a financial summary endpoint that calculates income, expenses, and net balance grouped by category.

Are there any prerequisites?

You should be comfortable with basic Python concepts like variables, functions, and dictionaries. No prior experience with web frameworks or APIs is needed.

Before We Start

Before We Start...

Set Up Your Python Environment

Before you can build API endpoints, you need a clean workspace. A dedicated project folder and an isolated Python environment keep your fintech API's dependencies separate from anything else on your machine.

In this step, you'll create a project folder, set up a virtual environment, and install FastAPI with all its standard dependencies.

In this step, get ready to:
  • Create a project folder and Python virtual environment.
  • Install FastAPI with standard dependencies.
  • Verify the installation works.
Create a project folder and set up a Python virtual environment

A virtual environment is a self-contained Python installation inside your project folder. It ensures FastAPI and its dependencies live only in this project, not system-wide.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir fintech-api
cd fintech-api
  • Confirm the folder exists by running:
ls

You should see the fintech-api folder listed in the output. The folder will be empty for now.

  • Create and activate your virtual environment by running the commands for your operating system:

macOS

python3 -m venv venv
source venv/bin/activate

Windows

python -m venv venv
.\venv\Scripts\activate

After activation, you should see (venv) at the start of your terminal prompt. This confirms your virtual environment is active.

What is a virtual environment?

A virtual environment creates an isolated space for your project's packages. Without one, installing FastAPI would affect every Python project on your machine.

With a virtual environment active, any packages you install with pip go into the venv folder instead of your system Python.

Don't see (venv) in your prompt?

Make sure you ran the activation command for your operating system. macOS uses source venv/bin/activate while Windows uses .\venv\Scripts\activate.

On Windows, you may need to allow script execution first. Run Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned and try the activation command again.

help me activate my Python virtual environment

Install FastAPI with standard dependencies

FastAPI ships with optional "standard" extras that include a development server, auto-reload, and other tools you'll need. Installing with the standard bundle gives you everything in one command.

  • Install FastAPI and its standard dependencies by running:
pip install "fastapi[standard]"

What does this command do?

The [standard] part tells pip to install FastAPI along with its recommended extras. This includes uvicorn (the server that runs your API), httpx (for testing), and the fastapi-cli tool you'll use to launch the dev server.

You should see pip downloading and installing several packages. The final lines will say Successfully installed followed by a list of packages.

Seeing a permissions error or install failure?

Make sure (venv) appears in your terminal prompt before running the install. If it doesn't, go back and activate your virtual environment first.

If you see a network timeout, check your internet connection and try running the command again.

help me install FastAPI in my virtual environment

Verify the installation by checking the FastAPI version

A quick check in the Python REPL confirms that FastAPI installed correctly and is importable.

  • Open the Python REPL by running:
python
  • Check the installed FastAPI version by running these two lines:
import fastapi
print(fastapi.__version__)

You should see 0.136.3 printed (or a similar recent version number).

  • Exit the Python REPL by running:
exit()

Seeing ModuleNotFoundError?

This means Python cannot find FastAPI. Check that (venv) is visible in your prompt. If the virtual environment is not active, the REPL uses your system Python which does not have FastAPI installed.

Exit the REPL with exit(), activate your virtual environment again, then re-enter the REPL with python.

help me fix ModuleNotFoundError for fastapi

Your Python environment is ready to go. Next up, you'll define the data model that powers every transaction in your fintech API.

Define Your Transaction Data Model

Your Python environment is ready with FastAPI installed. Now it's time to define the data structure your API will work with.

Every fintech app needs a clear definition of what a "transaction" looks like. In this step, you'll create a data model that validates transaction data automatically before your API ever processes it.

In this step, get ready to:
  • Create your main application file with FastAPI imports.
  • Define Pydantic models that validate financial transaction data.
  • Seed an in-memory database with sample transactions.
Create your application file and imports

Your API lives in a single file called main.py. This is where FastAPI looks for your app by default when you start the development server.

  • In VS Code, press Cmd+N (macOS) or Ctrl+N (Windows) to create a new file.
  • Save it immediately as main.py inside your project folder by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Add the imports and app instance by copying this code into main.py:
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from enum import Enum

# Create the FastAPI application instance
app = FastAPI(title="Fintech API", description="Personal finance API backend")

What does this code do?

The first three lines import the tools you need from three libraries:

  • FastAPI, HTTPException, and Query from fastapi give you the web framework, error handling, and query parameter validation.
  • BaseModel and Field from Pydantic let you define data shapes with automatic validation.
  • Enum from Python's standard library restricts a value to a fixed set of options.

The app = FastAPI(...) line creates your API application. The title and description appear in the auto-generated documentation page.

  • Save main.py.

You won't see any output yet. The next chunk defines the data models these imports support.

Seeing a red underline on the imports?

  • Make sure your virtual environment is still activated. You should see (venv) at the start of your terminal prompt.
  • In VS Code, check the bottom-right corner shows the Python interpreter from your venv folder. Click it to select the correct interpreter if it doesn't.

Help me fix my Python imports in VS Code.

Define your data models

Pydantic models act as gatekeepers for your API. They define what shape incoming data must have and reject anything that doesn't match.

  • Add the following model definitions below the app = FastAPI(...) line in main.py:
class TransactionType(str, Enum):
    income = "income"
    expense = "expense"


class TransactionCreate(BaseModel):
    amount: float = Field(gt=0, description="Transaction amount (must be positive)")
    category: str = Field(min_length=1, description="Transaction category")
    description: str = Field(default="", description="Optional description")
    transaction_type: TransactionType


class Transaction(TransactionCreate):
    id: int

What do these three classes do?

  • TransactionType is an enum that restricts transaction types to only "income" or "expense". Any other value gets rejected automatically.
  • TransactionCreate defines the fields a user sends when creating a transaction. Field(gt=0) ensures amounts are always positive. Field(min_length=1) prevents empty categories.
  • Transaction extends TransactionCreate by adding an id field. The server assigns this ID, so users never send it themselves.

Why two separate models?

TransactionCreate represents what the client sends (no ID). Transaction represents what the server stores and returns (with an ID). This separation is a common API design pattern that prevents clients from choosing their own IDs.

  • Save main.py.

You won't see a visible change yet. The next chunk uses these models to create sample data.

Seeing a syntax error underline?

  • Check that each class definition ends with a colon (:) after the class name.
  • Make sure the model code is below the app = FastAPI(...) line, not above it.
  • Verify that indentation uses consistent spaces (4 spaces per level) for all fields inside each class.

Help me fix my Pydantic model definitions.

Seed sample transaction data

Your API needs data to serve. Instead of setting up a database, you'll use an in-memory list that acts as a simple data store. This keeps the focus on API design patterns.

  • Add the following seed data below your class definitions in main.py:
# In-memory storage
transactions: list[Transaction] = [
    Transaction(id=1, amount=5000.00, category="salary", description="Monthly salary", transaction_type=TransactionType.income),
    Transaction(id=2, amount=45.99, category="food", description="Grocery shopping", transaction_type=TransactionType.expense),
    Transaction(id=3, amount=120.00, category="transport", description="Monthly transit pass", transaction_type=TransactionType.expense),
]

next_id = 4

What does this code do?

  • transactions is a Python list pre-loaded with three sample transactions. It simulates a database so your endpoints have real data to return.
  • Each Transaction(...) call creates a validated object using your Pydantic model. If any value violated your rules (like a negative amount), Python would raise an error here.
  • next_id = 4 tracks the next available ID number. When your API creates a new transaction, it uses this counter and increments it.
  • Save main.py.
  • Verify your code has no errors by running this in your terminal:
python main.py

The command should finish with no output and no errors. A clean exit means Python parsed your file successfully and all three sample transactions passed Pydantic's validation.

Seeing an error when you run the file?

  • If you see ModuleNotFoundError, your virtual environment is not activated. Reactivate it before running the command.
  • If you see ValidationError, check that all amounts are positive numbers and category strings are not empty in your seed data.
  • If you see NameError, make sure the TransactionType enum is defined above the transactions list.

Help me debug my main.py file.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete main.py file at this point:

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from enum import Enum

app = FastAPI(title="Fintech API", description="Personal finance API backend")


class TransactionType(str, Enum):
    income = "income"
    expense = "expense"


class TransactionCreate(BaseModel):
    amount: float = Field(gt=0, description="Transaction amount (must be positive)")
    category: str = Field(min_length=1, description="Transaction category")
    description: str = Field(default="", description="Optional description")
    transaction_type: TransactionType


class Transaction(TransactionCreate):
    id: int


# In-memory storage
transactions: list[Transaction] = [
    Transaction(id=1, amount=5000.00, category="salary", description="Monthly salary", transaction_type=TransactionType.income),
    Transaction(id=2, amount=45.99, category="food", description="Grocery shopping", transaction_type=TransactionType.expense),
    Transaction(id=3, amount=120.00, category="transport", description="Monthly transit pass", transaction_type=TransactionType.expense),
]

next_id = 4

Your data model is locked in. Next up, you'll build the GET endpoints that serve this transaction data to your frontend.

Build the Read Endpoints (GET)

Your data models are in place and your transactions list is seeded with sample records. Now it's time to build the endpoints that let your React Native app actually read that data.

In REST API design, GET endpoints are responsible for retrieving data without changing anything on the server. You'll build two: one to list all transactions (with filtering support) and one to fetch a single transaction by its ID.

In this step, get ready to:
  • Create a GET endpoint that returns all transactions with optional filtering.
  • Create a GET endpoint that returns a single transaction by ID.
  • Run the dev server and test both endpoints in Swagger UI.
Create the list transactions endpoint

Your mobile app needs a way to fetch all transactions at once. It also needs the ability to filter results by category or type so users can quickly find what they're looking for.

  • In main.py, scroll to the bottom of the file (below next_id = 4) and add this endpoint:
@app.get("/transactions", response_model=list[Transaction])
def get_transactions(
    category: str | None = Query(default=None, description="Filter by category"),
    transaction_type: TransactionType | None = Query(default=None, description="Filter by type"),
):
    # Start with all transactions
    result = transactions
    # Apply category filter if provided
    if category:
        result = [t for t in result if t.category == category]
    # Apply transaction type filter if provided
    if transaction_type:
        result = [t for t in result if t.transaction_type == transaction_type]
    return result

What does this code do?

The @app.get decorator registers this function as a GET endpoint at the /transactions path. The response_model=list[Transaction] tells FastAPI to validate and serialize the output as a list of Transaction objects.

The two parameters (category and transaction_type) become optional query parameters in the URL. When a client calls /transactions?category=food, FastAPI automatically passes "food" into the category argument. If the parameter is not provided, it defaults to None and the filter is skipped.

  • Save main.py.

You won't see a change yet because the server isn't running. The next substep adds the second endpoint, and then you'll start the server and test both.

Create the single transaction endpoint

Your app also needs to fetch details for one specific transaction. This uses a path parameter to identify which transaction to return.

  • Below the get_transactions function you just added, paste this second endpoint:
@app.get("/transactions/{transaction_id}", response_model=Transaction)
def get_transaction(transaction_id: int):
    # Search through transactions for matching ID
    for t in transactions:
        if t.id == transaction_id:
            return t
    # If no match found, return a 404 error
    raise HTTPException(status_code=404, detail="Transaction not found")

What does this code do?

The {transaction_id} in the path tells FastAPI to capture that part of the URL as a variable. When a client calls /transactions/2, FastAPI passes 2 as the transaction_id argument.

If no transaction matches that ID, the function raises an HTTPException with status code 404. This tells the client the resource doesn't exist, which is the standard REST convention for "not found."

  • Save main.py.

Seeing a red underline on the | symbol?

The str | None syntax requires Python 3.10 or higher. If your editor flags it, confirm you're using the correct Python interpreter from your virtual environment.

Help me fix the Python interpreter in VS Code

✔️ Awesome, I've got everything!

Great. Double check you've saved main.py before moving on to running the server.

ⓧ I'd like to double check the full code

Here's the complete main.py as it should look at this point:

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from enum import Enum

app = FastAPI(title="Fintech API", description="Personal finance API backend")


class TransactionType(str, Enum):
    income = "income"
    expense = "expense"


class TransactionCreate(BaseModel):
    amount: float = Field(gt=0, description="Transaction amount (must be positive)")
    category: str = Field(min_length=1, description="Transaction category")
    description: str = Field(default="", description="Optional description")
    transaction_type: TransactionType


class Transaction(TransactionCreate):
    id: int


# In-memory storage
transactions: list[Transaction] = [
    Transaction(id=1, amount=5000.00, category="salary", description="Monthly salary", transaction_type=TransactionType.income),
    Transaction(id=2, amount=45.99, category="food", description="Grocery shopping", transaction_type=TransactionType.expense),
    Transaction(id=3, amount=120.00, category="transport", description="Monthly transit pass", transaction_type=TransactionType.expense),
]

next_id = 4


@app.get("/transactions", response_model=list[Transaction])
def get_transactions(
    category: str | None = Query(default=None, description="Filter by category"),
    transaction_type: TransactionType | None = Query(default=None, description="Filter by type"),
):
    result = transactions
    if category:
        result = [t for t in result if t.category == category]
    if transaction_type:
        result = [t for t in result if t.transaction_type == transaction_type]
    return result


@app.get("/transactions/{transaction_id}", response_model=Transaction)
def get_transaction(transaction_id: int):
    for t in transactions:
        if t.id == transaction_id:
            return t
    raise HTTPException(status_code=404, detail="Transaction not found")
Run the server and test in Swagger UI

FastAPI comes with auto-generated interactive documentation called Swagger UI. You can use it to test your endpoints directly in the browser without writing any client code.

  • Start the development server by running this command:
fastapi dev main.py

What does this command do?

The fastapi dev command starts a local development server with auto-reload enabled. Every time you save main.py, the server automatically restarts with your changes.

  • You should see output in your terminal showing the server is running at http://127.0.0.1:8000.
  • Open your browser and navigate to http://127.0.0.1:8000/docs.

You should see the Swagger UI page with your API title "Fintech API" and both GET endpoints listed.

Don't see the Swagger UI page?

Make sure your terminal still shows the server running. If you see an error about a missing module, confirm your virtual environment is activated and you installed FastAPI with pip install "fastapi[standard]".

Help me troubleshoot my FastAPI dev server

  • In Swagger UI, click the GET /transactions row to expand it.
  • Click Try it out.
  • Leave both filter fields empty and click Execute.

You should see a JSON response containing all three seeded transactions.

  • Now test filtering by typing food in the category field and clicking Execute again.

You should see only the grocery shopping transaction returned.

  • Next, expand the GET /transactions/{transaction_id} row.
  • Click Try it out, enter 1 for the transaction_id field, and click Execute.

You should see the salary transaction returned with all its fields.

  • Test the error case by entering 99 for the transaction_id and clicking Execute.

You should see a 404 response with the message Transaction not found.

Why test both the happy path and the error case?

A well-built API handles not just successful requests, but also invalid ones. Your React Native app needs to know what happens when a transaction doesn't exist so it can show users an appropriate error message instead of crashing.

Your read endpoints are working and tested. Next up, you'll build the write endpoints that let your app create, update, and delete transactions.

Build the Write Endpoints (POST, PUT, DELETE)

Your API can read transaction data. Now it needs the ability to create, update, and delete transactions too.

These three operations complete the CRUD pattern (Create, Read, Update, Delete). Every backend that manages data needs all four. In this step, you will add POST, PUT, and DELETE endpoints to your API.

In this step, get ready to:
  • Build a POST endpoint to create new transactions.
  • Build a PUT endpoint to update existing transactions.
  • Build a DELETE endpoint to remove transactions.
Create the POST endpoint for new transactions

When a user records a new expense or income in your fintech app, the frontend sends the transaction data to your API. The API needs to accept that data, assign it a unique ID, and store it.

  • In main.py, scroll to the bottom of the file (below the get_transaction function).
  • Add the POST endpoint by copying this code at the bottom of the file:
@app.post("/transactions", response_model=Transaction, status_code=201)
def create_transaction(data: TransactionCreate):
    global next_id
    new_transaction = Transaction(id=next_id, **data.model_dump())
    transactions.append(new_transaction)
    next_id += 1
    return new_transaction

What does this code do?

This endpoint listens for POST requests at /transactions. The status_code=201 tells FastAPI to return a 201 Created response instead of the default 200.

  • data: TransactionCreate means FastAPI will automatically parse and validate the request body against your TransactionCreate model. No ID field is needed because the server assigns one.
  • global next_id lets the function modify the module-level next_id counter so each transaction gets a unique ID.
  • data.model_dump() converts the Pydantic model into a dictionary. The ** operator unpacks it into keyword arguments for creating the full Transaction object.
  • Save main.py.
  • Switch back to your Swagger UI at http://127.0.0.1:8000/docs and refresh the page. You should see a new green POST /transactions section appear in the list.

Don't see the new endpoint?

  • Make sure you saved main.py. The dev server auto-reloads on save.
  • Check your terminal for error messages. A syntax error (missing colon, wrong indentation) will prevent the reload.
  • Confirm the new function is at the module level (not indented inside another function).

Why isn't my new POST endpoint showing up in Swagger UI?

Create the PUT endpoint for updating transactions

Users make mistakes. Maybe they entered the wrong amount or picked the wrong category. The PUT endpoint lets them fix a transaction by replacing it with updated data.

  • Add the PUT endpoint below the create_transaction function you just wrote:
@app.put("/transactions/{transaction_id}", response_model=Transaction)
def update_transaction(transaction_id: int, data: TransactionCreate):
    for i, t in enumerate(transactions):
        if t.id == transaction_id:
            updated = Transaction(id=transaction_id, **data.model_dump())
            transactions[i] = updated
            return updated
    raise HTTPException(status_code=404, detail="Transaction not found")

What does this code do?

This endpoint accepts both a path parameter (transaction_id) and a request body (data). FastAPI knows which is which based on where they appear in the URL template versus the function signature.

  • enumerate(transactions) gives both the index i and the transaction t so you can replace the item in the list by position.
  • If no transaction matches the ID, the loop completes without returning, and the function raises a 404 error.
  • Save main.py.
  • Refresh your Swagger UI. You should see a new blue PUT /transactions/{transaction_id} section.

Seeing a server error in your terminal?

  • Check that raise HTTPException(status_code=404, detail="Transaction not found") is at the same indentation level as the for loop, not inside it.
  • Make sure the function signature has both parameters: transaction_id: int and data: TransactionCreate.

Help me fix my PUT endpoint code.

Create the DELETE endpoint for removing transactions

The final piece of CRUD is deletion. When a user wants to remove a transaction entirely, the DELETE endpoint handles that.

  • Add the DELETE endpoint below the update_transaction function:
@app.delete("/transactions/{transaction_id}")
def delete_transaction(transaction_id: int):
    for i, t in enumerate(transactions):
        if t.id == transaction_id:
            transactions.pop(i)
            return {"message": "Transaction deleted successfully"}
    raise HTTPException(status_code=404, detail="Transaction not found")

What does this code do?

  • transactions.pop(i) removes the transaction from the list by its index position.
  • Instead of returning the deleted object, this endpoint returns a simple confirmation message as a dictionary. FastAPI automatically serializes it to JSON.
  • Notice there is no response_model on this decorator. The response is a plain dictionary, so FastAPI infers the schema.
  • Save main.py.
  • Refresh your Swagger UI. You should now see all five endpoints listed: two GET (blue), one POST (green), one PUT (blue), and one DELETE (red).

Missing the DELETE endpoint?

  • Make sure the decorator is @app.delete (not @app.put copied from the previous function).
  • Check that the function name delete_transaction is unique. Duplicate function names will silently overwrite each other.

Why is my DELETE endpoint not appearing?

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and all five endpoints are visible in your Swagger UI.

ⓧ I'd like to double check the full code

Here is the full main.py at this point. Compare it against yours:

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from enum import Enum

app = FastAPI(title="Fintech API", description="Personal finance API backend")


class TransactionType(str, Enum):
    income = "income"
    expense = "expense"


class TransactionCreate(BaseModel):
    amount: float = Field(gt=0, description="Transaction amount (must be positive)")
    category: str = Field(min_length=1, description="Transaction category")
    description: str = Field(default="", description="Optional description")
    transaction_type: TransactionType


class Transaction(TransactionCreate):
    id: int


# In-memory storage
transactions: list[Transaction] = [
    Transaction(id=1, amount=5000.00, category="salary", description="Monthly salary", transaction_type=TransactionType.income),
    Transaction(id=2, amount=45.99, category="food", description="Grocery shopping", transaction_type=TransactionType.expense),
    Transaction(id=3, amount=120.00, category="transport", description="Monthly transit pass", transaction_type=TransactionType.expense),
]

next_id = 4


@app.get("/transactions", response_model=list[Transaction])
def get_transactions(
    category: str | None = Query(default=None, description="Filter by category"),
    transaction_type: TransactionType | None = Query(default=None, description="Filter by type"),
):
    result = transactions
    if category:
        result = [t for t in result if t.category == category]
    if transaction_type:
        result = [t for t in result if t.transaction_type == transaction_type]
    return result


@app.get("/transactions/{transaction_id}", response_model=Transaction)
def get_transaction(transaction_id: int):
    for t in transactions:
        if t.id == transaction_id:
            return t
    raise HTTPException(status_code=404, detail="Transaction not found")


@app.post("/transactions", response_model=Transaction, status_code=201)
def create_transaction(data: TransactionCreate):
    global next_id
    new_transaction = Transaction(id=next_id, **data.model_dump())
    transactions.append(new_transaction)
    next_id += 1
    return new_transaction


@app.put("/transactions/{transaction_id}", response_model=Transaction)
def update_transaction(transaction_id: int, data: TransactionCreate):
    for i, t in enumerate(transactions):
        if t.id == transaction_id:
            updated = Transaction(id=transaction_id, **data.model_dump())
            transactions[i] = updated
            return updated
    raise HTTPException(status_code=404, detail="Transaction not found")


@app.delete("/transactions/{transaction_id}")
def delete_transaction(transaction_id: int):
    for i, t in enumerate(transactions):
        if t.id == transaction_id:
            transactions.pop(i)
            return {"message": "Transaction deleted successfully"}
    raise HTTPException(status_code=404, detail="Transaction not found")

Your API now supports the full set of CRUD operations. Next up, you will test the complete lifecycle of a transaction from creation to deletion.

Test Your Complete API

You have all five endpoints built and your server running. Now it's time to put your API through its paces and confirm everything works together as a cohesive system.

Testing is where you prove your API handles the full lifecycle of financial data. You'll create transactions, filter them, update them, and delete them, verifying the correct HTTP status code comes back at each step.

In this step, get ready to:
  • Create a new expense transaction via the Swagger UI.
  • Verify query parameter filtering returns the correct subset of transactions.
  • Run a full CRUD lifecycle test and confirm proper status codes.
Create a new expense transaction via POST

The Swagger UI lets you send real requests to your API directly from the browser. You'll use it to create a brand new expense transaction and confirm the server responds with a 201 status code.

  • Switch back to your browser tab with the Swagger UI at http://127.0.0.1:8000/docs.
  • Find the POST /transactions endpoint and click on it to expand it.
  • Click the Try it out button on the right side.
  • Replace the example request body with the following JSON:
{
  "amount": 25.50,
  "category": "food",
  "description": "Lunch with coworker",
  "transaction_type": "expense"
}
  • Click the Execute button.

You should see a response with status code 201 in the Server response section. The response body shows your new transaction with an auto-assigned id of 4.

Why 201 and not 200?

A 201 Created status code tells the client that a new resource was successfully created on the server. It's more specific than a generic 200 OK, which just means the request succeeded.

Using precise status codes helps your React Native frontend know exactly what happened without needing to parse the response body.

Seeing a 422 Unprocessable Entity error?

This means your request body failed Pydantic validation. Check that amount is a number greater than 0, category is not empty, and transaction_type is exactly "income" or "expense".

Help me debug my 422 validation error in FastAPI

Verify filtering with query parameters

Your GET endpoint supports filtering by category and transaction type. This is how your React Native app will fetch just the data it needs for a specific screen.

  • Scroll up to the GET /transactions endpoint and click to expand it.
  • Click Try it out.
  • In the category parameter field, enter food.
  • In the transaction_type parameter field, enter expense.
  • Click Execute.

You should see a 200 response containing only transactions that match both filters. This includes the original grocery shopping transaction (id 2) and the lunch transaction (id 4) you just created.

What does the request URL look like?

Look at the Request URL shown in the Swagger UI response. It reads http://127.0.0.1:8000/transactions?category=food&transaction_type=expense. This is exactly the URL your React Native app would call to fetch filtered data.

Test the full CRUD lifecycle

A real fintech app creates, reads, updates, and deletes transactions constantly. This final test walks through that entire lifecycle on a single transaction to confirm every endpoint works together.

  • Create a new transaction by expanding POST /transactions, clicking Try it out, and using this request body:
{
  "amount": 75.00,
  "category": "entertainment",
  "description": "Concert tickets",
  "transaction_type": "expense"
}
  • Click Execute. Note the id in the 201 response (it should be 5).
  • Read it back by expanding GET /transactions/{transaction_id} and clicking Try it out.
  • Enter 5 in the transaction_id field and click Execute.

You should see a 200 response with the concert tickets transaction.

  • Update it by expanding PUT /transactions/{transaction_id} and clicking Try it out.
  • Enter 5 as the transaction_id.
  • Replace the request body with this updated version:
{
  "amount": 80.00,
  "category": "entertainment",
  "description": "Concert tickets (VIP upgrade)",
  "transaction_type": "expense"
}
  • Click Execute. You should see a 200 response with the updated amount of 80.00 and new description.
  • Delete it by expanding DELETE /transactions/{transaction_id} and clicking Try it out.
  • Enter 5 as the transaction_id and click Execute.

You should see a 200 response with the message "Transaction deleted successfully".

  • Confirm the delete worked by going back to GET /transactions/{transaction_id}.
  • Enter 5 again and click Execute.

This time you should see a 404 response with the detail "Transaction not found". That confirms the transaction was fully removed from memory.

Why does this lifecycle matter for fintech?

A fintech app handles real money. Every operation must behave predictably. If a DELETE doesn't actually remove data, duplicate transactions could appear. If a PUT silently fails, a user's budget corrections would vanish.

Testing the full lifecycle proves that your API handles each operation cleanly and returns the correct status code so your frontend knows exactly what happened.

Getting a 404 on the PUT or DELETE?

Make sure you're using the correct id from the POST response. If you restarted your server at any point, the in-memory data resets and IDs start over from 4 again.

Help me troubleshoot why my FastAPI endpoint is returning 404 when I expect it to find my transaction

Secret mission

Add a Financial Summary Endpoint

Your API can create, read, update, and delete transactions. But what good is raw transaction data if your app can't show the user their financial health at a glance? Build a summary endpoint that crunches the numbers for you.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • FastAPI dev server (running process in your terminal).
  • Project folder on your Desktop containing main.py and the venv virtual environment.

Keep everything running

No action needed. Choose this if you're still actively building or testing your API.

  • Your FastAPI dev server stays running in the terminal.
  • Your project folder and virtual environment remain on your Desktop.
  • You can resume editing main.py and testing endpoints at any time.

Pause - I'll come back to this later

Shut down the running server to free up your terminal. Your files stay on disk so you can pick up where you left off.

  • Switch to the terminal where your FastAPI server is running.
  • Press Ctrl+C to stop the server.
  • To restart later, activate your virtual environment and run fastapi dev main.py again.

Delete - I don't want to use this again

Remove all project files and start fresh. This deletes your code, virtual environment, and all installed packages.

  • Switch to the terminal where your FastAPI server is running.
  • Press Ctrl+C to stop the server.
  • Navigate out of the project folder and delete it by running these commands:
cd ~/Desktop
rm -rf fintech-api

On Windows, run this in PowerShell instead:

cd ~\Desktop
Remove-Item -Recurse -Force fintech-api
  • Confirm the folder is gone by running ls on your Desktop. You should no longer see the project folder listed.

Nice Work!

Nice Work!

Nice work! You've just built a complete fintech REST API backend using Python and FastAPI.

You've learned how to:

  • Built a FastAPI server with full CRUD operations for financial transactions, using proper HTTP status codes (200, 201, 404) and error handling.
  • Designed Pydantic models that automatically validate monetary amounts and transaction types before data ever reaches your logic.
  • Implemented query parameter filtering and auto-generated Swagger UI documentation your React Native app can use as a contract.
  • Secret Mission: Added a /summary endpoint that calculates total income, expenses, net balance, and per-category breakdowns with optional date filtering.

Ready to quiz yourself?