The TD Ameritrade APIs allow developers to create applications that perform authorized actions on brokerage accounts. These actions include executing trades, getting account balances and getting stock quotes. Therefore, you can use these capabilities to implement automated trading strategies. In this post, I will show how to use Node.js for integrating with the TD Ameritrade Authentication API to grant OAuth 2.0 based access to an application. To follow this example, make sure you meet the following two prerequisites.
- Install Node.js for your operating system (I used macOS X).
- Have an existing TD Ameritrade brokerage account or open a new one (for account access and trades).
Register for a new TD Ameritrade Developer Account
For gaining access to the TD Ameritrade APIs, you need to use the below link to register and to setup a new TD Ameritrade Developer account. Complete the registration form and click on the button Create new account to complete the process.

After that, log in to the developer account and navigate to APIs to view a list of all available TD Ameritrade APIs. All of these APIs require authorization according to the OAuth 2.0 protocol.

Create a new App on TD Ameritrade for Developers
To make authorized calls to the TD Ameritrade APIs, you need to create a new App. As a result, you will get a OAuth 2.0 user ID that is used for application authorization. Navigate to My Apps in the main menu and click on the Add a new App button.

Complete the below form to create a new App. The callback URL is used by the OAuth 2.0 authorization scheme for receiving a code, which can then be used to obtain an access token for making authorized calls to the TD Ameritrade APIs.
- App Name: Unique name of your app.
- Callback URL: URL of the local web application that will receive the OAuth 2.0 code. It needs to be a secure URL.
- OAuthUser ID: User id to use for OAuth 2.0 authorization scheme.

The new App is now listed under My Apps. Here, the app name is td-api-tester
, the OAuth 2.0 user ID is ferdinand1
and the callback URL is https://localhost:8443
. Make sure to wait until the new app has been approved before continuing.

You can click on the app name to view its details, including the keys and the configured callback URL. Furthermore, you can also delete the app on this page.

Create a Self-Signed SSL Certificate for Local Callback URL
The TD Ameritrade OAuth 2.0 authorization scheme requires a secure callback URL, that is it must be reachable via HTTPS. A self-signed certificate suffices and you can create one in a few steps with the OpenSSL toolkit. Install the program then create a new folder that will contain your SSL certificate files. After that, run the below command inside that folder.
Running the command should produce the output shown below and two new files, namely key.pem
and cert.pem
.
……….++
.+++
writing new private key to ‘key.pem’
If needed, you can view the certificate details by running the keytool
command.
Create Node.js Web Application for Receiving OAuth 2.0 Access Token
The source code of the Node.js web application is shown below. I saved it in a file called MyApp.js
, in the same folder as the two SSL certificate files. This Node.js program does the following:
- Load the SSL certificate and private key from the specified
*.pem
files. - Start the secure web server using the generated SSL credentials.
- Create handler for HTTPS GET requests to read the OAuth 2.0 code from the URL query string.
- Create POST request to use the OAuth 2.0 code to retrieve an access token. See documentation for this API call at https://developer.tdameritrade.com/authentication/apis/post/token-0.
- Send the POST request to the TD Ameritrade Authentication API and parse the response.
- Send HTML formatted response to the browser to show the OAuth 2.0 code and the access token that was received.
var fs = require('fs'); var https = require('https'); var request = require('request'); // // [1] Load SSL certificate and private key from files // var privateKey = fs.readFileSync('key.pem', 'utf8'); var certificate = fs.readFileSync('cert.pem', 'utf8'); var credentials = {key: privateKey, cert: certificate}; var express = require('express'); var app = express(); // // [2] Start a secure web server and listen on port 8443 // var httpsServer = https.createServer(credentials, app); console.log("Listening on port 8443..."); httpsServer.listen(8443); // // [3] Handle HTTPS GET requests at https://localhost:8443 // app.get('/', function(req, res){ console.log('New request'); let httpStatusCode = undefined; let httpErrorMsg = undefined; let oAuthCode = req.query.code; // get the OAuth 2.0 code from the request URL let oAuthReply = undefined; // // [4] POST request for obtaining OAuth 2.0 access token with code // var options = { url: 'https://api.tdameritrade.com/v1/oauth2/token', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, form: { 'grant_type': 'authorization_code', 'access_type': 'offline', 'code': oAuthCode, 'client_id': '[email protected]', 'redirect_uri': 'https://localhost:8443' } } // // [5] Make POST request // request(options, function(error, response, body) { httpStatusCode = (response === undefined) ? 0 : response.statusCode; httpErrorMsg = error; css = "style=\"overflow-wrap: break-word; width: 800px;\""; if (response.statusCode == 200) { oAuthReply = JSON.parse(body); } // // [6] Return view, showing the OAuth 2.0 code and access token // let html = "<html><body style=\"font-family: monospace;\"><table>" + "<tr><td width=\"150\">Status</td><td>" + httpStatusCode + "</td></tr>" + "<tr><td>OAuth 2.0 Code</td><td><div " + css + ">" + oAuthCode + "</div></td></tr>" + //"<tr><td>OAuth 2.0 Token</td><td><div " + css + ">" + oAuthReply.access_token + "</div></td></tr>" + "<tr><td>Full Response</td><td><div " + css + ">" + JSON.stringify(oAuthReply, null, 4) + "</div></td></tr>" + "</table></body></html>"; res.send(html); }); function errorHandler (err, req, res, next) { res.status(500) res.render('error', { error: err }) } });
Run the web server and the application with the command node MyApp.js
, executed from a terminal window.
Listening on port 8443…
Giving Your App Permission to Access a TD Ameritrade Account
You must use an authorization URL matching the below pattern for obtaining an OAuth 2.0 access token. Insert your redirect URI and client ID and open this URL in a browser. The client ID must be in upper case and appended by @AMER.OAUTHAP
.
<REDIRECT_URI>
=https://localhost:8443
<CLIENT_ID>
=[email protected]
Most importantly, the URL query string parameters need to be encoded using HTML URL encoding, so that the final URL looks like the one below.
When opening the URL in a browser, you will be prompted to log in to a TD Ameritrade brokerage account.

On the next page, click on Allow to grant permissions to your app so that it can access your TD Ameritrade brokerage account via the APIs.

After that, the page will redirect to your callback URL and pass the authorization code in the URL. As a result of using a self-signed SSL certificate here, the browser will show a security warning. In Chrome, click on the Advanced button and then on Proceed to localhost (unsafe) to continue.

The next page shows the HTML output of the Node.js web application. More importantly, the authorization code from the URL was used to obtain an access- and a refresh token.

Using the OAuth 2.0 Access Token to Make Authorized API Requests
The access token is valid for 30 minutes. In contrast, the long-lived refresh token is valid for 90 days. The refresh token is used to obtain additional access tokens. For a production grade application, the tokens should be stored securely and logic should be implemented to handle token expiration and for obtaining new access- and refresh tokens. You can now use the access token directly or in your application code to make authorized TD Ameritrade API calls for the given brokerage account. For example, the below screen shot shows a request for getting account details such as the balance. Refer to TD Ameritrade’s Accounts and Trading API for more information. Here, I used Google’s Postman tool to make the HTTP GET request. The OAuth 2.0 access token is passed in the header using the key Authorization
and value Bearer <access-token>
.

Using the OAuth 2.0 Refresh Token to Get a New Access Token
Once the access token is expired, the API will return a 401 Unauthorized
status code. In this case, a new access token must be obtained by with the long-lived refresh token.

To achieve this, make a POST request to https://api.tdameritrade.com/v1/oauth2/token
as shown below. Moreover, refer to TD Ameritrade’s Authentication API for additional details.

Good article, some small tips.
If you encounter the cert.pem could not be created then it is because you did not enter the country, email, location and other details.
Just type in “no” and use this command as shown. Notice I have just entered “no” to all details that is of no concern for this tutorial.
$openssl req -x509 -sha256 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365
Generating a 2048 bit RSA private key
.........................+++
.+++
writing new private key to 'key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:no
State or Province Name (full name) []:no
Locality Name (eg, city) []:no
Organization Name (eg, company) []:no
Organizational Unit Name (eg, section) []:no
Common Name (eg, fully qualified host name) []:no
Email Address []:no
==
This creates the cert.pem and key.pem files in the current folder.
Thanks for your feedback!
Awesome!!! It’s working for me! I’ve been struggling with the TD Ameritrade documentation for four weeks.Tried both python and node. Thanks from here to New Years. What a relief!
Thank you for this article!
Great article! Very helpful. I’m hoping to get you to look at my code below, where I try to load options chain. I keep getting http status 401. However, when I follow your example using postman and using my access token, it works fine. Can you see where my syntax is wrong? Thank you.
//Start Options Post
app.post("/options", function(req, res) {
let timeNow = Date.now();
if ( (timeNow - timeOfTokenAccess) / 1000 > 30 ) {
reqNewAccessToken();
}
var ticker = req.body.ticker;
const url = 'https://api.tdameritrade.com/v1/marketdata/chains';
var options = {
header: { 'Authorization': 'Bearer ' + accessToken },
url: url,
method: 'GET',
form: {
'symbol': ticker
}
}
request(options, function(error, response, body) {
let statusCode = response.statusCode;
console.log(accessToken, ticker);
if (statusCode == 200) {
let optionsChain = JSON.stringify(body);
res.render("options", {ticker: ticker, optionsChain: optionsChain})
} else {
res.render("error", {statusCode: statusCode, error: error});
}
});
});
//End Options Post
Hi,
401 “unauthorized” looks like a problem with how you pass the authorization header. This line: header:
{ ‘Authorization’: ‘Bearer ‘ + accessToken },
looks like it could be a problem. The quotation marks do not include the accessToken value. Try printing the options variable to make sure it results in the correct header string.Very good article. Helped me a lot. Thanks!
Anyone had any luck getting a post access token using PostMan? I can do receive a token when using the https://developer.tdameritrade.com/authentication/apis/post/token-0 form but when I copy and paste the same values on PostMan I keep on getting this error,
"error": "Failed to resolve API Key variable request.header.un"
Here is the relevant screenshot and code that gets generated via PostMan.
https://imgur.com/a/oUiLoMO
https://pastebin.com/9KvwAPH6
Thanks for any direction.
Hi Tom,
I have not tried a POST request yet. Once I do, I’ll let you know if it worked for me. If you figure it out before that, let me know!
Thx
Is Node.js required after the initial steps? Specifically, after a 30-min access-token is obtained? The GET request for account information is made directly to https://api.tdameritrade.com/v1/accounts using Postman. Was the request going thru the Node.js server?
Hi Robert,
there are different ways of obtaining access tokens. This NodeJS example is one option, showing how to do it programmatically. This approach can also be used as a basis for implementing a trading application. That is, it can be extended to store the tokens in a database and then use them to make authenticated requests for automated trades etc.
I finally got the refresh_token working! I tried standard Java.net classes, OkHttp3 classes, and scribejava for OAuth classes, NONE of which worked. Finally I tried Apache 4.5.9 HttpClient classes and it works! I actually went one level of abstraction beyond and used the Apache Fluent API on top which makes for simpler syntax.
Content c = org.apache.http.client.fluent.Request.Post("https://api.tdameritrade.com/v1/oauth2/token")
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.bodyForm(Form.form()
.add("grant_type", "refresh_token")
.add("refresh_token", "I3u15vcKV/ce5LqX7…”)
.add("client_id", "VZE...") // No need for the @AMER.OAUTHAP
.build())
.execute().returnContent();
String result = c.asString();
{
"access_token" : "CzJqQBzTgmEgb3BVpZmwEiMqY7AC...",
"expires_in" : 1800,
"token_type" : "Bearer"
}
Looks super helpful! Can’t get past the creation of the App unfortunately 🙁
Using “https://localhost” in the callback URL section, I receive an error message “ipAddress field is required.” Any hint?
Greg
Hello! I’ve written a client that does this authentication in Python, and recently, it seems to have broken. My self-signed certificate is getting rejected by the TDAmeritrade server with the following error:
ssl.SSLError: [SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] sslv3 alert certificate unknown (_ssl.c:1076)
I suspect because it is self-signed. Are you noticing a similar failure? I reran my code using the same certificate generation method that you’ve used here and reproduced the same failure. If your code is still working, then I must be mediating the SSL connection incorrectly.
Hello,
I am trying to get a new access_token via C# code but keep getting a 502 error: The remote server returned an error: (502) Bad Gateway. Below is the code that I am using. I have tried specifying a “redirect_uri” and have commented this out. I originally was using “http://localhost/Data” and saw someones comment that Secure URL is required. Do you have any suggestions to make my code work?
refresh_token = “LotsOfStuffHere”;
string url = “https://api.tdameritrade.com/v1/oauth2/token”;
using (WebClient webClient = new WebClient())
{
webClient.QueryString.Add(“grant_type”, “refresh_token”);
webClient.QueryString.Add(“refresh_token”, refresh_token);
webClient.QueryString.Add(“client_id”, client_id + “[email protected]”);
webClient.QueryString.Add(“Content-Type”, “application/x-www-form-urlencoded”);
//webClient.QueryString.Add(“redirect_uri”, “https://localhost/Data”);
try
{
string result = webClient.DownloadString(url);
Token token = JsonConvert.DeserializeObject(result);
return token.access_token;
}
catch (WebException e)
{
System.Diagnostics.Debug.WriteLine(Environment.NewLine + Environment.NewLine + “ERROR ERROR ERROR ERROR ERROR ERROR”);
System.Diagnostics.Debug.WriteLine(“ERROR:” + e.Message + Environment.NewLine);
}
}
can some one provide c# code for ameritrade oAuth?
Ayoo thanks for the guide. Finally got my person trading application up and running thanks to you. Appreciate it boss!
Hi there,
I have successfully obtained an access token, and can get live quotes and a transaction list from for my account, both programmatically and via TD’s “Try it out” interactive pages. But I can’t seem to place an order, or perform any other operations in the “Accounts and Trading” group using the very same access token. I always get
HTTP/1.1 401 Unauthorize
{
"error": "The access token being passed has expired or is invalid."
}
How come the same access token allows me to perform some operations but not others?
Thanks in advance.