Build a Banking Transactions REST API
Build a REST API with Flask that handles core banking operations.
Introduction
30 Second Summary
Every time you tap your phone to check your balance or send money to a friend, an API is doing the heavy lifting behind the scenes. Banks rely on these interfaces to process millions of transactions securely every day.
In this project, you will build a Banking Transactions REST API from scratch using Python and Flask. Your API will handle core banking operations like creating accounts, depositing funds, withdrawing money, and transferring between accounts.
What You'll Build
A fully functional REST API server running on your local machine that processes banking transactions with proper input validation and error handling.
By the end of this project, you'll have:
- A single Python file (app.py) containing a fully functional REST API with five endpoints.
- Hands-on experience with HTTP methods, status codes, and JSON responses you can talk about confidently.
- A tested API with input validation and error handling that rejects overdrafts, negative amounts, and missing fields.
- Secret Mission: Add transaction history tracking so each account has a full ledger of operations.
Are there any prerequisites?
You should have basic Python familiarity. If you are comfortable with variables, functions, dictionaries, and if statements, you are ready to go.
Set Up Your Development Environment
Before you can build any API endpoints, you need a code editor and the right libraries installed on your machine.
In this step, you'll install VS Code, set up a Python project folder with Flask, and confirm everything works by running a simple health check endpoint.
In this step, get ready to:
- Install VS Code and set up the terminal command to open it from the command line.
- Create a project folder with a Python virtual environment and Flask installed.
- Build a minimal Flask app and confirm it responds to requests.
Download and install VS Code
VS Code is the code editor you'll use to write your Python API.
- Go to https://code.visualstudio.com/ and click Download for macOS.
- Open the downloaded .dmg file.
- Drag Visual Studio Code.app to the Applications folder.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
Now you'll set up the code terminal command so you can open VS Code from the command line later.
- Open the Command Palette by pressing Cmd+Shift+P.
- Type shell command in the search bar.
- Click Shell Command: Install 'code' command in PATH.
What is VS Code?
VS Code (Visual Studio Code) is a free code editor made by Microsoft. It gives you syntax highlighting, file management, and an integrated terminal all in one window.
The code command you just installed lets you open files and folders in VS Code directly from Terminal.
Create your project folder and install Flask
Your API needs a dedicated folder to keep all its files together, plus Flask installed in an isolated environment.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Move to your Desktop and create the project folder by running these commands:
cd ~/Desktop
mkdir banking-api
cd banking-api
- Confirm the folder exists by running:
ls ~/Desktop
You should see banking-api listed in the output.
Next, you'll create a virtual environment. This keeps Flask and its dependencies contained inside this project folder instead of installing them across your whole system.
- Create and activate your virtual environment by running these commands:
python3 -m venv venv
source venv/bin/activate
What is a virtual environment?
A virtual environment is an isolated space where Python packages are installed just for one project. It prevents conflicts between different projects that might need different versions of the same library.
After activation, your terminal prompt changes to show (venv) at the beginning. This confirms the virtual environment is active.
Don't see (venv) in your prompt?
Make sure you ran both commands in order. The first command creates the environment. The second command activates it.
If you see an error about python3 not being found, confirm Python is available by running python3 --version.
Help me troubleshoot my virtual environment activation.
- Install Flask by running:
pip install Flask==3.1.3
- Verify Flask installed correctly by running:
python3 -c "import flask; print(flask.__version__)"
You should see 3.1.3 printed in the terminal.
Seeing an installation error?
Make sure your virtual environment is active. You should see (venv) in your prompt. Without it, pip may refuse to install packages on macOS due to system Python protections.
If your environment deactivated, re-run source venv/bin/activate and try the install command again.
Help me fix my Flask installation error.
Create and verify the starter app
Now you'll create a minimal Flask app with a single endpoint. This confirms your setup works before you add banking logic.
- Open your banking-api folder in VS Code by running:
code .
What does this command do?
The code . command opens the current folder (the dot means "here") in VS Code. This is why you installed the code command in PATH earlier.
- In VS Code, create a new file by pressing Cmd+N (macOS) or Ctrl+N (Windows).
- Save the file immediately with Cmd+S (macOS) or Ctrl+S (Windows) and name it app.py.
Why app.py?
Flask looks for a file named app.py by default when you run the flask run command. Naming your file this way means you won't need any extra flags to start your server.
- Add the following code to app.py:
from flask import Flask, request
# Create the Flask application instance
app = Flask(__name__)
# Health check endpoint - confirms the server is running
@app.route("/")
def health_check():
return {"status": "healthy"}
What does this code do?
from flask import Flask, request imports the Flask class (to create your app) and request (which you'll use in later steps to read incoming data).
app = Flask(__name__) creates your application instance. Flask uses __name__ to know where your project lives on disk.
The @app.route("/") decorator tells Flask to call health_check() whenever someone visits the root URL. Flask automatically converts the dictionary return into a JSON response.
- Save app.py with Cmd+S (macOS) or Ctrl+S (Windows).
- Switch back to your Terminal window where you activated the virtual environment.
- Start the Flask development server by running:
flask run --debug
What does --debug do?
The --debug flag enables auto-reload. Whenever you save changes to app.py, Flask automatically restarts the server so you don't have to stop and restart it manually.
You should see output indicating the server is running on http://127.0.0.1:5000. Leave this terminal running.
- Open a new Terminal tab by pressing Cmd+T.
- Test your endpoint by running:
curl http://127.0.0.1:5000/
You should see {"status":"healthy"} printed in the terminal. That confirms your Flask server is running and responding to requests.
Not seeing the healthy response?
Make sure the Flask server is still running in your first Terminal tab. If you see a connection error, switch back and check for error messages there.
If the server shows a syntax error, double-check your app.py code matches the example above exactly. Make sure the file is saved.
Help me troubleshoot my Flask server not responding.
✔️ Awesome, I've got everything!
Great. Make sure you've saved app.py and your server is still running in the background.
ⓧ I'd like to double check the full code
Here is the complete app.py file as it should look right now:
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def health_check():
return {"status": "healthy"}
Your development environment is ready and your server is responding. Next up, you'll build the endpoints that create and retrieve bank accounts.
Build Account Creation and Retrieval Endpoints
Your API server is running and responding with a health check. Now it needs to actually do something useful.
In this step, you'll build the two most fundamental endpoints for any banking API: one to create accounts and one to retrieve account details. These endpoints use different HTTP methods (POST for creating, GET for reading) and return appropriate status codes to communicate success or failure.
In this step, get ready to:
- Set up in-memory data storage for accounts.
- Build a POST endpoint to create new bank accounts.
- Build a GET endpoint to retrieve an account by ID.
Set up in-memory data storage
Before you can create accounts, you need somewhere to store them. A Python dictionary works perfectly as a simple in-memory database for development.
- In VS Code, open app.py.
- Find the line that says app = Flask(__name__).
- Add the following storage variables directly below it (leave a blank line between them):
# In-memory storage
accounts = {}
next_id = 1
What does this code do?
These two variables act as your temporary database.
- accounts is an empty dictionary that will hold all account data, keyed by account ID.
- next_id starts at 1 and increments each time a new account is created, giving each account a unique ID.
- Save app.py.
You won't see a visible change yet. The next chunk uses these variables to store new accounts.
Build the POST /accounts endpoint
A POST request signals that the client wants to create something new. Your endpoint will accept a JSON body with an account owner's name, validate it, and return the newly created account.
- In app.py, find the health_check function. Add the following endpoint below it (leave a blank line between them):
@app.route("/accounts", methods=["POST"])
def create_account():
global next_id
data = request.get_json()
if not data or "owner" not in data:
return {"error": "Missing required field: owner"}, 400
account = {
"id": next_id,
"owner": data["owner"],
"balance": 0.0
}
accounts[next_id] = account
next_id += 1
return account, 201
What does this code do?
- @app.route("/accounts", methods=["POST"]) tells Flask this function only responds to POST requests at the /accounts path.
- request.get_json() parses the JSON body sent by the client.
- The if not data or "owner" not in data check validates the input. If validation fails, the endpoint returns a 400 status code (Bad Request).
- Returning account, 201 sends the new account as JSON with a 201 status code (Created). Flask automatically converts the dictionary to JSON.
- Save app.py. The dev server auto-reloads your changes.
- Test the endpoint by running this curl command in a new Terminal tab:
curl -X POST http://127.0.0.1:5000/accounts -H "Content-Type: application/json" -d '{"owner": "Alice"}'
You should see a JSON response with Alice's new account: {"balance":0.0,"id":1,"owner":"Alice"}.
Seeing a 405 Method Not Allowed error?
- Make sure you included methods=["POST"] in the route decorator. Without it, Flask only allows GET requests.
- Check that your curl command includes -X POST. Without it, curl defaults to a GET request.
- Confirm the file is saved. The dev server only reloads when it detects a saved change.
Help me debug my POST /accounts endpoint
Build the GET /accounts/<id> endpoint
Now that you can create accounts, you need a way to look them up. A GET request retrieves data without modifying anything. This endpoint uses a URL variable to capture the account ID directly from the path.
- In app.py, add the following endpoint below the create_account function:
@app.route("/accounts/<int:account_id>", methods=["GET"])
def get_account(account_id):
if account_id not in accounts:
return {"error": "Account not found"}, 404
return accounts[account_id], 200
What does this code do?
- <int:account_id> in the route captures an integer from the URL and passes it to the function as the account_id parameter.
- If the ID is not in the accounts dictionary, the endpoint returns a 404 (Not Found) with an error message.
- If the account exists, it returns the account data with a 200 (OK) status.
- Save app.py.
- Retrieve Alice's account by running:
curl http://127.0.0.1:5000/accounts/1
You should see Alice's account data: {"balance":0.0,"id":1,"owner":"Alice"}.
What if you see an empty response or 404?
The dev server restarted when you saved the file, which cleared all in-memory data. You need to re-create Alice's account first by running the POST curl command from the previous substep, then try the GET again.
Seeing a 404 even after re-creating the account?
- Make sure your route uses <int:account_id> (with the int: prefix). Without it, Flask passes the value as a string and the dictionary lookup fails.
- Check that you're using the correct URL path. It should be /accounts/1 (not /account/1).
Help me debug my GET /accounts endpoint
✔️ 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 app.py at the end of this step:
from flask import Flask, request
app = Flask(__name__)
# In-memory storage
accounts = {}
next_id = 1
@app.route("/")
def health_check():
return {"status": "healthy"}
@app.route("/accounts", methods=["POST"])
def create_account():
global next_id
data = request.get_json()
if not data or "owner" not in data:
return {"error": "Missing required field: owner"}, 400
account = {
"id": next_id,
"owner": data["owner"],
"balance": 0.0
}
accounts[next_id] = account
next_id += 1
return account, 201
@app.route("/accounts/<int:account_id>", methods=["GET"])
def get_account(account_id):
if account_id not in accounts:
return {"error": "Account not found"}, 404
return accounts[account_id], 200
Your API can now create and retrieve accounts. Next up, you'll add deposit and withdrawal endpoints so these accounts can actually hold money.
Add Deposit and Withdrawal Endpoints
Your API can create and look up accounts. Now it needs to actually move money.
Deposits and withdrawals are the most common operations any banking API handles. In this step, you will build two new endpoints that modify an account's balance. Both endpoints need careful input validation to prevent invalid operations like negative deposits or overdrafts.
In this step, get ready to:
- Build a deposit endpoint that adds funds to an account.
- Build a withdrawal endpoint that subtracts funds with overdraft protection.
- Test both endpoints with success and error cases using curl.
Build the deposit endpoint
The deposit endpoint accepts a JSON body with an amount, validates the input, and adds the amount to the account's balance. It needs three layers of validation: the account must exist, the amount field must be present, and the amount must be a positive number.
- In app.py, scroll to the bottom of the file (below the get_account function) and add the deposit endpoint by copying this code:
@app.route("/accounts/<int:account_id>/deposit", methods=["POST"])
def deposit(account_id):
# Check if the account exists
if account_id not in accounts:
return {"error": "Account not found"}, 404
data = request.get_json()
# Validate that amount field is present
if not data or "amount" not in data:
return {"error": "Missing required field: amount"}, 400
amount = data["amount"]
# Validate amount is a positive number
if not isinstance(amount, (int, float)) or amount <= 0:
return {"error": "Amount must be a positive number"}, 400
# Add the deposit to the account balance
accounts[account_id]["balance"] += amount
return accounts[account_id], 200
What does this code do?
This endpoint handles three validation checks before allowing a deposit.
- The route /accounts/<int:account_id>/deposit captures the account ID from the URL. Flask automatically converts it to an integer.
- request.get_json() parses the incoming JSON body. If the body is missing or amount is not included, the endpoint returns a 400 error.
- isinstance(amount, (int, float)) ensures the value is actually a number, not a string or other type.
- The function returns the full updated account object with status 200 on success. Flask automatically converts the dictionary to a JSON response.
- Save app.py.
- In your terminal where Flask is running, you should see a message like Detected change in followed by Reloading. This confirms the server picked up your changes.
Don't see the reload message?
- Make sure you started the server with flask run --debug. Without the --debug flag, Flask will not auto-reload.
- Check that you saved the file in VS Code (Cmd+S).
Why isn't my Flask server auto-reloading?
Build the withdrawal endpoint
The withdrawal endpoint is nearly identical to deposit, with one critical addition. It checks that the account has enough funds before allowing the withdrawal. This is the overdraft protection that any real banking API would enforce.
- In app.py, add the withdrawal endpoint directly below the deposit function you just wrote:
@app.route("/accounts/<int:account_id>/withdraw", methods=["POST"])
def withdraw(account_id):
# Check if the account exists
if account_id not in accounts:
return {"error": "Account not found"}, 404
data = request.get_json()
# Validate that amount field is present
if not data or "amount" not in data:
return {"error": "Missing required field: amount"}, 400
amount = data["amount"]
# Validate amount is a positive number
if not isinstance(amount, (int, float)) or amount <= 0:
return {"error": "Amount must be a positive number"}, 400
# Check if the account has sufficient funds
if amount > accounts[account_id]["balance"]:
return {"error": "Insufficient funds"}, 400
# Subtract the withdrawal from the account balance
accounts[account_id]["balance"] -= amount
return accounts[account_id], 200
What does this code do?
This endpoint shares the same validation pattern as deposit, plus one extra check.
- amount > accounts[account_id]["balance"] is the overdraft protection. If the requested withdrawal exceeds the current balance, the API returns a 400 error with "Insufficient funds".
- This check only runs after confirming the amount is valid. The order of validation matters. You want to catch missing fields before checking business logic.
- Save app.py.
- Confirm the Flask server reloads again in your terminal.
Seeing a syntax error in the terminal?
- Check that your withdraw function is at the same indentation level as the deposit function above it. Python is strict about indentation.
- Make sure there is a blank line between the end of the deposit function and the @app.route decorator for withdraw.
Help me fix a syntax error in my Flask app after adding the withdraw endpoint.
✔️ Awesome, I've got everything!
Great. Double check you have saved app.py and the server has reloaded.
ⓧ I'd like to double check the full code
Here is the complete app.py file up to this point. Compare your code against it:
from flask import Flask, request
app = Flask(__name__)
# In-memory storage
accounts = {}
next_id = 1
@app.route("/")
def health_check():
return {"status": "healthy"}
@app.route("/accounts", methods=["POST"])
def create_account():
global next_id
data = request.get_json()
if not data or "owner" not in data:
return {"error": "Missing required field: owner"}, 400
account = {
"id": next_id,
"owner": data["owner"],
"balance": 0.0
}
accounts[next_id] = account
next_id += 1
return account, 201
@app.route("/accounts/<int:account_id>", methods=["GET"])
def get_account(account_id):
if account_id not in accounts:
return {"error": "Account not found"}, 404
return accounts[account_id], 200
@app.route("/accounts/<int:account_id>/deposit", methods=["POST"])
def deposit(account_id):
if account_id not in accounts:
return {"error": "Account not found"}, 404
data = request.get_json()
if not data or "amount" not in data:
return {"error": "Missing required field: amount"}, 400
amount = data["amount"]
if not isinstance(amount, (int, float)) or amount <= 0:
return {"error": "Amount must be a positive number"}, 400
accounts[account_id]["balance"] += amount
return accounts[account_id], 200
@app.route("/accounts/<int:account_id>/withdraw", methods=["POST"])
def withdraw(account_id):
if account_id not in accounts:
return {"error": "Account not found"}, 404
data = request.get_json()
if not data or "amount" not in data:
return {"error": "Missing required field: amount"}, 400
amount = data["amount"]
if not isinstance(amount, (int, float)) or amount <= 0:
return {"error": "Amount must be a positive number"}, 400
if amount > accounts[account_id]["balance"]:
return {"error": "Insufficient funds"}, 400
accounts[account_id]["balance"] -= amount
return accounts[account_id], 200
Test the validation logic with curl
Your endpoints are ready. Time to prove they work. You will test four scenarios: a successful deposit, a successful withdrawal, an overdraft attempt, and a negative amount attempt.
Why test error cases?
In banking, validation failures are just as important as successful operations. An API that accepts a negative deposit or allows an overdraft has a serious bug. Testing these cases proves your validation logic catches bad input correctly.
Before running these tests, you need a fresh account with a zero balance. Your server restarted at some point during development, which cleared the in-memory data. Create a new account first.
- Switch to your second terminal tab (the one you use for curl commands).
- Create a fresh account by running:
curl -X POST http://127.0.0.1:5000/accounts -H "Content-Type: application/json" -d '{"owner": "Alice"}'
You should see Alice's account returned with an id of 1 and a balance of 0.0.
- Deposit 500 into Alice's account by running:
curl -X POST http://127.0.0.1:5000/accounts/1/deposit -H "Content-Type: application/json" -d '{"amount": 500}'
You should see Alice's account with a balance of 500.0.
- Withdraw 200 from Alice's account by running:
curl -X POST http://127.0.0.1:5000/accounts/1/withdraw -H "Content-Type: application/json" -d '{"amount": 200}'
You should see Alice's account with a balance of 300.0.
- Test the overdraft protection by trying to withdraw more than the balance:
curl -X POST http://127.0.0.1:5000/accounts/1/withdraw -H "Content-Type: application/json" -d '{"amount": 9999}'
You should see a 400 response with {"error":"Insufficient funds"}.
- Test the negative amount validation by running:
curl -X POST http://127.0.0.1:5000/accounts/1/deposit -H "Content-Type: application/json" -d '{"amount": -50}'
You should see a 400 response with {"error":"Amount must be a positive number"}.
Getting 'Account not found' on every request?
- If your Flask server restarted, it cleared all in-memory data. You need to create the account again before depositing or withdrawing.
- Make sure the account ID in your URL matches the ID returned when you created the account. It should be 1 if this is the first account you created after a restart.
Help me debug why my deposit and withdraw endpoints keep returning Account not found.
Your API now handles deposits and withdrawals with full input validation. Next up, you will build the transfer endpoint that moves money between two accounts in a single operation.
Build the Transfer Endpoint
Your API can now create accounts, check balances, deposit, and withdraw funds. The final piece is the most complex banking operation: transferring money between two accounts.
Transfers are harder than single-account operations because they involve two accounts at once. Your endpoint needs to validate both the source and destination before moving any money.
In this step, get ready to:
- Build the POST /transfers endpoint with multi-account validation.
- Test a successful transfer between two accounts using curl.
- Verify error handling for missing fields, non-existent accounts, and insufficient funds.
Build the POST /transfers endpoint
A transfer needs to check three required fields, verify both accounts exist, confirm the sender has enough funds, and only then move the money. This is the most validation-heavy endpoint in the API.
- In app.py, scroll to the bottom of the file (below the withdraw function) and add the transfer endpoint by copying this code:
@app.route("/transfers", methods=["POST"])
def transfer():
data = request.get_json()
# Reject empty requests
if not data:
return {"error": "Missing request body"}, 400
# Ensure all three required fields are present
required_fields = ["from_account", "to_account", "amount"]
for field in required_fields:
if field not in data:
return {"error": f"Missing required field: {field}"}, 400
from_id = data["from_account"]
to_id = data["to_account"]
amount = data["amount"]
# Verify both accounts exist
if from_id not in accounts:
return {"error": "Source account not found"}, 404
if to_id not in accounts:
return {"error": "Destination account not found"}, 404
# Validate the transfer amount
if not isinstance(amount, (int, float)) or amount <= 0:
return {"error": "Amount must be a positive number"}, 400
# Check sender has enough funds
if amount > accounts[from_id]["balance"]:
return {"error": "Insufficient funds"}, 400
# Execute the transfer
accounts[from_id]["balance"] -= amount
accounts[to_id]["balance"] += amount
return {
"message": "Transfer successful",
"from_account": accounts[from_id],
"to_account": accounts[to_id]
}, 200
What does this code do?
This endpoint handles the full lifecycle of a money transfer between two accounts.
- required_fields loops through three expected keys and returns a 400 error if any are missing.
- Two separate if checks verify the source and destination accounts exist, returning 404 with specific messages for each.
- The amount validation and insufficient-funds check reuse the same pattern from your withdraw endpoint.
- Only after all five validations pass does the function subtract from the source and add to the destination.
- The response returns both updated accounts so the caller can confirm both balances changed.
- Save app.py.
- Your Flask dev server auto-reloads because you started it with --debug. Check the Terminal tab running the server for a "Restarting with stat" message.
Don't see the restart message?
- Make sure you saved the file. Unsaved changes won't trigger a reload.
- If the server crashed, check for a syntax error in your Terminal output. A missing colon or mismatched quote is the most common cause.
- If the server isn't running at all, activate your virtual environment with source venv/bin/activate and restart with flask run --debug.
Why isn't my Flask server reloading after I save?
✔️ Awesome, I've got everything!
Great. Double check you've saved app.py and the server reloaded successfully.
ⓧ I'd like to double check the full code
Here is the complete app.py file at this point:
from flask import Flask, request
app = Flask(__name__)
# In-memory storage
accounts = {}
next_id = 1
@app.route("/")
def health_check():
return {"status": "healthy"}
@app.route("/accounts", methods=["POST"])
def create_account():
global next_id
data = request.get_json()
if not data or "owner" not in data:
return {"error": "Missing required field: owner"}, 400
account = {
"id": next_id,
"owner": data["owner"],
"balance": 0.0
}
accounts[next_id] = account
next_id += 1
return account, 201
@app.route("/accounts/<int:account_id>", methods=["GET"])
def get_account(account_id):
if account_id not in accounts:
return {"error": "Account not found"}, 404
return accounts[account_id], 200
@app.route("/accounts/<int:account_id>/deposit", methods=["POST"])
def deposit(account_id):
if account_id not in accounts:
return {"error": "Account not found"}, 404
data = request.get_json()
if not data or "amount" not in data:
return {"error": "Missing required field: amount"}, 400
amount = data["amount"]
if not isinstance(amount, (int, float)) or amount <= 0:
return {"error": "Amount must be a positive number"}, 400
accounts[account_id]["balance"] += amount
return accounts[account_id], 200
@app.route("/accounts/<int:account_id>/withdraw", methods=["POST"])
def withdraw(account_id):
if account_id not in accounts:
return {"error": "Account not found"}, 404
data = request.get_json()
if not data or "amount" not in data:
return {"error": "Missing required field: amount"}, 400
amount = data["amount"]
if not isinstance(amount, (int, float)) or amount <= 0:
return {"error": "Amount must be a positive number"}, 400
if amount > accounts[account_id]["balance"]:
return {"error": "Insufficient funds"}, 400
accounts[account_id]["balance"] -= amount
return accounts[account_id], 200
@app.route("/transfers", methods=["POST"])
def transfer():
data = request.get_json()
if not data:
return {"error": "Missing request body"}, 400
required_fields = ["from_account", "to_account", "amount"]
for field in required_fields:
if field not in data:
return {"error": f"Missing required field: {field}"}, 400
from_id = data["from_account"]
to_id = data["to_account"]
amount = data["amount"]
if from_id not in accounts:
return {"error": "Source account not found"}, 404
if to_id not in accounts:
return {"error": "Destination account not found"}, 404
if not isinstance(amount, (int, float)) or amount <= 0:
return {"error": "Amount must be a positive number"}, 400
if amount > accounts[from_id]["balance"]:
return {"error": "Insufficient funds"}, 400
accounts[from_id]["balance"] -= amount
accounts[to_id]["balance"] += amount
return {
"message": "Transfer successful",
"from_account": accounts[from_id],
"to_account": accounts[to_id]
}, 200
Test the full transfer flow with curl
To test a transfer, you need two accounts with funds. Alice's account from the previous step already has a balance, so you just need to create a second account for Bob.
- In your second Terminal tab (the one you use for curl), create a second account by running this command:
curl -X POST http://127.0.0.1:5000/accounts -H "Content-Type: application/json" -d '{"owner": "Bob"}'
- You should see a JSON response with Bob's new account (id 2, balance 0.0).
- Transfer 100 from Alice (account 1) to Bob (account 2) by running:
curl -X POST http://127.0.0.1:5000/transfers -H "Content-Type: application/json" -d '{"from_account": 1, "to_account": 2, "amount": 100}'
- You should see a response with "Transfer successful", Alice's balance at 200.0, and Bob's balance at 100.0.
- Verify Alice's balance by running:
curl http://127.0.0.1:5000/accounts/1
- Verify Bob's balance by running:
curl http://127.0.0.1:5000/accounts/2
- Alice should show a balance of 200.0 and Bob should show 100.0.
Seeing "Account not found" for account 1?
If you stopped and restarted the server since the previous step, the in-memory data was wiped. Re-create Alice's account with the POST /accounts command, then deposit funds before testing the transfer.
Help me debug my transfer endpoint returning account not found errors.
Test error cases
A well-built API handles bad input gracefully. Test that your transfer endpoint rejects invalid requests with the correct HTTP status codes.
- Test a transfer from a non-existent account (should return 404) by running:
curl -X POST http://127.0.0.1:5000/transfers -H "Content-Type: application/json" -d '{"from_account": 999, "to_account": 2, "amount": 50}'
- You should see "Source account not found" with a 404 status.
- Test transferring more than the balance (should return 400) by running:
curl -X POST http://127.0.0.1:5000/transfers -H "Content-Type: application/json" -d '{"from_account": 1, "to_account": 2, "amount": 9999}'
- You should see "Insufficient funds" with a 400 status.
- Test a request with missing fields (should return 400) by running:
curl -X POST http://127.0.0.1:5000/transfers -H "Content-Type: application/json" -d '{"from_account": 1}'
- You should see "Missing required field: to_account" with a 400 status.
- Stop the Flask server by pressing Ctrl+C in the Terminal tab where it's running.
Why test error cases?
In a banking context, untested edge cases can mean lost money or security vulnerabilities. Demonstrating that your API rejects overdrafts, missing data, and invalid accounts shows you think about real-world reliability.
Secret mission
Add Transaction History
In real banking, every operation leaves a paper trail. Extend your API with a full transaction ledger so each account records every deposit, withdrawal, and transfer. Then add an endpoint to retrieve the complete history.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- Flask development server (running process on port 5000).
- banking-api project folder on your Desktop (contains app.py and the venv virtual environment).
Keep everything running
No action needed. Choose this if you're still actively building or want to demo the API again soon.
- Your Flask server continues running at http://127.0.0.1:5000.
- Your banking-api folder and virtual environment stay on your Desktop.
- VS Code remains installed on your machine.
Pause - I'll come back to this later
Stop the running server to free up the terminal, but keep all your files for later.
- In the terminal where Flask is running, press Ctrl+C to stop the server.
- Run deactivate to exit the virtual environment.
- To restart later, open Terminal, navigate to your banking-api folder, run source venv/bin/activate, then run flask run --debug.
Delete - I don't want to use this again
Remove all project files and start fresh.
- In the terminal where Flask is running, press Ctrl+C to stop the server.
- Run deactivate to exit the virtual environment.
- Delete the entire project folder by running:
cd ~/Desktop
rm -rf banking-api
- Confirm the folder is gone by running:
ls ~/Desktop | grep banking-api
You should see no output, confirming the folder has been removed.
Nice Work!
Nice Work!
You just built a fully functional Banking Transactions REST API from scratch using Python and Flask.
You've learned how to:
- Built a REST API server with five endpoints covering account creation, balance retrieval, deposits, withdrawals, and transfers.
- Added input validation and error handling that returns appropriate HTTP status codes (201, 200, 400, 404) for every request.
- Handled banking-specific logic including balance checks, overdraft prevention, and multi-account transfers.
- Secret Mission: Added transaction history tracking with a full audit ledger per account.
Ready to quiz yourself?