Skip to content

App tokens and market data

Describes stx-python 0.8.0, the current release.

An app token is your app acting as itself: no member, no sign-in, no consent screen. It reads the market and event catalog and streams the market data channels, which is how an app shows markets and prices to people who have not connected an STX account. It comes from the client_credentials grant, so it needs a client secret (a Web app or Server-to-server app). See App tokens for the protocol.

App tokens need an exchange that offers them; What you can do lists which do. In Ontario an app reads public market data only, with the market_data and events scopes.

from stx import oauth
app = oauth.OAuthClient.discover(
client_id="stxapp_client_g2XU2Kyg3EJoOhC9sndPe2bPvZENsbkkFkzCQFgkWHa0emOUt",
client_secret=CLIENT_SECRET,
region="us",
env="demo",
)
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:

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

app_client(scope) returns an STX client whose requests carry an app token. The SDK mints the token on first use, keeps it until 60 seconds before it expires, mints a new one then, and once more if the exchange answers 401. Every client built for the same scope shares one token. The token request and its answer look like this:

tokens = app.client_credentials("market_data events")
print(tokens.scope, tokens.token_type, tokens.expires_in, tokens.refresh_token)
market_data events bearer 3600 None

An app token has no refresh token: the SDK simply mints another.

Scope Calls
market_data markets(), iter_markets(), market(), and the market data channels: orderbook, ticker, trades, markets, market_stats, market_updates, market
events events(), iter_events(), event_players(), market_types()
markets.write create_markets(), granted by STX on request. See Create markets

Member calls (balance(), place_order()…) are refused with STXForbiddenException: an app token has no member. A scope your app does not hold is refused with oauth.STXOAuthException (invalid_scope), and a scope the exchange does not offer at all with STXNotAvailableError (scope:<name>).

With oauth.AsyncOAuthClient, the app client’s socket streams the market data channels with the app token:

import asyncio
from stx import oauth
async def main():
app = await oauth.AsyncOAuthClient.discover(
client_id=CLIENT_ID, client_secret=CLIENT_SECRET, region="us", env="demo"
)
async with app, app.app_client("market_data") as catalog:
page = await catalog.markets(status="open", trading=True, limit=1)
async with catalog.websocket() as ws:
channel = await ws.market(page.items[0].market_id)
market = (await channel.wait_snapshot())["reply"]
print(market["symbol"], market["last_traded_price"], market["max_price"], market["event_brief"])
await ws.ticker(on_message=lambda msg: print(msg.event, msg.payload))
await ws.run_forever()
asyncio.run(main())

Sample output:

STXMLB-26OCT182000LADMIL-GAMELAD 0.4000 1.0000 Oct 18 at 08:00 PM EDT

ws.market(market_id) is one market in full: the join reply is every market field plus ob, its aggregated book, then market_update carries the fields that change and order_book_update a fresh book about every 200 ms. Prices are dollar strings, as everywhere in the SDK. The socket mints a fresh app token on every reconnect. Every channel is in WebSockets.

An access token from anywhere (your own OAuth code, another service) can be used directly, without refresh:

from stx import STX
with STX(region="us", env="demo", access_token=token) as client:
print(client.markets(limit=1).items[0].symbol)