Build Your Own Brokerage With FastAPI - Part 3
We explain how to add the ability to fund accounts through automated clearing house (ACH) relationships to the back end.
Welcome back to our Build Your Own Brokerage series! Be sure to visit the first and second parts of this series if you’d like to follow the progression. The current version of the backend can be found on Alpaca’s GitHub.
Today we’re adding the ability to fund accounts through automated clearing house (ACH) relationships to the back end. This can be done in two steps: using Plaid to create ACH relationships and using Broker API to transfer account funds via an account’s ACH relationship. Let’s start off by implementing Plaid’s functionality.
Integrating Plaid API
To start using Plaid API, you’ll need API keys, so sign up for a free Plaid account. Integrating Plaid API into the backend can be split into 3 easy steps.
- Write schemas for the new data being created and passed around
- Develop a simple front end for Plaid’s client-side component, Plaid Link
- Create the endpoints Plaid Link uses to communicate with your back end server
Starting from the top of the list, let’s write those schemas.
Writing the New Schemas
The first schema contains the data used to exchange for a Plaid processor token. This is step 3 in the Plaid Link flow overview. The exchange data are two strings, a public token, and an account ID. A public token is used to initialize Link and the account ID identifies the account from which to transfer funds. A processor token is a string.
class PlaidExchangeInfo(BaseModel):
public_token: str
account_id: str
class ProcessorToken(BaseModel):
processor_token: str
Nice and easy, right? Next is step two, the front end.
Creating a Front End
The front end will be a simple React app that mimics Plaid’s Link Demo. Plaid Link is a client-side component that users interact with to link their accounts to Plaid. The ID for a chosen linked account will then generate a processor token. A processor token is what enables Alpaca Broker API to create an ACH relationship. I would highly recommend having the Link flow overview on-hand so that you can refer to it throughout this section.
Start off by creating your React app in a new folder and installing the necessary dependencies for Plaid and Sass.
npx create-react-app frontend
cd frontend
npm install node-sass
npm install --save react-plaid-link
Navigate inside of your app and delete everything inside the src/ folder. Plaid has made public a lightweight quickstart repo that we’ll be taking from to speed up development. Copy and paste all the files in their react/src/ folder into your own src/. From here we only need to make changes to the .jsx files. Starting with index.jsx, change the file to this content:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Now, move over to App.jsx. If you recall from the last parts, we have attached to our endpoints a check for a valid Cognito access token. Since this front end is just for Plaid Link, we’ll hardcode in a token to skirt around this problem for now.
// Hardcode this because our frontend does not have Cognito login yet
const cognito_token = "<VALID-TOKEN-HERE>"
Starting with step 1 in the Link flow, find createLinkToken. Change the parameters inside of the fetch request to match what your server is expecting. Note that we haven’t implemented this endpoint yet. Don’t forget to put the Cognito token into the headers too.
// Creates a Link token
const createLinkToken = React.useCallback(async () => {
// For OAuth, use previously generated Link token
if (window.location.href.includes("?oauth_state_id=")) {
const linkToken = localStorage.getItem('link_token');
setToken(linkToken);
} else {
const response = await fetch("http://localhost:8000/plaid/create_link_token", {
method: "POST",
headers: {
"Content-Type": "application/json",
"access-token": cognito_token,
},
});
const data = await response.json();
const linkToken = data.link_token
setToken(linkToken);
localStorage.setItem("link_token", linkToken);
}
}, [setToken]);
Onto step 2, find and modify the onSuccess callback. Add a second parameter to the function call so that you can receive a user’s metadata and account information. This example will arbitrarily store the first account ID inside of the array of accounts. It’s important that this account is a type that is valid for an ACH relationship (checking, saving). Change the request URL to reflect what the server is expecting for this step. Again, this endpoint is not yet implemented. Add the access token and the account ID and then save the response to this API request. The result will be the processor token. We won’t be using getBalance, so remove the call and its definition.
const onSuccess = useCallback(async (publicToken, metadata) => {
const firstAccountID = metadata.accounts[0].id
setLoading(true);
const response = await fetch("http://localhost:8000/plaid/exchange_public_token", {
method: "POST",
headers: {
"Content-Type": "application/json",
"access-token": cognito_token,
},
body: JSON.stringify({ public_token: publicToken, account_id: firstAccountID }),
});
const data = await response.json(); // Contains processor token
console.log(JSON.stringify(data));
}, []);
The front end is complete! Right now the link account button doesn’t work, though. This is because we haven’t implemented the endpoints that our front end talks to. Let’s do that now.
Integrating Plaid With Our Back End
Setting Up the Plaid Client
The endpoints and functions developed in this section require that we use an authenticated Plaid client. Find your API keys in your dashboard and save them inside the .env file.
PLAID_CLIENT_ID=<Your-ID-Here>
PLAID_SECRET=<Your-Secret-Here>
We’ll write the code to configure the Plaid client inside the config/ folder. Create a new file called plaid.py
. Make the necessary imports and store your Plaid keys so we can use them.
from plaid.api import plaid_api
import plaid
import os
from dotenv import load_dotenv
load_dotenv()
PLAID_CLIENT_ID = os.getenv('PLAID_CLIENT_ID')
PLAID_SECRET = os.getenv('PLAID_SECRET')
Plaid’s API client is instantiated using a configuration object. This object takes a host environment and API keys as parameters as seen in the quickstart docs.
host = plaid.Environment.Sandbox
configuration = plaid.Configuration(
host=host,
api_key={
'clientId': PLAID_CLIENT_ID,
'secret': PLAID_SECRET,
'plaidVersion': '2020-09-14'
}
)
Lastly, create a function to instantiate the client and make it easily accessible anywhere by import.
def get_plaid_client():
api_client = plaid.ApiClient(configuration)
client = plaid_api.PlaidApi(api_client)
return client
Now let’s move on to the routes, where we’ll be importing this client.
Writing the Endpoints
Referring back to the front end, we have two endpoints to implement here: one for creating a link token and another for exchanging public tokens for processor tokens. It will be helpful in this section to refer to Plaid’s API docs and their Alpaca partnership docs. Open your routes file and import your plaid configuration. Instantiate the client globally here so you can pass it around freely.
from ..config import database, plaid
plaid_client = plaid.get_plaid_client()
Starting off with creating a link token, this endpoint will take a post request with no parameters and returns a link token. In our case, we still want to pass the request as a parameter so that we can check the header for an access token.
# Create Plaid link token
@router.post("/plaid/create_link_token")
def create_link_token(request: Request):
link_token = crud.get_link_token(plaid_client, request)
return link_token
An implementation of this function is provided by Plaid in their Alpaca partnership docs, so paste that into your code, add Cognito validation, and return the response.
def get_link_token(plaid_client: plaid_api.PlaidApi, request: Request):
access_token = request.headers.get('access-token')
utils.authenticate_token(access_token)
# Create a link_token for the given user
request = LinkTokenCreateRequest(
products=[Products("auth")],
client_name="Plaid Test App",
country_codes=[CountryCode('US')],
language='en',
webhook='https://webhook.example.com',
user=LinkTokenCreateRequestUser(
client_user_id=os.environ.get("PLAID_CLIENT_ID")
)
)
response = plaid_client.link_token_create(request)
# Send the data to the client
return response.to_dict()
The next step in the flow is the exchange for a processor token.
# Get processor token from public token via Plaid
@router.post("/plaid/exchange_public_token")
async def exchange_token(plaid_response: schemas.PlaidExchangeInfo, request: Request):
processor_token = crud.get_processor_token(plaid_response, plaid_client, request)
return processor_token
The Plaid docs have an implementation of this one as well, so follow the same procedure as in the public token step. Since the front end isn’t fully functional, print out the processor token so that you can use it with Alpaca’s API.
def get_processor_token(plaid_response: schemas.PlaidExchangeInfo,
plaid_client: plaid_api.PlaidApi,
request: Request):
access_token = request.headers.get('access-token')
utils.authenticate_token(access_token)
# Exchange the public token from Plaid Link for an access token.
public_token = plaid_response.public_token
account_id = plaid_response.account_id
exchange_request = ItemPublicTokenExchangeRequest(public_token=public_token)
exchange_token_response = plaid_client.item_public_token_exchange(exchange_request)
access_token = exchange_token_response['access_token']
# Create a processor token for a specific account id.
create_request = ProcessorTokenCreateRequest(
access_token=access_token,
account_id=account_id,
processor="alpaca"
)
create_response = plaid_client.processor_token_create(create_request)
processor_token = create_response['processor_token']
print(f"Processor token is: {processor_token}")
return {"processor_token": processor_token}
Lastly, to facilitate the communication between the front end and back end, add CORS middleware to the main.py file of your application.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .routers import routes
app = FastAPI()
origins = [
"*",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(routes.router)
That finishes up all of the Plaid integrations for now. Now let’s bring in Broker API functionality.
Integrating Broker API’s Funding Functionality
The funding API enables bank/ACH connections and funds transfers in and out of accounts. In setting up this flow there are two steps -- creating an ACH relationship and transferring funds via an ACH relationship, outlined in the Alpaca-py funding docs.
Create an ACH Relationship
First, create the endpoint inside routes.
# Create ACH relationship using processor token
@router.post("/accounts/{identifier}/ach_relationship", response_model=schemas.Account)
async def create_ach_relationship(processor_token: schemas.ProcessorToken, identifier: str, request: Request, db: Session = Depends(database.get_db)):
relationship = crud.create_ach_relationship(processor_token, identifier, db, request)
return relationship
There's a code snippet of this outlined in the docs, but this implementation is different since we’re using Plaid. Import and use the CreatePlaidRelationshipRequest class to create ACH relationships via Plaid. Pass this object into the relationship creation function as ach_data and the rest of the flow is the same as in the docs.
def create_ach_relationship(processor_token: schemas.ProcessorToken, identifier: str, db: Session, request: Request):
# Obtain Alpaca ID from identifier
account = get_account(db, identifier, request)
alpaca_id = str(account.id)
broker_client = get_broker_client()
ach_data = CreatePlaidRelationshipRequest(processor_token=processor_token.processor_token)
ach_relationship = broker_client.create_ach_relationship_for_account(
account_id=alpaca_id,
ach_data=ach_data
)
return {"ach_relationship": ach_relationship}
Transferring Funds via ACH Relationship
Write the new endpoint inside of routes.
# Transfer money using ACH relationship
@router.post("/accounts/{identifier}/transfer", response_model=schemas.Account)
async def create_funds_transfer(request_params: schemas.FundsTransferRequest, identifier: str, request: Request, db: Session = Depends(database.get_db)):
transfer = crud.create_funds_transfer(request_params, identifier, db, request)
return transfer
Refer to the funding docs again for the implementation of this functionality. The few differences in your implementation are that we’re receiving variables as parameters instead of hardcoding them in.
def create_funds_transfer(request_params: schemas.FundsTransferRequest, identifier: str, db: Session, request: Request):
account = get_account(db, identifier, request)
alpaca_id = str(account.id)
relationship_id = request_params.relationship_id
transfer_amount = request_params.transfer_amount
broker_client = get_broker_client()
transfer_data = CreateACHTransferRequest(
amount=transfer_amount,
direction=TransferDirection.INCOMING,
timing=TransferTiming.IMMEDIATE,
relationship_id=relationship_id.relationship_id
)
transfer = broker_client.create_transfer_for_account(
account_id=alpaca_id,
transfer_data=transfer_data
)
return {"transfer_data": transfer}
Conclusion
In this blog, we outlined how to integrate Plaid Link on the front end, how to have your back end communicate with Plaid API, and how to use Plaid API and Broker API together to fund accounts at your brokerage. The docs from Plaid and Alpaca give you a good basis to build on, and the code we’ve written on top of those snippets helps you to integrate these examples into your own back end. Check out Automated Crypto Scalping with Alpaca for more use cases of Alpaca-py.
Please note that this article is for general informational purposes only. All screenshots are for illustrative purposes only.
Alpaca does not prepare, edit, or endorse Third Party Content. Alpaca does not guarantee the accuracy, timeliness, completeness or usefulness of Third Party Content, and is not responsible or liable for any content, advertising, products, or other materials on or available from third party sites.
Securities brokerage services are provided by Alpaca Securities LLC ("Alpaca Securities"), member FINRA/SIPC, a wholly-owned subsidiary of AlpacaDB, Inc. Technology and services are offered by AlpacaDB, Inc.
Cryptocurrency services are made available by Alpaca Crypto LLC ("Alpaca Crypto"), a FinCEN registered money services business (NMLS # 2160858), and a wholly-owned subsidiary of AlpacaDB, Inc. Alpaca Crypto is not a member of SIPC or FINRA. Cryptocurrencies are not stocks and your cryptocurrency investments are not protected by either FDIC or SIPC. Depending on your location, cryptocurrency services may be provided by West Realm Shires Services, Inc., d/b/a FTX US (NMLS #1957771). Please see the Disclosure Library for more information.
This is not an offer, solicitation of an offer, or advice to buy or sell securities or cryptocurrencies, or open a brokerage account or cryptocurrency account in any jurisdiction where Alpaca Securities, Alpaca Crypto, or FTX US respectively, are not registered or licensed, as applicable.