Skip to content

With the Python SDK

The Python SDK, stx-python, runs the whole OAuth flow for you on your server: stx.oauth creates the PKCE verifier and state, exchanges the code, refreshes and rotates tokens, and revokes them, and hands you an ordinary STX client for each member or for your app. How the flow works, what each scope allows and how to handle tokens safely are covered in the OAuth section. This page is the short tour; each step links to the Python SDK guide page with the full detail.

pip install stx-python

Build one OAuthClient per registered app, on your server. discover reads the exchange’s server metadata and takes every endpoint from it:

from stx import oauth
app = oauth.OAuthClient.discover(
client_id="stxapp_client_g2XU2Kyg3EJoOhC9sndPe2bPvZENsbkkFkzCQFgkWHa0emOUt",
client_secret=CLIENT_SECRET, # from your secret store, never sent to a browser
redirect_uri="https://your.app/callback", # exactly as registered
scope=["profile.read", "balance.read", "orders.read", "orders.write"],
region="us",
env="demo",
)

client_id, client_secret and redirect_uri can also come from STX_OAUTH_CLIENT_ID, STX_OAUTH_CLIENT_SECRET and STX_OAUTH_REDIRECT_URI. For asyncio, await oauth.AsyncOAuthClient.discover(...) gives the same methods to await.

The SDK keeps two kinds of state in stores you provide: sign-ins in progress, and each member’s tokens. oauth.MemoryPendingAuthorizationStore and oauth.MemoryTokenStore suit one process; in production, implement the same methods over your database and encrypt tokens at rest. See Two stores.

For an app with its own accounts, on any login system; see You have an app with its own accounts. Two routes on your server: one sends the member to STX, the other receives them back.

pending = oauth.MemoryPendingAuthorizationStore()
tokens = oauth.MemoryTokenStore()
@route("/connect-stx")
def connect_stx(request):
auth = app.begin_authorization(pending, data={"user_id": request.user.id})
return redirect(auth.url)
@route("/callback")
def callback(request):
done = app.complete_authorization(
pending, request.url, store=tokens, member_key=lambda data: data["user_id"]
)
print(done.member_key, done.scopes) # what the member granted, which can be less than you asked for
return redirect("/portfolio")

A member who declines, or whose account is still being verified, comes back without a code, and complete_authorization raises a typed exception: oauth.STXAccessDeniedException, oauth.STXAccountPendingException or oauth.STXTermsNotAcceptedException. Show your own “not connected” state and let them start again. The full flow, every option and every callback error are on Apps: connect members.

member_client returns an STX client acting for one member. Every method works as it does with an API key, within the scopes the member granted:

with app.member_client(tokens, "u1") as stx:
print(stx.balance().available_balance)
order = stx.place_order(market_id, "buy", "limit", price="0.40", quantity="1")
stx.cancel_order(order.id)

The client refreshes the access token 30 seconds before it expires and once more after a 401, and writes the rotated pair to your store (STX rotates refresh tokens; see Refreshing). With oauth.AsyncOAuthClient, the member client’s socket streams the member’s channels, including ws.account_view(), a live copy of their balance, open orders, fills and positions. See Act for the member.

With a client secret, your app can read the market catalogue and stream the market data channels as itself, with no member involved (an app token):

with app.app_client("market_data events") as catalog:
for market in catalog.markets(status="open", trading=True, limit=3):
print(market.symbol, market.title)

Sample output, from US demo:

STXMLB-26OCT182000LADMIL-GAMELAD MLB Playoff LAD @ MIL
STXMLB-26OCT182000LADMIL-GAMEMIL MLB Playoff LAD @ MIL
STXMLB-26OCT192000LADMIL-GAMELAD MLB Playoff LAD @ MIL

The SDK mints the app token on first use and renews it before it expires. An app token cannot make member calls: balance() or place_order() raise STXForbiddenException. In Ontario, once live, an app reads public market data only, with the market_data and events scopes, and does not act for members. Streaming as your app, and what each scope can call, are on App tokens and market data.

An approved app holding markets.write can also list its own player-prop markets; see Create markets and List your own markets on STX.

app.unlink(tokens, "u1")

unlink revokes the member’s grant at STX and deletes their tokens from your store. It returns True when STX confirmed the revoke, and deletes the local tokens either way.

For an app with no login of its own; see You’re building a new app with no login yet. oauth.Connect adds openid and a nonce, and verifies the ID token for you:

import os
from stx import oauth
connect = oauth.Connect(
issuer=os.environ["STX_BASE_URL"], # the STX host, e.g. the US demo
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
redirect_uri="https://your.app/auth/stx/callback",
scopes=["balance.read", "orders.read", "orders.write"],
)
@route("/auth/stx/start")
def start(request):
started = connect.start(connection=request.args.get("connection")) # "google", "apple", "x" or None
request.session["stx_sign_in"] = started.saved().to_dict()
return redirect(started.url)
@route("/auth/stx/callback")
def callback(request):
saved = oauth.SavedSignIn.from_dict(request.session.pop("stx_sign_in"))
done = connect.finish(request.url, saved)
user = users.find_or_create(stx_sub=done.identity.sub, email=done.identity.email)
tokens.set(user.id, done.tokens.to_stored())
return redirect("/")

Key your user record on done.identity.sub, not the email. users stands for your own user table. A bad ID token raises oauth.STXOAuthException with error == "invalid_id_token". See Sign in with STX.

Every error is a typed exception you can catch by class. The ones an app meets:

Exception When What to do
oauth.STXGrantRevokedException The member disconnected your app, or a refresh token was reused. Their tokens are already deleted from your store. Show “Connect your STX account” again.
stx.STXInsufficientScopeException The grant lacks a scope the call needs; exc.required_scopes names it. Ask the member to connect again with that scope.
stx.STXTermsRequiredException New STX terms are in effect and the member has not accepted them. Reads and cancels still work. Send them to oauth.terms_url_with_return(exc.terms_url, your_return_url). See Hosted pages.
stx.STXIpNotAllowedException A write from an address not on your app’s IP allow-list. Add the address in STX Apps. See IP allow-list.
stx.STXNotAvailableError The exchange does not offer the feature yet; exc.feature and exc.host name both. Check client.capabilities() first. See What you can do.
from stx import STXInsufficientScopeException, oauth
try:
with app.member_client(tokens, "u1") as stx:
stx.place_order(market_id, "buy", "limit", price="0.40", quantity="1")
except oauth.STXGrantRevokedException:
return redirect("/connect-stx")
except STXInsufficientScopeException as exc:
print("needs", exc.required_scopes)

On an exchange without OAuth, discover itself raises STXNotAvailableError. Captured on Ontario demo:

OAuthClient.discover: STXNotAvailableError: OAuth is not available on demo.stxapp.ca yet. feature='oauth' host='demo.stxapp.ca'

Every exception the SDK raises is on Errors & retries and in the API reference.

Piece What it is
OAuthClient, AsyncOAuthClient One registered app. begin_authorization() and complete_authorization() connect a member; member_client() and app_client() return an STX client; unlink() and revoke() disconnect; client_credentials() and refresh_token() call the token endpoint directly.
Connect, AsyncConnect Sign in with STX: start(), finish(), refresh(), user_info() and disconnect().
MemoryTokenStore, MemoryPendingAuthorizationStore In-memory stores for one process and tests. Your own stores implement the same methods, plain or async.
terms_url_with_return() The STX terms page with your return address, for STXTermsRequiredException.

Every class and method is in the API reference: OAuth apps and Sign in with STX.

The Python sample app is a working FastAPI app built on stx.oauth, with two login modes: members sign in with their STX account (LOGIN_MODE=stx), or sign in to your app and then connect their STX account (LOGIN_MODE=own). It covers the member dashboard (balance, positions, open orders, fills and settlements), placing and cancelling an order, the market list and a live order book on the app token, live account updates, disconnecting, and a page for each typed error. Its README walks through getting credentials at STX Apps and running it against the demo environment.