With this quickstart guide, we’ve tried to make it as easy as possible to get up and running with Plaid.

If you have any questions along the way, please head to the Help Center or refer to the API documentation.

We’ll start with an overview of Plaid and then jump start your integration with a step-by-step guide and code samples in Ruby, Python, and Node. If you’d like to follow along (and test it out!), clone the quickstart repository for the complete example apps. You’ll want to sign up for free API keys to get started.

Running the walkthrough app

git clone https://github.com/plaid/quickstart.git
cd quickstart/node
npm install

# Start the sample app using your API keys available from the Dashboard:
# https://dashboard.plaid.com
APP_PORT=8000 \
PLAID_ENV=sandbox \
node index.js
# Go to http://localhost:8000
git clone https://github.com/plaid/quickstart.git
cd quickstart/ruby

# Install dependencies

# Start the sample app using your API keys available from the Dashboard:
# https://dashboard.plaid.com
ruby app.rb
# Go to http://localhost:4567
git clone https://github.com/plaid/quickstart.git
cd quickstart/python
pip install -r requirements.txt

# Fill in your API keys (https://dashboard.plaid.com) in
# server.py and then start the sample app:
python server.py
# Go to http://localhost:5000

Platform Overview

To help you get oriented with Plaid’s API and what it can help you do, let’s start by defining some basics:

API keys

You have three different API keys

View in Dashboard

a non-sensitive, public identifier that is used to initialize Plaid Link

secret client_id

private identifiers that are required for accessing any financial data

these should never be shared in client-side code

Your API keys are the same across all three of our API environments. Use our Sandbox and Development environments to build out and test your integration with simulated and live users, respectively. You’ll move over to our Production environment once you’re ready to go live!

When using our Sandbox environment, use user_good as the username and pass_good for the password. See the docs for more.

API environments

  • sandbox: Stateful sandbox environment; use test credentials and build out and test your integration
  • development: Test your integration with live credentials; you will need to request access before you can access our Development environment
  • production: Production API environment; all requests are billed

Most API requests interact with an Item, a set of credentials (map of key value pairs) associated with a financial institution. Each Item can have many associated Accounts. For each Account, Plaid returns high-level information such as balance, name, and account type. Credit and depository Accounts may also have transactions associated with them.

A single end-user of your application might have accounts at different financial institutions, which means they would have multiple different Items.

Your users create Items through Link, a drop-in module that handles the credential and MFA validation process. Once an Item is created, Link passes a public_token that you exchange for an access_token from your backend app server.

That access_token and item_id uniquely identify the Item. You use the access_token along with your client_id and secret to access products available for an Item and make changes to it over time.

Read on for more information about each of our products and how you create and access data for an Item.

Plaid Products

Accessing data

Once you create an Item, you can then access data—such as transaction data and account and routing numbers—using our API endpoints. You access data for an Item using the Item’s access_token, which is specific to your API keys and cannot be shared or used by any other API keys. By default, an access_token never expires, but you can rotate it.

You can use the /item/get endpoint to inspect the status of an Item and see a list of all available products as well as the ones you're actively using. You can then call different product endpoints to retrieve data. Due to the complexity of communicating with financial institutions, the initial product data request may take some time.

To integrate an existing architecture with the Plaid API, you might try using a table to store access_token and item_id combinations that map to individual users in your system.

Products overview


Retrieve account and routing numbers for ACH authentication. No micro-deposits required.


Clean transaction data going back as far as 24 months. Transaction data may include context such as geolocation, merchant, and category information.


Identity information on file with the bank. Reduce fraud by comparing user-submitted data to validate identity.

This product has to be enabled separately. Contact us for more information


Check balances in real time to prevent non-sufficient funds fees.


Verify employment and income information.

This product has to be enabled separately. Contact us for more information

Institution coverage

As we noted above, an Item represents accounts at a given financial institution, bank, or credit union. Plaid supports thousands of different institutions, and we’re constantly working to add more. Though integration information does change, Link stays up-to-date with Plaid’s latest institution coverage at all times and makes it easy for users to find their intended institution.

Because product coverage does vary by institution, we recommend initializing Link with all the products that your integration will need.

Creating Items with Link and the API

Plaid Link Auth Flow

Now that you have API keys and know the basics of the Plaid API, it's time to integrate with Plaid Link, which will handle credential validation, multi-factor authentication, and error handling for each institution that we support. Link works across all modern browsers and platforms, including on iOS and Android.

You can also customize parts of Link's flow—including some copy elements, the institution select view, and the background color—and enable additional features like the Account Select view straight from the Dashboard. You can preview your changes in real-time and then publish them instantly once you're ready to go live.

Link allows your users to create Items and also update an Item if it goes into an error state, such as when they change their passwords or MFA information. You can initialize Link with multiple products, such as Transactions and Auth, making it easy to use multiple Plaid products right away.

Note that an end-to-end integration involves client-side and server-side configurations.

Plaid is processor-agnostic, but if you need help actually processing ACH payments, Plaid and Stripe have partnered to offer frictionless, tokenized money transfers—no need to handle an account or routing number. Use Plaid Link to instantly authenticate your customer’s account and automatically generate a Stripe bank account token so that you can accept ACH payments via Stripe’s ACH API.

Link’s integration is a pure JavaScript approach that you trigger via your own client-side code, and specify callbacks to handle the public_token after the user has authenticated and created an Item.

Using Link requires an understanding of how Plaid access_tokens and public_tokens work and should be used. Read on for a breakdown of the differences.

access_tokens and public_tokens

A public_token (which is returned in your Link onSuccess() callback) should be passed to your server, which will exchange it for an access_token. public_tokens are one-time use tokens and expire after 30 minutes. You can generate new public_tokens as needed via the /item/public_token/create endpoint.

An access_token is used to access product data for an Item. This should be stored securely, and never in client-side code. This is used to make authenticated requests to the Plaid API for the user. By default, access_tokens do not expire, though you can rotate them; if it winds up in an error state, the access_token will work again if the Item’s error is resolved. Each access_token is unique to a specific Item, and cannot be used to access other Items.

To integrate an existing architecture with the Plaid API, you might try a one-to-many relationship between your user and Items. For each Item, we recommend storing the item_id and access_token.

User authentication, Item creation, and the public_token

Once you’ve got your public_key from the Dashboard, the Link JavaScript integration is fairly straightforward.

Link allows you to create Items with multiple initial products. Link will only show the user institutions that support all of the products you specify. Valid initial products are transactions, auth, identity, and income.

In our example app, we’ll go ahead and open the 'Institution Select' view, so the user can choose which financial institution to authenticate. The global Plaid object has a .create({}) method, which we can customize to suit our needs.

We’ll use this to create an Item with transactions. You can still access other products for the Item later, even if you didn't initialize Link with it. Once the user has successfully authenticated, we’ll pass the public_token from our client-side code to the server.

<button id="link-button">Link Account</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
<script type="text/javascript">
(function($) {
  var handler = Plaid.create({
    clientName: 'Plaid Walkthrough Demo',
    env: 'sandbox',
    key: '[PUBLIC_KEY]', // Replace with your public_key to test with live credentials
    product: ['transactions'],
    webhook: 'https://requestb.in', // Optional – use webhooks to get transaction and error updates
    onLoad: function() {
      // Optional, called when Link loads
    onSuccess: function(public_token, metadata) {
      // Send the public_token to your app server.
      // The metadata object contains info about the institution the
      // user selected and the account ID, if the Account Select view
      // is enabled.
      $.post('/get_access_token', {
        public_token: public_token,
    onExit: function(err, metadata) {
      // The user exited the Link flow.
      if (err != null) {
        // The user encountered a Plaid API error prior to exiting.
      // metadata contains information about the institution
      // that the user selected and the most recent API request IDs.
      // Storing this information can be helpful for support.
    onEvent: function(eventName, metadata) {
      // Optionally capture Link flow events, streamed through
      // this callback as your users connect an Item to Plaid.
      // For example:
      // eventName = "TRANSITION_VIEW"
      // metadata  = {
      //   link_session_id: "123-abc",
      //   mfa_type:        "questions",
      //   timestamp:       "2017-09-14T14:42:19.350Z",
      //   view_name:       "MFA",
      // }

  $('#link-button').on('click', function(e) {

Once a user goes through this flow, an Item will be created with Transactions information, and the onSuccess() callback will pass the public_token back to the server. The server handles the exchange of the public_token for the access_token. This is also where your client_id and secret come into play.

Exchanging the public token for an access token

The next step is to exchange our public_token for an access_token and item_id. The access_token will allow us to make authenticated calls to the Plaid API. Doing so is as easy as calling the /item/public_token/exchange endpoint from our server-side handler. The item_id is used to identify Items in webhooks. We'll use a client library to make the API call.

Save the access_token and item_id in a secure datastore, as they’re used to access Item data and identify webhooks, respectively. A public_token is a one-time use token, so there is no need to store it.

var bodyParser = require('body-parser');
var envvar = require('envvar');
var express = require('express');
var moment = require('moment');
var plaid = require('plaid');

var PLAID_CLIENT_ID  = envvar.string('PLAID_CLIENT_ID');
var PLAID_SECRET     = envvar.string('PLAID_SECRET');
var PLAID_PUBLIC_KEY = envvar.string('PLAID_PUBLIC_KEY');
var PLAID_ENV        = envvar.string('PLAID_ENV', 'sandbox');

// We store the access_token in memory - in production, store it in a secure
// persistent data store
var ACCESS_TOKEN = null;
var PUBLIC_TOKEN = null;

var client = new plaid.Client(

// Accept the public_token sent from Link
var app = express();
app.post('/get_access_token', function(request, response, next) {
  PUBLIC_TOKEN = request.body.public_token;
  client.exchangePublicToken(PUBLIC_TOKEN, function(error, tokenResponse) {
    if (error != null) {
      var msg = 'Could not exchange public_token!';
      console.log(msg + '\n' + error);
      return response.json({error: msg});
    ACCESS_TOKEN = tokenResponse.access_token;
    ITEM_ID = tokenResponse.item_id;
    console.log('Access Token: ' + ACCESS_TOKEN);
    console.log('Item ID: ' + ITEM_ID);
    response.json({'error': false});
require 'sinatra'
require 'plaid'

set :public_folder, File.dirname(__FILE__) + '/public'

client = Plaid::Client.new(env: :sandbox,
                           client_id: ENV['PLAID_CLIENT_ID'],
                           secret: ENV['PLAID_SECRET'],
                           public_key: ENV['PLAID_PUBLIC_KEY'])

access_token = nil

post '/get_access_token' do
  exchange_token_response = client.item.public_token.exchange(params['public_token'])
  access_token = exchange_token_response['access_token']
  item_id = exchange_token_response['item_id']
  puts "access token: #{access_token}"
  puts "item ID: #{item_id}"
  content_type :json
import os
import datetime
import plaid
from flask import Flask
from flask import render_template
from flask import request
from flask import jsonify

app = Flask(__name__)

client = plaid.Client(os.environ['PLAID_CLIENT_ID'],

access_token = None
public_token = None

@app.route("/get_access_token", methods=['POST'])
def get_access_token():
  global access_token
  public_token = request.form['public_token']
  exchange_response = client.Item.public_token.exchange(public_token)
  print 'access token: ' + exchange_response['access_token']
  print 'item ID: ' + exchange_response['item_id']

  return jsonify(exchange_response)

At this point, you’ve successfully walked a user through creating an Item, gotten the public_token from Link, and exchanged it for an access_token and item_id.

You can access other products available for the Item, but the initial request may take some time as Plaid must communicate with the financial institution to retrieve the data.

Note that we need to persist the access_token and item_id somewhere. A public_token is required if the user needs to update the credentials in the future, but one can be generated with the Item's access_token, so there is no need to store the public_token.

Now that we have the Item's access_token, we can access Transaction and Auth information. Below, we’ll show how to fetch this data.

Accessing Item Data

Pulling Auth data

By posting to the /auth/get endpoint, we’ll see high-level account information for all available accounts and account numbers for checking and savings accounts.

Let’s add another server-side handler to our example app that will retrieve Auth data for the Item by using its access_token:

app.get('/auth', function(req, res, next) {
  // Retrieve Auth information for the Item, which includes high-level
  // account information and account numbers for depository auth.
  client.getAuth(ACCESS_TOKEN, function(error, numbersData) {
    if(error != null) {
      var msg = 'Unable to pull accounts from Plaid API.';
      console.log(msg + '\n' + error);
      return response.json({error: msg});
      error: false,
      accounts: numbersData.accounts,
      numbers: numbersData.numbers,
get '/auth' do
  # Retrieve Auth information for the Item, which includes high-level
  # account information and account numbers for depository accounts.
  auth_response = client.auth.get(access_token)
  content_type :json
get '/auth' do
@app.route("/auth", methods=['GET'])
def accounts():
  # Retrieve Auth information for the Item, which includes high-level
  # account information and account numbers for depository accounts.
  global access_token
  auth_response = client.Auth.get(access_token)
  return jsonify(auth_response)

Auth data comes back like the following:

  "accounts": [
      "account_id": "vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D",
      "balances": {
        "available": 100,
        "current": 110,
        "limit": null
      "mask": "0000",
      "name": "Plaid Checking",
      "official_name": "Plaid Gold Standard 0% Interest Checking",
      "subtype": "checking",
      "type": "depository"
  "numbers": [
      "account": "9900009606",
      "account_id": "vzeNDwK7KQIm4yEog683uElbp9GRLEFXGK98D",
      "routing": "011401533",
      "wire_routing": "021000021"

One thing to note is that not all accounts calculate the available balance. For instance, mortgages or student loans may not necessarily store an available balance. In such cases, Plaid will return an available balance of null.

Pulling Transaction data

Once the Item is created with Transactions, Plaid automatically starts the transaction retrieval and cleansing process. You can make a request to the /transactions/get endpoint to retrieve this data. Transaction webhooks can notify you about new transactions as they become available.

Attempting to retrieve transaction data for an Item before the initial pull has completed triggers a PRODUCT_NOT_READY error. If you encounter this, simply wait and retry your request or use webhooks to learn when transaction data is available.

The first transaction pull, the initial pull, pulls in 30 days of transaction data and takes between 30 and 240 seconds. Once the initial pull is completed, Plaid will begin the historical pull, which retrieves as much data as is available. From there, we’ll update the Item's data at set intervals throughout the day to collect all of the most recent transactions.

We work hard to provide as much historical data as possible, but there are limiting factors in the amount of information an institution holds (not to mention the length of time a user has had an account). See data availability by institution.

Transaction data with webhooks

Transaction data can be retrieved as it becomes available by calling the /transactions/get API endpoint—but you'll want to make sure Plaid actually has new data before doing so. Webhooks allow you to react when new data is available, rather than continuously polling for changes. With webhooks, you'll be notified of new transactions and can then retrieve the data as necessary.

Transaction notifications are posted as JSON to your webhook, and they might appear in a few different forms:

Initial transaction update

Always fired, usually within 30-240 seconds of the Item being created (if transactions is an initial product). The initial pull fetches 30 days of transaction data.

  "webhook_type": "TRANSACTIONS",
  "webhook_code": "INITIAL_UPDATE",
  "item_id": "wz666MBjYWTp2PDzzggYhM6oWWmBb",
  "error": null,
  "new_transactions": 19

Historical transaction update

Always fired, for historical transaction data beyond the initial pull.

  "webhook_type": "TRANSACTIONS",
  "webhook_code": "HISTORICAL_UPDATE",
  "item_id": "wz666MBjYWTp2PDzzggYhM6oWWmBb",
  "error": null,
  "new_transactions": 231

Default transaction pulls

Only fired when new transaction data is available as Plaid performs its regular updates of the Item.

  "webhook_type": "TRANSACTIONS",
  "webhook_code": "DEFAULT_UPDATE",
  "item_id": "wz666MBjYWTp2PDzzggYhM6oWWmBb",
  "error": null,
  "new_transactions": 3

Removed transactions

In some cases, an institution may remove a transaction (for instance, a temporary hold), and Plaid will react accordingly. You'll receive a webhook notification similar to the following:

  "webhook_type": "TRANSACTIONS",
  "webhook_code": "TRANSACTIONS_REMOVED",
  "removed_transactions": [
  "error": null,
  "item_id": "wz666MBjYWTp2PDzzggYhM6oWWmBb"

The item_id is passed with each webhook so you can match it with user records on your side. Once you've received a notification that transaction data is available, you can call the /transactions/get endpoint to retrieve it:

app.get('/transactions', function(request, response, next) {
  // Pull transactions for the Item for the last 30 days
  var startDate = moment().subtract(30, 'days').format('YYYY-MM-DD');
  var endDate = moment().format('YYYY-MM-DD');
  client.getTransactions(ACCESS_TOKEN, startDate, endDate, {
    count: 250,
    offset: 0,
  }, function(error, transactionsResponse) {
    if (error != null) {
      return response.json({error: error});
    console.log('pulled ' + transactionsResponse.transactions.length + ' transactions');
get '/transactions' do
  # Pull transactions for the Item for the last 30 days
  now = Date.today
  thirty_days_ago = (now - 30)
  transactions_response = client.transactions.get(access_token, thirty_days_ago, now)
  content_type :json
@app.route("/transactions", methods=['GET', 'POST'])
def transactions():
  global access_token
  # Pull transactions for the Item for the last 30 days
  start_date = "{:%Y-%m-%d}".format(datetime.datetime.now() + datetime.timedelta(-30))
  end_date = "{:%Y-%m-%d}".format(datetime.datetime.now())

  response = client.Transactions.get(access_token, start_date, end_date)
  return jsonify(response)

Returned transaction data would look like the following. (For a full overview of what each field means, visit our transaction data overview.

  "transactions": [
      "account_id": "XA96y1wW3xS7wKyEdbRzFkpZov6x1ohxMXwep",
      "amount": 78.5,
      "category": null,
      "category_id": null,
      "date": "2017-01-29",
      "location": {
        "street": "100 Market Street",
        "city": "San Francisco",
        "state": "CA",
        "zip": "94110"
      "name": "Touchstone Climbing",
      "payment_meta": {},
      "pending": false,
      "pending_transaction_id": null,
      "transaction_id": "4WPD9vV5A1cogJwyQ5kVFB3vPEmpXPS3qvjXQ",
      "transaction_type": "unresolved"

Handling Item Errors

Sometimes, a user might change a password with an institution, or they might lock themselves out of the account. When this happens, an Item enters an error state, at which point all data retrieval calls to the API for the Item will return an error code. You can still make requests to /item/get endpoint, even when an Item is in an error state.

The most common error is ITEM_LOGIN_REQUIRED, which means that the user needs to provide updated credentials and possibly MFA. It’s straightforward to flag and repair these scenarios using Link and the API.

Error webhooks

Webhooks go beyond transaction data: They can also be used to communicate certain errors, such as invalid credentials or a locked item. In the event this happens, you’ll receive a notification with an ITEM_LOGIN_REQUIRED error:

  "error": {
    "display_message": null,
    "error_code": "ITEM_LOGIN_REQUIRED",
    "error_message": "the login details of this item have changed and a user login is required to update this information.",
    "error_type": "ITEM_ERROR",
    "status": 400
  "item_id": "wz666MBjYWTp2PDzzggYhM6oWWmBb",
  "webhook_code": "ERROR",
  "webhook_type": "ITEM"

Though the most common error is ITEM_LOGIN_REQUIRED, you may run into a few others. For all possible error_codes, see the docs, or read on for a description of the most common errors:

Error code Explanation
ITEM_LOGIN_REQUIRED Indicates that the user must provide updated authentication in order for Plaid to continue updating the Item. This can happen when a user changes a password, MFA information, or if the account is locked. Use Link’s update mode to resolve this.
ITEM_NOT_SUPPORTED Indicates that Plaid is unable to support the Item. This is rare but can happen for some users with restrictions on their accounts at the institution.
MFA_NOT_SUPPORTED Indicates that the Item requires a type of MFA that Plaid does not support. In some cases, a user can disable the unsupported type of MFA.
NO_ACCOUNTS Indicates that there are no longer any accounts associated with the Item.
PRODUCT_NOT_READY Indicates that the requested product is not yet ready - Plaid is still working to pull the information from the financial institution. Retry the request later or use webhooks to determine when the products is ready.

Plaid Link makes it easy for your user to update their credentials as necessary, through what we call update mode. Update mode enables an end-user to re-authenticate an Item, at which point Plaid will resolve that Item into a functioning state. Once an Item has been updated, data access will be restored and transaction updates, if you're using them, will automatically resume.

Attempting to initialize Link in update mode for an Item that is not in an error state will result in an ITEM_NO_ERROR error. You can use the /sandbox/item/reset_login endpoint to put an Item into an error state in the Sandbox.

To configure update mode, generate a public_token for the Item server-side and then use that public_token to initialize Link. The Item’s access_token will not change.

// Create a one-time use public_token for the Item. This public_token can be used to
// initialize Link in update mode for the user.
app.get('/create_public_token', function(request, response, next) {
  client.createPublicToken(ACCESS_TOKEN, function(err, res) {
    if(error != null) {
      console.log(msg + "\n" + JSON.stringify(err));
      response.json({error: JSON.stringify(err)}):
    } else {
      // Use the public_token to initialize Link
      var PUBLIC_TOKEN = res.public_token;
      response.json({public_token: PUBLIC_TOKEN});
# Create a one-time use public_token for the Item. This public_token can be used to
# initialize Link in update mode for the user.
get '/create_public_token' do
  public_token_response = client.item.public_token.create(access_token)
  content_type :json
@app.route("/create_public_token", methods=['GET'])
def transactions():
  global access_token
  # Create a one-time use public_token for the Item. This public_token can be used to
  # initialize Link in update mode for the user.
  response = client.Item.public_token.create(access_token)
  return jsonify(response)

Then initialize Link client-side with the newly generated public_token:

var handler = Plaid.create({
  clientName: 'Plaid Walkthrough Demo',
  env: 'sandbox',
  product: ['transactions'],
  key: '[PUBLIC_KEY]',
  onSuccess: function(public_token, metadata) {},
  onExit: function(err, metadata) {},
  onEvent: function(eventName, metadata) {}

Link automatically handles the necessary API calls to update the Item. As we’ve noted, the Item's access_token does not change unless you explicitly rotate it. While you’re free to redo the storing and exchange token call, there’s no need to: The end-user has re-authenticated with the institution, and API product access calls should resume operating successfully.

To test Link’s update mode, you may want a to simulate putting an Item into an error state.

Note that Link will automatically configure itself to open with the user’s institution. Attempting to open Link with a specific institution in update mode will not work.

To do so, use the /sandbox/item/reset_login endpoint:

client.resetLogin(access_token, function(err, reset_login_response) {
  // Handle err
  // create a public_token for the Item
  client.createPublicToken(access_token, function(err, public_token_response) {
    // Handle err
    // Use the generated public_token to initialize Link in update mode
# Force a Sandbox Item into an error state
# create a public_token for the Item and use it to initialize Link in
# update mode.
public_token_response = client.item.public_token.create(access_token)
# Force a Sandbox Item into an error state
# create a public_token for the Item and use it to initialize Link in
# update mode.
public_token_response = client.Item.public_token.create(access_token)

Moving Forward

Migrating to production

Keep building your integration out in our Sandbox and Development API environments. When you're ready to go to Production, request access from the Dashboard. While you’re at it, be sure to also take a look at our Privacy Policy.

Getting help

Find answers to common integration and product questions at our Help Center. You can find all the code for this quickstart guide, including runnable sample apps, on GitHub.

We can’t wait to see what you build!