Library / SDK
plaid/plaid-python avatar
plaid/plaid-python

plaid-python: the generated Python client for Plaid's OpenAPI-backed API

Python bindings for Plaid

521 stars169 forksPythonMIT

At a glance

What is it?
plaid-python is Plaid's official Python client library, generated from the same OpenAPI spec as the other language SDKs, covering Link token exchange, transactions sync, asset reports and item removal. It suits a fintech developer integrating account data, and it targets one API version at a time, currently 2020-09-14.
Who is it for?
plaid-python fits a Python developer integrating Plaid who wants the official client with typed models and dated fields rather than hand-written HTTP calls, and it does not fit a project that needs several Plaid API versions supported at once, since each release only supports the latest, 2020-09-14. Before upgrading, read the API upgrade guide and the client changelog, because the library is regenerated monthly and a major version bump can carry breaking changes.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A generated client, and what that means for its edges

plaid-python is the official Python client library for the Plaid API, and the readme states the important fact about its provenance in the first line: it is generated from Plaid's OpenAPI spec.

That single fact explains most of the library's characteristics, both the good and the awkward. The good is that the models, the request classes and the endpoint signatures are consistent with the other Plaid SDKs in other languages, and that new API endpoints appear without anyone hand-writing Python for them. The readme notes the client library is typically updated on a monthly basis, and that the canonical source for the latest version number is the client library changelog, with new versions published as GitHub tags rather than as Releases, and also on PyPI.

The awkward is the shape of the API. A generated client exposes one request model class and one endpoint method per operation, and the readme's own examples show that, with a class imported from a module named after the operation, such as ItemPublicTokenExchangeRequest imported from plaid.model.item_public_token_exchange_request, and a method on the client named after the same operation.

That is a lot of ceremony for what looks like a function call, and it is the price of a spec-driven client. The alternative, a hand-written thin wrapper over requests, would be more pleasant to use and would drift from the spec the moment Plaid added a field.

The versioning model is the other thing to understand before you start. This release only supports the latest Plaid API version, 2020-09-14, and for information about what has changed between versions and how to update your integration the readme points at the API upgrade guide. Plaid uses semantic versioning for the client libraries, with potentially breaking changes indicated by a major version bump, and all users are strongly recommended to use a recent version because older versions do not contain support for new endpoints and fields.

The primary language is Python, the licence is MIT, the homepage is plaid.com/docs, the default branch is master, and the last push was on 2026-10-19. The release list is worth noting: the newest tagged releases are 16.0.0 from 2023-09-29 and a pair of 8.0.0 beta builds from 2021, while the packaging metadata carries a version string of 46.0.0, so the number to look at is the one in the changelog rather than the newest tag on the repository.

Setting up a client and calling an endpoint

Calling an endpoint requires creating a PlaidApi object, and the setup is three objects.

python
import plaid
from plaid.api import plaid_api

# Available environments are
# 'Production'
# 'Sandbox'
configuration = plaid.Configuration(
    host=plaid.Environment.Sandbox,
    api_key={
        'clientId': client_id,
        'secret': secret,
    }
)

api_client = plaid.ApiClient(configuration)
client = plaid_api.PlaidApi(api_client)

The configuration carries the host and the credentials, the ApiClient wraps the configuration and handles transport, and the PlaidApi is the object with the endpoint methods. The two available environments are Production and Sandbox, and the Sandbox one is what you develop against.

The credentials are a clientId and a secret passed as an api_key dictionary. There is no token exchange step in the setup itself, because Plaid's authentication model is that these credentials identify your application and the per-user credential, the access token, is a separate value you pass into individual requests.

Each endpoint returns a dictionary which contains the parsed JSON from the HTTP response. That is worth noting against the model classes mentioned later in the readme: the request side is typed with model classes, and the response side is a plain dictionary, so a response is accessed by key rather than by attribute.

The error handling is uniform. All non-200 responses will throw a plaid.ApiException.

python
import plaid
from plaid.model.asset_report_get_request import AssetReportGetRequest

try:
    request = AssetReportGetRequest(
        asset_report_token=asset_report_token,
    )
    return client.asset_report_get(request)
except plaid.ApiException as e:
    response = json.loads(e.body)
    # check the code attribute of the error to determine the specific error
    if response['error_code'] == 'ITEM_LOGIN_REQUIRED':
        # the users' login information has changed, generate a public_token
        # for the user and initialize Link in update mode to
        # restore access to this user's data
        # see https://plaid.com/docs/api/#updating-items-via-link
    else:
        ...

The pattern is that the exception body is JSON, so you parse it and branch on the error_code attribute. The example case is ITEM_LOGIN_REQUIRED, which means the user's bank login details changed and the integration has to send the user through Link in update mode to restore access. That is the single most common operational error in a Plaid integration, and the shape of the handler shows exactly where it surfaces: an exception, with the specific code inside the body.

Dates as datetime objects, and the timezone trap

One area where the generated client made a deliberate choice rather than mirroring the wire format is dates, and the readme spends a paragraph on it.

Dates and datetimes in requests, which are represented as strings in the API and in previous client library versions, are represented in this version of the Python client library as Python datetime.date or datetime.datetime objects. If you need to convert between dates and strings you can use the datetime.strptime method.

The reason for the change is type safety, and the reason it is called out is the cost. The datetime.strptime method will silently remove time zone information, and time zone information is required for request fields that accept datetimes. Failing to include time zone information, or passing in a string instead of a datetime.datetime object, will result in an error.

So the rule is: for a field the API reference documents as format: date, either of these is acceptable.

py
from datetime import date

a = date(2022, 5, 5)
b = date.fromisoformat('2022-05-05')

And for a field documented as format: date-time, the datetime has to carry a timezone.

py
from datetime import datetime, timezone

a = datetime(2022, 5, 5, 22, 35, 49, tzinfo=timezone.utc)

The failure mode is worth understanding rather than memorising. If you build a datetime with strptime from a string that has no offset, Python does not complain; it produces a naive datetime, and the request then fails at the API. The error will not point at the timezone, it will point at the field, so the debugging cost is in guessing which of your datetimes lost its offset.

The related conversion question has a short answer that the readme includes because it comes up often. The library uses models like TransactionsSyncResponse to encapsulate API responses, and to convert one to JSON you do this.

python
import json
...
response = ... # type TransactionsSyncResponse
# to_dict makes it first a python dictionary, and then we turn it into a string JSON.
json_string = json.dumps(response.to_dict(), default=str)

The to_dict method turns the model into a Python dictionary, and the default=str argument in json.dumps covers the values that are not natively JSON serialisable, which for a model with date fields is most of them.

The two flows you will write first, and the pagination that catches people out

Two examples in the readme cover the operations almost every integration needs.

The first is creating an Item using Link, which is exchanging a public_token from Plaid Link for a Plaid access token.

python
import plaid
from plaid.model.item_public_token_exchange_request import ItemPublicTokenExchangeRequest

# the public token is received from Plaid Link
exchange_request = ItemPublicTokenExchangeRequest(
    public_token=pt_response['public_token']
)
exchange_response = client.item_public_token_exchange(exchange_request)
access_token = exchange_response['access_token']

The flow is worth internalising because it explains Plaid's token model. The public token comes from the frontend, where the user completed the Link flow, and it is short-lived and safe to expose. The exchange happens server-side with your client credentials and yields the access token, which is the long-lived credential you use for everything else and which must never reach the browser.

The second is removing an Item.

python
import plaid
from plaid.model.item_remove_request import ItemRemoveRequest

# Provide the access token for the Item you want to remove
request = ItemRemoveRequest(
    access_token=accessToken
)
response = client.item_remove(request)

Removal takes the access token, and it is how you honour a data deletion request, so it is worth knowing that the call is straightforward.

The third example is retrieving transactions, and the readme labels it the preferred method. The call itself is simple, with a TransactionsSyncRequest carrying the access token, and the response transactions are read from the added key.

python
import plaid
from plaid.model.transactions_sync_request import TransactionsSyncRequest

request = TransactionsSyncRequest(
    access_token=access_token,
)
response = client.transactions_sync(request)
transactions = response['added']

But the important part is what comes after. The transactions in the response are paginated, so you make multiple calls while incrementing the cursor to retrieve all transactions, and the readme's example loops while the has_more field is true, rebuilding the request with the cursor from the previous response.

This is the single most common integration bug, because the first call succeeds and returns a plausible list, and the list is silently incomplete. A cursor-based loop that only fetches once gives you the most recent transactions and no error at all, and the symptom appears later as missing transactions for a customer rather than as a stack trace. The reason the readme spells it out with a comment is that the pagination is invisible in the type signatures.

The transaction sync endpoint is also the reason the access token is needed in every call rather than at setup time, and it is the endpoint that benefits most from the typed date handling, since transaction dates come back as date objects.

The development setup, and the version mismatch to watch

The repository shows a conventional generated-client layout with a build and test toolchain.

The top-level entries include a .circleci/ directory, a Dockerfile, a Makefile, a tox.ini, a requirements.txt, a setup.py, templates/ for the code generation templates, tests/, and a .openapi-generator/ directory with a matching .openapi-generator-ignore file. That last pair is the clearest evidence of how the library is produced, since it is where the generator's own configuration lives.

The Makefile is small and does the packaging.

bash
test:
	CLIENT_ID=$(CLIENT_ID) SECRET=$(SECRET) tox

setup:
	pip install -r requirements.txt

package-build:
	python setup.py sdist

package-check:
	twine check dist/*

package-publish:
	twine upload dist/*

The test target passes the Plaid credentials into tox as environment variables, which is how the suite reaches the API. The setup target installs the requirements, the package targets build an sdist, check it with twine and upload it.

The requirements file separates runtime and development dependencies. The runtime ones are nulltype, python_dateutil, setuptools and urllib3, and the development ones include pytest with xdist and cov, flake8 with a mutable check, sphinx and coverage. The setup.py declares the same runtime set with urllib3 at 1.25.3 or later, python-dateutil and nulltype, and requires Python 3.6 or later.

The Dockerfile is where the one inconsistency worth flagging appears. It is built FROM python:3.7, and the comment at the top says the image is also built in CircleCI so references to internal repositories are limited. It installs a pinned tox, 3.15.0, and the comment explains why: newer versions of tox fail on CircleCI for some reason, with a link to a discussion thread.

So the container pins both an old Python and an old tox, for reasons that are about the CI provider rather than about the library. That is a sign of a project whose test infrastructure has not been revisited, and it is the kind of thing to be aware of if you want to run the suite in a current environment.

The class of detail to keep in mind overall is the version one. The readme notes the library supports only the latest Plaid API version, 2020-09-14, that new versions are published monthly, and that older versions lack support for new endpoints and fields. The setup.py version string of 46.0.0 against a newest tag of 16.0.0 from 2023 is a reminder to read the changelog rather than to trust the tag list, and the readme's recommendation to use a recent version of the library is the concrete advice to follow.

Editorial conclusion

plaid-python fits a Python developer integrating Plaid who wants the official client with typed models and dated fields rather than hand-written HTTP calls, and it does not fit a project that needs several Plaid API versions supported at once, since each release only supports the latest, 2020-09-14. Before upgrading, read the API upgrade guide and the client changelog, because the library is regenerated monthly and a major version bump can carry breaking changes.

Frequently asked questions

How do I install plaid-python and which API version does it support?

Run pip3 install plaid-python, and note that the library only supports python3. This release only supports the latest Plaid API version, 2020-09-14, and the library is typically updated on a monthly basis with the changelog being the canonical source for the version number.

How do I create a Plaid client and call an endpoint?

Create a plaid.Configuration with the host, using plaid.Environment.Sandbox or Production, and an api_key dictionary holding the clientId and secret. Wrap it in a plaid.ApiClient, then create plaid_api.PlaidApi with that client. Each endpoint returns a dictionary with the parsed JSON.

How should I handle dates in plaid-python requests?

Request dates are datetime.date or datetime.datetime objects rather than strings. Time zone information is required for datetime fields, and the readme warns that datetime.strptime silently removes it, so use datetime.fromisoformat or pass tzinfo=timezone.utc as in the documented examples.

How do I retrieve all transactions with plaid-python?

Call client.transactions_sync with a TransactionsSyncRequest carrying the access token and read the transactions from the added key. The response is paginated, so you must make multiple calls while incrementing the cursor while the has_more field is true, otherwise you silently get only the first page.

Official sources

  1. License: MIT
  2. plaid/plaid-python on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/plaid-plaid-python.svg)](https://hysenlabs.com/projects/plaid-plaid-python)

Was this useful?