Authentication and Client Creation
By now, you should have followed the instructions in Getting Started and are ready to start making API calls. Read this page to learn how to get over the last remaining hurdle: OAuth authentication.
Before we begin, however, note that this guide is meant to users who want to run applications on their own machines, without distributing them to others. If you plan on distributing your app, or if you plan on running it on a server and allowing access to other users, this login flow is not for you.
OAuth Refresher
This section is purely for the curious. If you already understand OAuth (wow, congrats) or if you don’t care and just want to use this package as fast as possible, feel free to skip this section. If you encounter any weird behavior, this section may help you understand what’s going on.
Webapp authentication is a complex beast. The OAuth protocol was created to allow applications to access one anothers’ APIs securely and with the minimum level of trust possible. A full treatise on this topic is well beyond the scope of this guide, but in order to alleviate some of the confusion and complexity that seems to surround this part of the API, let’s give a quick explanation of how OAuth works in the context of TD Ameritrade’s API.
The first thing to understand is that the OAuth webapp flow was created to allow client-side applications consisting of a webapp frontend and a remotely hosted backend to interact with a third party API. Unlike the backend application flow, in which the remotely hosted backend has a secret which allows it to access the API on its own behalf, the webapp flow allows either the webapp frontend or the remotely host backend to access the API on behalf of its users.
If you’ve ever installed a GitHub, Facebook, Twitter, GMail, etc. app, you’ve seen this flow. You click on the “install” link, a login window pops up, you enter your password, and you’re presented with a page that asks whether you want to grant the app access to your account.
Here’s what’s happening under the hood. The window that pops up is the authentication URL, which opens a login page for the target API. The aim is to allow the user to input their username and password without the webapp frontend or the remotely hosted backend seeing it. On web browsers, this is accomplished using the browser’s refusal to send credentials from one domain to another.
Once login here is successful, the API replies with a redirect to a URL that the remotely hosted backend controls. This is the callback URL. This redirect will contain a code which securely identifies the user to the API, embedded in the query of the request.
You might think that code is enough to access the API, and it would be if the API author were willing to sacrifice long-term security. The exact reasons why it doesn’t work involve some deep security topics like robustness against replay attacks and session duration limitation, but we’ll skip them here.
This code is useful only for fetching a token from the authentication endpoint. This token is what we want: a secure secret which the client can use to access API endpoints, and can be refreshed over time.
If you’ve gotten this far and your head isn’t spinning, you haven’t been paying
attention. Security-sensitive protocols can be very complicated, and you should
never build your own implementation. Fortunately there exist very robust
implementations of this flow, and tda-api
’s authentication module makes
using them easy.
Fetching a Token and Creating a Client
tda-api
provides an easy implementation of the client-side login flow in the
auth
package. It uses a selenium webdriver to open the TD Ameritrade
authentication URL, take your login credentials, catch the post-login redirect,
and fetch a reusable token. It returns a fully-configured HTTP Client, ready
to send API calls. It also handles token refreshing, and writes updated tokens
to the token file.
These functions are webdriver-agnostic, meaning you can use whatever webdriver-supported browser you have available on your system. You can find information about available webdriver on the Selenium documentation.
- tda.auth.client_from_login_flow(webdriver, api_key, redirect_url, token_path, redirect_wait_time_seconds=0.1, max_waits=3000, asyncio=False, token_write_func=None, enforce_enums=True)
Uses the webdriver to perform an OAuth webapp login flow and creates a client wrapped around the resulting token. The client will be configured to refresh the token as necessary, writing each updated version to
token_path
.- Parameters:
webdriver – selenium webdriver which will be used to perform the login flow.
api_key – Your TD Ameritrade application’s API key, also known as the client ID.
redirect_url – Your TD Ameritrade application’s redirect URL. Note this must exactly match the value you’ve entered in your application configuration, otherwise login will fail with a security error.
token_path – Path to which the new token will be written. If the token file already exists, it will be overwritten with a new one. Updated tokens will be written to this path as well.
asyncio – If set to
True
, this will enable async support allowing the client to be used in an async environment. Defaults toFalse
enforce_enums – Set it to
False
to disable the enum checks on ALL the client methods. Only do it if you know you really need it. For most users, it is advised to use enums to avoid errors.
If for some reason you cannot open a web browser, such as when running in a cloud environment, the following function will guide you through the process of manually creating a token by copy-pasting relevant URLs.
- tda.auth.client_from_manual_flow(api_key, redirect_url, token_path, asyncio=False, token_write_func=None, enforce_enums=True)
Walks the user through performing an OAuth login flow by manually copy-pasting URLs, and returns a client wrapped around the resulting token. The client will be configured to refresh the token as necessary, writing each updated version to
token_path
.Note this method is more complicated and error prone, and should be avoided in favor of
client_from_login_flow()
wherever possible.- Parameters:
api_key – Your TD Ameritrade application’s API key, also known as the client ID.
redirect_url – Your TD Ameritrade application’s redirect URL. Note this must exactly match the value you’ve entered in your application configuration, otherwise login will fail with a security error.
token_path – Path to which the new token will be written. If the token file already exists, it will be overwritten with a new one. Updated tokens will be written to this path as well.
asyncio – If set to
True
, this will enable async support allowing the client to be used in an async environment. Defaults toFalse
enforce_enums – Set it to
False
to disable the enum checks on ALL the client methods. Only do it if you know you really need it. For most users, it is advised to use enums to avoid errors.
Once you have a token written on disk, you can reuse it without going through the login flow again.
- tda.auth.client_from_token_file(token_path, api_key, asyncio=False, enforce_enums=True)
Returns a session from an existing token file. The session will perform an auth refresh as needed. It will also update the token on disk whenever appropriate.
- Parameters:
token_path – Path to an existing token. Updated tokens will be written to this path. If you do not yet have a token, use
client_from_login_flow()
oreasy_client()
to create one.api_key – Your TD Ameritrade application’s API key, also known as the client ID.
asyncio – If set to
True
, this will enable async support allowing the client to be used in an async environment. Defaults toFalse
enforce_enums – Set it to
False
to disable the enum checks on ALL the client methods. Only do it if you know you really need it. For most users, it is advised to use enums to avoid errors.
The following is a convenient wrapper around these two methods, calling each when appropriate:
- tda.auth.easy_client(api_key, redirect_uri, token_path, webdriver_func=None, asyncio=False, enforce_enums=True)
Convenient wrapper around
client_from_login_flow()
andclient_from_token_file()
. Iftoken_path
exists, loads the token from it. Otherwise open a login flow to fetch a new token. Returns a client configured to refresh the token totoken_path
.Reminder: You should never create the token file yourself or modify it in any way. If
token_path
refers to an existing file, this method will assume that file is valid token and will attempt to parse it.- Parameters:
api_key – Your TD Ameritrade application’s API key, also known as the client ID.
redirect_url – Your TD Ameritrade application’s redirect URL. Note this must exactly match the value you’ve entered in your application configuration, otherwise login will fail with a security error.
token_path – Path that new token will be read from and written to. If If this file exists, this method will assume it’s valid and will attempt to parse it as a token. If it does not, this method will create a new one using
client_from_login_flow()
. Updated tokens will be written to this path as well.webdriver_func – Function that returns a webdriver for use in fetching a new token. Will only be called if the token file cannot be found.
asyncio – If set to
True
, this will enable async support allowing the client to be used in an async environment. Defaults toFalse
enforce_enums – Set it to
False
to disable the enum checks on ALL the client methods. Only do it if you know you really need it. For most users, it is advised to use enums to avoid errors.
If you don’t want to create a client and just want to fetch a token, you can use
the tda-generate-token.py
script that’s installed with the library. This
method is particularly useful if you want to create your token on one machine
and use it on another. The script will attempt to open a web browser and perform
the login flow. If it fails, it will fall back to the manual login flow:
# Notice we don't prefix this with "python" because this is a script that was
# installed by pip when you installed tda-api
> tda-generate-token.py --help
usage: tda-generate-token.py [-h] --token_file TOKEN_FILE --api_key API_KEY --redirect_uri REDIRECT_URI
Fetch a new token and write it to a file
optional arguments:
-h, --help show this help message and exit
required arguments:
--token_file TOKEN_FILE
Path to token file. Any existing file will be overwritten
--api_key API_KEY
--redirect_uri REDIRECT_URI
This script is installed by pip
, and will only be accessible if you’ve added
pip’s executable locations to your $PATH
. If you’re having a hard time, feel
free to ask for help on our Discord server.
Advanced Functionality
The default token fetcher functions are designed for ease of use. They make some
common assumptions, most notably a writable filesystem, which are valid for 99%
of users. However, some very specialized users, for instance those hoping to
deploy tda-api
in serverless settings, require some more advanced
functionality. This method provides the most flexible facility for fetching
tokens possible.
Important: This is an extremely advanced method. If you read the documentation and think anything other than “oh wow, this is exactly what I’ve been looking for,” you don’t need this function. Please use the other helpers instead.
- tda.auth.client_from_access_functions(api_key, token_read_func, token_write_func, asyncio=False, enforce_enums=True)
Returns a session from an existing token file, using the accessor methods to read and write the token. This is an advanced method for users who do not have access to a standard writable filesystem, such as users of AWS Lambda and other serverless products who must persist token updates on non-filesystem places, such as S3. 99.9% of users should not use this function.
Users are free to customize how they represent the token file. In theory, since they have direct access to the token, they can get creative about how they store it and fetch it. In practice, it is highly recommended to simply accept the token object and use
pickle
to serialize and deserialize it, without inspecting it in any way.Note the read and write methods must take particular arguments. Please see this example for details.
- Parameters:
api_key – Your TD Ameritrade application’s API key, also known as the client ID.
token_read_func – Function that takes no arguments and returns a token object.
token_write_func – Function that writes the token on update. Will be called whenever the token is updated, such as when it is refreshed. See the above-mentioned example for what parameters this method takes.
asyncio – If set to
True
, this will enable async support allowing the client to be used in an async environment. Defaults toFalse
enforce_enums – Set it to
False
to disable the enum checks on ALL the client methods. Only do it if you know you really need it. For most users, it is advised to use enums to avoid errors.
Troubleshooting
As simple as it seems, this process is complex and mistakes are easy to make. This section outlines some of the more common issues you might encounter. If you find yourself dealing with something that isn’t listed here, or if you try the suggested remedies and are still seeing issues, see the Getting Help page. You can also join our Discord server to ask questions.
tda-api
Hangs After Successful Login
After opening the login window, tda-api
loops and waits until the
webdriver’s current URL starts with the given redirect URI:
callback_url = ''
while not callback_url.startswith(redirect_url):
callback_url = webdriver.current_url
time.sleep(redirect_wait_time_seconds)
Usually, it would be impossible for a successful post-login callback to not
start with the callback URI, but there’s one major exception: when the callback
URI starts with http
. Behavior varies by browser and app configuration, but
a callback URI starting with http
can sometimes be redirected to one
starting with https
, in which case tda-api
will never notice the
redirect.
If this is happening to you, consider changing your callback URI to use
https
instead of http
. Not only will it make your life easier here, but
it is extremely bad practice to send credentials like this over an unencrypted
channel like that provided by http
.
WebDriverException: Message: 'chromedriver' executable needs to be in PATH
When creating a tda-api
token using a webrowser-based method like
client_from_login_flow()
or easy_client()
, the
library must control the browser using selenium. This is a Python library that
sends commands to the browser to perform operations like load pages, inject
synthetic clicks, enter text, and so on. The component which is used to send
these commands is called a driver.
Drivers are generally not part of the standard web browser installation, meaning
you must install them manually. If you’re seeing this or a similar message, you
probably haven’t installed the appropriate webdriver. Drivers are
available for most of the common web browsers, including Chrome, Firefox, and Safari.
Make sure you’ve installed the driver before attempting to create a token
using tda-api
.
OAuthError: invalid_grant: invalid_grant
tda-api
automatically refreshes your tokens: first, a new access token is
generated every 30 minutes as part of the regular OAuth authentication process.
Second, the refresh token (used to generate new access tokens) is itself
refreshed prior to expiring after 90 days. In particular, when the refresh token
is more than 85 days old, the library will interrupt any API call to perform the
refresh. This happens part of the regular operation of the library, and no
special action is needed to trigger it.
This usually works great, and most users don’t need to worry about refreshing tokens or logging in. However, note that the 85 day refresh token refresh is only triggered on API calls. If your application is not running or does not attempt to perform any API calls during this time, the refresh token will expire and you will see this error. Once the token expires, you cannot revive it, and you’ll need to delete it and create a new one.
Token Parsing Failures
tda-api
handles creating and refreshing tokens. Simply put, the user should
never create or modify the token file. If you are experiencing parse errors
when accessing the token file or getting exceptions when accessing it, it’s
probably because you created it yourself or modified it. If you’re experiencing
token parsing issues, remember that:
You should never create the token file yourself. If you don’t already have a token, you should pass a nonexistent file path to
client_from_login_flow()
oreasy_client()
. If the file already exists, these methods assume it’s a valid token file. If the file does not exist, they will go through the login flow to create one.You should never modify the token file. The token file is automatically managed by
tda-api
, and modifying it will almost certainly break it.You should never share the token file. If the token file is shared between applications, one of them will beat the other to refreshing, locking the slower one out of using
tda-api
.
If you didn’t do any of this and are still seeing issues using a token file that
you’re confident is valid, please file a ticket. Just remember, never share
your token file, not even with tda-api
developers. Sharing the token
file is as dangerous as sharing your TD Ameritrade username and password.
What If I Can’t Use a Browser?
Launching a browser can be inconvenient in some situations, most notably in
containerized applications running on a cloud provider. tda-api
supports two
alternatives to creating tokens by opening a web browser.
Firstly, the manual login flow flow allows you to go
through the login flow on a different machine than the one on which tda-api
is running. Instead of starting the web browser and automatically opening the
relevant URLs, this flow allows you to manually copy-paste around the URLs. It’s
a little more cumbersome, but it has no dependency on selenium.
Alterately, you can take advantage of the fact that token files are portable. Once you create a token on one machine, such as one where you can open a web browser, you can easily copy that token file to another machine, such as your application in the cloud. However, make sure you don’t use the same token on two machines. It is recommended to delete the token created on the browser-capable machine as soon as it is copied to its destination.