Go client for the tdameritrade api. Big thank you to Jon Cooper for creating a fork that made this project more complete. He also wrote the awesome documentation below.
import "github.com/zricethezav/go-tdameritrade"
go-tdameritrade handles all interaction with the TD Ameritrade REST API. See the TD Ameritrade developer site to learn how their APIs work. This is a very thin wrapper and does not perform any validation. go-tdameritrade doesn't support streaming yet.
There is an example of using OAuth2 to authenticate a user and use the services on the TD Ameritrade API in examples/webauth/webauth.go.
Authentication is handled by the Authenticator
struct and its methods StartOAuth2Flow
and FinishOAuth2Flow
.
You can get an authenticated tdameritrade.Client
from an authenticated request with the AuthenticatedClient
method, and use that to interact with the TD API.
See auth.go.
// Authenticator is a helper for TD Ameritrade's authentication.
// It authenticates users and validates the state returned from TD Ameritrade to protect users from CSRF attacks.
// It's recommended to use NewAuthenticator instead of creating this struct directly because TD Ameritrade requires Client IDs to be in the form clientid@AMER.OAUTHAP.
// This is not immediately obvious from the documentation.
// See https://developer.tdameritrade.com/content/authentication-faq
type Authenticator struct {
Store PersistentStore
OAuth2 oauth2.Config
}
The library handles state generation and the OAuth2 flow.
Users simply implement the PersistentStore
interface (see auth.go) and tell it how to store and retrieve OAuth2 state and an oauth2.Token
with the logged in user's credentials.
// PersistentStore is meant to persist data from TD Ameritrade that is needed between requests.
// Implementations must return the same value they set for a user in StoreState in GetState, or the login process will fail.
// It is meant to allow credentials to be stored in cookies, JWTs and anything else you can think of.
type PersistentStore interface {
StoreToken(token *oauth2.Token, w http.ResponseWriter, req *http.Request) error
GetToken(req *http.Request) (*oauth2.Token, error)
StoreState(state string, w http.ResponseWriter, req *http.Request) error
GetState(*http.Request) (string, error)
}
The library is centered around the tdameritrade.Client
.
It allows access to all services exposed by the TD Ameritrade REST API.
More information about each service can be found on TD Ameritrade's developer website.
// A Client manages communication with the TD-Ameritrade API.
type Client struct {
client *http.Client // HTTP client used to communicate with the API.
// Base URL for API requests. Defaults to the public TD-Ameritrade API, but can be
// set to any endpoint. This allows for more manageable testing.
BaseURL *url.URL
// services used for talking to different parts of the tdameritrade api
PriceHistory *PriceHistoryService
Account *AccountsService
MarketHours *MarketHoursService
Quotes *QuotesService
Instrument *InstrumentService
Chains *ChainsService
Mover *MoverService
TransactionHistory *TransactionHistoryService
User *UserService
Watchlist *WatchlistService
}
You get a tdameritrade.Client
from the FinishOAuth2
or AuthenticatedClient
method on the tdameritrade.Authenticator
struct.
More examples are in the examples directory.
clientID := os.Getenv("TDAMERITRADE_CLIENT_ID")
if clientID == "" {
log.Fatal("Unauthorized: No client ID present")
}
authenticator := tdameritrade.NewAuthenticator(
&HTTPHeaderStore{},
oauth2.Config{
ClientID: clientID,
Endpoint: oauth2.Endpoint{
TokenURL: "https://api.tdameritrade.com/v1/oauth2/token",
AuthURL: "https://auth.tdameritrade.com/auth",
},
RedirectURL: "https://localhost:8080/callback",
},
)
type TDHandlers struct {
authenticator *tdameritrade.Authenticator
}
func (h *TDHandlers) Authenticate(w http.ResponseWriter, req *http.Request) {
redirectURL, err := h.authenticator.StartOAuth2Flow(w, req)
if err != nil {
w.Write([]byte(err.Error()))
return
}
http.Redirect(w, req, redirectURL, http.StatusTemporaryRedirect)
}
func (h *TDHandlers) Callback(w http.ResponseWriter, req *http.Request) {
ctx := context.Background()
_, err := h.authenticator.FinishOAuth2Flow(ctx, w, req)
if err != nil {
w.Write([]byte(err.Error()))
return
}
http.Redirect(w, req, "/quote?ticker=SPY", http.StatusTemporaryRedirect)
}
type TDHandlers struct {
authenticator *tdameritrade.Authenticator
}
func (h *TDHandlers) Quote(w http.ResponseWriter, req *http.Request) {
ctx := context.Background()
client, err := h.authenticator.AuthenticatedClient(ctx, req)
if err != nil {
w.Write([]byte(err.Error()))
return
}
ticker, ok := req.URL.Query()["ticker"]
if !ok || len(ticker) == 0 {
w.Write([]byte("ticker is required"))
return
}
quote, _, err := client.Quotes.GetQuotes(ctx, ticker[0])
if err != nil {
w.Write([]byte(err.Error()))
return
}
body, err := json.Marshal(quote)
if err != nil {
w.Write([]byte(err.Error()))
return
}
w.Write(body)
}
Use at your own risk.